'Nswag (asp.net core) --> Typescript (Angular). ModelBinder not binding multipart endpoint
I have the following net core 6.0 controller:
[HttpPost("/clients/{clientId}/authority-to-release-form")]
[ProducesResponseType(200, Type = typeof(long))]
public async Task Post(long clientId, [FromForm][ModelBinder(BinderType = typeof(JsonModelBinder))]
AuthorityToReleaseForm form, [FromForm] IFormFile file)
{
Ok(form);
}
Nswag is generating the following Typescript for Angular use:
authorityToReleaseForm_Post(clientId: number, form?: AuthorityToReleaseForm | null | undefined, file?: FileParameter | null | undefined): Observable<number> {
let url_ = this.baseUrl + "/clients/{clientId}/authority-to-release-form";
if (clientId=== undefined || clientId=== null)
throw new Error("The parameter 'clientId' must be defined.");
url_ = url_.replace("{clientId}", encodeURIComponent("" + clientId));
url_ = url_.replace(/[?&]$/, "");
const content_ = new FormData();
if (form !== null && form !== undefined)
content_.append("form", form.toString());
if (file !== null && file !== undefined)
content_.append("file", file.data, file.fileName ? file.fileName : "file");
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Accept": "application/json"
})
};
return this.http.request("post", url_, options_).pipe(_observableMergeMap((response_ : any) => {
return this.processAuthorityToReleaseForm_Post(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processAuthorityToReleaseForm_Post(<any>response_);
} catch (e) {
return <Observable<number>><any>_observableThrow(e);
}
} else
return <Observable<number>><any>_observableThrow(response_);
}));
}
The .nswag config file is:
{
"runtime": "Net60",
"defaultVariables": null,
"documentGenerator": {
"webApiToOpenApi": {
"controllerNames": [
"server.Controllers.AuthorityToReleaseFormController"
],
"isAspNetCore": true,
"resolveJsonOptions": false,
"defaultUrlTemplate": "api/{controller}/{id?}",
"addMissingPathParameters": false,
"includedVersions": null,
"defaultPropertyNameHandling": "Default",
"defaultReferenceTypeNullHandling": "Null",
"defaultDictionaryValueReferenceTypeNullHandling": "NotNull",
"defaultResponseReferenceTypeNullHandling": "NotNull",
"defaultEnumHandling": "Integer",
"flattenInheritanceHierarchy": false,
"generateKnownTypes": true,
"generateEnumMappingDescription": false,
"generateXmlObjects": false,
"generateAbstractProperties": false,
"generateAbstractSchemas": true,
"ignoreObsoleteProperties": false,
"allowReferencesWithProperties": false,
"excludedTypeNames": [],
"serviceHost": null,
"serviceBasePath": null,
"serviceSchemes": [],
"infoTitle": "Server API",
"infoDescription": null,
"infoVersion": "1.0.0",
"documentTemplate": null,
"documentProcessorTypes": [],
"operationProcessorTypes": [],
"typeNameGeneratorType": null,
"schemaNameGeneratorType": null,
"contractResolverType": null,
"serializerSettingsType": null,
"useDocumentProvider": true,
"documentName": "v1",
"aspNetCoreEnvironment": null,
"createWebHostBuilderMethod": null,
"startupType": null,
"allowNullableBodyParameters": true,
"output": null,
"outputType": "Swagger2",
"assemblyPaths": [
"bin/Debug/net6.0/server.dll"
],
"assemblyConfig": null,
"referencePaths": [],
"useNuGetCache": false
}
},
"codeGenerators": {
"openApiToTypeScriptClient": {
"className": "ApiGeneratedService",
"moduleName": "",
"namespace": "",
"typeScriptVersion": 2.7,
"template": "Angular",
"promiseType": "Promise",
"httpClass": "HttpClient",
"withCredentials": false,
"useSingletonProvider": false,
"injectionTokenType": "InjectionToken",
"rxJsVersion": 6.0,
"dateTimeType": "Date",
"nullValue": "Null",
"generateClientClasses": true,
"generateClientInterfaces": false,
"generateOptionalParameters": true,
"exportTypes": true,
"wrapDtoExceptions": false,
"exceptionClass": "ApiException",
"clientBaseClass": null,
"wrapResponses": false,
"wrapResponseMethods": [],
"generateResponseClasses": true,
"responseClass": "SwaggerResponse",
"protectedMethods": [],
"configurationClass": null,
"useTransformOptionsMethod": false,
"useTransformResultMethod": false,
"generateDtoTypes": true,
"operationGenerationMode": "SingleClientFromOperationId",
"markOptionalProperties": true,
"generateCloneMethod": false,
"typeStyle": "Interface",
"classTypes": [],
"extendedClasses": [],
"extensionCode": null,
"generateDefaultValues": true,
"excludedTypeNames": [],
"excludedParameterNames": [],
"handleReferences": false,
"generateConstructorInterface": true,
"convertConstructorInterfaceData": false,
"importRequiredTypes": true,
"useGetBaseUrlMethod": false,
"baseUrlTokenName": "API_BASE_URL",
"queryNullValue": "",
"inlineNamedDictionaries": false,
"inlineNamedAny": false,
"templateDirectory": null,
"typeNameGeneratorType": null,
"propertyNameGeneratorType": null,
"enumNameGeneratorType": null,
"serviceHost": null,
"serviceSchemes": null,
"output": "../client/projects/core-lib/src/lib/app.api.generated.ts"
}
}
}
The problem is, we are posting to a multipart form endpoint (which is using a ModelBinder to bind to the Json model), but the Typescript is not stringifying the model and thus it's not binding.
ie.
generated code: content_.append("form", form.toString());
would have expected: content_.append("form", Json.stringify(form));
We are working around it by passing the form through like this:
const jsonForm = JSON.stringify(this.model.toForm()) as unknown as AuthorityToReleaseForm; this.api.authorityToReleaseForm_Post(this.clientId, jsonForm, this.file).subscribe();
This works but is pretty hacky.
Is there any way to get the generated code to stringify the json model data or a nicer work-around?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
