Reputation: 666
I'm trying to move from our current implementation of ApiController to ODataController since it's the only way I found possible to enable returning OData Json formatted data. (Same with the problem here but the solution didn't work for me)
I've been trying to workout ODataController and I found it working well enough following this. However, my project implements a different routing from the default OData routing of the simple"~/odata/Entity". I need to group my controllers into areas since there are some controllers that duplicates in name.
Following this and this, I was able to implement custom routing and running it seems to reach the right controller and pass through it successfully. However, I still get an error in Fiddler of
"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json;odata=minimalmetadata; streaming=true; charset=utf-8'."
with an inner exception of
The related entity set could not be found from the OData path. The related entity set is required to serialize the payload
and now I've been stuck for hours with it. Without the route handlers and simply accessing data through "~/odata/Entity", my code works well enough. It's just when I implement the custom routing that it fails after passing through my controller code.
Any help would be appreciated.
Here's some code:
Global.asax:
//Added this on App_Start
config.Services.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector(config));
//Snippet from RegisterRoutes
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Entity>("Entities");
IEdmModel model = modelBuilder.GetEdmModel();
config.Routes.MapODataRoute(
routeName: "ODataDefault",
routePrefix: "{version}/{area}/{controller}", //Works since I could reach my controller
model: model);
Controller:
[HttpGet]
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Entity> Get()
{
EntitySet entitySet = new EntitySet();
return entitySet.Entities;
}
CustomHttpControllerSelector:
Inheritted from this. Basically, this just parses the request to get the specific controller descriptor.
private HttpControllerDescriptor GetController(HttpRequestMessage request)
{
HttpControllerDescriptor descriptor = null;
IHttpRouteData routeData = request.GetRouteData();
// Get variables from the route data.
string versionName = null;
routeData.Values.TryGetValue("version", out versionName );
string areaName = null;
routeData.Values.TryGetValue("area", out areaName);
string controllerName = null;
routeData.Values.TryGetValue("controller", out controllerName);
string fullName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", versionName, areaName, controllerName);
// Search for the controller.
// _controllerTypes is a list of HttpControllerDescriptors
var type = _controllerTypes.Where(t => t.Key.EndsWith(fullName, StringComparison.OrdinalIgnoreCase)).Select(t => t.Value).FirstOrDefault();
if (type != null)
{
descriptor = new HttpControllerDescriptor(_configuration, controllerName, type);
}
return descriptor;
}
Upvotes: 2
Views: 7597
Reputation: 666
Eventually solved this just by removing the {controller} part of the routePrefix and implementing a custom IODataRoutingConvention to enter a default controller value when the initial parsing of the route doesn't detect a controller value. Code is as follows:
public string SelectController(ODataPath odataPath, HttpRequestMessage request)
{
string controllerName = ""
//do stuff here to select controller
return controllerName;
}
I'm guessing this is the long substitute for the default and the contraints parameters in ApiController's map routing
RouteTable.Routes.MapHttpRoute(
name: "RouteName",
routeTemplate: "{version}/{area}/{controller}",
defaults: new { controller = controllerName },
constraints: new { version = v1 }
);
Note: Posting this as answer as of now but if anyone has a better explanation, I'll check it up. :)
Upvotes: 2