Situation
I created the following Model classes
public class Car
{
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<PartState> PartStates {get;set; }
}
public class PartState
{
public int Id {get;set;}
public string State {get;set;}
public int CarId {get;set;}
public virtual Car Car {get;set;}
public int PartId {get;set;}
public virtual Part Part {get;set;}
}
public class Part
{
public int Id {get;set;}
public string Name {get;set;}
}
And a matching DbContext
public class CarContext : DbContext
{
public DbSet<Car> Cars {get;set;}
public DbSet<PartState> PartStates {get;set;}
public DbSet<Part> Parts {get;set;}
}
And created a WebApplication to make this available via odata, using the scaffolding template "Web API 2 OData Controller with Actions, using Entity Framework"
also i create following webapi config:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Car>("Cars");
builder.EntitySet<PartState>("PartStates");
builder.EntitySet<Part>("Parts");
var edmModel = builder.GetEdmModel();
config.Routes.MapODataRoute("odata", "odata", edmModel);
}
}
I now want to add the following Method to my Cars Controller
// GET: odata/Cars(5)/Parts
[Queryable]
public IQueryable<Part> GetParts([FromODataUri] int key)
{
var parts = db.PartStates.Where(s => s.CarId == key).Select(s => s.Part).Distinct();
return parts;
}
And retrieve the data with this Url:
http://localhost/odata/Cars(1)/Parts
But it does not work, instead i get the following error:
{
"odata.error":{
"code":"","message":{
"lang":"en-US","value":"No HTTP resource was found that matches the request URI 'http://localhost/odata/Cars(1)/Parts'."
},"innererror":{
"message":"No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.","type":"","stacktrace":""
}
}
}
Question
So my question is, is that even possible?!
I tried to create a Navigation property manually, and added it to the edm model, while this does resolve the issue to invoke the new method, it also introduces new Errors.
EDIT:
What id did try to add it manually in this way:
var edmModel = (EdmModel)builder.GetEdmModel();
var carType = (EdmEntityType)edmModel.FindDeclaredType("Car");
var partType = (EdmEntityType)edmModel.FindDeclaredType("Part");
var partsProperty = new EdmNavigationPropertyInfo();
partsProperty.TargetMultiplicity = EdmMultiplicity.Many;
partsProperty.Target = partType;
partsProperty.ContainsTarget = false;
partsProperty.OnDelete = EdmOnDeleteAction.None;
partsProperty.Name = "Parts";
var carsProperty = new EdmNavigationPropertyInfo();
carsProperty.TargetMultiplicity = EdmMultiplicity.Many;
carsProperty.Target = carType;
carsProperty.ContainsTarget = false;
carsProperty.OnDelete = EdmOnDeleteAction.None;
carsProperty.Name = "Cars";
var nav = EdmNavigationProperty.CreateNavigationPropertyWithPartner(partsProperty, carsProperty);
carType.AddProperty(nav);
config.Routes.MapODataRoute("odata", "odata", edmModel);
while this allowed me to invoke the above speciefied method trough the also above specified URL, it gave me the following error:
{
"odata.error":{
"code":"","message":{
"lang":"en-US","value":"An error has occurred."
},"innererror":{
"message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata=fullmetadata; charset=utf-8'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
"message":"The related entity set could not be found from the OData path. The related entity set is required to serialize the payload.","type":"System.Runtime.Serialization.SerializationException","stacktrace":" at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)\r\n at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
}
}
}
}
Taking @FengZhao's answer further, in order to get the url odata/Cars working you also need to register the navigation property link builder to entity set link builder.
You have to call "AddNavigationTarget" on the EntitySet. Assume that your namespace is "MyNamespace", then add the following code to your WebApiConfig.cs. In this way, retrieving the data with "Get: odata/Cars(1)/Parts" will work.
Web Api cannot resolve your URL against any of registered URI templates.
Use Route Debugger to figure this out. http://blogs.msdn.com/b/webdev/archive/2013/04/04/debugging-asp-net-web-api-with-route-debugger.aspx
I believe our problem is the
id
parameter that you pass via url Try making it more explicit