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

Unable to transform links with .Net Core 3.1 and Microsoft.AspNetCore.Mvc.Versioning #35

Open
doowruc opened this issue Mar 9, 2020 · 5 comments

Comments

@doowruc
Copy link

doowruc commented Mar 9, 2020

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

@doowruc
Copy link
Author

doowruc commented Mar 9, 2020

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);
}

@doowruc
Copy link
Author

doowruc commented Mar 9, 2020

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" });
});

@doowruc
Copy link
Author

doowruc commented Mar 11, 2020

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;
    }
}

@jamiecoh
Copy link
Member

This is great info. Thanks for posting it, hopefully help anyone in future.

@vpetkovic
Copy link

Sweet thanks! Any word when this will be pushed to a package?

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

3 participants