Search code examples
asp.net-coreodata

CreateRef method migrated to .Net Core results in 404, how to implement Create Relationships in OData with .Net Core


I have 2 POCOs, Lessons and Traits with int PKs. I have navigation properties set up such that I can successfully $expand like so:

  • http://localhost:54321/odata/Lessons?$expand=Traits
  • http://localhost:54321/odata/Traits?$expand=Lessons

My final hurdle in migrating project from Net 461 to .Net Core 2 is Creating Relationships.

Specifically, when I try to call the following method, with the following request, I get a 404.

[AcceptVerbs("POST", "PUT")]        
public async Task<IActionResult> CreateRef(
    [FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
    ....  Do Work
}

Postman request:

http://localhost:54321/odata/Lessons(1)/Traits/$ref

body:

 {
    "@odata.id":"http://localhost:54321/OData/traits(1)"
 }

The following is my Startup.Configure method.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    var builder = ConfigureOdataBuilder(app);

    app.UseMvc(routeBuilder =>
    {
        routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
        routeBuilder.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());

        // Work-around for #1175
        routeBuilder.EnableDependencyInjection();
        routeBuilder.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}"); // enable mvc controllers
    });            
}

private ODataConventionModelBuilder ConfigureOdataBuilder(IApplicationBuilder app)
{
    var builder = new ODataConventionModelBuilder(app.ApplicationServices);

    builder.EntitySet<Lessons>(nameof(Lessons));
    builder.EntitySet<Traits>(nameof(Traits));       

    return builder;
}

Question: How do I reach this controller method?

Things I have tried,


Solution

  • It was a long way, but I finally found the answer.

    [ODataRoute("lessons({lessonId})/traits({traitId})/$ref")]
    public IActionResult CreateRef([FromODataUri] int lessonId, [FromODataUri] int traitId)
    {
        //do work
    }
    

    Important: You have to call the id-params as I did. Don´t just call them Id - otherwise you´ll get a 404.

    One more thing...

    For those who tried the way from the microsoft docs - the Api-Names changed.. You don´t need them for this task, but if you have to convert an Uri to an OData-Path, here is an Uri-Extension doing this for you:

    public static Microsoft.AspNet.OData.Routing.ODataPath CreateODataPath(this Uri uri, HttpRequest request)
    {
        var pathHandler = request.GetPathHandler();
        var serviceRoot = request.GetUrlHelper().CreateODataLink(
                                request.ODataFeature().RouteName, 
                                pathHandler, 
                                new List<ODataPathSegment>());
    
        return pathHandler.Parse(serviceRoot, uri.LocalPath, request.GetRequestContainer());
    }
    

    If you have an Uri like this: http://localhost:54321/OData/traits(1) you can split this into OData-segments to get e.g. the navigation: returnedPath.NavigationSource or the specified key: returnedPath.Segments.OfType<KeySegment>().FirstOrDefault().Keys.FirstOrDefault().Value