visualeyes / halcyon

A HAL implementation for ASP.NET
MIT License
81 stars 30 forks source link

How can I accurately represent the dto that will be returned to a REST call with Swagger? #72

Open sw-bbrown opened 6 years ago

sw-bbrown commented 6 years ago

Hello,

We're trying to do these two things in an Asp.Net core Api:

We have Dto models similar to this:

public class FooDto
{
  public int Id {get; set;}
  public string Value {get; set}
}

Which when returned out of the API have a self link added by creating a new Halcyon.HAL.HALResponse , which looks like this (sorry for adding comments to json but i wanted to be clear):

// run-time json
{
  "Id": 1,
  "Value": "some value",
  "_links": {
    "self": {
      "href": "/foo/1"
    }
  }
}

The Problem But when we instruct NSwag about the Dto with an attribute on the controller like this [SwaggerResponse(HttpStatusCode.OK, typeof(FooDto))] The swagger page does not include the "_links" field in the representation of the return model.

// swagger documentation json
{
  "Id": "int",
  "Value": "string"
  // Links section should be here
}

If we give Swagger the HALResponse type: [SwaggerResponse(HttpStatusCode.OK, typeof(HALResponse))], the swagger page simply displays a json model with "model" and "config" sections and no other content.

I've search around the web without finding a solution that still uses the Halcyon.HAL.HALResponse. I did see that issue #56 mentioned Swagger but it didn't seem to touch on this issue except in one un-responded-to comment on the bottom.

I can see a way to get swagger the correct model- add the _links property to the Dtos, but that means throwing out Halcyon, so I wanted to check here first.

The Issue: How can we register a type such that NSwag/Swagger will properly document the model that will be returned from the API when returning something like this:

            return this.HAL(fooModel, new Link[] {
                new Link("self", "/api/foo/{id}")
            });
macux commented 5 years ago

Would like a solution to this too, however it'll need some kind of customisation of NSwag rather than halcyon given that NSwag will use either the Produces[ResponseType]Attribute(s) or SwaggerResponseAttribute with a type to determine what the response is.

As you've seen there is no type that contains both your actual response and the HAL info and there's no way of constructing that type automatically given, for example, an embedded object receives its name from a string at runtime.

The solution would be to write a custom ContractResolver which NSwag supports (or possibly a JsonConverter). You would then map your return type to the HAL based on your own conventions, e.g. for a simple object perhaps you'd only add a _self link whereas for any IEnumerable you'd add _self/first/prev/next/last and maybe a count. For an embedded IEnumerable you'd also need an extra bit of jiggery pockery to get the name of the collection, which could be done with a Description attribute on the base DTO type that's in the IEnumerable. A few hoops to jump through but suspect it's all possible, you'd certainly end up with an opinionated ContractResolver but suspect that would work OK.

Feel free to post your solution here when you've written it! :wink:

macux commented 5 years ago

FWIW I've got this working in a simple API, result was though that dropping halycon was the simplest thing to do and adding a couple of dto base classes (one for simple objects and another for collections). Currently I've got a custom contract resolver that just renames the embedded property on the collection dto to the actual collection name but I may change this at some point to just inherit from the base dto classes if the HAL gets more complex.