Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting related entities ASP.NET WebApi OData v4 results in "No HTTP resource was found that matches the request URI"

I followed this asp.net tutorial by Mike Wasson, and managed to set up the related entities just fine, but when I applied this logic to my project the more complex entity relations (in that there are more of them; that's the only difference) wouldn't succeed in an OData call, I got a 404 with this payload:

{
  "error": {
    "code": "",
    "message": "No HTTP resource was found that matches the request URI 'http://localhost:19215/Menus(c94f7f98-6987-e411-8119-984be10349a2)/MenuPermissions'.",
    "innererror": {
      "message": "No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.",
      "type": "",
      "stacktrace": ""
    }
  }
}

The tutorial doesn't mention having to set up EdmModel navigations and Mike Wasson makes a point of pointing out that "asp.net is official documentation :-)"; so, I have spent a while trying to get these related entities working, thinking I had set up the project incorrectly.

I thought it might have something to do with the version of ASP.NET OData libraries that NuGet was installing (the NuGet Console installs 6.9.x, whereas the NuGet Dialog installs 6.5.x). I also wondered if it was because I set the project up as a completely empty project and then use OWIN, so I tried it with a pure ASP.NET templated solution. I also tried a couple of other possible solutions: OData-route-attributes on my controller methods; and including my data layer and models all in the same library (I have them separated out to keep DRY); I even attempted to use the WebApi route debugger by Rick Anderson - I wouldn't attempt to use this again!

All to no avail.

There was a brief moment when they worked, but I don't know why; they ceased to work on the next build/run - I guess I changed something in between, but it was very minor and I was losing confidence at every step.

Then I decided that Mike Wasson must've just taken the path of least resistance in his tutorial and so I reverted to this SO question/answer and modified it for use with ODataConventionModelBuilder and reuse, as I will explain in my answer below.

If anyone knows of a simpler way of getting this to work, please let me know, otherwise I recommend just biting the bullet and writing those EdmModel-Navigations in my answer below.

like image 666
lukkea Avatar asked Dec 19 '14 15:12

lukkea


2 Answers

As I mention in the question, I tried many solutions to get this to work, but none were consistent in actually solving the problem and I kept avoiding the solution laid out in this SO question/answer because the tutorial is specifically for v4 and I figured that answer must be for an older version (how unwise).

So that answer does solve the problem, but requires some work to fit directly into OData v4 and an ODataConventionModelBuilder; this is why I have posted this question and answer; to provide a solution, specifically for OData v4 and ODataConventionModelBuilder, in the hope that others won't lose the time I have looking into this.

First, set up your EdmModel:

private static IEdmModel GetEdmModel()
{
    var builder = new ODataConventionModelBuilder();
    builder.EnableLowerCamelCase();
    builder.EntitySet<Menu>("Menus");
    builder.EntitySet<MenuPermission>("MenuPermissions");
    var edmModel = builder.GetEdmModel();
    AddNavigations(edmModel); //see below for this method
    return edmModel;
}

Second AddNavigations:

private static void AddNavigations(IEdmModel edmModel)
{
    AddMenuPermissionsNavigation(edmModel);
}

private static void AddMenuPermissionsNavigation(IEdmModel edmModel)
{
    var menus = (EdmEntitySet) edmModel.EntityContainer.FindEntitySet("Menus");
    var menuPermissions = (EdmEntitySet)edmModel.EntityContainer.FindEntitySet("MenuPermissions");
    var menuType = (EdmEntityType) edmModel.FindDeclaredType("iiid8.cms.data.models.Menu"); //"iiid8.cms.data.models" is the C# namespace
    var menuPermissionType = (EdmEntityType)edmModel.FindDeclaredType("iiid8.cms.data.models.MenuPermission"); //as above, "iiid8.cms.data.models" is the C# namespace
    AddOneToManyNavigation("MenuPermissions", menus, menuPermissions, menuType, menuPermissionType);
    AddManyToOneNavigation("Menu", menus, menuPermissions, menuType, menuPermissionType);
}

private static void AddOneToManyNavigation(string navTargetName, EdmEntitySet oneEntitySet, EdmEntitySet manyEntitySet,
    EdmEntityType oneEntityType, EdmEntityType manyEntityType)
{
    var navPropertyInfo = new EdmNavigationPropertyInfo
    {
        TargetMultiplicity = EdmMultiplicity.Many,
        Target = manyEntityType,
        ContainsTarget = false,
        OnDelete = EdmOnDeleteAction.None,
        Name = navTargetName
    };
    oneEntitySet.AddNavigationTarget(oneEntityType.AddUnidirectionalNavigation(navPropertyInfo), manyEntitySet);
}

private static void AddManyToOneNavigation(string navTargetName, EdmEntitySet oneEntitySet, EdmEntitySet manyEntitySet,
    EdmEntityType oneEntityType, EdmEntityType manyEntityType) {
    var navPropertyInfo = new EdmNavigationPropertyInfo {
        TargetMultiplicity = EdmMultiplicity.One,
        Target = oneEntityType,
        ContainsTarget = false,
        OnDelete = EdmOnDeleteAction.None,
        Name = navTargetName
    };
    manyEntitySet.AddNavigationTarget(manyEntityType.AddUnidirectionalNavigation(navPropertyInfo), oneEntitySet);
}

Finally, call GetEdmModel from WebApiConfig.Register

config.MapODataServiceRoute("odata", null, GetEdmModel());

Now call your OData service's one-to-many and many-to-one navigations from your client and all should be good with your world. In my case the calls look like this:

One-to-many:

http://localhost:19215/Menus(c94f7f98-6987-e411-8119-984be10349a2)/MenuPermissions

Many-to-one:

http://localhost:19215/MenuPermissions(ba0da52a-6c87-e411-8119-984be10349a2)/Menu

This answer assumes you set up the rest of your project just like Mike Wasson suggests in the tutorial linked in the question (that link is to Part 3 - you will need to follow Part 1 first!).

like image 140
lukkea Avatar answered Oct 04 '22 09:10

lukkea


I am using ASP.NET 5, Web API 2.2, and Entity Framework.

Another developer and I have also spent hours trying to figure out why, after following that same tutorial to a T, we couldn't get a relational route like the following to return anything other than a 404:

/odata/Supplier(1)/Products

We also tried the route debugger referenced in the OP, and it failed to produce anything other than a blank screen.

Luckily, for our needs, one of our random experiments worked, and that was to use the ODataRoute attribute like such:

    [EnableQuery]
    [ODataRoute("Suppliers({key})/Products")]
    public IQueryable<Product> GetProductsForSupplier([FromODataUri] int key)
    {
        ...
    }
like image 29
l p Avatar answered Oct 04 '22 08:10

l p