3
votes

Using .net Core 1.1, with the Microsoft.AspNetCore.OData libraries, I am able to get an OData endpoint working with my simple controller to perform get, $expand, and other queries. However, I can't get it to return the $metadata to be returned. This question ($Metadata with WebAPi OData Attribute Routing Not Working) is for the same problem, however the .Net APIs have changed since this was posted.

Is there a setting, flag, or something else I need to enable?

This (http://localhost:52315/odata) seems to return the meta data,

{
  "@odata.context":"http://localhost:52315/odata/$metadata","value":[
    {
      "name":"Users","kind":"EntitySet","url":"Users"
    },{
      "name":"HelloComplexWorld","kind":"FunctionImport","url":"HelloComplexWorld"
    }
  ]
}

this (http://localhost:52315/odata/$metadata) gives me the error:

An unhandled exception occurred while processing the request.
NotSupportedException: No action match template '$metadata'
in 'MetadataController'

Microsoft.AspNetCore.OData.Routing.Conventions.DefaultODataRoutingConvention.SelectAction(RouteContext routeContext)

My Startup.cs looks like this:

public void Configure(IApplicationBuilder app) {
   app.UseDeveloperExceptionPage();
   app.UseOData("odata");
   app.UseMvcWithDefaultRoute();
}

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc().AddWebApiConventions();
    services.AddSingleton<ISampleService, ApplicationDbContext>();
    services.AddOData<ISampleService>(builder =>
    {
         builder.Namespace = "Sample";
         builder.EntityType<ApplicationUser>();
         builder.EntityType<Product>();
         builder.Function("HelloComplexWorld").Returns<Permissions>();
    });
}

NOTE: I can work around it by adding this at the start of my ConfigureServices(...) method, though it seems wrong given $metadata support should be part of the core platform.

app.Use(async (context, next) => {
     if (0 == string.Compare(context.Request.Path, @"/odata/$metadata", true)) {
        context.Request.Path = "/odata";
   }
   await next.Invoke();
});
2

2 Answers

1
votes

I revisited this today and had more success using the Microsoft.AspNetCore.OData.vNext 6.0.2-alpha-rtm package. Referencing this example, the metadata and default OData routing worked as expected. My minimal configuration is:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOData();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        var modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.EntitySet<Document>("Documents");

        app.UseMvc(builder =>
        {
            builder.MapODataRoute("odata", modelBuilder.GetEdmModel());
        });
    }
0
votes

The /odata/$metadata route should return "an XML representation of the service’s data model" (EDMX) (according to the OData v4 Web API documentation). This is not the same as the service root /odata/, which returns the top level description of resources published by the OData service (as shown in your example).

I encountered the same issue using the pre-release Microsoft.AspNetCore.OData 1.0.0-rtm-00015, since an official release is not yet available (see open issue on OData Web API repo).

To illustrate the point, you could manually emit the metadata, as in the crude example below. (You can find the InMemoryMessage class in the OData/odata.net GitHub repo.)

However, I would suggest waiting for an official release of OData ASP.NET Core as, quoting from the above issue, the "branch is still in its early stages and we may take a different approach once we have our architecture finalized". So things may well change... and $metadata should definitely work "out of the box"!

        app.Use((context, func) =>
        {
            if (context.Request.Path.StartsWithSegments(new PathString("/data/v1/$metadata"), StringComparison.OrdinalIgnoreCase))
            {
                var model = app.ApplicationServices.GetService<IEdmModel>();

                MemoryStream stream = new MemoryStream();
                InMemoryMessage message = new InMemoryMessage() {Stream = stream};

                ODataMessageWriterSettings settings = new ODataMessageWriterSettings();

                ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage)message, settings, model);
                writer.WriteMetadataDocument();

                string output = Encoding.UTF8.GetString(stream.ToArray());

                return context.Response.WriteAsync(output);
            }

            return func();
        });