preface

In the current trend of software development, whether it is the separation of the front and back ends or the transformation of services, the back end is more through the construction of API interface services to provide business support for web, APP, Desktop and other clients. How to build a standard, easy to understand API interface is what we backend developers need to consider. In this article, I’ll list some tips I use to build interface services using ASP.NET Core Web API. If there are any mistakes, please point them out.

Storage address: github.com/Lanesra712/…

Step by Step

Since some of the points covered in this article have been explained in previous articles, I will only explain how to use them in ASP.NET Core Web API without going into too much detail. If you need to know more about it, you can jump to the external chain address given in the article to see.

The code used in this article was built on.NET Core 2.2 +.NET Standard 2.0, so please be aware that if you use a different version than I did, you may end up implementing different code. In the meantime, all of the sample code in this article will be stored in the Github repO listed in the introduction. I will try to commit the development of each feature point, and I will update it from time to time to build a domain-driven back-end project template. If it helps, Welcome to keep watching.

1. Use lowercase routes

As I mentioned in my previous article (building more readable ASP.NET Core routing), because.net uses Pascal class naming by default, the route generated by default will end up with a mix of case and case. Although in.NET Core all upper and lower case routing addresses end up on the correct resource, in order to better comply with the front-end specifications, we first modify the default generated routing address format as outlined in the previous article.

Because ultimately we want to implement Restful apis, we first need to change the default generated URL address to all lowercase.

public void ConfigureServices(IServiceCollection services)
{
    // Use lowercase URL routing mode
    services.AddRouting(options =>
    {
        options.LowercaseUrls = true;
    });
}
Copy the code

Build more readable ASP.NET Core routes

If you look at the API Controller generated in the.net Core default template, take a closer look. This is actually using the feature routing, so we can’t use the traditional routing template defined by startup. UseMvc. Or modify our generated routing address format directly with the UseMvcWithDefaultRoute method in startup. Configure.

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase{}Copy the code

Allow cross-domain requests

Whether it is the transformation of the back-end interface as a service, or just the development of the front-end and back-end separation project, our front-end project and back-end interface are usually not deployed together, so we need to solve the problem of cross-domain access involved in the front-end access interface.

For cross-domain requests, we can use JSONP, or by configuring the response header to the Nginx server, or using CORS, or some other solution. You can choose, but here I’m going to configure support for CORS directly in the backend interface.

In.net Core, already in Microsoft. AspNetCore. Cors this library by adding support for the Cors, because this library is in. We have set up NET Core SDK, So we don’t need Nuget to install it, we can just use it.

In.net Core configuration in CORS rules, we can use at Startup. ConfigureServices this method to add different authorization policy, Then configure the Attribute EnableCors for a Controller or Action. If the policy name is specified, the specified policy is used. If the policy name is not specified, the default configuration applies. Similarly, we can set only one policy and configure it directly for the entire project. Here I use a common cross-domain request configuration for the entire project.

When configuring the CORS policy, we can specify that only requests from certain URLS are allowed to access our interface, or that only certain HTTP methods are allowed to access our interface, or that the header of the request must contain certain information to access our interface.

In the code below, I define a cross-domain request policy for the entire project, where I simply set up control over the URL address of the interface requestor by reading the data in the configuration file so that only certain IP addresses can access our interface.

public class Startup
{
    // The default cross-domain request policy name
    private const string _defaultCorsPolicyName = "Ingos.Api.Cors";

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(
            // Add a CORS authorization filter
            options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName))
        ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        // Configure the CORS authorization policy
        services.AddCors(options => options.AddPolicy(_defaultCorsPolicyName,
            builder => builder.WithOrigins(
                    Configuration["Application:CorsOrigins"]
                    .Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray()
                )
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials()));
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // Allow cross-domain request accessapp.UseCors(_defaultCorsPolicyName); }}Copy the code

For example, in the following Settings, I only allow this address to access our interface. If multiple addresses need to be specified, they can be separated by English.

"Application": {"CorsOrigins": "http://127.0.0.1:5050"}Copy the code

In some cases, we just need to change the value to * if we don’t want to restrict it.

"Application": {
    "CorsOrigins": "*"
}
Copy the code

3. Add interface version control

In some scenarios involving interface function upgrade, when the interface logic needs to be modified but the interface of the old version cannot be disabled, you can add the version information to the interface to reduce the impact on the original interface. If you want to learn more, check out this article, “ASP.NET Core: building apis with version control.”

Before implementing the interface with version control, we need to add the following two DLLS via Nuget. Since I configured them in the ingos.api.core class library, I installed them under this class library. You will need to choose whether to install it in the Api project or in another library depending on your situation.

Install-Package Microsoft.AspNetCore.Mvc.Versioning
Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
Copy the code

After the installation is complete, we can in a Startup. ConfigureServices method, interface configuration version information for the project, I used here is the version number added to the URL address of the interface.

Because for all middleware configuration on Startup. The ConfigureServices method, in order to maintain the method of pure sex, here I wrote an extension method is used to configure our API version, after can be called directly.

public static class ApiVersionExtension
{
    /// <summary>
    ///Add API version control extension methods
    /// </summary>
    /// <param name="services">A collection of services injected in the life cycle<see cref="IServiceCollection"/></param>
    public static void AddApiVersion(this IServiceCollection services)
    {
        // Add API version support
	    services.AddApiVersioning(o =>
	    {
	        // Whether to return the API version information in the header information in the response
	        o.ReportApiVersions = true;
	
	        // The default API version
	        o.DefaultApiVersion = new ApiVersion(1.0);
	
	        // If the API version is not specified, set the API version to the default version
	        o.AssumeDefaultVersionWhenUnspecified = true;
	    });

	    // Configure the API version
	    services.AddVersionedApiExplorer(option =>
	    {
	        // API version group name
	        option.GroupNameFormat = "'v'VVVV";
	
	        // If the API version is not specified, set the API version to the default version
	        option.AssumeDefaultVersionWhenUnspecified = true; }); }}Copy the code

The extension method is implemented as shown in the code above, and can then be called directly from the ConfigureServices method.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Config api version
    services.AddApiVersion();
}
Copy the code

Now delete the valuesControllers generated by default when the project is created and create a v1 folder in the Controllers directory. Add a UsersController to retrieve user resources for the system, and now the file structure of the project is shown below.

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

Add support for Swagger interface documentation

In the case of separate front-end and back-end development, we need to provide the front-end developers with an interface document, so that the front-end developers know what HTTP methods or parameters to pass to the back-end interface, so as to obtain the correct data. Swagger provides a way to automatically generate interface documents. It also provides postman-like functionality for real-time call testing of interfaces.

First, we need to add the Swashbuckle.AspNetCore DLL through Nuget, and then we can configure Swagger from there.

Install-Package Swashbuckle.AspNetCore
Copy the code

Similar to the version information of API interface configuration above, HERE I still use the way of building extension method to realize the configuration of Swagger middleware. See my previous article (ASP.NET Core: building an API with version control) for details of the configuration process, and only the code that was finally configured is listed here.

public static void AddSwagger(this IServiceCollection services)
{
    // Configure Swagger file information
    services.AddSwaggerGen(s =>
    {
        // Generate API documentation based on the API version information
        //
        var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();

        foreach (var description in provider.ApiVersionDescriptions)
        {
            s.SwaggerDoc(description.GroupName, new Info
            {
                Contact = new Contact
                {
                    Name = "Danvic Wang",
                    Email = "[email protected]",
                    Url = "https://yuiter.com"
                },
                Description = "Ingos.API Interface Documentation",
                Title = "Ingos.API",
                Version = description.ApiVersion.ToString()
            });
        }

        // Replace the version information parameter with the actual version number in the API address displayed in the Swagger document
        s.DocInclusionPredicate((version, apiDescription) =>
        {
            if(! version.Equals(apiDescription.GroupName))return false;

            var values = apiDescription.RelativePath
                .Split('/')
                .Select(v => v.Replace("v{version}", apiDescription.GroupName)); apiDescription.RelativePath = string.Join("/", values);
            return true;
        });

        // Parameters are named with a hump
        s.DescribeAllParametersInCamelCase();

        // Cancelling the API document requires version information
        s.OperationFilter<RemoveVersionFromParameter>();

        // Obtain the interface document description
        var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
        var apiPath = Path.Combine(basePath, "Ingos.Api.xml");
        s.IncludeXmlComments(apiPath, true);
    });
}
Copy the code

When configured, we can enable the Swagger document in the Startup class.

public void ConfigureServices(IServiceCollection services)
{
    // Add support for Swagger documents
    services.AddSwagger();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
    // Enable Swagger document
    app.UseSwagger();
    app.UseSwaggerUI(s =>
    {
        // The latest version of API documentation is loaded by default
        foreach (var description in provider.ApiVersionDescriptions.Reverse())
        {
            s.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json".$"Sample API {description.GroupName.ToUpperInvariant()}"); }}); }Copy the code

public class RemoveVersionFromParameter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        var versionParameter = operation.Parameters.Single(p => p.Name == "version"); operation.Parameters.Remove(versionParameter); }}Copy the code

After we implement the custom interface, we can call this filter method in the previous extension method for Swagger to remove the version information. The location of the extension method is shown below.

public static void AddSwagger(this IServiceCollection services)
{
    // Configure Swagger file information
    services.AddSwaggerGen(s =>
    {
        // Cancelling the API document requires version information
        s.OperationFilter<RemoveVersionFromParameter>();
    });
}
Copy the code

The final result is shown in the figure below. It can be seen that version information is no longer in the parameter list, but it will be automatically added for us during the interface test.

Build Restful interfaces

When the interface return value is not built in a Restful style, we might be used to adding an indication of whether the interface request was successful to the information returned by the interface, as in the example in the following code.

{
	sueecss: true
	msg: '',
	data: [{
		id: '20190720214402',
		name: 'zhangsan'
	}]
}
Copy the code

However, when we want to build Restful interfaces, we can no longer design this way, and we should indicate the success of the visit by returning the HTTP response status code. Some commonly used HTTP status codes are listed in the following table.

The HTTP status code meaning explain
200 OK Used for general success returns, not for request error returns
201 Created The resource is created
202 Accepted A return for asynchronous processing of a resource that merely indicates that the request has been received. Processing that takes a long time is usually done asynchronously
204 No Content This state may appear in PUT, POST, or DELETE requests and generally indicates the presence of the resource, but no resource-related status or information is returned in the message body
400 Bad Request It is used to return general error messages on the client side. It can also be used for errors other than 4XX errors. The error message is usually placed in the body
401 Unauthorized The interface needs to be authenticated by authorization
403 Forbidden The current resource is not accessible
404 Not Found No corresponding information can be found
500 Internal Server Error Server internal error

We know that HTTP has four predicate methods: Get, Post, Put, and Delete. In the past, we probably used Get and Post more than Put and Delete. Similarly, if we need to create Restful interfaces, we need to define HTTP methods for the interfaces based on the generic functional definitions of these four HTTP method predicates.

HTTP predicate methods explain
GET Obtaining Resource Information
POST Submit new resource information
PUT Update existing resource information
DELETE Delete the resource

For example, for a method that gets all resources, we might define the default HTTP status code that the interface returns as 200 or 400. When the status code is 200, the data is retrieved successfully and the interface can return normally. When the status code is 400, the interface has an access problem. An error message object is returned.

In ASP.NET Core Web API, we can define the return status code of the interface by adding the ProducesResponseType feature to the Action. The ProducesResponseType feature can be accessed by pressing the F12 key. We can see that there are two constructors for this feature. We can define only the HTTP status code that the interface returns or the specific object information that is returned along with the status code that the interface returns.

/// <summary>
///Get all user information
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserListDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Get()
{
    // 1. Obtain resource data

    // 2. Check whether the data is obtained successfully
    if (true)
        return Ok(new List<UserListDto>());
    else
        return BadRequest(new
        {
            statusCode = StatusCodes.Status400BadRequest,
            description = "Misdescription",
            msg = "Error message"
        });
}
Copy the code

Of course, when the HTTP return status code of the interface is 400, we will eventually return our custom error message object, so it is best to add the returned object information as a parameter to the ProducesResponseType feature in order not to confuse the backend.

The HTTP status code Method names
200 OK()
201 Created()
202 Accepted()
204 NoContent()
400 BadRequest()
401 Unauthorized()
403 Forbid()
404 NotFound()

Use a Web API analyzer

In the above example, because we need to specify the HTTP status code that the interface should return, we need to add the ProducesResponseType feature in advance. At some point, we might have added an HTTP status code return to the code, but forgot to add the feature description. So is there a convenient way to prompt us?

Core in ASP.NET 2.2 and later to update the ASP.NET version of the Core, we can go through the Nuget add Microsoft. AspNetCore. Mvc. Api. Analyze the package, so as to realize to Analyze our Api, First we need to add this package to our API project.

Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers
Copy the code

For example, in the interface code below, we search for user data according to the unique identity of the user. When the data cannot be obtained, the HTTP status code returned is 400, and we only add the feature description of HTTP status code 200. At this point, the parser takes the missing feature description of the HTTP 404 status code as a warning and provides an option to fix the problem, which will automatically add features.

/// <summary>
///Get user details
/// </summary>
/// <param name="id">Unique user identification</param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserEditDto), StatusCodes.Status200OK)]
public IActionResult Get(string id)
{
    // 1. Obtain user information based on Id
    UserEditDto user = null;

    if (user == null)
        return NotFound();
    else
        return Ok(user);
}
Copy the code

At the time of filling the properties, analyzer also help us to add a ProducesDefaultResponseType features. According to the Swagger Default Response document pointed to in the Microsoft document, if the Response structure returned by our interface is the same regardless of the state, We can directly use ProducesDefaultResponseType properties to specify the response the response of the structure, without the need to add a feature each HTTP status.

conclusion

In this article, I’ve introduced some tips and solutions for using ASP.NET Core Web API. If you can help me, I’d be glad to. In the meantime, if you have a better solution, or one for some of the Web API potholes you’ve stumbled on before, feel free to suggest it in the comments section.

Of the pit

Personal Profile: 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 programmer. Personal blog: yuiter.com blog garden blog: www.cnblogs.com/danvic712