Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OData routing causing exception with versioned API endpoints #1122

Open
JackDC16 opened this issue Feb 28, 2025 · 3 comments
Open

OData routing causing exception with versioned API endpoints #1122

JackDC16 opened this issue Feb 28, 2025 · 3 comments

Comments

@JackDC16
Copy link

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.

@JackDC16
Copy link
Author

UPDATE:

I've managed to get past the exception by removing the RouteAttribute from my controllers, but my endpoints are being duplicated in the OData route debug page:

Image

@commonsensesoftware
Copy link
Collaborator

If you have the world's simplest repro, that would be really useful in troubleshooting; otherwise, I can try to recreate what you've shown so far.

@JackDC16
Copy link
Author

JackDC16 commented Mar 3, 2025

Sorry, I don't have a repro that I can provide. I'd have to work on putting something together this week when I get some time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants