IUrlHelper.Link returns null for OData route name
@xuzhg is already working on this.
Since Jul 11, 2022.
- Dominant language
- C#
- Stars
- 505
- Forks
- 186
- PR merge metrics
- No merged PRs in 30d
Description
Assemblies affected
Microsoft.AspNetCore.OData 8.0.10
Describe the bug
Failed to generate absolute URL for the specified OData route name.
Reproduce steps
- Create Asp.Net Core Web API project
- Include Nuget packages:
- Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer 5.0.0
- Microsoft.AspNetCore.OData 8.0.10
- Microsoft.AspNetCore.OData.NewtonsoftJson 8.0.4
- Swashbuckle.AspNetCore 5.6.3
- Swashbuckle.AspNetCore.Newtonsoft 6.3.1
- Add ResourceData
- Define routes
- Add two controllers:
- ResourceManagementController - non-OData controller for managing resources
- ResourceDataController - OData controller for resource data requests
- Start application
Repo: https://github.com/ivanenkomaksym/ODataCoreUrlHelper
Data Model
ResourceData type:
public class ResourceData
{
public string Id { get; init; }
}
Define routes:
internal static class Routes
{
public const string VersionedRoutePrefix = "api/v{version:apiVersion}";
public const string ResourceDataControllerRoute = "api/v{version:apiVersion}/resources/{token}";
public const string GetResourceDataRoute = nameof(ResourceDataController) + "_" + nameof(ResourceDataController.GetData);
}
Non-OData controller for managing resources:
[ApiController]
[ApiVersion("1.0")]
[Route(Routes.VersionedRoutePrefix)]
public class ResourceManagementController : ControllerBase
{
[HttpPost("resources")]
public IActionResult Create(ApiVersion version)
{
var location = Url.Link(Routes.GetResourceDataRoute, new
{
token = "id1",
version = version.ToString()
});
return Created(location, "id1");
}
}
When creating a resource I want to already include in Location response headers URL link to OData resource data request endpoint.
OData controller for resource data requests:
[ApiController]
[ApiVersion("1.0")]
[Produces(MediaTypeNames.Application.Json)]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Select | AllowedQueryOptions.Count | AllowedQueryOptions.Skip | AllowedQueryOptions.Top | AllowedQueryOptions.Expand)]
[Route(Routes.ResourceDataControllerRoute)]
public class ResourceDataController : ODataController
{
[HttpGet("data", Name = Routes.GetResourceDataRoute)]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ResourceData))]
public IActionResult GetData(string token)
{
var resource = new ResourceData
{
Id = token
};
return Ok(resource);
}
}
Configure Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddOData(options =>
{
options.Select().Count().SkipToken().Expand().SetMaxTop(10);
options.AddRouteComponents(Routes.ResourceDataControllerRoute, BuildEdmModel());
})
.AddODataNewtonsoftJson();
services.AddApiVersioning(options => options.AssumeDefaultVersionWhenUnspecified = false);
services.AddVersionedApiExplorer(options => options.SubstituteApiVersionInUrl = true);
services.AddSwaggerGen(setupAction =>
{
var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
var info = new OpenApiInfo
{
Title = "ODataCoreUrlHelper",
Version = description.ApiVersion.ToString()
};
setupAction.SwaggerDoc(description.GroupName, info);
}
});
services.AddSwaggerGenNewtonsoftSupport();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider versionDescription)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseODataRouteDebug();
app.UseHttpsRedirection();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(setupAction =>
{
foreach (var description in versionDescription.ApiVersionDescriptions)
{
setupAction.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", $"Test v{description.GroupName}");
}
});
}
private static IEdmModel BuildEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<ResourceData>("data");
return builder.GetEdmModel();
}
}
Request/Response
- https://localhost:5001/api/v1/resources/1/data - works as expected

- https://localhost:5001/api/v1/resources - fails

Expected behavior
It is expected that
var location = Url.Link(Routes.GetResourceDataRoute, new
{
token = "id1",
version = version.ToString()
});
correctly generates URL to OData controller endpoint defined as
[HttpGet("data", Name = Routes.GetResourceDataRoute)]
Additional context
If commenting out OData route configuration
services.AddControllers().AddOData(options =>
{
options.Select().Count().SkipToken().Expand().SetMaxTop(10);
//options.AddRouteComponents(Routes.ResourceDataControllerRoute, BuildEdmModel());
})
- https://localhost:5001/api/v1/resources - works as expected and includes URL location of https://localhost:5001/api/v1/resources/1/data in response headers

- https://localhost:5001/api/v1/resources/1/data - doesn't anymore include OData context

Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.