【问题标题】:web api 2 attribute based route generation基于 web api 2 属性的路由生成
【发布时间】:2013-10-31 20:21:56
【问题描述】:

我一直在搞乱 MVC5 和 WebApi2。在某一时刻,RouteAttributes 似乎有一个基于约定的自动名称——“ControllerName.ActionName”。我有一个带有许多 ApiControllers 的大型 api 和一个使用属性定义的自定义路由。我可以直接使用这些 url,而且效果很好,ApiExplorer 也可以很好地使用它。

然后我需要为我的 dto 对象中的某些字段生成链接作为更新 url。我试过打电话:

Url.Link("", new { controller = "...", action = "...", [其他数据...] })

但它使用了不可用的默认全局路由。

是否无法为没有使用 UrlHelper.Link 定义名称的基于属性的路由生成链接?

任何意见将不胜感激,谢谢。

【问题讨论】:

    标签: c# asp.net-mvc-5 asp.net-web-api-routing


    【解决方案1】:

    使用algorithms described here,我选择使用 ApiExplorer 来获取与给定值集匹配的路由。

    使用示例:

    [RoutePrefix( "api/v2/test" )]
    public class EntityController : ApiController {
        [Route( "" )]
        public IEnumerable<Entity> GetAll() {
            // ...
        }
    
        [Route( "{id:int}" )]
        public Entity Get( int id ) {
            // ...
        }
    
        // ... stuff
    
        [HttpGet]
        [Route( "{id:int}/children" )]
        public IEnumerable[Child] Children( int id ) {
            // ...
        }
    }
    
    ///
    /// elsewhere
    ///
    
    // outputs: api/v2/test/5
    request.HttpRouteUrl( HttpMethod.Get, new {
        controller = "entity",
        id = 5
    } )
    
    // outputs: api/v2/test/5/children
    request.HttpRouteUrl( HttpMethod.Get, new {
        controller = "entity",
        action = "children",
        id = 5
    } )
    

    实现如下:

    public static class HttpRouteUrlExtension {
        private const string HttpRouteKey = "httproute";
    
        private static readonly Type[] SimpleTypes = new[] {
            typeof (DateTime), 
            typeof (Decimal), 
            typeof (Guid), 
            typeof (string), 
            typeof (TimeSpan)
        };
    
        public static string HttpRouteUrl( this HttpRequestMessage request, HttpMethod method, object routeValues ) {
            return HttpRouteUrl( request, method, new HttpRouteValueDictionary( routeValues ) );
        }
    
        public static string HttpRouteUrl( this HttpRequestMessage request, HttpMethod method, IDictionary<string, object> routeValues ) {
            if ( routeValues == null ) {
                throw new ArgumentNullException( "routeValues" );
            }
    
            if ( !routeValues.ContainsKey( "controller" ) ) {
                throw new ArgumentException( "'controller' key must be provided", "routeValues" );
            }
    
            routeValues = new HttpRouteValueDictionary( routeValues );
            if ( !routeValues.ContainsKey( HttpRouteKey ) ) {
                routeValues.Add( HttpRouteKey, true );
            }
    
            string controllerName = routeValues[ "controller" ].ToString();
            routeValues.Remove( "controller" );
    
            string actionName = string.Empty;
            if ( routeValues.ContainsKey( "action" ) ) {
                actionName = routeValues[ "action" ].ToString();
                routeValues.Remove( "action" );
            }
    
            IHttpRoute[] matchedRoutes = request.GetConfiguration().Services
                                                .GetApiExplorer().ApiDescriptions
                                                .Where( x => x.ActionDescriptor.ControllerDescriptor.ControllerName.Equals( controllerName, StringComparison.OrdinalIgnoreCase ) )
                                                .Where( x => x.ActionDescriptor.SupportedHttpMethods.Contains( method ) )
                                                .Where( x => string.IsNullOrEmpty( actionName ) || x.ActionDescriptor.ActionName.Equals( actionName, StringComparison.OrdinalIgnoreCase ) )
                                                .Select( x => new {
                                                    route = x.Route,
                                                    matches = x.ActionDescriptor.GetParameters()
                                                               .Count( p => ( !p.IsOptional ) &&
                                                                       ( p.ParameterType.IsPrimitive || SimpleTypes.Contains( p.ParameterType ) ) &&
                                                                       ( routeValues.ContainsKey( p.ParameterName ) ) &&
                                                                       ( routeValues[ p.ParameterName ].GetType() == p.ParameterType ) )
                                                } )
                                                .Where(x => x.matches > 0)
                                                .OrderBy( x => x.route.DataTokens[ "order" ] )
                                                .ThenBy( x => x.route.DataTokens[ "precedence" ] )
                                                .ThenByDescending( x => x.matches )
                                                .Select( x => x.route )
                                                .ToArray();
    
            if ( matchedRoutes.Length > 0 ) {
                IHttpVirtualPathData pathData = matchedRoutes[ 0 ].GetVirtualPath( request, routeValues );
    
                if ( pathData != null ) {
                    return new Uri( new Uri( httpRequestMessage.RequestUri.GetLeftPart( UriPartial.Authority ) ), pathData.VirtualPath ).AbsoluteUri;
                }
            }
    
            return null;
        }
    }
    

    【讨论】:

    • 另外,我可能会将其作为 HttpRequestMessage 的扩展删除,并在方法中使用 GlobalConfiguration 代替..
    • 不要。 GlobalConfiguration 仅在使用 IIS 托管时可用,在 Owin 中不可用。
    【解决方案2】:

    Route Names 呢?也许您可以像在视图中一样在 DTO 中公开它们。

    控制器

    [Route("menu", Name = "mainmenu")]
    public ActionResult MainMenu() { ... }`
    

    查看

    <a href="@Url.RouteUrl("mainmenu")">Main menu</a>
    

    【讨论】:

    • 这可行,问题出在自定义 api 路由上。为每个人指定一个名称是少数且乏味的。我想知道是否有办法解决这个问题。
    • 嗯,想到了反思。你看过蒂姆斯科特的solution吗?也许有一个合适的或让你走上正确的轨道。
    • 我明天上班去看看。我已经使用 ApiExplorer 和 T4 为客户端上的 api 辅助方法生成 javascript。我也可能会朝着这个方向发展。我不介意反射,只要它最终可以维护。
    • 我会假设这曾经有效,但是从 MVC5 开始,使用属性路由,我似乎无法使用@Url.RouteUrl(routeName) 生成到 webapi2 操作的路由,即使在路由属性。它只是提供一个空白字符串。
    猜你喜欢
    • 2017-03-24
    • 2019-10-30
    • 2014-05-28
    • 1970-01-01
    • 1970-01-01
    • 2014-03-31
    • 2015-08-11
    • 2013-07-10
    • 2014-04-19
    相关资源
    最近更新 更多