About Net6 # minimum # API usage details

Time:2022-5-11

preface

Follow With the release of net6, Microsoft has also improved its previous ASP Net core construction mode, using the new minimum API mode. The previous default method is to register IOC and middleware related in startup, but in the minimum API mode, you can build an ASP by simply writing a few lines of code Net core’s web application is really very simple. Coupled with c# the top-level declaration of global using and program, the minimal API becomes more concise. I have to say Net team has really made a lot of efforts in recent years. Next, let’s briefly introduce this minimalist use mode.

Mode of use

Since it is very simple, how simple is it. I believe the students who have downloaded visual studio 2022 have used it to create ASP Net core 6 project. The default mode is the minimal API mode, which makes the structure of the whole web program look simpler. Coupled with Microsoft’s improvement on lambda, it can mark the attribute of lambda parameters. In some scenarios, it can even give up defining the controller class.

Build a web application in a few lines of code

The simplest way to use the minimal API is to build a webapi program through three lines of code. The code is as follows


var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World");
app.Run();

Yes, you’re right. Just run it like this. It’s monitored by defaulthttp://localhost:5000andhttps://localhost:5001So input directly in the browserhttp://localhost:5000Address can see the browser outputHello Worldword.

Change listening address

If you want to change the service port it listens on, you can change it in the following way


var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World");
app.Run("http://localhost:6666");

If you want to listen to multiple ports at the same time, you can use the following method


var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:6666");
app.Urls.Add("http://localhost:8888");
app.MapGet("/", () => "Hello World");
app.Run();

Or set the listening information and environment variables directly through the environment variablesASPNETCORE_URLSThe value of is the complete listening URL address, so you can directly omit the relevant information configured in the program


ASPNETCORE_URLS=http://localhost:6666

If multiple listening URL addresses are set, semicolons can be used between multiple addresses;Separate multiple values


ASPNETCORE_URLS=http://localhost:6666;https://localhost:8888

If you want to listen to all IP addresses of this machine, you can use the following methods


var app = WebApplication.Create(args);
app.Urls.Add("http://*:6666");
app.Urls.Add("http://+:8888");
app.Urls.Add("http://0.0.0.0:9999");
app.MapGet("/", () => "Hello World");
app.Run();

Similarly, you can add listening addresses by adding environment variables


ASPNETCORE_URLS=http://*:6666;https://+:8888;http://0.0.0.0:9999

Log operation

Log operation is also a common operation. In the minimal API, Microsoft simply put it forward to directly simplify the operation, as shown below

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddJsonConsole();
var app = builder.Build();
app. Logger. Loginformation ("read configuration information: {content}", builder.configuration.getsection ("consumption") Get<ConsulOption>());
app.Run();

Basic environment configuration

Whatever we did before Net core development or present Net6 development has the configuration of the basic environment, which includesApplicationNameContentRootPath EnvironmentNameRelated, but in the minimal API, it can be configured in a unified way

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging
});

Console. Writeline ($"Application Name: {builder. Environment. ApplicationName}");
Console. Writeline ($"environment variable: {builder. Environment. Environmentname}");
Console. Writeline ($"contentroot Directory: {builder. Environment. Contentrootpath}");

var app = builder.Build();

Or it is configured through environment variables, and the final effect is the same

  • ASPNETCORE_ENVIRONMENT
  • ASPNETCORE_CONTENTROOT
  • ASPNETCORE_APPLICATIONNAME

Host related settings

We were in the previous In the net core development mode, the program is basically started by building the host, such as the previous web host or the later generic host. These operations can also be carried out in the minimum API. For example, let’s simulate the way the previous generic host configures the web program


var builder = WebApplication.CreateBuilder(args);
builder.Host.ConfigureDefaults(args).ConfigureWebHostDefaults(webBuilder =>
{
    webBuilder.UseStartup<Startup>();
});

var app = builder.Build();

If you just configure the web host, the minimal API also provides another more direct way, as shown below


var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseStartup<Startup>();
builder.WebHost.UseWebRoot("webroot");

var app = builder.Build();

Default container replacement

Many times, when we use IOC, we will use other three-party IOC frameworks, such as Autofac, which is well-known. We also introduced that its essential way is to use itUseServiceProviderFactoryThe following methods can be used to replace the registration of containers and the provision of services in the minimum API

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
//Configurecontainer can be configured in startup as follows
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Middleware related

I believe everyone has read it carefullyWebApplication.CreateBuilder(args).Build()Built in this way is aWebApplicationClass, and webapplication implementsIApplicationBuilderInterface. So its essence is the same as that in startupConfigureMethods are consistent. For example, we configure a swagger program as an example

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
//Judge environmental variables
if (app.Environment.IsDevelopment())
{
    //Exception handling Middleware
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI();
}
//Enable static files
app.UseStaticFiles();

app.UseAuthorization();
app.MapControllers();

app.Run();

The commonly used middleware configuration is the same as before, because the essence isIApplicationBuilderThe extension method is simple hereList it

Middleware name describe API
Authentication Authentication Middleware app.UseAuthentication()
Authorization Authorization middleware app.UseAuthorization()
CORS Cross domain middleware app.UseCors()
Exception Handler Global exception handling middleware app.UseExceptionHandler()
Forwarded Headers Proxy header information forwarding middleware app.UseForwardedHeaders()
HTTPS Redirection HTTPS redirection middleware app.UseHttpsRedirection()
HTTP Strict Transport Security (HSTS) Security enhancement middleware with special response header app.UseHsts()
Request Logging HTTP request and response logging middleware app.UseHttpLogging()
Response Caching Output caching middleware app.UseResponseCaching()
Response Compression Response compression middleware app.UseResponseCompression()
Session Session Middleware app.UseSession()
Static Files Static file middleware app.UseStaticFiles(), app.UseFileServer()
WebSockets Websocket supports middleware app.UseWebSockets()

Request processing

We can useWebApplicationMediumMap{HTTPMethod}Related extension methods are used to handle HTTP requests in different ways, such as the requests related to get, post, put and delete in the following example


app.MapGet("/", () => "Hello GET");
app.MapPost("/", () => "Hello POST");
app.MapPut("/", () => "Hello PUT");
app.MapDelete("/", () => "Hello DELETE");

If you want a routing address to handle requests of multiple HTTP methods, you can use the mapmethods method, as shown below


app.MapMethods("/multiple", new[] { "GET", "POST","PUT","DELETE" }, (HttpRequest req) => $"Current Http Method Is {req.Method}" );

Through the above example, we can not only see how to handle different HTTP requests, but also see that the minimum API can infer how to handle requests according to the type of delegation. For example, in the above example, we did not write the code related to response write, but the output is the content of the delegation, because the delegates in our above example meet the requirementsFunc<string>So the minimum API automatically processes and outputs the returned information. In fact, it can process as long as it meets the delegate type. Next, let’s make it simple. First, the form of local function


static string LocalFunction() => "This is local function";
app.MapGet("/local-fun", LocalFunction);

It can also be an instance method of a class


HelloHandler helloHandler = new HelloHandler();
app.MapGet("/instance-method", helloHandler.Hello);

class HelloHandler
{
    public string Hello()
    {
        return "Hello World";
    }
}

Or is it a static method of a class


app.MapGet("/static-method", HelloHandler.SayHello);

class HelloHandler
{
    public static string SayHello(string name)
    {
        return $"Hello {name}";
    }
}

In fact, the essence is the same, that is to convert them into executable entrustment. No matter what form, it can meet the conditions of entrustment.

Routing constraints

The minimum API also supports the restriction of routing rules. This is similar to the way we used useendpoints before. For example, I restrict the routing parameters to integer only. If they are not satisfied, 404 will be returned


app.MapGet("/users/{userId:int}", (int userId) => $"user id is {userId}");
app.MapGet("/user/{name:length(20)}", (string name) => $"user name is {name}");

There are several other routing constraints that are often used, and they are not many. There are probably the following. Please list them brieflyform

limit Examples Matching example explain
int {id:int} 123456789, -123456789 Match any integer
bool {active:bool} true, false Match “true” or “false” ignore case
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Matches a value that satisfies the datetime type
decimal {price:decimal} 49.99, -1,000.01 Matches a value that satisfies the {decimal type
double {height:double} 1.234, -1,001.01e8 Matches a value that satisfies the type {double}
float {height:float} 1.234, -1,001.01e8 Matches a value that satisfies the {float} type
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Match values that satisfy the guid type
long {ticks:long} 123456789, -123456789 Matches a value that satisfies the {long} type
minlength(value) {username:minlength(4)} KOBE String length must be 4 characters
maxlength(value) {filename:maxlength(8)} CURRY String length cannot exceed 8 characters
length(length) {filename:length(12)} somefile.txt The character length of the string must be 12 characters
length(min,max) {filename:length(8,16)} somefile.txt The character length of the string must be between 8 and L6
min(value) {age:min(18)} 20 Integer value must be greater than 18
max(value) {age:max(120)} 119 Integer value must be less than 120
range(min,max) {age:range(18,120)} 100 The integer value must be between 18 and 120
alpha {name:alpha} Rick The string must consist of one or more A-Z alphabetic characters and is not case sensitive.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 The string must match the specified regular expression.
required {name:required} JAMES The request information must contain this parameter

Model binding

Before we use ASP Net core controller, model binding will definitely be used. Its function is to simplify our parsing of HTTP request information, which is also the core function of MVC framework. It can directly map the request information into c# a simple type or poco. At minimal APIMap{HTTPMethod}Rich model binding operations can also be carried out in related methods. At present, there are several binding sources that can be supported as follows

  • Route (route parameter)
  • QueryString
  • Header
  • Body (such as JSON)
  • Services (i.e. types registered through iservicecollection)
  • Custom binding

Binding example

Next, let’s first look at the binding routing parameters


app.MapGet("/sayhello/{name}", (string name) => $"Hello {name}");

You can also use a mixture of routing and querystring


app.MapGet("/sayhello/{name}", (string name,int? age) => $"my name is {name},age {age}");

It should be noted here that my age parameter is marked with a flag that can be empty. If not, the age parameter must be passed in the request parameter of the URL, otherwise an error will be reported. This is still different from our previous operations.

Specific classes can also be bound to models. For example, we define poco named goods here for demonstration

app. Mappost ("/ goods", (goods goods) = > $"goods {goods. Gname} added successfully");

class Goods
{
    public int GId { get; set; }
    public string GName { get; set; }
    public decimal Price { get; set; }
}

It should be noted that the HTTP methods get, head, options and delete will not bind the model from the body. If you need to obtain the body information in the get request, you can read it directly from httprequest.

If we need to use a specific instance registered through iservicecollection, we can operate through model binding (many people like to call it method injection, but strictly speaking, it is implemented by defining the relevant operations of model binding). Moreover, we also simplify the specific operation, so we don’t need to operate on specific parametersFromServicesAttributeMarked

var builder = WebApplication.CreateBuilder(args);
builder. Services. Addscoped < person > (provider = > new() {id = 1, name = "Yi", sex = "man"});
var app = builder.Build();

app.MapGet("/", (Person person) => $"Hello {person.Name}!");
app.Run();

If it is mixed use, you can also not specify a specific bindsource for marking, provided that the names of these values are unique among different binding sources. This feeling reminds me of learning mvc4 at the beginning The randomness of model binding at 0, such as the following example


app.MapGet("/sayhello/{name}", (string name,int? age,Person person) => $"my name is {name},age {age}, sex {person.Sex}");

The source of model binding parameters in the above example can beyes

parameter Binding source
name Routing parameters
age querystring
person Dependency injection

Not only that, it also supports more complexThis makes model binding more flexible, such as the following example

app. Mappost ("/ goods", (goods goods, person) = > $"{person. Name} adding goods {goods. Gname} succeeded");

Its model bindingThe value source of can be

parameter Binding source
goods JSON in body
person Dependency injection

Of course, ifYou want to make the source of model binding clearer, or you want to specify the binding source of specific parameters. Anyway, it is flexible. For example, the above example can be modified to display the declaration

app. Mappost ("/ goods", ([frombody] goods, goods, [fromservices] person person) = > $"{person. Name} adding goods {goods. Gname} succeeded");

In many cases, we may declare the execution delegation of map related methods by defining classes and methods. At this time, flexible model binding can still be carried out, and you may also find that although the direct way of lambda expression supports nullable types, it does not support default parameters, that is, the form of method default parameters, such as

app.MapPost("/goods", GoodsHandler.AddGoods);

class GoodsHandler
{
    Public static string addgoods (goods goods, person, int age = 20) = > $"{person. Name} adding goods {goods. Gname} succeeded";
}

Of course, you can also display the parameters of addgoods method by model binding, which is really very flexible

Public static string addgoods ([frombody] goods goods, [fromservices] person person, [fromquery] int age = 20) = > $"{person. Name} successfully added goods {goods. Gname}";

When using map related methods, because the related logic is written directly in the program entry program or other poco, there is no way to operate directly when using httpcontext, httprequest and httpresponse related instances. At this time, it is also necessary to obtain the corresponding instances by mode binding


app.MapGet("/getcontext",(HttpContext context,HttpRequest request,HttpResponse response) => response.WriteAsync($"IP:{context.Connection.RemoteIpAddress},Request Method:{request.Method}"));

Custom binding

Minimal API adopts a new way to define model binding. This way is based on convention. It does not need to register in advance, nor does it need to integrate any classes or implement any interfaces. It only needs to exist in custom classesTryParseandBindAsyncThe difference between the two methods is

  • The tryparse method is to transform and bind the routing parameters, URL parameters and header related information
  • Bindasync can convert and bind any requested information, which is more powerful than tryparse

Next, we will demonstrate the use of these two methods respectively. The first is the tryparse method


app.MapGet("/address/getarray",(Address address) => address.Addresses);

public class Address
{
    public List<string>? Addresses { get; set; }

    public static bool TryParse(string? addressStr, IFormatProvider? provider, out Address? address)
    {
        var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (addresses != null && addresses.Any())
        {
            address = new Address { Addresses = addresses.ToList() };
            return true;
        }
        address = new Address();
        return false;
    }
}

In this way, we can complete the simple conversion binding operation. From the writing method, we can see that there are certain restrictions on the tryparse method, but the operation is relatively simple. At this time, we simulate the request

http://localhost:5036/address/getarray?address= Shandong, Shanxi, Henan, Hebei

When the request is completed, you will get the following results

[“Shandong”, “Shanxi”, “Henan”, “Hebei”]

Then let’s transform the above example, use bindasync to convert the results, and see the different operations


app.MapGet("/address/getarray",(Address address) => address.Addresses);

public class Address
{
    public List<string>? Addresses { get; set; }

    public static ValueTask<Address?> BindAsync(HttpContext context, ParameterInfo parameter)
    {
        string addressStr = context.Request.Query["address"];
        var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        Address address = new();
        if (addresses != null && addresses.Any())
        {
            address.Addresses = addresses.ToList();
            return ValueTask.FromResult<Address?>(address);
        }
        return ValueTask.FromResult<Address?>(address);
    }
}

Same requesthttp://localhost:5036/address/getarray?address= Shandong, Shanxi, Henan, HebeiThe address will get the same result as above. How to choose it? Students can use it on demand, and the effect is the same. If both exist in the classTryParseandBindAsyncMethod, then it will only executeBindAsyncmethod.

Output results

I believe that through the other examples above, we can see some results output in the minimum API. To sum up, it can be divided into three cases

  • IResultThe result output can contain any value output, including asynchronous tasks
  • Task<IResult>andValueTask<IResult>stringText type output, including asynchronous tasks
  • Task<string>andValueTask<string>TObject type output, such as custom entities and anonymous objects, including asynchronous tasksTask<T>andValueTask<T>

Next, we will briefly demonstrate several examples to see how to operate. The first is to output the text type


app.MapGet("/hello", () => "Hello World");

Then output an object type. The object type can contain objects, collections or even anonymous objects, or the httpresponse object we demonstrated above. The object here can be understood as the object-oriented object, which can meet the response output requirements

app.MapGet("/simple", () => new { Message = "Hello World" });
//Or
app.MapGet("/array",()=>new string[] { "Hello", "World" });
//Or the return result of EF
app.Map("/student",(SchoolContext dbContext,int classId)=>dbContext.Student.Where(i=>i.ClassId==classId));

There is also a form encapsulated by Microsoft for us, that is, the return isIResultAs a result of the type, Microsoft is also very considerate and has unified a static package for usResultsClass, which is convenient for us to use. Let’s briefly demonstrate this operation

//Successful results
app.MapGet("/success",()=> Results.Ok("Success"));
//Failure result
app.MapGet("/fail", () => Results.BadRequest("fail"));
//404 results
app.MapGet("/404", () => Results.NotFound());
//Return according to logical judgment
app.Map("/student", (SchoolContext dbContext, int classId) => {
    var classStudents = dbContext.Student.Where(i => i.ClassId == classId);
    return classStudents.Any() ? Results.Ok(classStudents) : Results.NotFound();
});

We also mentioned aboveResultsClass is actually an additional layer encapsulated by Microsoft. All static methods in it returnIResultThe interface has many implemented classes to meet different output results, such asResults.File("foo.text")The essence of a method is to return aFileContentResultInstance of type


public static IResult File(byte[] fileContents,string? contentType = null,
string? fileDownloadName = null,
bool enableRangeProcessing = false,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue? entityTag = null)
=> new FileContentResult(fileContents, contentType)
{
    FileDownloadName = fileDownloadName,
    EnableRangeProcessing = enableRangeProcessing,
    LastModified = lastModified,
    EntityTag = entityTag,
};

OrResults.Json(new { Message="Hello World" })The essence is to return oneJsonResultInstance of type


public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)
            => new JsonResult
            {
                Value = data,
                JsonSerializerOptions = options,
                ContentType = contentType,
                StatusCode = statusCode,
            };

Of course, we can also customize itIResultFor example, we need to output a piece of HTML code. Microsoft is very considerate to provide us with an extension class that specifically extends resultsIResultExtensionsBased on this class, we can complete the extension of iresult

static class ResultsExtensions
{
    //Write extension method based on iresultextensions
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions, nameof(resultExtensions));
        //Custom htmlresult is the implementation class of iresult
        return new HtmlResult(html);
    }
}

class HtmlResult:IResult
{
    //Used to receive HTML strings
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    /// <summary>
    ///Write your own output logic in this method
    /// </summary>
    /// <returns></returns>
    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

After defining these, we can directly use the extension method we defined in the results class, as follows


app.MapGet("/hello/{name}", (string name) => Results.Extensions.Html(@$"<html>
    <head><title>Index</title></head>
    <body>
        <h1>Hello {name}</h1>
    </body>
</html>"));

It should be noted here that our customized extension method must be based onIResultExtensionsExtended, and then use it againResults.ExtensionsThis attribute, because this attribute isIResultExtensionsType, and then we extend it ourselvesResults.Extensions.Htmlmethod.

summary

In this paper, we mainly introduce ASP Net core 6 minimum API. I believe you also have a certain understanding of it Net6 is also the default project mode. On the whole, it is very simple, concise, powerful and flexible. It has to be said that the minimum API is very applicable in many scenarios. Of course, I have seen comments on it in other places. There are different opinions. The author believes that no technology is a silver bullet, and its existence is reasonable. If your project is standardized and reasonable, then using the minimum API is absolutely enough. If you don’t want to use it or can’t use it, it doesn’t matter. It’s good to achieve the results you want. In fact, there’s nothing to evaluate.

Here is a brief talk about Net6 , minimal , API usage is introduced here, more related Net6 # minimum API content, please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you can support developeppaer in the future!