Overview

In the previous article, we simplified the abP project template by building a simplified project template. In the process of use, because the API we exposed needed to contain the version information, we took the way to include the API version number in the resource URI. Because the default API of ABP does not have the concept of version, in order to achieve API version, we need to transform the API route of ABP project to meet our requirements. This article is a demonstration of how to implement this transformation process, which I hope will help you

The complete project template is shown below

Template source address: github.com/danvic712/i…

Step by Step

In an ABP project, the DEFINITION of an API interface can be implemented in two ways

  1. In traditional Web API implementations, resource apis are built by defining controllers
  2. Application Services defined in the project are automatically exposed as API interfaces through the built-in Auto API Controller function of ABP framework

Because we will use both of these approaches in our projects, we need to support API versioning for both approaches

For the first way of API versioning support, I mentioned in the previous article, if you need, you can click here, here will not be repeated, this article mainly focuses on how to modify the API interface automatically generated by ABP, implement the API version information to add to the route

Because I am using a simplified ABP template, which differs from the assembly names in the default ABP project, the mapping between assemblies is shown below, which you can change against the default project

  • xxx.API => xxx.HttpApi.Host
  • xxx.Application => xxx.Application

Add assembly

For the API versioning implementation, this is also based on the following two libraries, so we need to add references to these two assemblies in the project through Nuget before using them

## Add SUPPORT for multiple versions of API
Install-Package Microsoft.AspNetCore.Mvc.Versioning

## Added API version display support for Swagger documents
Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
Copy the code

Since the *.Versioning assembly is referenced indirectly in the ABP assembly already used in the xxx.API project, you can choose not to add the *.Versioning.ApiExplorer to the project

The XXX.Application library is not associated with Swagger Settings, so you only need to add a reference to *.Versioning in the project

Routing modification

Once the required assembly references have been added, the route format generated by ABP can be modified for the purpose of adding API version information to the route address

For interfaces that expose API services by creating controllers, we can add the ApiVersion feature directly to controller or Action and then modify the feature route, as shown in the sample code below

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class VaulesController : ControllerBase
{
	// action ...
}
Copy the code

For the API automatically generated by ABP based on application Service, in the default project template, you can find the following configuration in the *HttpApiHostModule class, resulting in the API routing format shown below

public override void ConfigureServices(ServiceConfigurationContext context)
{
	var configuration = context.Services.GetConfiguration();
	var hostingEnvironment = context.Services.GetHostingEnvironment();
	
	ConfigureConventionalControllers(context);
}

private void ConfigureConventionalControllers()
{
	Configure<AbpAspNetCoreMvcOptions>(options =>
	{
		options.ConventionalControllers.Create(typeof(XXXApplicationModule).Assembly);
	});
}
Copy the code

According to the abP document, based on the conventional definition, all apis automatically generated according to application Service will start with/API, while */app/* in the routing path can be adjusted by changing the value of RootPath variable. For example, you could change your app to your-api-path-define

private void ConfigureConventionalControllers()
{
	Configure<AbpAspNetCoreMvcOptions>(options =>
	{
		options.ConventionalControllers.Create(typeof(XXXApplicationModule).Assembly, opts =>
            {
                opts.RootPath = "your-api-path-define";
            });
	});
}
Copy the code

/ API /your-api-path-define/*, so we can change the variable value to include the version of the API in the route, eg. / API /v1/*

So once we find something that we can change, we need to think about how we can change it, and if we write the dead variable here as v1 or v2, This means that the application Service in the entire XXXApplicationModule assembly generates a limited version of the API, resulting in poor scalability, so a dynamic configuration is required

Therefore, by using the component package referenced above, we choose to add ApiVersion to indicate the VERSION information of the API mapped by the application service. For example, the API version generated below is 1.0

[ApiVersion("1.0")]
public class BookAppService :
	CrudAppService<
		Book, / /The Book entity
		BookDto, / /Used to show books
		Guid, / /Primary key of the book entity
		PagedAndSortedResultRequestDto, / /Used for paging/sorting
		CreateUpdateBookDto>, // Used to create/update a book
	IBookAppService // implement the IBookAppService
{
	public BookAppService(IRepository<Book, Guid> repository)
		: base(repository){}}Copy the code

Once the API version of the service is defined, the value of the RootPath parameter can be replaced by the value of the route template variable, because the route here is less deterministic than the original route. So here we put the route configuration method in abP’s PreConfigureServices lifecycle function. The code in this function is executed before the ConfigureServices method is executed for all modules of the project. The adjusted code is as follows

public override void PreConfigureServices(ServiceConfigurationContext context)
{
	PreConfigure<AbpAspNetCoreMvcOptions>(options =>
	{
		// Dynamically set routing information based on API version information
		options.ConventionalControllers.Create(typeof(IngosAbpTemplateApplicationModule).Assembly,
			opts => { opts.RootPath = "v{version:apiVersion}"; });
	});
}

public override void ConfigureServices(ServiceConfigurationContext context)
{
	var configuration = context.Services.GetConfiguration();
	var hostingEnvironment = context.Services.GetHostingEnvironment();
    
    ConfigureConventionalControllers(context);
}

private void ConfigureConventionalControllers(ServiceConfigurationContext context)
{
    // Based on the configuration in PreConfigureServices
	Configure<AbpAspNetCoreMvcOptions>(options => { context.Services.ExecutePreConfiguredActions(options); });
}
Copy the code

Of course, this is only for our own application services for the version of the abP framework contains some API interface, can be directly in the PreConfigureServices function by specifying the VERSION of the API. For example, here I set the version of the permission related API interface to 1.0

PS, which is set for the version of the built-in API of the framework, does not change the routing address of the interface, but only exposes the built-in API when swagger is displayed in groups according to the API version number

public override void PreConfigureServices(ServiceConfigurationContext context)
{
	PreConfigure<AbpAspNetCoreMvcOptions>(options =>
	{
		// Dynamically set routing information based on API version information
		options.ConventionalControllers.Create(typeof(IngosAbpTemplateApplicationModule).Assembly,
			opts => { opts.RootPath = "v{version:apiVersion}"; });

		// Specify the built-in permission related API version 1.0
		options.ConventionalControllers.Create(typeof(AbpPermissionManagementHttpApiModule).Assembly,
			opts => { opts.ApiVersions.Add(new ApiVersion(1.0)); });
	});
}
Copy the code

After configuring the route, you can inject the API version service and the API Explorer service used for Swagger into the IServiceCollection

private void ConfigureConventionalControllers(ServiceConfigurationContext context)
{
	Configure<AbpAspNetCoreMvcOptions>(options => { context.Services.ExecutePreConfiguredActions(options); });

	context.Services.AddAbpApiVersioning(options =>
	{
		options.ReportApiVersions = true;

		options.AssumeDefaultVersionWhenUnspecified = true;

		options.DefaultApiVersion = new ApiVersion(1.0);

		options.ApiVersionReader = new UrlSegmentApiVersionReader();

		var mvcOptions = context.Services.ExecutePreConfiguredActions<AbpAspNetCoreMvcOptions>();
		options.ConfigureAbp(mvcOptions);
	});

	context.Services.AddVersionedApiExplorer(option =>
	{
		option.GroupNameFormat = "'v'VVV";

		option.AssumeDefaultVersionWhenUnspecified = true;
	});
}
Copy the code

Swagger transformation

Since there is no concept of API version in the project before transformation, the default swagger will show all interfaces, and when the project can support API versioning, different JSON files should be generated based on API version. So swagger can be grouped based on the version of the API

Because in the code above have the API explorer services into the IServiceCollection, so it can be used directly IApiVersionDescriptionProvider access to the API version information, Accordingly, different Swagger JSON files are generated. The configuration codes related to Swagger are as follows

public override void ConfigureServices(ServiceConfigurationContext context)
{
	var configuration = context.Services.GetConfiguration();
	var hostingEnvironment = context.Services.GetHostingEnvironment();
    
    ConfigureSwaggerServices(context);
}

public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
	var app = context.GetApplicationBuilder();

	app.UseSwagger();
	app.UseAbpSwaggerUI(options =>
	{
		options.DocumentTitle = "IngosAbpTemplate API";

		// Displays the latest version of the API by default
		//
		var provider = context.ServiceProvider.GetRequiredService<IApiVersionDescriptionProvider>();
		var apiVersionList = provider.ApiVersionDescriptions
			.Select(i => $"v{i.ApiVersion.MajorVersion}")
			.Distinct().Reverse();
		foreach (var apiVersion in apiVersionList)
			options.SwaggerEndpoint($"/swagger/{apiVersion}/swagger.json".$"IngosAbpTemplate API {apiVersion? .ToUpperInvariant()}");
	});
}

private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration)
{
	context.Services.AddAbpSwaggerGenWithOAuth(
		configuration["AuthServer:Authority"],
		options =>
		{
			// Get the API version information
			var provider = context.Services.BuildServiceProvider()
				.GetRequiredService<IApiVersionDescriptionProvider>();

			// Generate Swagger based on the larger version
			foreach (var description in provider.ApiVersionDescriptions)
				options.SwaggerDoc(description.GroupName, new OpenApiInfo
				{
					Contact = new OpenApiContact
					{
						Name = "Danvic Wang",
						Email = "[email protected]",
						Url = new Uri("https://yuiter.com")
					},
					Description = "IngosAbpTemplate API",
					Title = "IngosAbpTemplate API",
					Version = $"v{description.ApiVersion.MajorVersion}"
				});

			options.DocInclusionPredicate((docName, description) =>
			{
				// Get the major version, do not display if it is not the API of that version
				var apiVersion = $"v{description.GetApiVersion().MajorVersion}";

				if(! docName.Equals(apiVersion))return false;

				// Replace route parameters
				var values = description.RelativePath
					.Split('/')
					.Select(v => v.Replace("v{version}", apiVersion));

				description.RelativePath = string.Join("/", values);

				return true;
			});

			// Cancelling the API document requires version information
			options.OperationFilter<RemoveVersionFromParameter>();
		});
}
Copy the code

Since then, the entire API versioning adjustment has been completed, and the complete code can be viewed on Github by clicking here. The final result is shown below

Signature

Author: Mo Mo Mo Mo Xiaoyu

Biography: Born in 1996, born in a fourth-tier city in Anhui province, graduated from Top 10 million universities. .NET programmer, gunslinger, cat. It will begin in December 2016. NET programmer career, Microsoft. NET technology stalwart, aspired to be the cloud cat kid programming for Google the best. NET programmers

Personal blog: Yuiter.com

Garden blog blog: www.cnblogs.com/danvic712