到不同 OData 控制器的不同路由

different routing to different OData controllers

我想创建几个 OData 控制器,每个控制器都有自己的路由,这不是控制器名称。 我试过使用 ODataRoutePrefix 属性,但出现错误:

The path template 'People/Person' on the action 'Add' in controller 'Person' is not a valid OData path template. Resource not found for the segment 'People'.

这是控制器:

  namespace MyODataWebApplication.Controllers
{
    [ODataRoutePrefix("People")]
    public class PersonController : ODataController
    {
        private readonly List<Person> _persons = new List<Person>
        {
            new Person {Id = "1234", FirstName = "Person1", LastName = "Last1", Age = 25},
            new Person {Id = "2345", FirstName = "Person2", LastName = "Last2", Age = 45},
            new Person {Id = "3456", FirstName = "Person3", LastName = "Last3", Age = 25}
        };

        [ODataRoute("Person")]
        [HttpGet]
        public IQueryable<Person> Get()
        {
            return _persons.AsQueryable();
        }
    }
}

这是我的 WebApiConfig:

 public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var r = ODataRoutingConventions.CreateDefaultWithAttributeRouting("ODataRoute", config);
        config.MapODataServiceRoute("ODataRoute", "odata", GetEdmModel(), new DefaultODataPathHandler(), r);
    }

    public static IEdmModel GetEdmModel()
    {
        var builder = new ODataConventionModelBuilder
        {
            Namespace = "ODataTesting",
            ContainerName = "ODataTestingContainer"
        };

        builder.EntitySet<Person>("Person");
        builder.EntitySet<Animal>("Animal");

        return builder.GetEdmModel();
    }

}

人物实体非常简单:

   public sealed class Person
{
    [Key]
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

有什么想法可以调用{my prefix}/odata/people/Person? 我现在使用的是 .Net framework 4.7 谢谢!

在您的 Register 方法中,您已经明确表示要创建默认路由约定,但允许属性覆盖控制器和端点名称,然后使用这些路线映射您的 EdmModel

The common misconception is that CreateDefaultWithAttributeRouting allows you to define your own custom routes, all it is really doing though is allowing you to change how those default routes are mapped to your controllers.

这在文档中有点模棱两可,OData 的重点是遵循标准,而不是破坏它们。 ODataRoutePrefix 的目的是允许您将代码中不遵循标准 OData 命名约定的控制器映射到 标准 路线,这样你仍然可以符合标准,而不必为现有 API 重构整个代码库,或者至少这样你就可以管理你的代码和 class 名称.

Lets be clear: OData Routes are validated against and therefore MUST be mapped against the EdmModel. You cannot make arbitrary routes via attributes, the attributes on your controller class and methods simply tell the HTTP processing pipeline how to identify the correct methods to invoke.

TL;DR

以下是从标准示例开始的演练,滚动到符合您要求的最终解决方案,然后回头阅读其余部分以决定您是否仍要实施原始路线,我怀疑 /people/person 是一个错误,或者 /people 应该 return all/people/person(key) 应该 return 只有一个...

OData 路由属性的正确用法

OData v4 Routing Conventions are an OASIS standard, read the spec here.

因此,对于您的示例,如果删除 ODataRoutePrefix ODataRoute 属性的标准路由,会将此 url 映射到你的 Get() 方法:

/odata/Person

这解决了,因为您的控制器 class 名称符合预期的约定 PersonController

此期望由 GetEdmModel() 中的这一行设置:

builder.EntitySet<Person>("Person");

需要明确的是,默认约定是 class 具有前缀“Person”和后缀“Controller”(因此命名为 PersonController)并继承自 ODataController

同样的概念也适用于 ODataRouteAttribute。根据规范,预期路由到return所有资源是/Resource,单个资源的预期路由是/Resource(key)

For the end points, you almost always need to specify the [ODataRoute] for the standard CRUD endpoints, even on your conforming controllers, however if your do not have to provide the template if the rest of your method matches the expected conventions.

默认 路由约定期望您的控制器上的端点类似于:

[HttpGet]
[ODataRoute]
public IHttpActionResult Get([FromODataUri] int key)
{
    return _persons.Single(x => x.Id == key);
}

因此,使用 ODataRoutePrefixAttribute 的正确方法是当您的控制器 class 没有 常规 名称,但您仍然希望它解析为OData 标准约定,因此下面的内容会违反标准约定。

namespace MyODataWebApplication.Controllers
{
    [ODataRoutePrefix("Person")]
    public class UnconventionalControllerName : ODataController
    {
        private readonly List<Person> _persons = new List<Person>
        {
            new Person {Id = "1234", FirstName = "Person1", LastName = "Last1", Age = 25},
            new Person {Id = "2345", FirstName = "Person2", LastName = "Last2", Age = 45},
            new Person {Id = "3456", FirstName = "Person3", LastName = "Last3", Age = 25}
        };

        [ODataRoute()]
        [HttpGet]
        public IQueryable<Person> ReturnThemAll()
        {
            return _persons.AsQueryable();
        }

        [ODataRoute("({theIdOfTheOneYouWant})")]
        [HttpGet]
        public Person JustOnePlease(int theIdOfTheOneYouWant)
        {
            return _persons.Single(x => x.Id == key);
        }
    }
}

变通

尽管我怀疑这是否适用于您的整个 API,但满足您要求的一种选择是将整个 EdmModel 映射到前缀 odata/people .
如果这样做,您可能只会在 EdmModel

中指定 people 相关实体
public static void Register(HttpConfiguration config)
{
    var r = ODataRoutingConventions.CreateDefaultWithAttributeRouting("ODataRoute", config);
    config.MapODataServiceRoute("ODataRoute", "odata/people", GetEdmModel(), new DefaultODataPathHandler(), r);
}

但是您仍然需要从 ODataRouteAttribute 中删除 ODataRoutPrefixAttribute 和“人物”模板:

namespace MyODataWebApplication.Controllers
{
    // Remove this attribute, this controller already matches the convention
    //[ODataRoutePrefix("People")]
    public class PersonController : ODataController
    {
        private readonly List<Person> _persons = new List<Person>
        {
            new Person {Id = "1234", FirstName = "Person1", LastName = "Last1", Age = 25},
            new Person {Id = "2345", FirstName = "Person2", LastName = "Last2", Age = 45},
            new Person {Id = "3456", FirstName = "Person3", LastName = "Last3", Age = 25}
        };

        [ODataRoute()] // remove "Person" template
        [HttpGet]
        public IQueryable<Person> Get()
        {
            return _persons.AsQueryable();
        }
    }
}

自定义路线

您当然仍然可以使用标准 System.Web.Http 路由并将其映射到您的 OData 控制器以执行,ODataController 毕竟继承自 ApiController,但这些路由不会被发布或通过 OData $metadata

公开

如果你想像这样 /odata/Person/People 通过 url 从 PersonController 公开 People 的自定义集合,那么可以这样配置:

builder.EntitySet<Person>("Person")
       .EntityType.Collection.Function("People")
       .ReturnsCollectionFromEntitySet<Person>("Person");

然后在您的控制器上您可以拥有:

// using conventional name, no need for [ODataRoute] 
[HttpGet]
[EnableQuery]
public IQueryable<Person> People(ODataQueryOptions<Person> options)
{
    return _persons.AsQueryable();
}

终于找到原来需要的路线了!

没有判断...如果你想让{my prefix}/odata/people映射到你的PersonController那么你首先需要将配置更改为期望这条路线,那么你的 ODataRoutePrefix 可以使用,但是它不会将 /Person 路由映射到 get 方法,要做到这一点你必须使用前面的技巧来声明一个自定义集合函数,结果看起来像这样:

builder.EntitySet<Person>("People")
       .EntityType.Collection.Function("Person")
       .ReturnsCollectionFromEntitySet<Person>("People");

控制器:

namespace MyODataWebApplication.Controllers
{
    [ODataRoutePrefix("People")]
    public class PersonController : ODataController
    {
        private readonly List<Person> _persons = new List<Person>
        {
            new Person {Id = "1234", FirstName = "Person1", LastName = "Last1", Age = 25},
            new Person {Id = "2345", FirstName = "Person2", LastName = "Last2", Age = 45},
            new Person {Id = "3456", FirstName = "Person3", LastName = "Last3", Age = 25}
        };

        [ODataRoute("Person")]
        [HttpGet]
        public IQueryable<Person> Get()
        {
            return _persons.AsQueryable();
        }
    }
}

差异很难发现,在这个例子中尤其复杂,其中路由、控制器和 class 名称都在 People[=125= 之间的英语复数约定之间切换]、。例如,我很难确定您错误地使用了这些元素中的哪一个,所以我基于您的数据 class 名称是您想要的名称的假设进入。

无论哪种方式,您预期的 URL 都是 非常规的 ,根据定义,很难使用标准约定来实现。