Git Product home page Git Product logo

riskfirst.hateoas's Introduction

RiskFirst.Hateoas

CI Build

An implementation of HATEOAS for aspnet core web api projects which gives full control of which links to apply to models returned from your api. In order to communicate varying state to the end-user, this library fully integrates with Authorization, and allows arbitrary conditions to determine whether to show or hide HATEOAS links between api resources.

Getting started

Install the package from Nuget.org

PM> Install-Package RiskFirst.Hateoas

This will include the dependency RiskFirst.Hateoas.Models which was introduced in version 3.0.0 to remove the AspNetCore dependencies from assemblies referencing the LinkContainer base classes.

Configure the links to include for each of your models.

public class Startup
{
  public void ConfigureServices(IServicesCollection services)
  {
    services.AddLinks(config =>
    {
      config.AddPolicy<MyModel>(policy => {
          policy.RequireSelfLink()
                .RequireRoutedLink("all", "GetAllModelsRoute")
                .RequireRoutedLink("delete", "DeleteModelRoute", x => new { id = x.Id });
      });
    });
  }
}

Inject ILinksService into any controller (or other class in your project) to add links to a model.

[Route("api/[controller]")]
public class MyController : Controller
{
    private readonly ILinksService linksService;

    public MyController(ILinksService linksService)
    {
        this.linksService = linksService;
    }

    [HttpGet("{id}",Name = "GetModelRoute")]
    public async Task<MyModel> GetMyModel(int id)
    {
         var model = await myRepository.GetMyModel(id);
         await linksService.AddLinksAsync(model);
         return model;
    }
    [HttpGet(Name="GetAllModelsRoute")]
    public async Task<IEnumerable<MyModel>> GetAllModels()
    {
         //... snip .. //
    }

    [HttpDelete("{id}",Name = "DeleteModelRoute")]
    public async Task<MyModel> DeleteMyModel(int id)
    {
         //... snip .. //
    }
}

The above code would produce a response as the example below

{
  "id": 1,
  "someOtherField": "foo",
  "_links": {
    "self": {
      "rel": "MyController\\GetModelRoute",
      "href": "https://api.example.com/my/1",
      "method": "GET"
    },
    "all": {
      "rel": "MyController\\GetAllModelsRoute",
      "href": "https://api.example.com/my",
      "method": "GET"
    },
    "delete": {
      "rel": "MyController\\DeleteModelRoute",
      "href": "https://api.example.com/my/1",
      "method": "DELETE"
    }
  }
}

or if you're using XML

<?xml version="1.0"?>
<MyModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <link href="https://api.example.com/my/1" method="GET" rel="self"/>
  <link href="https://api.example.com/my" method="GET" rel="all"/>
  <link href="https://api.example.com/my/1" method="DELETE" rel="delete"/>
  <Id>1</Id>
  <SomeOtherField>foo</SomeOtherField>
</MyModel>

Multiple policies for a model

It is possible to specify multiple named policies for a model during startup by providing a policy name to AddPolicy. For example, you could have the default (unnamed) policy give basic links when the model is part of a list, but more detailed information when a model is returned alone.

public class Startup
{
  public void ConfigureServices(IServicesCollection services)
  {
    services.AddLinks(config =>
    {
      config.AddPolicy<MyModel>(policy => {
          policy.RequireRoutedLink("self","GetModelRoute", x => new {id = x.Id })
      });

      config.AddPolicy<MyModel>("FullInfo",policy => {
          policy.RequireSelfLink()
                .RequireRoutedLink("all", "GetAllModelsRoute")
                .RequireRoutedLink("parentModels", "GetParentModelRoute", x => new { parentId = x.ParentId });
                .RequireRoutedLink("subModels", "GetSubModelsRoute", x => new { id = x.Id });
                .RequireRoutedLink("delete", "DeleteModelRoute", x => new { id = x.Id });
      });
    });
  }
}

With a named policy, this can be applied at runtime using an overload of AddLinksAsync which takes a policy name:

await linksService.AddLinksAsync(model,"FullInfo");

You can also markup your controller method with a LinksAttribute to override the default policy applied. The below code would apply the "FullInfo" profile to the returned model without having to specify the policy name in the call to AddLinksAsync.

[Route("api/[controller]")]
public class MyController : Controller
{
    private readonly ILinksService linksService;

    public MyController(ILinksService linksService)
    {
        this.linksService = linksService;
    }

    [HttpGet("{id}",Name = "GetModelRoute")]
    [Links(Policy = "FullInfo")]
    public async Task<MyModel> GetMyModel(int id)
    {
         var model = await myRepository.GetMyModel(id);
         await linksService.AddLinksAsync(model);
         return model;
    }
}

Another way to achieve the same thing is to mark the actual object with the LinksAttribute:

[Links(Policy="FullInfo")]
public class MyModel : LinkContainer
{ }

[Route("api/[controller]")]
public class MyController : Controller
{
    private readonly ILinksService linksService;

    public MyController(ILinksService linksService)
    {
        this.linksService = linksService;
    }

    [HttpGet("{id}",Name = "GetModelRoute")]
    public async Task<MyModel> GetMyModel(int id)
    {
         MyModel model = await myRepository.GetMyModel(id);
         await linksService.AddLinksAsync(model);
         return model;
    }
}

There are further overloads of AddLinksAsync which take an instance of ILinksPolicy or an array of ILinksRequirement which will be evaluated at runtime. This should give complete control of which links are applied at any point within your api code.

Configuring Href and Rel transformations

There should not have much need to change how the Href is transformed, however one common requirement is to output relative instead of absolute uris. This can be tried in the Basic Sample

services.AddLinks(config =>
{
  config.UseRelativeHrefs();
  ...
});

Both Href and Rel transformations can be fully controlled by supplying a class or Type which implements ILinkTransformation.

services.AddLinks(config =>
{
  // supply a type implementing ILinkTransformation
  config.UseHrefTransformation<MyHrefTransformation>();
  // or supply an instance
  config.UseRelTransformation(new MyRelTransformation());
});

Alternatively, transformations can be configured using a builder syntax

services.AddLinks(config =>
{
  // output a uri for the rel values
  config.ConfigureRelTransformation(transform => transform.AddProtocol()
                                                          .AddHost()
                                                          .AddVirtualPath(ctx => $"/rel/{ctx.LinkSpec.ControllerName}/{ctx.LinkSpec.RouteName}");
});

Both ways of customizaing transformations can be seen in the LinkConfigurationSample.

Authorization and Conditional links

It is likely that you wish to control which links are included with each model, and one common requirement is to only show links for which the current user is authorized. This library fully integrates into the authorization pipeline and will apply any authorization policy you have applied to the linked action.

To enable authorization on a link provide the AuthorizeRoute condition.

public class Startup
{
  public void ConfigureServices(IServicesCollection services)
  {
    services.AddLinks(config =>
    {
      config.AddPolicy<MyModel>("FullInfo",policy => {
          policy.RequireSelfLink()
                .RequireRoutedLink("all", "GetAllModelsRoute")
                .RequireRoutedLink("parentModels", "GetParentModelRoute",
                                      x => new { parentId = x.ParentId }, condition => condition.AuthorizeRoute());
                .RequireRoutedLink("subModels", "GetSubModelsRoute",
                                      x => new { id = x.Id }, condition => condition.AuthorizeRoute());
                .RequireRoutedLink("delete", "DeleteModelRoute",
                                      x => new { id = x.Id }, condition => condition.AuthorizeRoute());
      });
    });
  }
}

In the above example, GetParentModelRoute, GetSubModelsRoute & DeleteModelRoute will not be shown to a user who does not have access to those routes as defined by their authorization policies. See the Microsoft documentation for more information on authrization within an aspnet core webapi project.

As with the above examples, there are further condition methods which allow you to specifiy a policy name, an absolute policy or a set of requirements.

You can also conditionally show a link based on any boolean logic by using the Assert condition. For example, there is a method which allows you to add common paging links to paged results of objects. You may decide these are not worthwhile if there is a total of only one page of results.

options.AddPolicy<IPageLinkContainer>(policy =>
{
    policy.RequireelfLink("all")
            .RequirePagingLinks(condition => condition.Assert(x => x.PageCount > 1 ));
});

Further customization

You are free to add your own requirements using the generic Requires method on LinksPolicyBuilder. In addition, you must write an implementation of ILinksHandler to handle your requirement. For example, you may have a requirement on certain responses to provide a link back to your api root document. Define a simple requirement for this link.

using RiskFirst.Hateoas;

public class ApiRootLinkRequirement : ILinksRequirement
{
    public ApiRootLinkRequirement()
    {
    }
    public string Id { get; set; } = "root";
}

Given this requirement, we need a class to handle it, which must implement ILinkHandler and handle your requirement.

using RiskFirst.Hateoas;

public class ApiRootLinkHandler : LinksHandler<ApiRootLinkRequirement>
{
    protected override Task HandleRequirementAsync(LinksHandlerContext context, ApiRootLinkRequirement requirement)
    {
        var route = context.RouteMap.GetRoute("ApiRoot"); // Assumes your API has a named route "ApiRoot".
        context.Links.Add(new LinkSpec(requirement.Id, route));
        context.Handled(requirement);
        return Task.CompletedTask;
    }
}

Finally register your Handler with IServicesCollection and use the requirement within your link policy

public class Startup
{
  public void ConfigureServices(IServicesCollection services)
  {
    services.AddLinks(config =>
    {
      config.AddPolicy<MyModel>(policy =>
      {
          policy.RequireRoutedLink("self","GetModelRoute", x => new {id = x.Id })
                .Requires<ApiRootLinkRequirement>();
      });
    });

    services.AddTransient<ILinksHandler,ApiRootLinkHandler>();
  }
}

This example is demonstrated in the CustomRequirementSample

There are many additional parts of the framework which can be extended by writing your own implementation of the appropriate interface and registering it with IServicesCollection for dependency injection. For example, you could change the way that links are evaluated and applied to your link container by implementing your own ILinksEvaluator

using RiskFirst.Hateoas;

public class Startup
{
  public void ConfigureServices(IServicesCollection services)
  {
    services.AddLinks(options => {
        ...
    });
    services.AddTransient<ILinksEvaluator, MyLinksEvaluator>();
  }
}

The list of interfaces which have a default implementation, but which can be replaced is:

  • ILinkAuthorizationService, controls how links are authorized during link condition evaluation.
  • ILinksEvaluator, controls how links are evaluated and transformed before being written to the returned model.
  • ILinksHandlerContextFactory, controls how the context is created which is passed through the requirement handlers during processing.
  • ILinksPolicyProvider, provides lookup for ILinkPolicy instances by resource type and name.
  • ILinksService, the main entrypoint into the framework, this interface is injected into user code to apply links to api resources.
  • ILinkTransformationContextFactory, controls how the transformation context is created during transformation for rel & href properies of links.
  • IRouteMap, controls how your API is indexed to allow links between routes.

Troubleshooting

Upgrading from v1.0.x to v1.1.x

The change from version 1.0.x to 1.1.x was mostly non-breaking, however if you have implemented any custom requirement handlers as described in the example above the signature of the base class LinksHandler changed slightly to remove the duplicate declaration of the generic type TResource.

In v1.0.x your code may have looked like:

public class MyCustomHandler : ILinksHandler { ... }

It should now inherit from LinksHandler<TRequirement> making implementation simpler, and giving a type-safe override of HandleRequirementAsync giving access to your correctly-typed requirement.

public class MyCustomHandler : LinksHandler<MyCustomRequirement>

riskfirst.hateoas's People

Contributors

coldridj avatar dnikolovv avatar gibletto avatar hannan-gc avatar hannanmumtaz avatar hendonnelly avatar jbicks avatar jessesingleton avatar jmontagu avatar justinbicknell avatar nicky-iliev avatar tasmith81 avatar wigmorewelsh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

riskfirst.hateoas's Issues

Breaking Change Introduced in v1.2.0

v1.1.3 of the NuGet package has the minimum requirements of .Net 4.5.1 and .Net Standard 1.6.

v1.2.0 has the minimum requirements of .Net 4.6.1 and .Net Standard 2.0.

This is a breaking change because you've dropped support for older (and still widely used!) platforms and so the version number of the package should have been incremented to v2.0 as per SemVer 2.0 rules.

RouteMap is empty

When riskfirst.hateoas package is referenced by a different project in the solution and not directly referenced by the Web Project the RouteMap has no routes.

Solution Setup.

  • MySolution.Models - contains project models, and references RiskFirst.Hateoas package.
  • MySolution.WebApi - References MySolution.Models, and Hateoas Services are configured.

When i add the RiskFirst.Hateoas package to MySolution.WebApi the RouteMap is correctly populated.
Am not logging this as a bug, it was just interesting behavior that was tricky to diagnose.

A common standard needs to be agreed upon

If you look at the examples in the readme, you will see that the link names (e.g. self) are different from the link relations (the rel tag).

{
   id:1,
   someOtherField: "foo",
   _links : {
    self: {
      rel: "MyController\GetModelRoute",
      href: "https://api.example.com/my/1"
      method: "GET"
    },
    all: {
      rel: "MyController\GetAllModelsRoute",
      href: "https://api.example.com/my"
      method: "GET"
    },
    delete: {
      rel: "MyController\DeleteModelRoute",
      href: "https://api.example.com/my/1"
      method: "DELETE"
    }
  }
}

This works okay but doesn't adhere to the Hypertext Application Language (HAL) standard.

Quoting the Wikipedia article - Links have a target URI, as well as the name of the link (referred to as 'rel'). However, the concept of a link name and its rel are separate in the current implementation.

If we wanted to use XML instead of JSON (as mentioned in this issue) we don't know what approach to take.

In most examples (e.g. https://tools.ietf.org/html/draft-michaud-xml-hal-02#section-6) the value of the rel tag is what we would have as a link name in the JSON format.

<resource rel="self" href="/orders">
    <link rel="next" href="/orders?page=2"/>
    <link rel="find" href="/orderse{/id}" templated="true"/>
</resource>

This is probably going to be a major change, bumping the current version to 4.0, but it needs to be decided.

Suggestion for better decoupling/separation of concerns

Thank you for writing this very helpful HATEOAS library. I have a suggestion that I think might improve it. Please let me know if you think it's a good idea.

Currently whatever library contains the data transfer objects (DTOs) that are used to pass representations of domain objects between the server and the client must have a dependency on RiskFirst.Hateoas. Furthermore, every DTO class must inherit from RiskFirst.Hateoas.Models.LinkContainer, and every collection of DTOs must be an ItemsLinkContainer.

It seems to me that this is not ideal. We might (at least hypthetically) want to reuse those same DTOs in another part of the application that deals clients that aren't RESTful or aren't even using HTTP...perhaps a Console client or WinForms or WPF or something else entriely.

Because DTOs are part of the core, maybe they shouldn't be tightly coupled to a library that specializes in HTTP communication. I was wondering if LinkContainer could become LinkContainer<T> where T is your DTO class, thus keeping DTO separate from the HATEOAS object. Something similar could be done with the collection class ItemsLinkContainer<T>.

What do you think... Is it possible? Is it a good idea? Could it be done without negatively affecting the output when the LinkContainer<T> is serialized to JSON?

New release soon?

Its been 2 years since your last nuget publish but this repo is actively maintained. Are you going to publish sometime soon?

Property name, "_links" verus "links". Configurable?

It seems that HATEOAS has no defined preference for the use of "links" or "_links". Some libraries use one or the other.

As part of our API definition, we have published the contracts using "links" but this library uses "_lnks" as the property name. I don't really want to change the contract since customer have already started building clients against that published contract.

Is there any way to configure this property name?

Incorrectly configured route causes a NRE from LinksTransformationBuilder.AddRoutePath

An incorrectly configured route, for example badly defined parameters

// Note: id in attr vs myObjectId as param name
[HttpGet("{id:int}",Name="MyBadRoute")
public async Task<MyObject> GetMyObject(int myObjectId)
{
}

And routed via RequiresRoutedLink

options.AddPolicy<MyObject>(policy => policy.RequiresRoutedLink("blah","MyBadRoute",x => new {myObjectId = x.MyObjectId});

Will cause a NullReferenceException due to GetVirtualPath returning null in LinksTransformationBuilderExtensions

...
var virtualPathData = ctx.Router.GetVirtualPath(virtualPathContext);
return virtualPathData.VirtualPath;

How to add links to collection of <Model>

In the README.MD is this example

    [HttpGet(Name="GetAllModelsRoute")]
    public async Task<IEnumerable<MyModel>> GetAllModels()
    {
         //... snip .. //
    }

But how would you implement the AddLinksAsync part?

As far as I can see ILinksService doesn't accept an IEnumerable<MyModel> as parameter for AddLinksAsync...

Something like this would be nice:

    [HttpGet(Name="GetAllModelsRoute")]
    public async Task<IEnumerable<MyModel>> GetAllModels()
    {
         var all = await _dbContext.Models.ToListAsync();
         await linksService.AddLinksAsync(all);
         return all;
    }

Could not load type 'Microsoft.AspNetCore.Http.Internal.QueryCollection' from assembly 'Microsoft.AspNetCore.Http, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.

Hi,

first of all love the library!.

I'm just having a minor issue with paging when using .NET 5.

Basically the LinksHandlerContext has this line:

public IQueryCollection CurrentQueryValues => ActionContext?.HttpContext?.Request?.Query ?? new QueryCollection();

and by removing the 'new QueryCollection', it allows the operation to work as expected.

I have a change which seems to fix it for me, i was hoping you guys could review and push a new build.

Thanks

Multi-Targeting Support For .Net Framework 4.5.1

This library currently only supports .Net Standard 1.6 which means it is currently unusable on the full .Net Framework.

However, all of the dependencies of this project support multi-targeting .Net Framework 4.5.1. I can't raise a Pull Request for this right now, but the required changes are:

In RiskFirst.Hateoas.csproj, change TargetFramework to:

<TargetFrameworks>netstandard1.6;net451</TargetFrameworks>

In SelfLinkRequirement.cs, change Task.CompletedTask to Task.FromResult(true) (the former is only available in .Net 4.6+ whereas the latter is pretty much equivalent but works in .Net 4.5 also).

Define Link class as [Keyless] for .NET 6

Starting a new API with .NET 6, I've got an error when trying to migrate the database. It says that the entity type 'Link' requires a primary key or need to be defined as Keyless.

Having multiple HttpMethod attributes on a route breaks DefaultRoutMap

A controller with 2 HttpMethod attributes

 [HttpPut(), HttpPatch(Name = "PatchModelRoute")]
public async Task<SomeModel> Update(...)
 { ... }

Generates the following error

System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type found.
   at System.Attribute.GetCustomAttribute(MemberInfo element, Type attributeType, Boolean inherit)
   at System.Reflection.CustomAttributeExtensions.GetCustomAttribute[T](MemberInfo element)
   at RiskFirst.Hateoas.DefaultRouteMap.<>c.<.ctor>b__5_4(<>f__AnonymousType0`2 m)
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
   at RiskFirst.Hateoas.DefaultRouteMap..ctor(IActionContextAccessor contextAccessor, ILogger`1 logger)

Not working with nginx load balancer

I have an Nginx load balancer that passes host onto the header when I make a request but my hypermedia links aren't including the port (8080). It works fine when I run my api instances separately but not with the nginx load balancer.

image

  services.AddLinks(config =>
  {
    config.AddPolicy<User>(policy =>
    {
      policy.RequireSelfLink()
            .RequireRoutedLink("get", "GetUser", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("post", "PostUser", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("put", "PutUser", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("delete", "DeleteUser", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("all", "GetUsers", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("requests", "GetRequestsByUserId", x => new { id = x.Id })
            .RequireRoutedLink("root", "ApiRoot");
    });

    config.AddPolicy<Request>(policy =>
    {
      policy.RequireSelfLink()
            .RequireRoutedLink("post", "PostRequest", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("put", "PutRequest", x => new { id = x.Id })
            .RequireRoutedLink("delete", "DeleteRequest", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("all", "GetRequests", x => new { id = x.Id }, condition => condition.AuthorizeRoute())
            .RequireRoutedLink("open", "GetOpenRequests")
            .RequireRoutedLink("filter1", "GetRequestsByCityState")
            .RequireRoutedLink("filter2", "GetRequestsByZip")
            .RequireRoutedLink("root", "ApiRoot");
    });

    config.AddPolicy<Root>(policy =>
    {
      policy.RequireSelfLink()
            .RequireRoutedLink("users", "GetUsers")
            .RequireRoutedLink("requests", "GetRequests")
            .RequireRoutedLink("account", "PostUser")
            .RequireRoutedLink("reset", "ResetPassword");
    });
  });

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

Multiple route attribute in same name is not supported while configuring for the HATEOAS library

Scenerio: 

[HttpGet("~/areas/location/{id:int}", Name = "Get Locations")]  //Here  Name attribute is same as below route also.

[HttpGet("~/location/{id:int}", Name = "Get Locations")]  //route name aslo same ("Get Locations")

public async Task<IActionResult> Getlocation(int id)

{     

 _linksService.AddLinksAsync(locationmodel);     

// Already we have     

 }

we are aware that , route Name attribute cannot be support same name with more than one route.
Expected: 

[HttpGet("~/areas/location/{id:int}")]  //-- Here We will remove that Name attribute and instead of that we will pass that text in another parameter as below stated.[HttpGet("~/location/{id:int}")]

public async Task<IActionResult> Getlocation(int id)

{           

    _linksService.AddLinksAsync(locationmodel,"Get Locations");   

  // Possible to pass Rel info   (Or)

_linksService.AnotherExtensionMethod(locationmodel,"Get Locations");     

// Possible to pass Rel info             

}   

 

Also, I have configured the Startup like below:

services.AddLinks(config =>
{
config.UseRelativeHrefs();
config.ConfigureRelTransformation(transform => transform.Add(ctx => $"{ctx.LinkSpec.RouteName}"));

config.AddPolicy<Location>("LocationsPolicy", policy =>
{
policy.RequireRoutedLink("users", "Get Locations", x => new { id = x.Id });

});
});


Output :
"_links": {
"users": {
"rel": "Get Locations",
"href": "/api/locations/1/users",
"method": "GET"
}

Problem: In this case i have to create indivitual policy for each action methods.  N number of actions are there(with same name).

Can someone throw more light on how to resolve the issue?

Your help is much appreciated

DependencyContext.Default is null

Hello, has anyone used the package with Autofaq ? It seems the dependecy context is null in that case.
The issue is happening when the package is called from another dll project. I will provide more information, in case needed.

p.s. returning a new empty list here

internal class DefaultAssemblyLoader : IAssemblyLoader
{
public IEnumerable<Assembly> GetAssemblies()
{

should do the work (I think).

DefaultRouteMap is Transient

The DefaultRouteMap ctor does an Assembly scan per dependency. This is very slow.

I fixed it by adding

services.AddSingletone<IRouteMap, DefaultRouteMap>()

If it is intended to be transient, the assembly scan should be cached.

#28

Link Policies should be combinable

I originally had this functionality, and it makes sense. I just want to define a policy against IPagedLinkContainer to add paging links if there is more than one page and use that on every policy for PagedLinkContainer<TResource> results.

I removed it, and forgot to make IPagedLinkContainer public (#2)

RequireSelfLink fails when no name is specified in the method httpheader

Hello,

If you set a RequireSelfLink policy for an entity type returned by a method with no route name in the HttpGet/HttpPost/... attribute, the system fails because the next line returns null in LinkTransformationBuilderExtensions:

var path = ctx.LinkGenerator.GetPathByRouteValues(ctx.HttpContext, ctx.LinkSpec.RouteName, ctx.LinkSpec.RouteValues);

If you don't specify a route name, the library sets the method full name (Namespace.Controller.MethodName) in RouteInfo. The LinkGenerator is not able to get the route with this name, but if you set a null value, it works.

Fix:

Change RouteInfo to allow null values:

RouteName = name; // ?? $"{methodInfo?.ControllerType.Namespace}.{methodInfo?.ControllerType?.Name}.{methodInfo?.MethodName}";

And remove the null check LinkTransformationBuilderExtensions.AddRoutePath.

Well, I have to say that I have changed the library target to net6.0 but the routing library is the same since netcore 3.0.

DefaultRouteMap doesn't work when integration testing

In DefaultRouteMap, the routes are discovered using Assembly.GetExecutingAssembly() and then searching for types which extend Controller.

However, when you are performing an integration test using Microsoft.AspNetCore.TestHost (as recommended by Microsoft here) then the 'executing' assembly is actually the test assembly, not the service assembly. This means the routes in the service assembly are never discovered and so custom link policies are never applied.

Unable to add multiple routes, while configuring for the HATEOAS library

I am having an api like below with two routes (one for backward compatibility)

[HttpGet("~/v2/location/{id:int}", Name = "LocationV2")]
[HttpGet("~/location/{id:int}", Name = "Location")]
public async Task<IActionResult> Getlocation(int id)
{
	....
	_linksService.AddLinksAsync(locationmodel);
	....
}

Also, I have configured the Startup like below

 services.AddLinks(config =>
{
	config.UseRelativeHrefs();
	config.ConfigureRelTransformation(transform => transform.Add(ctx => $"{ctx.LinkSpec.RouteName}"));

	config.AddPolicy<Location>(policy =>
	{
		policy.RequireRoutedLink("users", "Get Users for a Location", x => new { id = x.Id });
		policy.RequireRoutedLink("areas", "Get Areas for a Location", x => new { locationId = x.Id });
	});
}); 

I am getting an exception "Multiple custom attributes of the same type found", while trying to call the api. It fails in the AddLinksAsync method. Can someone throw more light on how to resolve the issue?

Your help is much appreciated

Thanks

Cannot add pagelinks, getting NullReferenceException

While trying to call linksService.AddLinksAsync(result) I am getting NullReferenceException.

at RiskFirst.Hateoas.DefaultRouteMap.GetCurrentRoute()
at RiskFirst.Hateoas.DefaultLinksService.d__8`1.MoveNext()

Is there a documentation where it shows an example of using page links?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.