Search code examples
swaggerswagger-uiswagger-2.0swagger-codegennswag

nswag generates proxy which spoils the URL


The general idea of nswag software is amazing.

The guys have totally ruined it though.

I'm really now thinking of dropping it for the following reasons:

  • overcomplicated

  • problematic

  • extremely poorly documented

  • unpopular

Regarding my version - "[email protected]".

My service is supposed to pass a compound structure (e.g. nested arrays) - but in recent versions it passes all of the content via the URL and here's what I mean:

enter image description here

Moreover, recent versions of it don't generate input classes - e.g. my API controller has action ImportEntries(ImportEntriesInput input)

nswag no longer generates input class (I mean ImportEntriesInput) - instead it just lists all of its members:

For example compare

importEntries(input: ImportEntriesInput | null | undefined): Observable<VocabularyDto> {

with

importEntries(entries: CrawlerEntryDto[] | null | undefined, vocabularyId: number | undefined, newVocabulary: boolean | undefined, typeId: number | undefined, name: string | null | undefined, notes: string | null | undefined): Observable<VocabularyDto | null> {

Maybe the guys who develop it find it okay, but I'd say this totally overcomplicates the whole approach and is too bad.

I wasn't really able to find documentation covering this part.

Anyone knows how to solve this?


Also, here's the bit where it creates the content being passes in URL:

importEntries(entries: CrawlerEntryDto[] | null | undefined, vocabularyId: number | undefined, newVocabulary: boolean | undefined, typeId: number | undefined, name: string | null | undefined, notes: string | null | undefined): Observable<VocabularyDto | null> {
    let url_ = this.baseUrl + "/api/Import/ImportEntries?";
    if (entries !== undefined)
        entries && entries.forEach((item, index) => { 
            for (let attr in item)
                url_ += "entries[" + index + "]." + attr + "=" + encodeURIComponent("" + item[attr]) + "&";
        });
    if (vocabularyId === null)
        throw new Error("The parameter 'vocabularyId' cannot be null.");
    else if (vocabularyId !== undefined)
        url_ += "vocabularyId=" + encodeURIComponent("" + vocabularyId) + "&"; 
    if (newVocabulary === null)
        throw new Error("The parameter 'newVocabulary' cannot be null.");
    else if (newVocabulary !== undefined)
        url_ += "newVocabulary=" + encodeURIComponent("" + newVocabulary) + "&"; 
    if (typeId === null)
        throw new Error("The parameter 'typeId' cannot be null.");
    else if (typeId !== undefined)
        url_ += "typeId=" + encodeURIComponent("" + typeId) + "&"; 
    if (name !== undefined)
        url_ += "name=" + encodeURIComponent("" + name) + "&"; 
    if (notes !== undefined)
        url_ += "notes=" + encodeURIComponent("" + notes) + "&"; 
    url_ = url_.replace(/[?&]$/, "");

    let options_ : any = {
        observe: "response",
        responseType: "blob",
        headers: new HttpHeaders({
            "Content-Type": "application/json", 
            "Accept": "application/json",
            'Authorization': 'Bearer ' + localStorage.getItem('token')
        })
    };

    return this.http.request("post", url_, options_).flatMap((response_ : any) => {
        return this.processImportEntries(response_);
    }).catch((response_: any) => {
        if (response_ instanceof HttpResponseBase) {
            try {
                return this.processImportEntries(<any>response_);
            } catch (e) {
                return <Observable<VocabularyDto | null>><any>Observable.throw(e);
            }
        } else
            return <Observable<VocabularyDto | null>><any>Observable.throw(response_);
    });
}

Quite appalling, isn't it?

swaggerToTypeScriptClient bit from config:

"codeGenerators": {
    "swaggerToTypeScriptClient": {
      "className": "{controller}ServiceProxy",
      "moduleName": "",
      "namespace": "",
      "typeScriptVersion": 2.0,
      "template": "Angular",
      "promiseType": "Promise",
        "httpClass": "HttpClient",
      "dateTimeType": "MomentJS",
      "nullValue": "Undefined",
      "generateClientClasses": true,
      "generateClientInterfaces": false,
      "generateOptionalParameters": false,
      "wrapDtoExceptions": false,
      "wrapResponses": false,
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "useTransformOptionsMethod": false,
      "useTransformResultMethod": false,
      "generateDtoTypes": true,
      "operationGenerationMode": "MultipleClientsFromPathSegments"
      "markOptionalProperties": false,
      "generateCloneMethod": true,
      "typeStyle": "Class",
      "extensionCode": "service.extensions.ts",
      "generateDefaultValues": true,
      "excludedTypeNames": [],
      "handleReferences": false,
      "generateConstructorInterface": true,
      "importRequiredTypes": true,
      "useGetBaseUrlMethod": false,
      "baseUrlTokenName": "API_BASE_URL",
      "injectionTokenType": "InjectionToken",
      "output": "../src/shared/service-proxies/service-proxies.ts"
    },

Solution

  • This fixes the issue with URL mentioned in my post above.

    That wasn't documented, but in order for nswag to work properly with ASP.NET Core you are meant to apply [FromBody] attribute to every action accepting data

    e.g.

    public async Task<VocabularyDto> ImportEntries([FromBody] ImportEntriesInput input)