7
votes

I am evaluating how to add hypermedia links to DTO responses. Although there is no standard, add List to the response DTOs seems to be the suggested approach.

Do you know of any example or reference of implementation using ServiceStack framework?

Adding List is ok for me, but my doubts are about where to put the logic of the following links (Within the service or a specialized class that holds the state machine?) and where to resolve the routes (A filter?)

Thanks.

[Update] From ServiceStack version v3.9.62 it is posible to access Routes configuration via EndpointHost.Config.Metadata.Routes.RestPath, so the solution provided by tgmdbm can be improved withouth the need of "IReturn + Routes attributes", just using Metadata.Routes information. In fact all service metadata can be queried and used to cross-cutting concerns. Servicestack rocks.

1

1 Answers

9
votes

The way I do this currently is I pass back a response dto which implements an interface

public interface IHaveLinks
{
  [IgnoreDataMember]
  IEnumerable<Link> Links { get; }
}

public class Link
{
  public string Name { get; set; }
  public IReturn Request { get; set; }
  public string Method { get; set; }
}

Then I use a response filter to generate the urls and populate the response headers with the links.

this.ResponseFilters.Add((req, res, dto) =>
{
  if (!(dto is IHaveLinks))
    return;

  var links = (dto as IHaveLinks).Links

  if(links == null || !links.Any())
    return;

  var linksText = links
    .Select(x => string.Format("<{0}>; rel={1}"), x.Request.ToUrl(x.Method), x.Name));

  var linkHeader = string.Join(", ", linksText);

  res.AddHeader("Link", linkHeader);
});

This seems the cleanest way. The Link object above effectively says "If you make this request with this method you will get back the named resource". The only HTTP thing that bleeds up to the BLL is Method. But you could get rid of that and only pass back GET urls. Or map it to some generalised "operation"?

As an example:

public class ExampleService : Service
{
  public ExamplesResponse Get(ExamplesRequest request)
  {
    var page = request.Page;
    var data = // get data;

    return new ExamplesResponse
      {
        Examples = data,
        Links = new []
          {
            new Link { Name = "next", Request = request.AddPage(1), Method = "GET" },
            new Link { Name = "previous", Request = request.AddPage(-1), Method = "GET" },
          }
      }
  }
}

[Route("/examples/{Page}")]
public class ExamplesRequest : IReturn<ExamplesResponse>
{
  public int Page { get; set; }

  // ...
}

(The AddPage method returns a clone of the request and sets the Page property appropriately.)

Hope that helps.