Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abstract Generic ODataController Class Leads To 'No HTTP resource was found'

I am trying to abstract the auto-generated ODataController class in VS 2013 because the code looks identical across different controllers except the name of the POCO, so, I did the following:

 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 is an interface that forces the T parameter to have a readable/writable Id integer property.

The implementation looks like this (POCOs and DataContexts should've already been created)

public class MyObjectsController : ODataControllerBase<MyObject,MyDbContext>
{
    public MyObjectsController() : base()
    {
    }

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

Now, my WebApiConfig's Register function contains the following only:

public static void Register(HttpConfiguration config)
{
    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<MyObject>("MyObjects");
    config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());  
}

I run the project, http://localhost:10000/odata/MyObjects and I get the response:

<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>

What is missing? What should I remove? Is this something we can't do, i.e. are we really required to inherit ODataController directly with no intermediate parent class?

like image 861
Mickael Caruso Avatar asked Jan 13 '14 22:01

Mickael Caruso


1 Answers

In one of our projects We also use a generic ODataController base class where we actually use GetEntity for retrieving single entities and GetEntitySet for retrieving a list of entities.

According to your supplied URL and the resulting error message, the ODATA framework cannot find an ODataAction for ~/entityset. As you have given http://localhost:10000/odata/MyObjects as the example, the action in question cannot be public SingleResult<T> GetEntity([FromODataUri] int key) as this only corresponds to a query like this http://localhost:10000/odata/MyObjects(42).

Our code for a generic controller looks like this:

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

The code for a specific controller then is as short as this:

public partial class KeyNameValuesController : OdataControllerBase<T>
{
    public KeyNameValuesController(/* ... */)
        : base()
    {
        // there is nothing to be done here
    }
}

However we found out that both Get methods (for single result and enumerable result) actually have to start with Get. First we tried List instead of GetEntitySet and this did not work, as the framework then expects a POST for the List action).

You can actually verify and diagnose the resolving process by supplying a custom IHttpActionSelector as described in Routing and Action Selection in ASP.NET Web API (ahving a look at ASP.NET WEB API 2: HTTP Message Lifecycle might also be worth it).

So actually it is possible to use GetEntity as your method name as you originally tried in your example and there is no need to rename it to simple Get. In addition, there is no need for any modification in your ODATA configuration.

like image 119
Ronald Rink 'd-fens' Avatar answered Oct 22 '22 01:10

Ronald Rink 'd-fens'