cru*_*ush 21 c# asp.net asp.net-web-api attributerouting asp.net-web-api-routing
我已将ASP.NET MVC5应用程序配置为使用AttributeRouting进行WebApi:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
}
}
Run Code Online (Sandbox Code Playgroud)
我有ApiController如下:
[RoutePrefix("api/v1/subjects")]
public class SubjectsController : ApiController
{
[Route("search")]
[HttpPost]
public SearchResultsViewModel Search(SearchCriteriaViewModel criteria)
{
//...
}
}
Run Code Online (Sandbox Code Playgroud)
我想为我的WebApi控制器操作生成一个URL,而不必指定显式路由名称.
根据CodePlex上的这个页面,所有MVC路由都有一个不同的名称,即使它没有指定.
如果没有指定的路由名称,Web API将生成默认路由名称.如果特定控制器上的操作名称只有一个属性路由,则路径名称将采用"ControllerName.ActionName"形式.如果该控制器上有多个具有相同操作名称的属性,则会添加后缀以区分路径:"Customer.Get1","Customer.Get2".
在ASP.NET上,它没有确切地说明默认命名约定是什么,但它确实表明每个路由都有一个名称.
在Web API中,每个路由都有一个名称.路由名称对于生成链接非常有用,因此您可以在HTTP响应中包含链接.
基于这些资源,以及StackOverflow用户Karhgath的回答,我被认为以下会产生一个指向我的WebApi路由的URL:
@(Url.RouteUrl("Subjects.Search"))
Run Code Online (Sandbox Code Playgroud)
但是,这会产生错误:
在路径集合中找不到名为"Subjects.Search"的路径.
我已经根据我在StackOverflow上找到的其他答案尝试了一些其他变体,没有成功.
@(Url.Action("Search", "Subjects", new { httproute = "" }))
@(Url.HttpRouteUrl("Search.Subjects", new {}))
Run Code Online (Sandbox Code Playgroud)
实际上,即使在属性中提供Route名称,也只能使用:
@(Url.HttpRouteUrl("Search.Subjects", new {}))
Run Code Online (Sandbox Code Playgroud)
其中"Search.Subjects"被指定为Route属性中的路径名称.
我不想被迫为我的路线指定一个唯一的名称.
如何生成WebApi控制器操作的URL而无需在Route属性中明确指定路由名称?
CodePlex的默认路由命名方案是否可能已更改或记录错误?
有没有人对正确检索已使用AttributeRouting设置的路由的URL的方法有所了解?
Nko*_*osi 12
通过检查Web Api IApiExplorer以及强类型表达式来查找路径,我能够生成WebApi2 URL,而无需Name在Route属性路由上指定属性.
我创建了一个帮助扩展,它允许我UrlHelper在MVC剃刀中使用强类型表达式.这非常适合在视图中解析我的MVC控制器的URI.
<a href="@(Url.Action<HomeController>(c=>c.Index()))">Home</a>
<li>@(Html.ActionLink<AccountController>("Sign in", c => c.Signin(null)))</li>
<li>@(Html.ActionLink<AccountController>("Create an account", c => c.Signup(), htmlAttributes: null))</li>
@using (Html.BeginForm<ToolsController>(c => c.Track(null), FormMethod.Get, htmlAttributes: new { @class = "navbar-form", role = "search" })) {...}
Run Code Online (Sandbox Code Playgroud)
我现在有一个视图,我试图使用knockout将一些数据发布到我的web api,并且需要能够做这样的事情
var targetUrl = '@(Url.HttpRouteUrl<TestsApiController>(c => c.TestAction(null)))';
Run Code Online (Sandbox Code Playgroud)
所以我不必硬编码我的网址(魔术字符串)
我目前用于获取Web API网址的扩展方法的实现在以下类中定义.
public static class GenericUrlActionHelper {
/// <summary>
/// Generates a fully qualified URL to an action method
/// </summary>
public static string Action<TController>(this UrlHelper urlHelper, Expression<Action<TController>> action)
where TController : Controller {
RouteValueDictionary rvd = InternalExpressionHelper.GetRouteValues(action);
return urlHelper.Action(null, null, rvd);
}
public const string HttpAttributeRouteWebApiKey = "__RouteName";
public static string HttpRouteUrl<TController>(this UrlHelper urlHelper, Expression<Action<TController>> expression)
where TController : System.Web.Http.Controllers.IHttpController {
var routeValues = expression.GetRouteValues();
var httpRouteKey = System.Web.Http.Routing.HttpRoute.HttpRouteKey;
if (!routeValues.ContainsKey(httpRouteKey)) {
routeValues.Add(httpRouteKey, true);
}
var url = string.Empty;
if (routeValues.ContainsKey(HttpAttributeRouteWebApiKey)) {
var routeName = routeValues[HttpAttributeRouteWebApiKey] as string;
routeValues.Remove(HttpAttributeRouteWebApiKey);
routeValues.Remove("controller");
routeValues.Remove("action");
url = urlHelper.HttpRouteUrl(routeName, routeValues);
} else {
var path = resolvePath<TController>(routeValues, expression);
var root = getRootPath(urlHelper);
url = root + path;
}
return url;
}
private static string resolvePath<TController>(RouteValueDictionary routeValues, Expression<Action<TController>> expression) where TController : Http.Controllers.IHttpController {
var controllerName = routeValues["controller"] as string;
var actionName = routeValues["action"] as string;
routeValues.Remove("controller");
routeValues.Remove("action");
var method = expression.AsMethodCallExpression().Method;
var configuration = System.Web.Http.GlobalConfiguration.Configuration;
var apiDescription = configuration.Services.GetApiExplorer().ApiDescriptions
.FirstOrDefault(c =>
c.ActionDescriptor.ControllerDescriptor.ControllerType == typeof(TController)
&& c.ActionDescriptor.ControllerDescriptor.ControllerType.GetMethod(actionName) == method
&& c.ActionDescriptor.ActionName == actionName
);
var route = apiDescription.Route;
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary(routeValues));
var request = new System.Net.Http.HttpRequestMessage();
request.Properties[System.Web.Http.Hosting.HttpPropertyKeys.HttpConfigurationKey] = configuration;
request.Properties[System.Web.Http.Hosting.HttpPropertyKeys.HttpRouteDataKey] = routeData;
var virtualPathData = route.GetVirtualPath(request, routeValues);
var path = virtualPathData.VirtualPath;
return path;
}
private static string getRootPath(UrlHelper urlHelper) {
var request = urlHelper.RequestContext.HttpContext.Request;
var scheme = request.Url.Scheme;
var server = request.Headers["Host"] ?? string.Format("{0}:{1}", request.Url.Host, request.Url.Port);
var host = string.Format("{0}://{1}", scheme, server);
var root = host + ToAbsolute("~");
return root;
}
static string ToAbsolute(string virtualPath) {
return VirtualPathUtility.ToAbsolute(virtualPath);
}
}
Run Code Online (Sandbox Code Playgroud)
InternalExpressionHelper.GetRouteValues检查表达式并生成RouteValueDictionary将用于生成URL 的表达式.
static class InternalExpressionHelper {
/// <summary>
/// Extract route values from strongly typed expression
/// </summary>
public static RouteValueDictionary GetRouteValues<TController>(
this Expression<Action<TController>> expression,
RouteValueDictionary routeValues = null) {
if (expression == null) {
throw new ArgumentNullException("expression");
}
routeValues = routeValues ?? new RouteValueDictionary();
var controllerType = ensureController<TController>();
routeValues["controller"] = ensureControllerName(controllerType); ;
var methodCallExpression = AsMethodCallExpression<TController>(expression);
routeValues["action"] = methodCallExpression.Method.Name;
//Add parameter values from expression to dictionary
var parameters = buildParameterValuesFromExpression(methodCallExpression);
if (parameters != null) {
foreach (KeyValuePair<string, object> parameter in parameters) {
routeValues.Add(parameter.Key, parameter.Value);
}
}
//Try to extract route attribute name if present on an api controller.
if (typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(controllerType)) {
var routeAttribute = methodCallExpression.Method.GetCustomAttribute<System.Web.Http.RouteAttribute>(false);
if (routeAttribute != null && routeAttribute.Name != null) {
routeValues[GenericUrlActionHelper.HttpAttributeRouteWebApiKey] = routeAttribute.Name;
}
}
return routeValues;
}
private static string ensureControllerName(Type controllerType) {
var controllerName = controllerType.Name;
if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException("Action target must end in controller", "action");
}
controllerName = controllerName.Remove(controllerName.Length - 10, 10);
if (controllerName.Length == 0) {
throw new ArgumentException("Action cannot route to controller", "action");
}
return controllerName;
}
internal static MethodCallExpression AsMethodCallExpression<TController>(this Expression<Action<TController>> expression) {
var methodCallExpression = expression.Body as MethodCallExpression;
if (methodCallExpression == null)
throw new InvalidOperationException("Expression must be a method call.");
if (methodCallExpression.Object != expression.Parameters[0])
throw new InvalidOperationException("Method call must target lambda argument.");
return methodCallExpression;
}
private static Type ensureController<TController>() {
var controllerType = typeof(TController);
bool isController = controllerType != null
&& controllerType.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)
&& !controllerType.IsAbstract
&& (
typeof(IController).IsAssignableFrom(controllerType)
|| typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(controllerType)
);
if (!isController) {
throw new InvalidOperationException("Action target is an invalid controller.");
}
return controllerType;
}
private static RouteValueDictionary buildParameterValuesFromExpression(MethodCallExpression methodCallExpression) {
RouteValueDictionary result = new RouteValueDictionary();
ParameterInfo[] parameters = methodCallExpression.Method.GetParameters();
if (parameters.Length > 0) {
for (int i = 0; i < parameters.Length; i++) {
object value;
var expressionArgument = methodCallExpression.Arguments[i];
if (expressionArgument.NodeType == ExpressionType.Constant) {
// If argument is a constant expression, just get the value
value = (expressionArgument as ConstantExpression).Value;
} else {
try {
// Otherwise, convert the argument subexpression to type object,
// make a lambda out of it, compile it, and invoke it to get the value
var convertExpression = Expression.Convert(expressionArgument, typeof(object));
value = Expression.Lambda<Func<object>>(convertExpression).Compile().Invoke();
} catch {
// ?????
value = String.Empty;
}
}
result.Add(parameters[i].Name, value);
}
}
return result;
}
}
Run Code Online (Sandbox Code Playgroud)
诀窍是获取行动的路线并使用它来生成URL.
private static string resolvePath<TController>(RouteValueDictionary routeValues, Expression<Action<TController>> expression) where TController : Http.Controllers.IHttpController {
var controllerName = routeValues["controller"] as string;
var actionName = routeValues["action"] as string;
routeValues.Remove("controller");
routeValues.Remove("action");
var method = expression.AsMethodCallExpression().Method;
var configuration = System.Web.Http.GlobalConfiguration.Configuration;
var apiDescription = configuration.Services.GetApiExplorer().ApiDescriptions
.FirstOrDefault(c =>
c.ActionDescriptor.ControllerDescriptor.ControllerType == typeof(TController)
&& c.ActionDescriptor.ControllerDescriptor.ControllerType.GetMethod(actionName) == method
&& c.ActionDescriptor.ActionName == actionName
);
var route = apiDescription.Route;
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary(routeValues));
var request = new System.Net.Http.HttpRequestMessage();
request.Properties[System.Web.Http.Hosting.HttpPropertyKeys.HttpConfigurationKey] = configuration;
request.Properties[System.Web.Http.Hosting.HttpPropertyKeys.HttpRouteDataKey] = routeData;
var virtualPathData = route.GetVirtualPath(request, routeValues);
var path = virtualPathData.VirtualPath;
return path;
}
Run Code Online (Sandbox Code Playgroud)
所以现在如果我有以下api控制器
[RoutePrefix("api/tests")]
[AllowAnonymous]
public class TestsApiController : WebApiControllerBase {
[HttpGet]
[Route("{lat:double:range(-90,90)}/{lng:double:range(-180,180)}")]
public object Get(double lat, double lng) {
return new { lat = lat, lng = lng };
}
}
Run Code Online (Sandbox Code Playgroud)
到目前为止,当我测试它时,大部分工作
@section Scripts {
<script type="text/javascript">
var url = '@(Url.HttpRouteUrl<TestsApiController>(c => c.Get(1,2)))';
alert(url);
</script>
}
Run Code Online (Sandbox Code Playgroud)
我知道/api/tests/1/2,这就是我想要的,我相信会满足你的要求.
请注意,对于具有路径属性的操作,它也会默认返回到UrlHelper Name.
根据CodePlex上的此页面,即使未指定,所有MVC路由也具有唯一的名称。
Codeplex上的文档适用于WebApi 2.0 beta,此后情况似乎有所变化。
我已经调试了属性路由,看起来WebApi可以为所有操作创建单个路由,而无需RouteName使用name 指定MS_attributerouteWebApi。
您可以在_routeCollection._namedMap字段中找到它:
GlobalConfiguration.Configuration.Routes)._routeCollection._namedMap
Run Code Online (Sandbox Code Playgroud)
该集合中也填充了已命名的路由,这些路由的路由名称是通过属性明确指定的。
生成URL时,Url.Route("RouteName", null);它将在_routeCollection字段中搜索路由名称:
VirtualPathData virtualPath1 =
this._routeCollection.GetVirtualPath(requestContext, name, values1);
Run Code Online (Sandbox Code Playgroud)
并且它将仅在其中找到使用路由属性指定的路由。或config.Routes.MapHttpRoute当然。
我不想被迫为我的路线指定唯一的名称。
不幸的是,如果不显式指定路由名称,就无法为WebApi操作生成URL。
实际上,即使在属性中提供Route名称似乎也只能用于
Url.HttpRouteUrl
是的,这是因为API路由和MVC路由使用不同的集合来存储路由并具有不同的内部实现。
| 归档时间: |
|
| 查看次数: |
2077 次 |
| 最近记录: |