diff --git a/docs/en/modules/tenant-management.md b/docs/en/modules/tenant-management.md index 101c7f076f..b19f390a41 100644 --- a/docs/en/modules/tenant-management.md +++ b/docs/en/modules/tenant-management.md @@ -146,7 +146,7 @@ This section can be used as a reference if you want to [customize](../framework/ * `TenantAppService` -In addition to tenant CRUD operations, `ITenantAppService` provides `GetDefaultConnectionStringAsync`, `UpdateDefaultConnectionStringAsync` and `DeleteDefaultConnectionStringAsync`. The HTTP API exposes these operations as `GET`, `PUT` and `DELETE` on `/api/multi-tenancy/tenants/{id}/default-connection-string`; the `PUT` request receives `defaultConnectionString` as a query parameter. +In addition to tenant CRUD operations, `ITenantAppService` provides `GetDefaultConnectionStringAsync`, `UpdateDefaultConnectionStringAsync` and `DeleteDefaultConnectionStringAsync`. The HTTP API exposes these operations as `GET`, `PUT` and `DELETE` on `/api/multi-tenancy/tenants/{id}/default-connection-string`; the `PUT` request receives `defaultConnectionString` in the request body, as a JSON string with the `application/json` content type. #### Permissions diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi.Client/ClientProxies/multi-tenancy-generate-proxy.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi.Client/ClientProxies/multi-tenancy-generate-proxy.json index de1f6494f6..1122511d38 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi.Client/ClientProxies/multi-tenancy-generate-proxy.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi.Client/ClientProxies/multi-tenancy-generate-proxy.json @@ -496,7 +496,7 @@ "isOptional": false, "defaultValue": null, "constraintTypes": null, - "bindingSourceId": "ModelBinding", + "bindingSourceId": "Body", "descriptorName": "" } ], diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs index f9dee1c9c3..4312066e02 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs @@ -63,7 +63,7 @@ public class TenantController : AbpControllerBase, ITenantAppService //TODO: Thr [HttpPut] [Route("{id}/default-connection-string")] - public virtual Task UpdateDefaultConnectionStringAsync(Guid id, [DisableAuditing] string defaultConnectionString) + public virtual Task UpdateDefaultConnectionStringAsync(Guid id, [FromBody] [DisableAuditing] string defaultConnectionString) { return TenantAppService.UpdateDefaultConnectionStringAsync(id, defaultConnectionString); } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/wwwroot/client-proxies/multi-tenancy-proxy.js b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/wwwroot/client-proxies/multi-tenancy-proxy.js index 1ef5cddae9..242fb054a5 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/wwwroot/client-proxies/multi-tenancy-proxy.js +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/wwwroot/client-proxies/multi-tenancy-proxy.js @@ -58,9 +58,10 @@ volo.abp.tenantManagement.tenant.updateDefaultConnectionString = function(id, defaultConnectionString, ajaxParams) { return abp.ajax($.extend(true, { - url: abp.appPath + 'api/multi-tenancy/tenants/' + id + '/default-connection-string' + abp.utils.buildQueryString([{ name: 'defaultConnectionString', value: defaultConnectionString }]) + '', + url: abp.appPath + 'api/multi-tenancy/tenants/' + id + '/default-connection-string', type: 'PUT', - dataType: null + dataType: null, + data: JSON.stringify(defaultConnectionString) }, ajaxParams)); }; diff --git a/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template b/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template index aaa4220d9b..70e3138fdd 100644 --- a/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template +++ b/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template @@ -13,6 +13,10 @@ export class <%= name %>Service { const responseType = isBlob ? 'Blob' : body.responseType; const httpResponseType = body.httpResponseType; const acceptHeader = body.acceptHeader; + const headerEntries = []; + if (acceptHeader) { headerEntries.push("Accept: '" + acceptHeader + "'"); } + if (body.contentTypeHeader) { headerEntries.push("'Content-Type': '" + body.contentTypeHeader + "'"); } + const headers = headerEntries.length ? '{ ' + headerEntries.join(', ') + ' }' : ''; const resourceParameters = signature.parameters.filter(p => p.name !== 'config'); const resourceType = resourceParameters.length ? `{ ${resourceParameters.map(p => `${p.name}${p.optional}: ${p.type}`).join('; ')} }` @@ -33,8 +37,8 @@ export class <%= name %>Service { method: '<%= body.method %>',<% if (httpResponseType && httpResponseType !== 'json') { %> responseType: '<%= httpResponseType %>',<% } %><% - if (acceptHeader) { %> - headers: { Accept: '<%= acceptHeader %>' },<% } %> + if (headers) { %> + headers: <%= headers %>,<% } %> url: <%= body.url %>,<% if (body.dictParamVar && !body.params.length) { %> params: <%= body.dictParamVar %>,<% } %><% @@ -53,8 +57,8 @@ export class <%= name %>Service { method: '<%= body.method %>',<% if (httpResponseType && httpResponseType !== 'json') { %> responseType: '<%= httpResponseType %>',<% } %><% - if (acceptHeader) { %> - headers: { Accept: '<%= acceptHeader %>' },<% } %> + if (headers) { %> + headers: <%= headers %>,<% } %> url: <%= body.url %>,<% if (body.dictParamVar && !body.params.length) { %> params: <%= body.dictParamVar %>,<% } %><% diff --git a/npm/ng-packs/packages/schematics/src/models/method.ts b/npm/ng-packs/packages/schematics/src/models/method.ts index c160359770..aa55077a2c 100644 --- a/npm/ng-packs/packages/schematics/src/models/method.ts +++ b/npm/ng-packs/packages/schematics/src/models/method.ts @@ -47,6 +47,7 @@ export class Body { responseType: string; httpResponseType?: 'json' | 'text' | 'blob' | 'arraybuffer'; acceptHeader?: string; + contentTypeHeader?: string; url: string; registerActionParameter = (param: ParameterInBody) => { @@ -68,7 +69,15 @@ export class Body { this.params.push(paramName === value ? value : `${getParamName(paramName)}: ${value}`); break; case eBindingSourceId.FormFile: + this.body = value; + break; case eBindingSourceId.Body: + /* Angular sends a plain string body as text/plain, but the endpoint expects a JSON string. */ + if (param.typeSimple === 'string') { + this.body = `JSON.stringify(${value})`; + this.contentTypeHeader = 'application/json'; + break; + } this.body = value; break; case eBindingSourceId.Path: diff --git a/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts b/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts index 9e35e9ef8c..021046fb57 100644 --- a/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts +++ b/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts @@ -76,6 +76,50 @@ describe('createActionToBodyMapper — string return value', () => { }); }); +describe('createActionToBodyMapper — body parameters', () => { + const mapBody = createActionToBodyMapper(); + + test('string body parameter is JSON encoded and gets an application/json content type', () => { + const body = mapBody( + buildAction({ + httpMethod: 'POST', + parameters: [ + { + nameOnMethod: 'connectionString', + name: 'connectionString', + type: 'System.String', + typeSimple: 'string', + bindingSourceId: eBindingSourceId.Body, + }, + ], + } as Partial), + ); + + expect(body.body).toBe('JSON.stringify(connectionString)'); + expect(body.contentTypeHeader).toBe('application/json'); + }); + + test('object body parameter is passed as is', () => { + const body = mapBody( + buildAction({ + httpMethod: 'POST', + parameters: [ + { + nameOnMethod: 'input', + name: 'input', + type: 'My.Project.UserDto', + typeSimple: 'My.Project.UserDto', + bindingSourceId: eBindingSourceId.Body, + }, + ], + } as Partial), + ); + + expect(body.body).toBe('input'); + expect(body.contentTypeHeader).toBeUndefined(); + }); +}); + describe('createActionToBodyMapper — IRemoteStreamContent return value', () => { const mapBody = createActionToBodyMapper(); diff --git a/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts b/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts index 611c0ef599..10cdbd5da8 100644 --- a/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts +++ b/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts @@ -70,6 +70,7 @@ interface MockBody { responseTypeWithNamespace: string; httpResponseType?: string; acceptHeader?: string; + contentTypeHeader?: string; body?: string; params: string[]; dictParamVar?: string; @@ -85,6 +86,7 @@ function makeBody(overrides: Partial): MockBody { responseTypeWithNamespace: 'any', httpResponseType: undefined, acceptHeader: undefined, + contentTypeHeader: undefined, body: undefined, params: [], dictParamVar: undefined, @@ -106,6 +108,33 @@ describe('proxy service template — rendered output', () => { expect(output).not.toContain('headers:'); }); + test('string body emits a content type header and a JSON encoded body', () => { + const output = render(buildContext({ + method: 'POST', + body: 'JSON.stringify(connectionString)', + contentTypeHeader: 'application/json', + responseType: 'boolean', + responseTypeWithNamespace: 'boolean', + })); + + expect(output).toContain("headers: { 'Content-Type': 'application/json' }"); + expect(output).toContain('body: JSON.stringify(connectionString)'); + }); + + test('accept and content type headers are emitted together', () => { + const output = render(buildContext({ + method: 'POST', + body: 'JSON.stringify(value)', + contentTypeHeader: 'application/json', + acceptHeader: 'text/plain', + httpResponseType: 'text', + responseType: 'string', + responseTypeWithNamespace: 'string', + })); + + expect(output).toContain("headers: { Accept: 'text/plain', 'Content-Type': 'application/json' }"); + }); + test('resource api mode emits requestResource helper for GET methods', () => { const ctx = buildContext({ responseType: 'MyDto', diff --git a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json index bb4cf82675..79b44f205a 100644 --- a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json +++ b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json @@ -493,7 +493,7 @@ "isOptional": false, "defaultValue": null, "constraintTypes": null, - "bindingSourceId": "ModelBinding", + "bindingSourceId": "Body", "descriptorName": "" } ], diff --git a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts index 28174f8ad8..7995594b38 100644 --- a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts +++ b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts @@ -67,8 +67,9 @@ export class TenantService { updateDefaultConnectionString = (id: string, defaultConnectionString: string) => this.restService.request({ method: 'PUT', + headers: { 'Content-Type': 'application/json' }, url: `/api/multi-tenancy/tenants/${id}/default-connection-string`, - params: { defaultConnectionString }, + body: JSON.stringify(defaultConnectionString), }, { apiName: this.apiName }); }