RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.77k stars 1.29k forks source link

Enable binding a complex object with an array element to WebApi2 #492

Closed fabrikam-champions closed 7 years ago

fabrikam-champions commented 7 years ago

I need to modify the TypeScript template for angular2 to be able to send a complex object (that has an array element) in the Get method via URI. so, instead of this generated code:

    if (filter_Filters !== undefined)
        filter_Filters.forEach(item => { url_ += "Filter.Filters=" + encodeURIComponent("" + item) + "&"; });

I want it to be:

if (filter_Filters !== undefined)
    filter_Filters.forEach((item, index) => {
        for (let attr in item) {
            url_ += "Filter.Filters[" + index + "]." + attr + "=" + encodeURIComponent("" + item[attr]) + "&";
        }
    });

how can i do this?

fabrikam-champions commented 7 years ago

@rsuter Thank you for applying my edit. hope to find it soon in the next release.

RicoSuter commented 7 years ago

The problem with this PR: It is not allowed to set the "schema" of a query/path parameter, and thus your will never be used for a valid Swagger specification... Where do you get a Swagger spec where a query/path parameter has a "schema"?

public bool IsObjectArray => IsArray && Schema.Item?.Type == JsonObjectType.Object;
RicoSuter commented 7 years ago

See http://swagger.io/specification/#parameterObject

RicoSuter commented 7 years ago

"schema" is only allowed for body parameters

fabrikam-champions commented 7 years ago

It's very reasonable though,

  1. I need to pass a DataSourceRequest object to the get method's URL public DataSourceResult<UserDTO> Get([FromQuery]DataSourceRequest dataSourceRequest) so that it contains the paging, sort and filter paramters
    public class DataSourceRequest
    {
    public int Take { get; set; }
    public int Skip { get; set; }
    public IEnumerable<Sort> Sort { get; set; }
    public Filter Filter { get; set; }
    }
  2. the Filter property may be nested
    public class Filter
    {
    public string Field { get; set; }
    public string Operator { get; set; }
    public string Value { get; set; }
    public string Logic { get; set; }
    public IEnumerable<Filter> Filters { get; set; }
    }
  3. MVC allows this type of binding http://stackoverflow.com/questions/17578878/using-fromuri-attribute-bind-complex-object-with-nested-array http://stackoverflow.com/questions/31376560/complex-parameter-binding-array-of-nested-class-in-web-api
  4. The Swashbuckle's Swagger spec (as this sample) gives the required specifications for this case.

all what I'm missing is to get NSwag handles this type of binding. and I don't care much if this handling happens in the TypeScript side or the T4 template side.

RicoSuter commented 7 years ago

In the sample spec, your DataSourceRequest and Filter are flat parameters. Your PR will not work with that spec - we have to merge these parameters into a complex parameter first... and then decompose the instances in the client method (similar to your PR)

fabrikam-champions commented 7 years ago

Honestly, I didn't test the latest commit locally before pushing it. but I did now and it worked only when I changed the IsObjectArray condition to accept None object types:

public bool IsObjectArray =>
    IsArray &&
    (Schema.Item?.Type == JsonObjectType.Object ||
    Schema.Item?.Type == JsonObjectType.None);

Is it suitable to do so? or would break something else?

RicoSuter commented 7 years ago

I think its better to use:

    public bool IsObjectArray => IsArray && (Schema.Item?.Type == JsonObjectType.Object || Schema.Item?.IsAnyType == true);
RicoSuter commented 7 years ago

Is the correct code generated with this change?

fabrikam-champions commented 7 years ago

Yes. and the modification you suggested about the condition gives the same result. but I don't feel it's right to consider ANY type as an Object. or may be it's a misleading name for the property "IsAnyType"!

RicoSuter commented 7 years ago

Any can be any object, primitive types (like string and integer) must be defined... so its fine internally

fabrikam-champions commented 7 years ago

I got the latest release today and enjoyed my issue fixed. :+1: Thanks a lot. :handshake: note that I discovered the same problem here but I'm fine with NSwag now :wink:

RicoSuter commented 7 years ago

:+1: enjoy

RicoSuter commented 7 years ago

According to http://swagger.io/specification/#itemsObject complex array items are not supported and your code will never be called for a valid Swagger spec:

The value MUST be one of "string", "number", "integer", "boolean", or "array".

... and thus the type is never "object" or empty (any type)