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.
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:
[FromQuery] + a complex typed input the type name of the complex type is NOT included in the open api specification (which makes sense)requestDatarequestData.QueryParametersAsObject 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:
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:
{ while retaining the formatting I needed for the properties of the anonymous property bag without using a variable:{%- assign openBraces = "{" -%}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:
assign nonQueryParameters = operation.Parameters | where: "IsQuery", falseassign hasWrappedQueryParameter = operation.IsGetOrHead and operation.QueryParameters.size > 0
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).