Description
I have inherited a .NET 6 Web API project utilising OData and the Asp.Versioning packages. The versioning functionality was not 100% correct, and now that the project is required for more regular usage, I want to implement the versioning correctly.
The issue I am hitting is this runtime exception:
Attribute routes with the same name 'odata/v{version:apiVersion}/Parts' must have the same template: Action: '...v2.Controllers.PartsController.Get' - Template: 'odata/v{version:apiVersion}/Parts/odata/v{version:apiVersion}/Parts' Action: '...v2.Controllers.PartsController.Get' - Template: 'odata/v{version:apiVersion}/Parts' Action: '...v1.Controllers.PartsController.Get' - Template: 'odata/v{version:apiVersion}/Parts' Action: '...v1.Controllers.PartsController.Get' - Template: 'odata/v{version:apiVersion}/Parts'
My controllers look like:
namespace ...Api.Versions.v1.Controllers
{
[ApiExplorerSettings(GroupName = "v1")]
[ApiVersion("1")]
[Route("odata/v{version:apiVersion}/[controller]")]
public class PartsController : ODataController
{
[Produces("application/json")]
[EnableQuery]
[HttpGet]
public async Task<IQueryable<Part>> Get()
{
...
}
}
}
and
namespace ...Api.Versions.v2.Controllers
{
[ApiExplorerSettings(GroupName = "v2")]
[ApiVersion("2")]
[Route("odata/v{version:apiVersion}/[controller]")]
public class PartsController : ODataController
{
[Produces("application/json")]
[EnableQuery]
[HttpGet]
public async Task<IQueryable<Part>> Get()
{
...
}
}
}
My Startup.cs has the following configuration:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllers()
.AddOData(options =>
{
options.EnableQueryFeatures(maxTopValue: 8000);
options.TimeZone = TimeZoneInfo.Utc;
});
services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1);
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddMvc()
.AddOData(options =>
{
options.AddRouteComponents("odata/v{version:apiVersion}");
})
.AddODataApiExplorer(options =>
{
options.GroupNameFormat = "'v'V";
options.SubstituteApiVersionInUrl = true;
});
...
}
public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
...
}
and my EDM configuration is as follows:
namespace ...Api.Utilities.ModelConfiguration
{
using Asp.Versioning;
using Asp.Versioning.OData;
using Microsoft.OData.ModelBuilder;
public class PartModelConfiguration : IModelConfiguration
{
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix)
{
switch (apiVersion.MajorVersion)
{
case 1:
ConfigureV1(builder);
break;
case 2:
ConfigureV2(builder);
break;
default:
ConfigureCurrent(builder);
break;
}
}
private void ConfigureV1(ODataModelBuilder builder) =>
ConfigureCurrent(builder);
private void ConfigureV2(ODataModelBuilder builder) =>
ConfigureCurrent(builder);
private EntityTypeConfiguration<Part> ConfigureCurrent(ODataModelBuilder builder)
{
var part = builder.EntitySet<Part>("Parts").EntityType;
part.HasKey(p => p.IdPart);
return part;
}
}
}
Right now there is no difference between models for v1 and v2, this is purely to test functionality.
I'm trusting that the above configuration is automatically registered for DI as per the last section on this page: https://github.com/dotnet/aspnet-api-versioning/wiki/OData-Model-Configurations
I feel like there is some small piece of config that I've missed, but struggling to find what that might be.