Skip to content

Versioned OData attribute routing not working on ASP.NET Core 3.1 and ASP.NET Core 5.0 (with or without Endpoint routing enabled) #710

Closed
@deepakku-work

Description

@deepakku-work

Hi,

I'm using Microsoft.AspNetCore.OData.Versioning version 5.0.0.

I just created a sample WeatherForecastsController project for ASP.NET Core 5.0, added the recommended startup settings (along with the ones for versioning the API), decorated the controller and the action methods with the OData routing attributes but it's not working as expected. Note: This used to work for me in ASP.NET Core 2.2 with the API versioning. I tried with Endpoint routing enabled as well as disabled with no luck. I don't know what changed or if I'm doing anything wrong.

I have 3 methods defined in the controller:

  • CreateWeatherForecast (POST)
  • GetWeatherForecasts (essentially a "List")
  • GetWeatherForecast (GET by ID)

As you can see from the REST requests below, it's not honoring the OData routes and seems to be only going by convention:

GET /v1 HTTP/1.1
HTTP/1.1 200 OK
{
	"@odata.context": "https://localhost:5001/v1/$metadata",
	"value": [
		{
			"name": "WeatherForecasts",
			"kind": "EntitySet",
			"url": "WeatherForecasts"
		}
	]
}

GET https://localhost:5001/v1/weatherforecasts/1ad7afe0-49df-48f4-be0c-d9c1452865a4 HTTP/1.1
HTTP/1.1 200 OK
{
	"@odata.context": "https://localhost:5001/v1/$metadata#WeatherForecasts/$entity",
	"id": "1ad7afe0-49df-48f4-be0c-d9c1452865a4",
	"date": "2020-12-18T19:31:12.5176592-08:00",
	"temperatureC": -15,
	"summary": "Cool"
}

GET https://localhost:5001/v1/weatherforecasts HTTP/1.1
HTTP/1.1 404 Not Found
Content-Length: 0

When I rename the List method (GetWeatherForecasts) to just "Get", the List call starts working again:

GET /v1/weatherforecasts HTTP/1.1
HTTP/1.1 200 OK
{
    "@odata.context": "https://localhost:5001/v1/$metadata#WeatherForecasts",
    "value": [
    {
	    "id": "80a99a08-fd0b-4b75-8875-c1aa4693fde3",
	    "date": "2020-12-18T19:34:20.3323084-08:00",
	    "temperatureC": 38,
	    "summary": "Sweltering"
    },
    {
	    "id": "3912b265-0ada-4ac9-a32d-af799e7fa4c7",
	    "date": "2020-12-19T19:34:20.3959579-08:00",
	    "temperatureC": 44,
	    "summary": "Hot"
    },
    {
	    "id": "3acc83a1-dafb-4f56-9e44-c6172873a23a",
	    "date": "2020-12-20T19:34:20.3963945-08:00",
	    "temperatureC": 48,
	    "summary": "Cool"
    },
    {
	    "id": "351ef82f-c18f-48b7-b057-dd6f67f940b1",
	    "date": "2020-12-21T19:34:20.3965475-08:00",
	    "temperatureC": 50,
	    "summary": "Sweltering"
    },
    {
	    "id": "5ff56225-4f22-49a6-b00a-9a3277e8195d",
	    "date": "2020-12-22T19:34:20.3966482-08:00",
	    "temperatureC": 43,
	    "summary": "Cool"
    }
    ]
}

Same with POST - when I rename it to PostWeatherForecast or just Post, it works; not otherwise.

The test code is available at: https://github.com/deepakku-work/WeatherForecast. For reference, the relevant details are shared below. Any pointers on what I'm doing wrong / if this is a known issue / pointers on how I can debug this further will be very much appreciated.

Project file:

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OData" Version="7.5.2" />
    <PackageReference Include="Microsoft.AspNetCore.OData.Versioning" Version="5.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.OData.Versioning.ApiExplorer" Version="5.0.0" />
  </ItemGroup>

Startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
        // Note: I have tried services.AddMvcCore() with EnableEndpointRouting set to false here as well.
        services.AddControllers();
        services.AddApiVersioning();
        services.AddOData().EnableApiVersioning();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, VersionedODataModelBuilder modelBuilder)
    {
        app.UseRouting();

        // Note: I have tried app.UseMvc() here (when I disabled endpoint routing) as well with no luck.
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            // Note: I have tried endpoints.MapODataRoute() here as well and it does not work.
            endpoints.MapVersionedODataRoute("versioned-odata", "v{version:apiVersion}", modelBuilder);
        });
    }

WeatherForecast model:

      public class WeatherForecast
      {
              public string 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; }
      }

WeatherForecastODataConfiguration:

    public class WeatherForecastODataConfiguration : IModelConfiguration
    {
        public void Apply(ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix)
        {
            switch (apiVersion.MajorVersion)
            {
                default:
                    ConfigureV1(builder);
                    break;
            }
        }

        private static void ConfigureV1(ODataModelBuilder builder)
        {
            var weatherForecastType = builder.EntitySet<WeatherForecast>("WeatherForecasts").EntityType;
            weatherForecastType.HasKey(r => r.Id);
            weatherForecastType.Property(r => r.Id).IsOptional();
        }
    }

WeatherForecastsController:

    [ApiVersion("1.0")]
    [ODataRoutePrefix("WeatherForecasts")]
    [ApiExplorerSettings(IgnoreApi = false)]
    public class WeatherForecastsController : ODataController
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastsController> _logger;

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

        [HttpPost]
        [ODataRoute]
        [ProducesResponseType(typeof(WeatherForecast), StatusCodes.Status200OK)]
        public WeatherForecast CreateWeatherForecast()
        {
            var rng = new Random();
            return new WeatherForecast
            {
                Id = Guid.NewGuid().ToString(),
                Date = DateTime.Now.AddDays(Enumerable.Range(1, 5).First()),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            };
        }

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

        [HttpGet]
        [ODataRoute("{id}")]
        [ProducesResponseType(typeof(WeatherForecast), StatusCodes.Status200OK)]
        public WeatherForecast GetWeatherForecast(string id)
        {
            var rng = new Random();
            return new WeatherForecast
            {
                Id = id,
                Date = DateTime.Now.AddDays(Enumerable.Range(1, 5).First()),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            };
        }

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions