Aspnet-api-versioning: Endpoint routing with apiversioning support for OData core 7.4.1

Created on 8 Jul 2020  路  9Comments  路  Source: microsoft/aspnet-api-versioning

Endpoint routing for OData core seems to be supported since 7.4.0, I am just wondering whether the apiversioning can work with it or not?

I have the following code in my startup:

            services.AddApiVersioning(options =>
            {
                options.ReportApiVersions = true;
                options.AssumeDefaultVersionWhenUnspecified = true;
                options.DefaultApiVersion = new ApiVersion(1, 0);
            });

            services.AddVersionedApiExplorer(options =>
            {
                options.GroupNameFormat = "'v'VVV";
                options.SubstituteApiVersionInUrl = true;
            });

            services.AddODataApiExplorer(options =>
            {
                options.GroupNameFormat = "'v'VVV";
                options.SubstituteApiVersionInUrl = true;
            });

            services.AddOData().EnableApiVersioning();

            services.AddControllers(options =>
                {
                    foreach (var outputFormatter in options.OutputFormatters.OfType<ODataOutputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
                    {
                        outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/prs.odatatestxx-odata"));
                    }

                    foreach (var inputFormatter in options.InputFormatters.OfType<ODataInputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
                    {
                        inputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/prs.odatatestxx-odata"));
                    }
                }).AddControllersAsServices().SetCompatibilityVersion(CompatibilityVersion.Latest);

And

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.Select().Filter().OrderBy().Count().MaxTop(10);
                endpoints.MapODataRoute("odata", "odata", this.GetEdmModel());
                endpoints.EnableDependencyInjection();
            });

I can sort of get the ApiVersioning to work for a GET OData API, but it doesn't seem to work for POST - I always hit the following NRE:

System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.AspNetCore.Mvc.Routing.ApiVersionMatcherPolicy.EvaluateApiVersion(CandidateSet candidates, ApiVersion apiVersion)
   at Microsoft.AspNetCore.Mvc.Routing.ApiVersionMatcherPolicy.ApplyAsync(HttpContext httpContext, CandidateSet candidates)
   at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.SelectEndpointWithPoliciesAsync(HttpContext httpContext, IEndpointSelectorPolicy[] policies, CandidateSet candidateSet)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.<Invoke>g__AwaitMatch|8_1(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)

NOTE: even for the get API, it doesn't work properly - If I call the GET API with a non-existent version, I get the same NRE.

Did I misconfig something or it's indeed not supported? Thanks in advance!

asp.net core duplicate odata

All 9 comments

Hi @commonsensesoftware , can you take a look at this?

@dxynnez , from my understanding, Endpoint routing with OData is not supported at this time.

https://github.com/microsoft/aspnet-api-versioning/issues/613
https://github.com/microsoft/aspnet-api-versioning/issues/616

From the issues, it looks like @commonsensesoftware will be looking at it, but has not had a chance as yet. He has made a comment in in one of the above issues that it might just work, but without MapVersionedODataRoutes being implemented, unless you have only one version, I am not sure how it does. I should say that I could very well be wrong about this point, and if you have got it somewhat working, I would be interested to see what you have done as in my case I only have GET requests.

Having the exakt same issue and I cant for the world figure out how to solve it. Still no solution available for Microsoft.AspNetCore.OData 7.4.1 and Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer? Real shame

As a sidenote though the endpoint routing does work, API calls are successful as long as [ApiController] attribute is removed. Sadly then it does not get included by the ApiExplorer.

After looking through some code I found out how to do it. Order of registration is important, below code seems to work perfectly

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddVersionedApiExplorer(
                options =>
                {
                    options.GroupNameFormat = "'v'VVV";
                    options.SubstituteApiVersionInUrl = true;
                });

            services.AddApiVersioning(o =>
            {
                o.ReportApiVersions = true;
                o.AssumeDefaultVersionWhenUnspecified = true;
                o.DefaultApiVersion = new ApiVersion(1, 0);
            });

            services.AddOData().EnableApiVersioning();

            services.AddODataApiExplorer(
                options =>
                {
                    options.GroupNameFormat = "'v'VVV";
                    options.SubstituteApiVersionInUrl = true;
                });


            services.AddControllers();

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
            });

            SetOutputFormatters(services);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.Select().Filter().Expand().MaxTop(10);
                endpoints.MapODataRoute("odata", "odata/v{version:apiVersion}", GetEdmModel());
            });

            var provider = app.ApplicationServices.GetService<IApiVersionDescriptionProvider>();
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                foreach (var description in provider.ApiVersionDescriptions)
                {
                    c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
                        description.GroupName.ToUpperInvariant());
                }
            });
        }

        IEdmModel GetEdmModel()
        {
            var builder = new ODataConventionModelBuilder();
            builder.EntitySet<WeatherForecast>("WeatherForecast");
            return builder.GetEdmModel();
        }

        private static void SetOutputFormatters(IServiceCollection services)
        {
            services.AddMvcCore(options =>
            {
                IEnumerable<ODataOutputFormatter> outputFormatters =
                    options.OutputFormatters.OfType<ODataOutputFormatter>()
                        .Where(foramtter => foramtter.SupportedMediaTypes.Count == 0);

                foreach (var outputFormatter in outputFormatters)
                {
                    outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata"));
                }
            });
        }
    }

    public class WeatherForecast
    {
        public Guid Id { get; set; }

        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { get; set; }
    }

    [Route("odata/v{version:apiVersion}/weatherforecast")]
    [ApiVersion("1.0")]
    [ApiExplorerSettings(IgnoreApi = false)]
    public class WeatherForecastController : ODataController
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        [EnableQuery]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Id = Guid.NewGuid(),
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }

@david-ernstsson-stratsys can you include a sample of your IApplicationBuilder code? I can't get it to work.

@david-ernstsson-stratsys Can you show and example of where you are setting up the Version in the URL?

@StevenLiekens @rusdawg400 Fully working code above.

Also note that builder.EntitySet<WeatherForecast>("WeatherForecast"); cant be changed to WeatherForecasts (with an s at the end) or anything similar change or it'll fail

@david-ernstsson-stratsys, there's still a problem because you can't use VersionedODataModelBuilder in the call to MapODataRoute.

I've tried many different things and I feel confident to say that there's no way to get API versioning to work with OData endpoint routing right now.

PS: I'm not trying to be rude, just trying to put my two cents in. This won't work without changes to the library.

  1. Add an overload for IEndpointRouteBuilder.MapODataRoute(VersionedODataModelBuilder)
  2. Make endpoint routing work with other version schemes (query string, request header)

5.0.0-RC.1 is now available with support for _Endpoint Routing_. There are some significant changes. Be sure to review the release notes and sample applications. The official release will likely occur after 2 weeks of _burn-in_. Please report any difficulty as a new issue. Thanks.

Was this page helpful?
0 / 5 - 0 ratings