我想创建几个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标准,请阅读此处的规范。
因此,对于您的示例,如果您删除了ODataRoutePrefix
和ODataRoute
属性,则标准路由会将此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();
}
}
}
这种差异很难发现,在这个例子中尤其复杂,因为路由、控制器和类名都在People、Person和Person之间的英语复数约定之间切换。例如,我很难确定您错误地使用了这些元素中的哪一个,所以我假设您的数据类名就是您想要的。
无论哪种方式,您期望的URL都是非常规的,根据定义,这使得使用标准约定很难实现。