Nswag: Option to use property bag for calling GET operations in Angular

Created on 31 Oct 2018  路  5Comments  路  Source: RicoSuter/NSwag

When calling a POST request of a WebAPI you can use a class to represent the payload, and the typescript client will generate an interface or class for it. This obviously means the parameter order doesn't matter. You call it like this from Angular:

this.thingClient.postNewThing( { id: 'all', thingName: 'Dog' } )

I want to be able to do this with GET with the Angular client, however I don't see a way to do so that doesn't require me to keep the list of parameters in sync. If I use a helper payload object with a GET then the client tries to send it in the body which is no good.

I'm using [FromQuery] in .NET API method - which currently generates a signature like this in the typescript client - all parameters in an ordered list:

ThingClient.searchThings(parameter1: string, parameter2: string, parameter3:string);

Obviously I have to call these in the correct order, or it will break. This is fine for just a couple parameters, but can be dangerous if you make sweeping changes to your API. What I'd like to do is use an interface like this when calling the API - so I can put the parameters in any order and the compiler will tell me if the name of anything changes.

this.thingClient.getThings({ parameter2: '2', parameter1: '111', parameter3: '333');

This would be a very simple implementation - driven by a flag - and the new generated code would look like this:

// use options property bag, with optional parameters as appropriate
getThings(options: { parameter1?: string, parameter2?: string, parameter3?: string}): Observable<ThingReport | null> 
{
     // construct the URL from non empty parameters
     if (options.parameter1!== undefined) { url_ += "parameter1="... }
     }

You wouldn't even need to define a new interface explicitly, the parameters just get put into a 'bag' and then the URL is constructed from whatever values aren't undefined.

I think this would be a new option because generating two signatures would be problematic. I would much prefer this over the ordered parameter list.

NSwag.CodeGeneration.TypeScript enhancement

Most helpful comment

It makes sense to have this option, but it should be completely typed not just an any property bag and it should be behind a new setting so that we dont break existing users.

Also it should be implemented for all TypeScript templates, not only Angular (if possible).

All 5 comments

It makes sense to have this option, but it should be completely typed not just an any property bag and it should be behind a new setting so that we dont break existing users.

Also it should be implemented for all TypeScript templates, not only Angular (if possible).

@RicoSuter We absolutely need this and I'm happy to implement it within the next days, but its hard to understand where to start. Could you provide a pointer? It looks like this cannot be handled by updating the typescript templates alone as the properties get injected by outside code (outside of the template).

Where in the code does the generation of the properties happen? Once this is changed it should be no problem to adjust the templates (as existing code would be reused with minor modifications).

Las step would be to provide a config option to make this optional, again I would be grateful for pointers where this sits in the codebase.

Ok after intial review here are some observations:

  • When using [FromQuery] + a complex typed input the type name of the complex type is NOT included in the open api specification (which makes sense)
  • Assuming that generation can only happend with what is in the specification information the first learning is that we either need to use an anonymous type (with typed properties) or generate some name (I'm currently leaning towards the first)
  • I see a two phased approach to implement this within nswag

    • at first template only (we will do the angular one first)

    • hard code (no option) that all get requests (identify based on http method on operation) get wrapped into an anonymous type (update generation code of method signature e.g AngularClient.liquid), say into a variable named requestData

    • hard code (no option) Client.RequestUrl.liquid to prepend all variable names with requestData.

    • upside, no work needed in .NET projects

    • using not very accessible to others

    • with changes in .Net code

    • extend operation (? not sure if this is the right place) with a flag like QueryParametersAsObject

    • extend the above templates to react based on value of this newly created flag

Am I on the right track here?

Here is a REALLY simple version of wrapping get/head parameters into property bag when there are query string parameters. Templates can be found here:
https://github.com/ntziolis/NSwag/tree/b49d0fdf7cd81d60f38a35baed0d1b9dd673a0cc/src/NSwag.CodeGeneration.TypeScript/Templates

Some notes:

  • Angular Only (but should be very easy to adapt as its only a single line that is angular specific)
  • The conditions for wrapping are (Get or Head) AND has query parameters
  • If so it assumes ALL parameters are query parameters and it wraps them into an object
  • properties are fully typed
  • wrapping type is anonymous

This suffices for our use case as we don't mix path parameters and query params. But I'll be working on making this more solid moving forward.

The solution has since been extended to only wrap query parameters:
https://github.com/ntziolis/NSwag/tree/master/src/NSwag.CodeGeneration.TypeScript/Templates

Note:

  • the solution now works as expected
  • that said it still contains a liquid hack as I was unable to generate a { while retaining the formatting I needed for the properties of the anonymous property bag without using a variable:
    {%- assign openBraces = "{" -%}
  • I'd be grateful for anybody with more liquid experience to suggest how to get rid of this
  • again this has no effect on the end result, its just ugly

I did run into some issues that caused this to take much longer than necessary, which should be called out in the template section of the documentation:

  • NSwag uses DotLiquid and DOtLiquid does NOT many of the documented liquid keywords
  • DotLiquid does NOT support

    • the liquid where filter:

      assign nonQueryParameters = operation.Parameters | where: "IsQuery", false

    • assigning a variable with the result of a bool condition:

      assign hasWrappedQueryParameter = operation.IsGetOrHead and operation.QueryParameters.size > 0

Was this page helpful?
0 / 5 - 0 ratings