用于' Cast'的OData V4 WebAPI路由约定

时间:2016-01-05 19:04:37

标签: c# asp.net-web-api odata

使用以下查询的基本Get方法构建ODataController:

http://localhost:8080/api/Bases

非常简单:

[EnableQuery]
public IHttpActionResult Get()
{
    return Ok(new List<Base>());
}

以同样的方式,我试图实施&#34;演员&#34; route(&#34;〜/ entityset / cast&#34;),它在OData V4 convention part 4.9中定义,但这完全没有记录。所以我挖了一些source code,发现对于以下网址:

http://localhost:8080/api/Bases/MyNamespace.DerivedA

我可以在同一个控制器中定义以下方法:

[EnableQuery]
public IHttpActionResult GetFromDerivedA()
{
    return Ok(new List<DerivedA>());
}

哪个有用但我有十几种类型从Base继承。我没有为每个派生类型声明一个方法,而是使用类似的方法:

[EnableQuery]
public IHttpActionResult GetFrom<T>()
    where T : Base
{
    return Ok(new List<T>());
}

我正在使用:

  • Microsoft.AspNet.WebApi 5.2.3
  • Microsoft.OData 6.13.0
  • Microsoft.AspNet.OData 5.6.0

更新

我可以创建a new RoutingConvention并使覆盖的SelectAction返回我的泛型方法,但似乎我不得不忘记通用方法方法:

  

&#34;无法在控制器&#39; MyProject.Controllers.BasesController&#39;上调用操作方法'System.Web.Http.IHttpActionResult GetFrom[T]()'。   因为动作方法是通用方法。&#34;

那么,这可能吗?

[EnableQuery]
public IHttpActionResult GetFrom(Type derivedType)
{
    //snip!
}

如果没有,还有其他想法吗?

1 个答案:

答案 0 :(得分:1)

这是我通过一些反思来实现这一目标的方法。这是一个漫长的过程,但最终的控制器方法非常简单,值得。

首先,创建一个新的RoutingConvention。请注意,我们将把所有强制转换请求转发给名为GetFrom的方法:

public class CastRoutingConvention : EntitySetRoutingConvention
{
    public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
    {
        if (odataPath.PathTemplate == "~/entityset/cast")
        {
            HttpMethod httpMethod = controllerContext.Request.Method;
            var collectionType = (IEdmCollectionType)odataPath.EdmType;
            var entityType = (IEdmEntityType)collectionType.ElementType.Definition;

            var type = AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => !a.IsDynamic)
                .SelectMany(a => a.DefinedTypes)
                .FirstOrDefault(t => t.FullName == entityType.FullTypeName());

            controllerContext.RouteData.Values["type"] = type;

            if (httpMethod == HttpMethod.Get)
                return "GetFrom";
            else if (httpMethod == HttpMethod.Post)
                return "PostFrom";
            else
                return base.SelectAction(odataPath, controllerContext, actionMap);
        }
        else
            return base.SelectAction(odataPath, controllerContext, actionMap);
    }
}

接下来,将其添加到OData配置:

public static void Register(HttpConfiguration config)
{
    config.MapHttpAttributeRoutes();
    var builder = new ODataConventionModelBuilder() { Namespace = "Default" };
    builder.DataServiceVersion = Version.Parse("4.0");

    //snip! entity configuration

    var conventions = ODataRoutingConventions.CreateDefault();
    conventions.Insert(0, new CastRoutingConvention());

    config.MapODataServiceRoute(
        routeName:"ODataRoute", 
        routePrefix: "api",
        routingConventions: conventions,
        pathHandler: new DefaultODataPathHandler(),
        model: builder.GetEdmModel());
}

现在,因为默认模型绑定器不会从路径数据字典中读取任意参数名称,所以路由数据需要a custom model binder

using System;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;

namespace Example
{
    public class RouteDataModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            object model;

            if (!actionContext.RequestContext.RouteData.Values.TryGetValue(bindingContext.ModelName, out model))
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, $"No route data named '{bindingContext.ModelName}'.");
                return false;
            }
            else if (!bindingContext.ModelType.IsAssignableFrom(model.GetType()))
            {
                try
                {
                    model = Convert.ChangeType(model, bindingContext.ModelType);
                }
                catch
                {
                    bindingContext.ModelState.AddModelError(bindingContext.ModelName, $"Route data cannot be converted to type '{bindingContext.ModelType.FullName}'.");
                    return false;
                }
            }

            bindingContext.Model = model;
            return true;
        }
    }

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
    public class RouteDataAttribute : ModelBinderAttribute
    {
        public RouteDataAttribute()
        {
            this.BinderType = typeof(RouteDataModelBinder);
        }
    }
}

最后,在控制器中添加所需的方法。请注意它是多么微不足道:

[EnableQuery]
public IHttpActionResult GetFrom([RouteData]Type type)
{
    var ofType = typeof(Queryable).GetMethod("OfType").MakeGenericMethod(type);
    return Ok((IQueryable<Base>)ofType.Invoke(null, new object[] { this.Context.Bases }));
}

由于我正在使用Entity Framework而我无法使用GetType(),因此我必须使用另一个反射技巧来使用OfType<T>()实例调用Type。如果你正在处理内存中的实体,只需废弃最后一部分并使用普通的:

return Ok(inRamEntities.Where(e => e.GetType() == type));