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



我想创建几个OData控制器,每个控制器都有自己的路由,而不是控制器名称。我尝试过使用ODataRoutePrefix属性,但我得到了一个错误:

控制器中操作"Add"上的路径模板"People/Person"Person"不是有效的OData路径模板。找不到的资源"人物"部分。

这是控制器:

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}/odatea/people/Persen?我正在使用。Net framework 4.7谢谢

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

常见的误解是CreateDefaultWithAttributeRouting允许您定义自己的自定义路由,但它真正做的只是允许您更改这些默认路由映射到控制器的方式。

这在文档中有点模棱两可,OData的全部目的是遵循标准,而不是破坏标准。ODataRoutePrefix的目的是允许您将代码中不遵循标准OData命名约定的控制器映射到标准路由,这样您仍然可以遵守标准,而无需为现有的API重构整个代码库,或者至少可以按需管理代码和类名。

让我们明确一点:OData Routes是根据验证的,因此必须映射到EdmModel。您不能通过属性使任意路由,控制器类和方法上的属性只是告诉HTTP处理管道如何识别要调用的正确方法。

TL;DR

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

OData路由属性的正确使用

OData v4路由约定是OASIS标准,请阅读此处的规范。

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

/odata/Person

这是因为您的控制器类名与预期的约定PersonController匹配。

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

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

要明确的是,默认约定是具有前缀"的类;人";,后缀";控制器";(因此命名为PersonController)并继承自ODataController

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

对于端点,您几乎总是需要为标准CRUD端点指定[ODataRoute],即使是在符合要求的控制器上,但是如果您的方法的其余部分符合预期约定,则不必提供模板。

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

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

因此,使用ODataRoutePrefixAttribute的正确方法是当控制器类没有常规名称时,但您仍然希望它解析为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中指定人员相关实体

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

然而,您仍然需要移除CCD_;"人";来自ODataRouteAttribute:的模板

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

这种差异很难发现,在这个例子中尤其复杂,因为路由、控制器和类名都在PeoplePersonPerson之间的英语复数约定之间切换。例如,我很难确定您错误地使用了这些元素中的哪一个,所以我假设您的数据类名就是您想要的。

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

最新更新