9
votes

I'm currently experimenting with OData endpoints in ASP.NET MVC 4 Web API. I like the concept and try to come up with efficient ways to use it in our project. One question I have is the following: we have a service that is able to return an IQueryable and takes the name of an entity as Input:

public IQueryable GetAll(string entityName);

In standard Web API (as opposed to OData Controllers) I can create a generic controller, that can be called in the form /api/entities/{entityName} and returns the IQueryable. In case of an OData Controller, I carry out the following entity-specific steps:

  1. Register the entities in the model.
  2. Create a separate Controller for each entity that derives from EntitySetController<>.

I want to use the generic service and avoid as much entity-specific implementations as possible. The first step can easily be automated if the service can return a list of the entities and the corresponding types. That leaves step 2, because up to now I need to create a specific controller for each entity. I also want to avoid that and create a generic controller that uses the generic service.

Can anyone recommend a solution, maybe by influencing OData routing?

2

2 Answers

12
votes

You can create a custom routing convention that selects the same controller no matter what the entity set is. Example,

public class CustomControllerRoutingConvention : IODataRoutingConvention
{
    public string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
    {
        return null;
    }

    public string SelectController(ODataPath odataPath, HttpRequestMessage request)
    {
        return "SomeFixedContrllerNameWithoutTheControllerSuffix";
    }
}

You can register that routing convention using the following code,

IList<IODataRoutingConvention> routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new CustomControllerRoutingConvention());
config.Routes.MapODataRoute("OData", "odata", builder.GetEdmModel(), new DefaultODataPathHandler(), routingConventions);
7
votes

I came up against the same problem, and ended up writing a custom IHttpControllerSelector instead of an IODataRoutingConvention. IODataRoutingConvention looks like a good option if your generic controller doesn't require generics :) . But since IODataRoutingConvention.SelectController() only returns a string, I don't see how it will work for instantiating a controller with generic type parameters.

I decided this problem needs a good, general-purpose, open-source solution - so I created one: https://github.com/EntityRepository/ODataServer . It's prerelease now, but I'm currently doing a lot of work on it. I think there's more to it than just choosing the right controller, there are general patterns to define for shared controllers, and by default Web API OData expects strongly typed and strongly named navigation properties which makes it challenging to create a re-usable implementation.