diff --git a/docs/en/API/API-Versioning.md b/docs/en/API/API-Versioning.md new file mode 100644 index 0000000000..5eefb129c3 --- /dev/null +++ b/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(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 GetAsync(); +} + +public interface IBookV2AppService : IApplicationService +{ + Task GetAsync(); + + Task 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 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 GetAsync() + { + return await _bookAppService.GetAsync(); + } + + [HttpGet] + [Route("{isbn}")] + public async Task 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 +{ + public virtual async Task GetAsync() + { + return await RequestAsync(nameof(GetAsync)); + } +} + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(IBookV2AppService), typeof(BookV2ClientProxy))] +public partial class BookV2ClientProxy : ClientProxyBase, IBookV2AppService +{ + public virtual async Task GetAsync() + { + return await RequestAsync(nameof(GetAsync)); + } + + public virtual async Task GetAsync(string isbn) + { + return await RequestAsync(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(); +var bookV4AppService = _abpApplication.ServiceProvider.GetRequiredService(); +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(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(); + Configure(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(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, ConfigureSwaggerOptions>(); + + context.Services.AddAbpSwaggerGen( + options => + { + // add a custom operation filter which sets default values + options.OperationFilter(); + + options.CustomSchemaIds(type => type.FullName); + }); + + Configure(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(); + // 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 \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 943408d70a..6df98cb05a 100644 --- a/docs/en/docs-nav.json +++ b/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" } ] },