20

我已經配置了我的ASP.NET應用程序MVC5使用AttributeRouting爲的WebAPI:如何在不使用AttributeRouting的Route屬性上指定名稱的情況下生成WebApi2 URL?

public static class WebApiConfig 
{ 
    public static void Register(HttpConfiguration config) 
    { 
     config.MapHttpAttributeRoutes(); 
    } 
} 

我有一個ApiController如下:

[RoutePrefix("api/v1/subjects")] 
public class SubjectsController : ApiController 
{ 
    [Route("search")] 
    [HttpPost] 
    public SearchResultsViewModel Search(SearchCriteriaViewModel criteria) 
    { 
     //... 
    } 
} 

我想生成一個網址我的WebAPI控制器而不必指定明確的路由名稱。

根據this page on CodePlex,即使未指定,所有MVC路由都有不同的名稱。

In the absence of a specified route name, Web API will generate a default route name. If there is only one attribute route for the action name on a particular controller, the route name will take the form "ControllerName.ActionName". If there are multiple attributes with the same action name on that controller, a suffix gets added to differentiate between the routes: "Customer.Get1", "Customer.Get2".

On ASP.NET,它沒有說什麼是默認的命名約定,但它確實表明,每路都有一個名字。

In Web API, every route has a name. Route names are useful for generating links, so that you can include a link in an HTTP response.

基於這些資源和answer by StackOverflow user Karhgath,我認爲,導致下面會產生一個URL到我的WebAPI路線:

@(Url.RouteUrl("Subjects.Search")) 

然而,這將產生一個錯誤:

A route named 'Subjects.Search' could not be found in the route collection.

我已經嘗試了一些基於StackOverflow上找到的其他答案的其他變體,但都沒有成功。

@(Url.Action("Search", "Subjects", new { httproute = "" })) 

@(Url.HttpRouteUrl("Search.Subjects", new {})) 

事實上,即使是在屬性提供了一個路線名稱似乎只是一起工作:其中「Search.Subjects」被指定爲在路徑屬性的路徑名稱

@(Url.HttpRouteUrl("Search.Subjects", new {})) 

我不想強制爲我的路線指定一個唯一的名稱。

如何生成我的WebApi控制器操作的URL,而不必在Route屬性中顯式指定路由名稱?

默認路由命名方案是否有可能在CodePlex上發生了更改或記錄不正確?

有沒有人有一些正確的方式來檢索已經設置了AttributeRouting的路由URL的一些見解?

回答

3

According to this page on CodePlex, all MVC routes have a distinct name, even if it is not specified.

codeplex上的文檔適用於WebApi 2.0測試版,看起來事情自此以後就發生了變化。

我有調試過的屬性路由,它看起來像WebApi爲所有動作創建單一路由,但沒有指定RouteName,名稱爲MS_attributerouteWebApi

可以在_routeCollection._namedMap現場發現:

GlobalConfiguration.Configuration.Routes)._routeCollection._namedMap 

這個系列還填充了這是通過屬性明確指定路由名稱命名路由。

當你生成Url.Route("RouteName", null);_routeCollection現場搜索路線名稱網址:

VirtualPathData virtualPath1 = 
    this._routeCollection.GetVirtualPath(requestContext, name, values1); 

它將只查找路由指定路由屬性存在。當然也可以用config.Routes.MapHttpRoute

I don't want to be forced to specify a unique name for my routes.

不幸的是,沒有明確指定路由名稱的情況下無法爲WebApi動作生成URL。

In fact, even providing a Route name in the attribute only seems to work with Url.HttpRouteUrl

是的,這是因爲API路線和MVC路由使用不同的集合來存儲和航線有不同的內部實現。

+1

我希望MS很快就能理清整個混​​亂。 Web API和MVC應該完全分離或完全相同。現在我們遇到了一個糟糕的混合體系,這個混合體系破壞了兼容性和普遍困惑的開發者。 – MarioDS 2017-06-01 10:10:37

+1

@MarioDS,他們實際上在ASP.NET Core中做過。 – 2017-06-01 10:19:52

+0

不幸的是ASP.NET Core不適合我們。 – MarioDS 2017-06-01 11:27:23

11

通過檢查Web Api的IApiExplorer以及強類型表達式來查找路由我可以生成WebApi2 URL,但在屬性路由的Route屬性上未指定Name

我創建了一個助手擴展,它允許我在MVC剃鬚刀中使用UrlHelper強類型表達式。這對於在視圖中解析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" })) {...}  

現在我在那裏,我試圖用基因敲除一些數據發佈到我的網頁API的景色,需要能夠做這樣的事

var targetUrl = '@(Url.HttpRouteUrl<TestsApiController>(c => c.TestAction(null)))'; 

讓我不要不必硬編碼我的網址(魔術字符串)

我的獲取Web API url的擴展方法的當前實現在以下類中定義。

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); 
    } 
} 

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; 
    } 
} 

訣竅是獲取操作的路由並使用它來生成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; 
} 

所以現在例如如果我使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 }; 
    } 
} 

作品大部分到目前爲止當我測試它

@section Scripts { 
    <script type="text/javascript"> 
     var url = '@(Url.HttpRouteUrl<TestsApiController>(c => c.Get(1,2)))'; 
     alert(url); 
    </script> 
} 

我得到/api/tests/1/2,這就是以下我想和我相信會滿足您的要求。

注意,它也將回到默認的UrlHelper用於與具有Name路由屬性行動。

0

第一件事情是,如果你要訪問的路線,然後肯定您需要爲該就像我們在普通的C#編程中使用任何其他變量的唯一標識符。

因此,如果爲每個路由定義的唯一名稱是你頭疼,但我仍然認爲你將不得不使用它,因爲它提供的效益要好得多。

好處:想想要你的路線更改爲新值的場景,但它會要求你改變整個applciation該值,無論你使用它。 在這種情況下,它會有所幫助。

以下是代碼示例生成路由名稱的鏈接。

public class BooksController : ApiController 
{ 
    [Route("api/books/{id}", Name="GetBookById")] 
    public BookDto GetBook(int id) 
    { 
     // Implementation not shown... 
    } 

    [Route("api/books")] 
    public HttpResponseMessage Post(Book book) 
    { 
     // Validate and add book to database (not shown) 

     var response = Request.CreateResponse(HttpStatusCode.Created); 

     // Generate a link to the new book and set the Location header in the response. 
     string uri = **Url.Link("GetBookById", new { id = book.BookId });** 
     response.Headers.Location = new Uri(uri); 
     return response; 
    } 
} 

請仔細閱讀本link

是的,你是要去必要性,以便與您要訪問的輕鬆訪問這些定義此路由名稱。您想要的基於慣例的鏈接生成目前不可用。

我還想再補充一點,如果這對你來說確實非常重要,那麼我們可以寫出自己的幫助器方法,它將採用兩個參數{ControllerName}和{ActionName},並返回路由值使用一些邏輯。

讓我們知道,如果你真的認爲它值得這樣做。

+0

「靜態」路由名稱的一個大問題是,您無法動態引用路由。我的具體情況是我有一個自定義的'IHttpActionResult',我想爲被調用的動作生成一個URL,只能使用不同的參數。嗯,動作和參數......聽起來像一條路線嗎?只有**任何**行動才能返回這個結果,我們如何找出路線?對,你不能。靜態路由名稱本身並不是問題,只是Web API根據<當前HTTP請求,HTTP上下文,動作上下文,您命名> – MarioDS 2017-06-08 13:07:57

相關問題