Abstract Generic ODataController Class Leads To 'No HTTP resource was found'
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.
To determine which action to invoke, the framework uses a routing table. The Visual Studio project template for Web API creates a default route:
routes.MapHttpRoute(name: "API Default",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional });
Routing by Action Name
With the default routing template, Web API uses the HTTP method to select the action. However, you can also create a route where the action name is included in the URI:
routes.MapHttpRoute(name: "ActionApi",routeTemplate: "api/{controller}/{action}/{id}",defaults: new { id = RouteParameter.Optional });
I configured config as follows:
config.Routes.MapHttpRoute( name: "GetMessage", routeTemplate: "api/{controller}/{action}/{quoteName}", defaults: new { quoteName = RouterParameters.Optional } );
Access your URI like this:
http://localhost:42201/api/Extract/GetMessage/Q3
OR
http://localhost:42201/api/Extract/GetMessage/?quotename=Q3