Skip to content

OData routing causing exception with versioned API endpoints #1423

Closed
@JackC-jbtc

Description

@JackC-jbtc

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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions