抽象的通用 ODataController 类导致“未找到 HTTP 资源”

Posted

技术标签:

【中文标题】抽象的通用 ODataController 类导致“未找到 HTTP 资源”【英文标题】:Abstract Generic ODataController Class Leads To 'No HTTP resource was found' 【发布时间】:2014-02-01 20:44:29 【问题描述】:

我正在尝试抽象 VS 2013 中自动生成的 ODataController 类,因为除了 POCO 的名称之外,不同控制器的代码看起来相同,因此,我执行了以下操作:

 public abstract class ODataControllerBase<T,DB> : ODataController
        where T : class, IIdentifiable, new()
        where DB : DbContext, new() 
 
     protected DB _DataContext;

     public ODataControllerBase() : base()
     
         _DataContext = new DB();
     

     // only one function shown for brevity
     [Queryable]
     public SingleResult<T> GetEntity([FromODataUri] int key)
     
         return SingleResult.Create(_DataContext.Set<T>().Where(Entity => Entity.Id.Equals(key)));
       
 

IIdentifiable 是一个接口,它强制 T 参数具有可读/可写的 Id 整数属性。

实现看起来像这样(POCO 和 DataContexts 应该已经创建好了)

public class MyObjectsController : ODataControllerBase<MyObject,MyDbContext>

    public MyObjectsController() : base()
    
    

    // That's it - done because all the repetitive code has been abstracted.

现在,我的 WebApiConfig 的注册函数只包含以下内容:

public static void Register(HttpConfiguration config)

    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<MyObject>("MyObjects");
    config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());  

我运行该项目,http://localhost:10000/odata/MyObjects 并得到响应:

<m:error>
   <m:code/>
   <m:message xml:lang="en-US">No HTTP resource was found that 
      matches the request URI `http://localhost:10000/odata/MyObjects.`
   </m:message>
   <m:innererror>
       <m:message>No routing convention was found to select an action 
            for the OData path with template '~/entityset'.
       </m:message>
       <m:type/>
       <m:stacktrace/>
   </m:innererror>
 </m:error>

缺少什么?我应该删除什么?这是我们不能做的事情吗,即我们真的需要直接继承ODataController而不需要中间父类吗?

【问题讨论】:

你有返回所有对象的动作吗?例如Get()这样的动作? 是的。示例函数是一个返回一个对象的 Get 函数...除非 Get 操作也必须命名为 GetMyObject,因为操作名称是路由敏感的?如果是这样,那么这就是其中的秘密之一。 问题已修复:将操作 GetEntity([FromODataUri]int key) 更改为普通 Get([FromODataUri]int key)。抽象控制器时,不要在 CRUD 操作上附加任何内容。 @MickaelCaruso - 你想自己发布答案,这个问题从外面看起来没有答案 我遇到了同样的问题,但我的问题是由于在 BaseController 中的 Get 方法上使用 protected 而不是 public 引起的。基本方法必须是公开的。 【参考方案1】:

在我们的一个项目中,我们还使用了一个通用的 ODataController 基类,我们实际上使用 GetEntity 检索单个实体,使用 GetEntitySet 检索实体列表。

根据您提供的 URL 和生成的错误消息,ODATA 框架找不到 ~/entitysetODataAction。正如您以http://localhost:10000/odata/MyObjects 为例,相关操作不能是public SingleResult&lt;T&gt; GetEntity([FromODataUri] int key),因为这仅对应于http://localhost:10000/odata/MyObjects(42) 之类的查询。

我们的通用控制器代码如下所示:

public abstract class OdataControllerBase<T> : ODataController
    where T : class, IIdentifiable, new()

    protected OdataControllerBase(/* ... */)
        : base()
    
        // ...
    

    public virtual IHttpActionResult GetEntity([FromODataUri] long key, ODataQueryOptions<T> queryOptions)
    
        // ...

        return Ok(default(T));
    

    public virtual async Task<IHttpActionResult> GetEntitySet(ODataQueryOptions<T> queryOptions)
    
        // ...

        return Ok<IEnumerable<T>>(default(List<T>));
    

    public virtual IHttpActionResult Put([FromODataUri] long key, T modifiedEntity)
    
        // ...

        return Updated(default(T));
    

    public virtual IHttpActionResult Post(T entityToBeCreated)
    
        // ...

        return Created(default(T));
    

    [AcceptVerbs(HTTP_METHOD_PATCH, HTTP_METHOD_MERGE)]
    public virtual IHttpActionResult Patch([FromODataUri] long key, Delta<T> delta)
    
        // ...

        return Updated(default(T));
    

    public virtual IHttpActionResult Delete([FromODataUri] long key)
    
        // ...

        return Updated(default(T));
    

特定控制器的代码如下所示:

public partial class KeyNameValuesController : OdataControllerBase<T>

    public KeyNameValuesController(/* ... */)
        : base()
    
        // there is nothing to be done here
    

但是我们发现两个 Get 方法(用于单个结果和可枚举结果)实际上都必须以 Get 开头。首先,我们尝试使用List 而不是GetEntitySet,但这不起作用,因为框架随后期望POST 用于List 操作。

您实际上可以通过提供自定义IHttpActionSelector 来验证和诊断解决过程,如Routing and Action Selection in ASP.NET Web API 中所述(看看ASP.NET WEB API 2: HTTP Message Lifecycle 也可能值得)。

所以实际上可以使用GetEntity 作为您最初在示例中尝试的方法名称,并且无需将其重命名为简单的Get。此外,无需对您的 ODATA 配置进行任何修改。

【讨论】:

【参考方案2】:

为了确定调用哪个操作,框架使用路由表。 Web API 的 Visual Studio 项目模板创建默认路由:

routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/controller/id",
defaults: new  id = RouteParameter.Optional 
);

按动作名称路由

使用默认路由模板,Web API 使用 HTTP 方法来选择操作。但是,您也可以创建一个路由,其中​​操作名称包含在 URI 中:

routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/controller/action/id",
defaults: new  id = RouteParameter.Optional 
);

我的配置如下:

config.Routes.MapHttpRoute(
            name: "GetMessage",
            routeTemplate: "api/controller/action/quoteName",
            defaults: new  quoteName = RouterParameters.Optional 
        );

像这样访问你的 URI:

http://localhost:42201/api/Extract/GetMessage/Q3

http://localhost:42201/api/Extract/GetMessage/?quotename=Q3

【讨论】:

以上是关于抽象的通用 ODataController 类导致“未找到 HTTP 资源”的主要内容,如果未能解决你的问题,请参考以下文章

OData 8 - ODataController 上的新输入参数语法是啥?

公开 DTO 时的 ApiController 与 ODataController

具有通用参数和抽象类的泛型

抽象通用视图持有者的类预期的 Kotlin 一种类型参数

抽象类和接口

JAVA学习笔记-抽象类