hallo icon indicating copy to clipboard operation
hallo copied to clipboard

How to support creating links for sub-collections in the PagedListRepresentation sample?

Open jasonmitchell opened this issue 4 years ago • 6 comments

The Hallo.Sample project includes an example PagedListRepresentation<T> which accepts the baseUrl for the collection. Below is the snippet of how this is constructed.

https://github.com/jasonmitchell/hallo/blob/db5d14e163b3ed0bd656b544d7a7c1fd353b2840/Hallo.Sample/Models/Hypermedia/PersonRepresentation.cs#L24-L28

This works fine for constant base URLs like in this example but is more difficult when the base URL is dynamic. For example, if the URL to the collection was /race/123/cars (where 123 is the resource id) how do we support getting the race id into the base URL?

jasonmitchell avatar Feb 23 '21 16:02 jasonmitchell

This is pretty fiddly. I think it's going to be tricky to come up with a universal solution for this which remains inline with the intention that Hallo requires no modification to controllers or models, and which doesn't have an over-complicated setup.

Two very similar options are below.

The first requires IActionContextAccessor as a parameter and then accesses the RouteData to find the raceId. This is highly MVC-specific as this interface comes from the Microsoft.AspNetCore.Mvc namespace.

public class CarListRepresentation : PagedListRepresentation<Car>
{
    public CarListRepresentation(CarRepresentation carRepresentation, IActionContextAccessor actionContextAccessor)
        : base($"/races/{actionContextAccessor.ActionContext.RouteData.Values["raceId"]}/cars", carRepresentation)
    { }
}

The second option is basically the same but makes use of Microsoft.AspNetCore.Http.IHttpContextAccessor and so is not specific to MVC though I haven't tested this outside of MVC yet.

public class CarListRepresentation : PagedListRepresentation<Car>
{
    public CarListRepresentation(CarRepresentation carRepresentation, IHttpContextAccessor httpContextAccessor)
        : base($"/races/{httpContextAccessor.HttpContext!.GetRouteValue("raceId")}/cars", carRepresentation)
    { }
}

Both options require intimate knowledge of the route values from the endpoint which could potentially be a fragile dependency - someone might do a seemingly harmless rename on the route value which would quietly break this. It's also pretty ugly looking but it could be tidied up with a developers own abstraction if needs be.

This also increases the risk of weird lifestyle issues if using the ASP.NET Core dependency injection mechanism and a developer registers Hal<T> services as singletons because the base URL in this example would always have the first raceId handled by the endpoint. This could be avoided by a different implementation of PagedListRepresentation<T> which resolves the baseUrl differently (e.g. by an abstract method).

Lastly these options really limit the portability of representations. If we wanted to reuse this same representation for a sub-collection under another resource then we couldn't use the same CarListRepresentation across all (and perhaps this is ok).

I will keep experimenting with options for this though I think some trade offs will have to be accepted in this scenario.

jasonmitchell avatar Feb 23 '21 22:02 jasonmitchell

Example resolving baseUrl via abstract method:

CarListRepresentation:

public class CarListRepresentation : PagedListRepresentation<Car>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CarListRepresentation(CarRepresentation carRepresentation, IHttpContextAccessor httpContextAccessor)
        : base(carRepresentation)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override string GetBaseUrl() =>
        $"/races/{_httpContextAccessor.HttpContext!.GetRouteValue("raceId")}/cars";
}

jasonmitchell avatar Feb 23 '21 22:02 jasonmitchell

That or just use the full url https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.extensions.urihelper.getencodedurl

jchannon avatar Feb 24 '21 23:02 jchannon

Actually you won't want that as you'll want relative paths but you could just use the request path?

jchannon avatar Feb 25 '21 07:02 jchannon

Yeah you could. I think there are lots of options for this and I'm not sure that the library can cover all scenarios for everyone. What might help is a better sample which shows a more complex scenario which can at least provide a starting point for people to adapt.

jasonmitchell avatar Feb 25 '21 19:02 jasonmitchell

Yarp!

On Thu, 25 Feb 2021 at 19:00, Jason Mitchell [email protected] wrote:

Yeah you could. I think there are lots of options for this and I'm not sure that the library can cover all scenarios for everyone. What might help is a better sample which shows a more complex scenario which can at least provide a starting point for people to adapt.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/jasonmitchell/hallo/issues/42#issuecomment-786129193, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAZVJS6PDXRC5BHNNEYYCTTA2M5LANCNFSM4YCZTJXA .

jchannon avatar Feb 25 '21 19:02 jchannon