Unable to transform links with .Net Core 3.1 and Microsoft.AspNetCore.Mvc.Versioning
I have been adding links successfully to a .Net Core 3.1 API project.
Today I have tried to add versioning to my API via the Microsoft.AspNetCore.Mvc.Versioning package and am now getting errors in LinkTransformationBuilderExtensions.AddRoutePath.
Specifically, ctx.LinkGenerator.GetPathByRouteValues() returns null for insert and delete links.
System.InvalidOperationException: Invalid path when adding route 'InsertValueRoute'. RouteValues: action=Get,controller=Values,version=1
at RiskFirst.Hateoas.LinkTransformationBuilderExtensions.<>c.<AddRoutePath>b__2_0(LinkTransformationContext ctx) in C:\Source\Doowruc\GitHub\Doowruc\riskfirst.hateoas\src\RiskFirst.Hateoas\LinkTransformationBuilderExtensions.cs:line 33
at RiskFirst.Hateoas.BuilderLinkTransformation.<>c__DisplayClass2_0.<Transform>b__0(StringBuilder sb, Func`2 transform) in C:\Source\Doowruc\GitHub\Doowruc\riskfirst.hateoas\src\RiskFirst.Hateoas\BuilderLinkTransformation.cs:line 21
at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
at RiskFirst.Hateoas.BuilderLinkTransformation.Transform(LinkTransformationContext context) in C:\Source\Doowruc\GitHub\Doowruc\riskfirst.hateoas\src\RiskFirst.Hateoas\BuilderLinkTransformation.cs:line 19
at RiskFirst.Hateoas.DefaultLinksEvaluator.BuildLinks(IEnumerable`1 links, ILinkContainer container) in C:\Source\Doowruc\GitHub\Doowruc\riskfirst.hateoas\src\RiskFirst.Hateoas\DefaultLinksEvaluator.cs:line 25
I have added a .Net Core 3.1 sample project to my fork (https://github.com/doowruc/riskfirst.hateoas) which demonstrates the issue. This is a copy of the existing classes in the BasicSimple sample
It appears to be because the HttpContext RouteValues now contains a "version" which is not in the LinkSpec RouteValues.
The following code inserted prior to var path = ctx.LinkGenerator.GetPathByRouteValues(ctx.HttpContext, ctx.LinkSpec.RouteName, ctx.LinkSpec.RouteValues); fixes it, however this is probably not the best way to include it in the LinkSpec!
var contextRouteValues = ctx.HttpContext.Features.Get<IRouteValuesFeature>()?.RouteValues;
if (contextRouteValues != null && contextRouteValues.ContainsKey("version") && !ctx.LinkSpec.RouteValues.ContainsKey("version"))
{
var version = contextRouteValues["version"];
ctx.LinkSpec.RouteValues.Add("version", version);
}
Further debugging suggests it is because of the lack of getValues Func being passed on RequireRoutedLink
If I add in version as the function, then it works, without the need to ament the extension as per my previous comment:
config.AddPolicy<ItemsLinkContainer<ValueInfo>>(policy =>
{
policy.RequireSelfLink()
.RequireRoutedLink("insert", "InsertValueRoute", x => new { version = "1" });
});
I have figured out how to sort this with a LinksHandler
public class VersionLinkRequirement<TResource> : LinksHandler<VersionLinkRequirement<TResource>>, ILinksRequirement
{
public string Id { get; set; }
public string RouteName { get; set; }
public Func<TResource, RouteValueDictionary> GetRouteValues { get; set; }
protected override Task HandleRequirementAsync(LinksHandlerContext context, VersionLinkRequirement<TResource> requirement)
{
if (string.IsNullOrEmpty(requirement.RouteName))
{
context.Skipped(requirement, LinkRequirementSkipReason.Error, $"Requirement did not have a RouteName specified for link: {requirement.Id}");
return Task.CompletedTask;
}
var route = context.RouteMap.GetRoute(requirement.RouteName);
if (route == null)
{
context.Skipped(requirement, LinkRequirementSkipReason.Error, $"No route was found for route name: {requirement.RouteName}");
return Task.CompletedTask;
}
var values = new RouteValueDictionary();
if (requirement.GetRouteValues != null)
{
values = requirement.GetRouteValues((TResource)context.Resource);
}
var link = new LinkSpec(requirement.Id, route, values);
if (context.ActionContext.RouteData.Values.TryGetValue("version", out var version))
{
link.RouteValues.Add("version", version);
}
context.Links.Add(link);
context.Handled(requirement);
return Task.CompletedTask;
}
}
This is great info. Thanks for posting it, hopefully help anyone in future.
Sweet thanks! Any word when this will be pushed to a package?