Browse Source

Merge pull request #11551 from abpframework/Api-Versioning

Compatible with API Versioning.
pull/11578/head
Halil İbrahim Kalkan 5 years ago
committed by GitHub
parent
commit
497fdd7f62
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 349
      docs/en/API/API-Versioning.md
  2. 4
      docs/en/docs-nav.json
  3. 347
      docs/zh-Hans/API/API-Versioning.md
  4. 4
      docs/zh-Hans/docs-nav.json
  5. 3
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs
  6. 34
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiDescriptionExtensions.cs
  7. 27
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs
  8. 17
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
  9. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  10. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Versioning/HttpContextRequestedApiVersion.cs
  11. 2
      framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs
  12. 10
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs
  13. 22
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/CurrentApiVersionInfo.cs
  14. 10
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ICurrentApiVersionInfo.cs
  15. 8
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs
  16. 5
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs
  17. 2
      framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs

349
docs/en/API/API-Versioning.md

@ -0,0 +1,349 @@
# API Versioning System
ABP Framework integrates the [ASPNET-API-Versioning](https://github.com/dotnet/aspnet-api-versioning/wiki) feature and adapts to C# and JavaScript Static Client Proxies and [Auto API Controller](API/Auto-API-Controllers.md).
## Enable API Versioning
```cs
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ChangeControllerModelApiExplorerGroupName = false;
});
}
```
## C# and JavaScript Static Client Proxies
This feature does not compatible with [URL Path Versioning](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-URL-Path), I suggest you always use [Versioning-via-the-Query-String](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-Query-String).
### Example
**Application Services:**
```cs
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync();
}
public interface IBookV2AppService : IApplicationService
{
Task<BookDto> GetAsync();
Task<BookDto> GetAsync(string isbn);
}
```
**HttpApi Controillers:**
```cs
[Area(BookStoreRemoteServiceConsts.ModuleName)]
[RemoteService(Name = BookStoreRemoteServiceConsts.RemoteServiceName)]
[ApiVersion("1.0", Deprecated = true)]
[ApiController]
[ControllerName("Book")]
[Route("api/BookStore/Book")]
public class BookController : BookStoreController, IBookAppService
{
private readonly IBookAppService _bookAppService;
public BookController(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
[HttpGet]
public async Task<BookDto> GetAsync()
{
return await _bookAppService.GetAsync();
}
}
[Area(BookStoreRemoteServiceConsts.ModuleName)]
[RemoteService(Name = BookStoreRemoteServiceConsts.RemoteServiceName)]
[ApiVersion("2.0")]
[ApiController]
[ControllerName("Book")]
[Route("api/BookStore/Book")]
public class BookV2Controller : BookStoreController, IBookV2AppService
{
private readonly IBookV2AppService _bookAppService;
public BookV2Controller(IBookV2AppService bookAppService)
{
_bookAppService = bookAppService;
}
[HttpGet]
public async Task<BookDto> GetAsync()
{
return await _bookAppService.GetAsync();
}
[HttpGet]
[Route("{isbn}")]
public async Task<BookDto> GetAsync(string isbn)
{
return await _bookAppService.GetAsync(isbn);
}
}
```
**Generated CS and JS proxies:**
```cs
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBookAppService), typeof(BookClientProxy))]
public partial class BookClientProxy : ClientProxyBase<IBookAppService>, IBookAppService
{
public virtual async Task<BookDto> GetAsync()
{
return await RequestAsync<BookDto>(nameof(GetAsync));
}
}
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBookV2AppService), typeof(BookV2ClientProxy))]
public partial class BookV2ClientProxy : ClientProxyBase<IBookV2AppService>, IBookV2AppService
{
public virtual async Task<BookDto> GetAsync()
{
return await RequestAsync<BookDto>(nameof(GetAsync));
}
public virtual async Task<BookDto> GetAsync(string isbn)
{
return await RequestAsync<BookDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
{
{ typeof(string), isbn }
});
}
}
```
```js
// controller bookStore.books.book
(function(){
abp.utils.createNamespace(window, 'bookStore.books.book');
bookStore.books.book.get = function(api_version, ajaxParams) {
var api_version = api_version ? api_version : '1.0';
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/BookStore/Book' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '',
type: 'GET'
}, ajaxParams));
};
})();
// controller bookStore.books.bookV2
(function(){
abp.utils.createNamespace(window, 'bookStore.books.bookV2');
bookStore.books.bookV2.get = function(api_version, ajaxParams) {
var api_version = api_version ? api_version : '2.0';
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/BookStore/Book' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '',
type: 'GET'
}, ajaxParams));
};
bookStore.books.bookV2.getAsyncByIsbn = function(isbn, api_version, ajaxParams) {
var api_version = api_version ? api_version : '2.0';
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/BookStore/Book/' + isbn + '' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '',
type: 'GET'
}, ajaxParams));
};
})();
```
## Manually change version
If an application service class supports multiple versions. You can inject `ICurrentApiVersionInfo` to switch versions in C#.
```cs
var currentApiVersionInfo = _abpApplication.ServiceProvider.GetRequiredService<ICurrentApiVersionInfo>();
var bookV4AppService = _abpApplication.ServiceProvider.GetRequiredService<IBookV4AppService>();
using (currentApiVersionInfo.Change(new ApiVersionInfo(ParameterBindingSources.Query, "4.0")))
{
book = await bookV4AppService.GetAsync();
logger.LogWarning(book.Title);
logger.LogWarning(book.ISBN);
}
using (currentApiVersionInfo.Change(new ApiVersionInfo(ParameterBindingSources.Query, "4.1")))
{
book = await bookV4AppService.GetAsync();
logger.LogWarning(book.Title);
logger.LogWarning(book.ISBN);
}
```
We have made a default version in the JS proxy. Of course, you can also manually change the version.
```js
bookStore.books.bookV4.get("4.0") // Manually change the version.
//Title: Mastering ABP Framework V4.0
bookStore.books.bookV4.get() // The latest supported version is used by default.
//Title: Mastering ABP Framework V4.1
```
## Auto API Controller
```cs
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpAspNetCoreMvcOptions>(options =>
{
//2.0 Version
options.ConventionalControllers.Create(typeof(BookStoreWebAppModule).Assembly, opts =>
{
opts.TypePredicate = t => t.Namespace == typeof(BookStore.Controllers.ConventionalControllers.v2.TodoAppService).Namespace;
opts.ApiVersions.Add(new ApiVersion(2, 0));
});
//1.0 Compatibility version
options.ConventionalControllers.Create(typeof(BookStoreWebAppModule).Assembly, opts =>
{
opts.TypePredicate = t => t.Namespace == typeof(BookStore.Controllers.ConventionalControllers.v1.TodoAppService).Namespace;
opts.ApiVersions.Add(new ApiVersion(1, 0));
});
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var preActions = context.Services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>();
Configure<AbpAspNetCoreMvcOptions>(options =>
{
preActions.Configure(options);
});
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.ConfigureAbp(preActions.Configure());
});
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ChangeControllerModelApiExplorerGroupName = false;
});
}
```
## Swagger/VersionedApiExplorer
```cs
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
context.Services.AddVersionedApiExplorer(
options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
context.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
context.Services.AddAbpSwaggerGen(
options =>
{
// add a custom operation filter which sets default values
options.OperationFilter<SwaggerDefaultValues>();
options.CustomSchemaIds(type => type.FullName);
});
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ChangeControllerModelApiExplorerGroupName = false;
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseErrorPage();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAbpRequestLocalization();
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
var provider = app.ApplicationServices.GetRequiredService<IApiVersionDescriptionProvider>();
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
app.UseConfiguredEndpoints();
}
```
## Custom multi-version API controller.
ABP Framework will not affect to your APIs, you can freely implement your APIs according to Microsoft's documentation.
Get more from https://github.com/dotnet/aspnet-api-versioning/wiki
## Sample source code
You can get the complete sample source code in https://github.com/abpframework/abp-samples/tree/master/Api-Versioning

4
docs/en/docs-nav.json

@ -590,6 +590,10 @@
{
"text": "Swagger Integration",
"path": "API/Swagger-Integration.md"
},
{
"text": "API Versioning",
"path": "API/API-Versioning.md"
}
]
},

347
docs/zh-Hans/API/API-Versioning.md

@ -0,0 +1,347 @@
# ABP版本控制系统
ABP框架集成了[ASPNET-API-版本控制](https://github.com/dotnet/aspnet-api-versioning/wiki)功能并适配C#和JavaScript静态代理和[自动API控制器](API/Auto-API-Controllers.md).
## 启用API版本控制
```cs
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ChangeControllerModelApiExplorerGroupName = false;
});
}
```
## C# 和 JavaScript 静态客户端代理
这个功能不兼容[URL路径版本控制](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-URL-Path), 建议你始终使用[Query-String版本控制](https://github.com/dotnet/aspnet-api-versioning/wiki/Versioning-via-the-Query-String)
### 示例
**Application Services:**
```cs
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync();
}
public interface IBookV2AppService : IApplicationService
{
Task<BookDto> GetAsync();
Task<BookDto> GetAsync(string isbn);
}
```
**HttpApi Controillers:**
```cs
[Area(BookStoreRemoteServiceConsts.ModuleName)]
[RemoteService(Name = BookStoreRemoteServiceConsts.RemoteServiceName)]
[ApiVersion("1.0", Deprecated = true)]
[ApiController]
[ControllerName("Book")]
[Route("api/BookStore/Book")]
public class BookController : BookStoreController, IBookAppService
{
private readonly IBookAppService _bookAppService;
public BookController(IBookAppService bookAppService)
{
_bookAppService = bookAppService;
}
[HttpGet]
public async Task<BookDto> GetAsync()
{
return await _bookAppService.GetAsync();
}
}
[Area(BookStoreRemoteServiceConsts.ModuleName)]
[RemoteService(Name = BookStoreRemoteServiceConsts.RemoteServiceName)]
[ApiVersion("2.0")]
[ApiController]
[ControllerName("Book")]
[Route("api/BookStore/Book")]
public class BookV2Controller : BookStoreController, IBookV2AppService
{
private readonly IBookV2AppService _bookAppService;
public BookV2Controller(IBookV2AppService bookAppService)
{
_bookAppService = bookAppService;
}
[HttpGet]
public async Task<BookDto> GetAsync()
{
return await _bookAppService.GetAsync();
}
[HttpGet]
[Route("{isbn}")]
public async Task<BookDto> GetAsync(string isbn)
{
return await _bookAppService.GetAsync(isbn);
}
}
```
**生成 CS 和 JS 代理:**
```cs
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBookAppService), typeof(BookClientProxy))]
public partial class BookClientProxy : ClientProxyBase<IBookAppService>, IBookAppService
{
public virtual async Task<BookDto> GetAsync()
{
return await RequestAsync<BookDto>(nameof(GetAsync));
}
}
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBookV2AppService), typeof(BookV2ClientProxy))]
public partial class BookV2ClientProxy : ClientProxyBase<IBookV2AppService>, IBookV2AppService
{
public virtual async Task<BookDto> GetAsync()
{
return await RequestAsync<BookDto>(nameof(GetAsync));
}
public virtual async Task<BookDto> GetAsync(string isbn)
{
return await RequestAsync<BookDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
{
{ typeof(string), isbn }
});
}
}
```
```js
// controller bookStore.books.book
(function(){
abp.utils.createNamespace(window, 'bookStore.books.book');
bookStore.books.book.get = function(api_version, ajaxParams) {
var api_version = api_version ? api_version : '1.0';
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/BookStore/Book' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '',
type: 'GET'
}, ajaxParams));
};
})();
// controller bookStore.books.bookV2
(function(){
abp.utils.createNamespace(window, 'bookStore.books.bookV2');
bookStore.books.bookV2.get = function(api_version, ajaxParams) {
var api_version = api_version ? api_version : '2.0';
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/BookStore/Book' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '',
type: 'GET'
}, ajaxParams));
};
bookStore.books.bookV2.getAsyncByIsbn = function(isbn, api_version, ajaxParams) {
var api_version = api_version ? api_version : '2.0';
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/BookStore/Book/' + isbn + '' + abp.utils.buildQueryString([{ name: 'api-version', value: api_version }]) + '',
type: 'GET'
}, ajaxParams));
};
})();
```
## 手动更改版本
如果应用服务支持多版本, 你可以注入 `ICurrentApiVersionInfo` 来切换版本.
```cs
var currentApiVersionInfo = _abpApplication.ServiceProvider.GetRequiredService<ICurrentApiVersionInfo>();
var bookV4AppService = _abpApplication.ServiceProvider.GetRequiredService<IBookV4AppService>();
using (currentApiVersionInfo.Change(new ApiVersionInfo(ParameterBindingSources.Query, "4.0")))
{
book = await bookV4AppService.GetAsync();
logger.LogWarning(book.Title);
logger.LogWarning(book.ISBN);
}
using (currentApiVersionInfo.Change(new ApiVersionInfo(ParameterBindingSources.Query, "4.1")))
{
book = await bookV4AppService.GetAsync();
logger.LogWarning(book.Title);
logger.LogWarning(book.ISBN);
}
```
在JS代理中有一个默认版本, 当然你也可以手动更改.
```js
bookStore.books.bookV4.get("4.0") // Manually change the version.
//Title: Mastering ABP Framework V4.0
bookStore.books.bookV4.get() // The latest supported version is used by default.
//Title: Mastering ABP Framework V4.1
```
## 自动API控制器
```cs
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpAspNetCoreMvcOptions>(options =>
{
//2.0 Version
options.ConventionalControllers.Create(typeof(BookStoreWebAppModule).Assembly, opts =>
{
opts.TypePredicate = t => t.Namespace == typeof(BookStore.Controllers.ConventionalControllers.v2.TodoAppService).Namespace;
opts.ApiVersions.Add(new ApiVersion(2, 0));
});
//1.0 Compatibility version
options.ConventionalControllers.Create(typeof(BookStoreWebAppModule).Assembly, opts =>
{
opts.TypePredicate = t => t.Namespace == typeof(BookStore.Controllers.ConventionalControllers.v1.TodoAppService).Namespace;
opts.ApiVersions.Add(new ApiVersion(1, 0));
});
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var preActions = context.Services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>();
Configure<AbpAspNetCoreMvcOptions>(options =>
{
preActions.Configure(options);
});
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.ConfigureAbp(preActions.Configure());
});
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ChangeControllerModelApiExplorerGroupName = false;
});
}
```
## Swagger/VersionedApiExplorer
```cs
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpApiVersioning(options =>
{
// Show neutral/versionless APIs.
options.UseApiBehavior = false;
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
context.Services.AddVersionedApiExplorer(
options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
context.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
context.Services.AddAbpSwaggerGen(
options =>
{
// add a custom operation filter which sets default values
options.OperationFilter<SwaggerDefaultValues>();
options.CustomSchemaIds(type => type.FullName);
});
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ChangeControllerModelApiExplorerGroupName = false;
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseErrorPage();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAbpRequestLocalization();
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
var provider = app.ApplicationServices.GetRequiredService<IApiVersionDescriptionProvider>();
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
app.UseConfiguredEndpoints();
}
```
## 自定义多版本API控制器
ABP框架不会影响你的API, 你可以根据微软文档自由的实现你的API.
参阅: https://github.com/dotnet/aspnet-api-versioning/wiki
## 示例源码
你可以在这里得到完整的示例源码: https://github.com/abpframework/abp-samples/tree/master/Api-Versioning

4
docs/zh-Hans/docs-nav.json

@ -444,6 +444,10 @@
"path": "API/Application-Configuration.md"
}
]
},
{
"text": "API版本控制",
"path": "API/API-Versioning.md"
}
]
},

3
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs

@ -16,11 +16,14 @@ public class AbpAspNetCoreMvcOptions
public bool EnableRazorRuntimeCompilationOnDevelopment { get; set; }
public bool ChangeControllerModelApiExplorerGroupName { get; set; }
public AbpAspNetCoreMvcOptions()
{
ConventionalControllers = new AbpConventionalControllerOptions();
IgnoredControllersOnModelExclusion = new HashSet<Type>();
AutoModelValidation = true;
EnableRazorRuntimeCompilationOnDevelopment = true;
ChangeControllerModelApiExplorerGroupName = true;
}
}

34
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiDescriptionExtensions.cs

@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc;
public static class ApiDescriptionExtensions
{
public static bool IsRemoteService(this ApiDescription actionDescriptor)
{
if (actionDescriptor.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(controllerActionDescriptor.MethodInfo);
if (remoteServiceAttr != null && remoteServiceAttr.IsEnabled)
{
return true;
}
remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(controllerActionDescriptor.ControllerTypeInfo);
if (remoteServiceAttr != null && remoteServiceAttr.IsEnabled)
{
return true;
}
if (typeof(IRemoteService).IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo))
{
return true;
}
}
return false;
}
}

27
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpRemoteServiceApiDescriptionProvider.cs

@ -42,7 +42,7 @@ public class AbpRemoteServiceApiDescriptionProvider : IApiDescriptionProvider, I
{
foreach (var apiResponseType in GetApiResponseTypes())
{
foreach (var result in context.Results.Where(IsRemoteService))
foreach (var result in context.Results.Where(x => x.IsRemoteService()))
{
var actionProducesResponseTypeAttributes =
ReflectionHelper.GetAttributesOfMemberOrDeclaringType<ProducesResponseTypeAttribute>(
@ -85,29 +85,4 @@ public class AbpRemoteServiceApiDescriptionProvider : IApiDescriptionProvider, I
return _options.SupportedResponseTypes;
}
protected virtual bool IsRemoteService(ApiDescription actionDescriptor)
{
if (actionDescriptor.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(controllerActionDescriptor.MethodInfo);
if (remoteServiceAttr != null && remoteServiceAttr.IsEnabled)
{
return true;
}
remoteServiceAttr = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<RemoteServiceAttribute>(controllerActionDescriptor.ControllerTypeInfo);
if (remoteServiceAttr != null && remoteServiceAttr.IsEnabled)
{
return true;
}
if (typeof(IRemoteService).IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo))
{
return true;
}
}
return false;
}
}

17
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs

@ -8,7 +8,9 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
@ -64,6 +66,19 @@ public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvide
}
}
foreach (var (_, module) in model.Modules)
{
var controllers = module.Controllers.GroupBy(x => x.Value.Type).ToList();
foreach (var controller in controllers.Where(x => x.Count() > 1))
{
var removedController = module.Controllers.RemoveAll(x => x.Value.IsRemoteService && controller.OrderBy(c => c.Value.ControllerGroupName).Skip(1).Contains(x));
foreach (var removed in removedController)
{
Logger.LogInformation($"The controller named '{removed.Value.Type}' was removed from ApplicationApiDescriptionModel because it same with other controller.");
}
}
}
return model;
}
@ -87,6 +102,8 @@ public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvide
var controllerModel = moduleModel.GetOrAddController(
_options.ControllerNameGenerator(controllerType, setting),
FindGroupName(controllerType) ?? apiDescription.GroupName,
apiDescription.IsRemoteService(),
apiDescription.GetProperty<ApiVersion>()?.ToString(),
controllerType,
_modelOptions.IgnoredInterfaces
);

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -207,7 +207,7 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
protected virtual void ConfigureApiExplorer(ControllerModel controller)
{
if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
if (Options.ChangeControllerModelApiExplorerGroupName && controller.ApiExplorer.GroupName.IsNullOrEmpty())
{
controller.ApiExplorer.GroupName = controller.ControllerName;
}

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Versioning/HttpContextRequestedApiVersion.cs

@ -6,7 +6,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Versioning;
public class HttpContextRequestedApiVersion : IRequestedApiVersion
{
public string Current => _httpContextAccessor.HttpContext?.GetRequestedApiVersion().ToString();
public string Current => _httpContextAccessor.HttpContext?.GetRequestedApiVersion()?.ToString();
private readonly IHttpContextAccessor _httpContextAccessor;

2
framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs

@ -170,7 +170,7 @@ public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
{
if (ControllerWithAttributeRoute.Contains(controller))
{
if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
if (Options.ChangeControllerModelApiExplorerGroupName && controller.ApiExplorer.GroupName.IsNullOrEmpty())
{
controller.ApiExplorer.GroupName = controller.ControllerName;
}

10
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs

@ -38,6 +38,7 @@ public class ClientProxyBase<TService> : ITransientDependency
protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceHttpClientAuthenticator>();
protected ClientProxyRequestPayloadBuilder ClientProxyRequestPayloadBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyRequestPayloadBuilder>();
protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyUrlBuilder>();
protected ICurrentApiVersionInfo CurrentApiVersionInfo => LazyServiceProvider.LazyGetRequiredService<ICurrentApiVersionInfo>();
protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue arguments = null)
{
@ -62,10 +63,14 @@ public class ClientProxyBase<TService> : ITransientDependency
{
throw new AbpException($"The API description of the {typeof(TService).FullName}.{methodName} method was not found!");
}
return new ClientProxyRequestContext(
action,
action.Parameters
.GroupBy(x => x.NameOnMethod)
//TODO: make names configurable
.Where(x => action.SupportedVersions.Any() && x.Key != "api-version" && x.Key !="apiVersion")
.Select((x, i) => new KeyValuePair<string, object>(x.Key, arguments.Values[i].Value))
.ToDictionary(x => x.Key, x => x.Value),
typeof(TService));
@ -156,6 +161,11 @@ public class ClientProxyBase<TService> : ITransientDependency
protected virtual async Task<ApiVersionInfo> GetApiVersionInfoAsync(ClientProxyRequestContext requestContext)
{
if (CurrentApiVersionInfo.ApiVersionInfo != null)
{
return CurrentApiVersionInfo.ApiVersionInfo;
}
var apiVersion = await FindBestApiVersionAsync(requestContext);
//TODO: Make names configurable?

22
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/CurrentApiVersionInfo.cs

@ -0,0 +1,22 @@
using System;
using System.Threading;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Http.Client.ClientProxying;
public class CurrentApiVersionInfo : ICurrentApiVersionInfo, ISingletonDependency
{
public ApiVersionInfo ApiVersionInfo => _currentApiVersionInfo.Value;
private readonly AsyncLocal<ApiVersionInfo> _currentApiVersionInfo = new AsyncLocal<ApiVersionInfo>();
public virtual IDisposable Change(ApiVersionInfo apiVersionInfo)
{
var parent = ApiVersionInfo;
_currentApiVersionInfo.Value = apiVersionInfo;
return new DisposeAction(() =>
{
_currentApiVersionInfo.Value = parent;
});
}
}

10
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ICurrentApiVersionInfo.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.Http.Client.ClientProxying;
public interface ICurrentApiVersionInfo
{
ApiVersionInfo ApiVersionInfo { get; }
IDisposable Change(ApiVersionInfo apiVersionInfo);
}

8
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs

@ -12,6 +12,10 @@ public class ControllerApiDescriptionModel
public string ControllerGroupName { get; set; }
public bool IsRemoteService { get; set; }
public string ApiVersion { get; set; }
public string Type { get; set; }
public List<ControllerInterfaceApiDescriptionModel> Interfaces { get; set; }
@ -23,12 +27,14 @@ public class ControllerApiDescriptionModel
}
public static ControllerApiDescriptionModel Create(string controllerName, string groupName, Type type, [CanBeNull] HashSet<Type> ignoredInterfaces = null)
public static ControllerApiDescriptionModel Create(string controllerName, string groupName, bool isRemoteService, string apiVersion, Type type, [CanBeNull] HashSet<Type> ignoredInterfaces = null)
{
return new ControllerApiDescriptionModel
{
ControllerName = controllerName,
ControllerGroupName = groupName,
IsRemoteService = isRemoteService,
ApiVersion = apiVersion,
Type = type.FullName,
Actions = new Dictionary<string, ActionApiDescriptionModel>(),
Interfaces = type

5
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs

@ -49,9 +49,10 @@ public class ModuleApiDescriptionModel
return Controllers[controller.Type] = controller;
}
public ControllerApiDescriptionModel GetOrAddController(string name, string groupName, Type type, [CanBeNull] HashSet<Type> ignoredInterfaces = null)
public ControllerApiDescriptionModel GetOrAddController(string name, string groupName, bool isRemoteService, string apiVersion, Type type, [CanBeNull] HashSet<Type> ignoredInterfaces = null)
{
return Controllers.GetOrAdd(type.FullName, () => ControllerApiDescriptionModel.Create(name, groupName, type, ignoredInterfaces));
var key = apiVersion.IsNullOrWhiteSpace() ? type.FullName : $"{apiVersion + "."}{type.FullName}";
return Controllers.GetOrAdd(key, () => ControllerApiDescriptionModel.Create(name, groupName, isRemoteService, apiVersion, type, ignoredInterfaces));
}
public ModuleApiDescriptionModel CreateSubModel(string[] controllers, string[] actions)

2
framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs

@ -114,7 +114,7 @@ public class JQueryProxyScriptGenerator : IProxyScriptGenerator, ITransientDepen
if (versionParam != null)
{
var version = FindBestApiVersion(action);
script.AppendLine($" var {ProxyScriptingJsFuncHelper.NormalizeJsVariableName(versionParam.Name)} = '{version}';");
script.AppendLine($" var {ProxyScriptingJsFuncHelper.NormalizeJsVariableName(versionParam.Name)} = api_version ? api_version : '{version}';");
}
script.AppendLine(" return abp.ajax($.extend(true, {");

Loading…
Cancel
Save