Browse Source

Merge pull request #26049 from abpframework/maliming/tenant-connection-string-in-body

Send the tenant connection string in the request body
pull/26063/head
İsmail ÇAĞDAŞ 3 days ago
committed by GitHub
parent
commit
b80ec2f3c3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      docs/en/modules/tenant-management.md
  2. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi.Client/ClientProxies/multi-tenancy-generate-proxy.json
  3. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs
  4. 5
      modules/tenant-management/src/Volo.Abp.TenantManagement.Web/wwwroot/client-proxies/multi-tenancy-proxy.js
  5. 12
      npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template
  6. 9
      npm/ng-packs/packages/schematics/src/models/method.ts
  7. 44
      npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts
  8. 29
      npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts
  9. 2
      npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json
  10. 3
      npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts

2
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

2
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": ""
}
],

2
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);
}

5
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));
};

12
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 %>,<% } %><%

9
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:

44
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<Action>),
);
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<Action>),
);
expect(body.body).toBe('input');
expect(body.contentTypeHeader).toBeUndefined();
});
});
describe('createActionToBodyMapper — IRemoteStreamContent return value', () => {
const mapBody = createActionToBodyMapper();

29
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>): 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',

2
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": ""
}
],

3
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<any, void>({
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 });
}

Loading…
Cancel
Save