Since Web API OData V6.0.0 beta, we have integrated with the popular dependency injection (DI) framework Microsoft.Extensions.DependencyInjection. By means of DI, we can significantly improve the extensibility of Web API OData as well as simplify the APIs exposed to the developers. Meanwhile, we have incorporated DI support throughout the whole OData stack (including ODataLib, Web API OData and RESTier) thus the three layers can consistently share services and custom implementations via the unified DI container in an OData service. For example, if you register an
ODataPayloadValueConverter in a RESTier API class, the low-level ODataLib will be aware of that and use it automatically because they share the same DI container.
For the fundamentals of DI support in OData stacks, please refer to this docs from ODataLib. After understanding that, we can now take a look at how Web API OData implements the container, takes use of it and injects it into ODataLib.
Implement the Container Builder
By default, if you don’t provide a custom container builder, Web API OData will use the
DefaultContainerBuilder which implements
IContainerBuilder from ODataLib. The default implementation is based on the Microsoft DI framework introduced above and what it does is just delegating the builder operations to the underlying
But if you want to use a different DI framework (e.g., Autofac) or make some customizations to the default behavior, you will need to either implement your own container builder from
IContainerBuilder or inherit from the
DefaultContainerBuilder. For the former one, please refer to the docs from ODataLib. For the latter one, here is a simple example to illustrate how to customize the default container builder.
After implementing the container builder, you need to register that container builder in
HttpConfiguration to tell Web API OData that you want to use your custom one. Please note that you MUST call
EnableDependencyInjection because the root container will be actually created in these two methods. Setting the container builder factory after its creation is meaningless. Of course, if you wish to keep the default container builder implementation,
UseCustomContainerBuilder doesn’t need to be called at all.
Register the Required Services
Basic APIs to register the services have already been documented here. Here we mainly focus on the APIs from Web API OData that help to register the services into the container builder. The key API to register the required services for an OData service is an overload of
MapODataServiceRoute which takes a
configureAction to configure the container builder (i.e., register the services).
Theoretically you can register any service within the
configureAction but there are two mandatory services that you are required to register: the
IEdmModel and a collection of
IRoutingConvention. Without them, the OData service you build will NOT work correctly. Here is an example of calling the API where a custom batch handler
MyBatchHandler is registered. You are free to register any other service you like to the
You might also find that we still preserve the previous overloads of
MapODataServiceRoute which take batch handlers, path handlers, HTTP message handlers, etc. They are basically wrapping the first overload that takes a
configureAction. The reason why we keep them is that we want to give the users convenience to create OData services and bearings to the APIs they are familiar with.
Once you have called any of the
MapODataServiceRoute overloads, the dependency injection for that OData route is enabled and an associated root container is created. As we internally maintain a dictionary to map the route name to its corresponding root container (1-1 mapping), multiple OData routes (i.e., calling
MapODataServiceRoute multiple times) are still working great and the services registered in different containers (or routes) will not impact each other. That said, if you want a custom batch handler to work in the two OData routes, register them twice.
Enable Dependency Injection for HTTP Routes
It’s also possible that you don’t want to create OData routes but just HTTP routes. The dependency injection support will NOT be enabled right after you call
MapHttpRoute. In this case, you have to call
EnableDependencyInjection to enable the dependency injection support for ALL HTTP routes. Please note that all the HTTP routes share the SAME root container which is of course different from the one of any OData route. That said calling
EnableDependencyInjection has nothing to do with
Please also note that the order of
EnableDependencyInjection doesn’t matter because they have no dependency on each other.
Manage and Access the Request Container
Given a root container, we can create scoped containers from it, which is also known as request containers. Mostly you don’t need to manage the creation and destruction of request containers yourself but there are some rare cases you have to touch them. Say you want to implement your custom batch handler, you have the full control of the multi-part batch request. You parse and split it into several batch parts (or sub requests) then you will be responsible for creating and destroying the request containers for the parts. They are implemented as extension methods to
To create the request container, you need to call the following extension method on a request. If you are creating the request container for a request that comes from an HTTP route, just pass
null for the
To delete the request container from a request, you need to call the following extension method on a request. The parameter
dispose indicates whether to dispose that request container after deleting it from the request. Disposing a request container means that all the scoped and transient services within that container will also be disposed if they implement
To get the request container associated with that request, simply call the following extension method on a request. Note that you don’t need to provide the route name to get the request container because the container itself has already been stored in the request properties during
CreateRequestContainer. There is also a little trick in
GetRequestContainer that if you have never called
CreateRequestContainer on the request but directly call
GetRequestContainer, it will try to create the request container for all the HTTP routes and return that container. Thus the return value of
GetRequestContainer should never be
Please DO pay attention to the lifetime of the services. DON’T forget to delete and dispose the request container if you create it yourself. And scoped services will be disposed after the request completes.
Services Available in Web API OData
Currently services Available in Web API OData include:
IODataPathHandlerwhose default implementation is
DefaultODataPathHandlerand lifetime is
XXXQueryValidatorwhose lifetime are all
ODataXXXDeserializerwhose lifetime are all
Singleton. But please note that they are ONLY effective when
DefaultODataDeserializerProviderare present. Custom serializer and deserializer providers are NOT guaranteed to call those serializers and deserializers from the DI container.
ODataDeserializerProviderwhose implementation types are
DefaultODataDeserializerProviderrespectively and lifetime are all
Singleton. Please note that you might lose all the default serializers and deserializers registered in the DI container if you don’t call into the default providers in your own providers.
IAssembliesResolverwhose implementation type is the default one from ASP.NET Web API.
FilterBinderwhose implementation type is
EnableQueryAttributeinstance will create its own
FilterBinder. Override it if you want to customize the process of binding a $filter syntax tree.