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!
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.
IEndpointRouteBuilder.MapODataRoute(VersionedODataModelBuilder)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.