This article explains how to update an existing ASP.NET Core 2.2 project to ASP.NET Core 3.0. It might be helpful to create a new ASP.NET Core 3.0 project to:
Compare with the ASP.NET Core 2.2 code.
Copy the relevant changes to your ASP.NET Core 3.0 project.
The Visual Studio Code instructions use the .NET CLI for ASP.NET Core development functions such as project creation. You can follow these instructions on any platform (macOS, Linux, or Windows) and with any code editor. Minor changes may be required if you use something other than Visual Studio Code.
If your solution relies upon a global.json file to target a specific .NET Core SDK version, update its version property to the 3.0 version installed on your machine:
A large number of NuGet packages aren't produced for ASP.NET Core 3.0. Such package references should be removed from your project file. Consider the following project file for an ASP.NET Core 2.2 web app:
Features of ASP.NET Core that were available through one of the packages listed above are available as part of the Microsoft.AspNetCore.App shared framework. The shared framework is the set of assemblies (.dll files) that are installed on the machine and includes a runtime component and a targeting pack. For more information, see The shared framework.
Projects that target the Microsoft.NET.Sdk.Web SDK implicitly reference the Microsoft.AspNetCore.App framework.
No additional references are required for these projects:
Framework-dependent builds of console apps that use a package that depends on the ASP.NET Core shared framework may give the following runtime error:
It was not possible to find any compatible framework version
The specified framework 'Microsoft.AspNetCore.App', version '3.0.0' was not found.
- No frameworks were found.
Microsoft.AspNetCore.App is the shared framework containing the ASP.NET Core runtime and is only present on the dotnet/core/aspnet Docker image. The 3.0 SDK reduces the size of framework-dependent builds using ASP.NET Core by not including duplicate copies of libraries that are available in the shared framework. This is a potential savings of up to 18 MB, but it requires that the ASP.NET Core runtime be present / installed to run the app.
To determine if the app has a dependency (either direct or indirect) on the ASP.NET Core shared framework, examine the runtimeconfig.json file generated during a build/publish of your app. The following JSON file shows a dependency on the ASP.NET Core shared framework:
If your app is using Docker, use a base image that includes ASP.NET Core 3.0. For example, docker pull mcr.microsoft.com/dotnet/core/aspnet:3.0.
Add package references for removed assemblies
ASP.NET Core 3.0 removes some assemblies that were previously part of the Microsoft.AspNetCore.App package reference. To visualize which assemblies were removed, compare the two shared framework folders. For example, a comparison of versions 2.2.7 and 3.0.0:
To continue using features provided by the removed assemblies, reference the 3.0 versions of the corresponding packages:
A template-generated web app with Individual User Accounts requires adding the following packages:
Formatting and content negotiation support for System.Net.HttpClient: The Microsoft.AspNet.WebApi.Client NuGet package provides useful extensibility to System.Net.HttpClient with APIs such as ReadAsAsync and PostJsonAsync. However, this package depends on Newtonsoft.Json, not System.Text.Json. That means, for example, that serialization property names specified by JsonPropertyNameAttribute (System.Text.Json) are ignored. There's a newer NuGet package that contains similar extension methods but uses System.Text.Json: System.Net.Http.Json.
The following image shows the deleted and changed lines in an ASP.NET Core 2.2 Razor Pages Web app:
In the preceding image, deleted code is shown in red. The deleted code doesn't show cookie options code, which was deleted prior to comparing the files.
The following image shows the added and changed lines in an ASP.NET Core 3.0 Razor Pages Web app:
In the preceding image, added code is shown in green. For information on the following changes:
app.UseAuthorization was added to the templates to show the order authorization middleware must be added. If the app doesn't use authorization, you can safely remove the call to app.UseAuthorization.
Projects that target Microsoft.NET.Sdk.Web implicitly reference analyzers previously shipped as part of the Microsoft.AspNetCore.Mvc.Analyzers package. No additional references are required to enable these.
Projects default to the in-process hosting model in ASP.NET Core 3.0 or later. You may optionally remove the <AspNetCoreHostingModel> property in the project file if its value is InProcess.
Kestrel
Configuration
Migrate Kestrel configuration to the web host builder provided by ConfigureWebHostDefaults (Program.cs):
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(serverOptions =>
{
// Set properties and call methods on options
})
.UseStartup<Startup>();
});
If the app creates the host manually with ConfigureWebHost instead of ConfigureWebHostDefaults, call UseKestrel on the web host builder:
public static void Main(string[] args)
{
var host = new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureWebHost(webBuilder =>
{
webBuilder.UseKestrel(serverOptions =>
{
// Set properties and call methods on options
})
.UseIISIntegration()
.UseStartup<Startup>();
})
.Build();
host.Run();
}
Connection Adapters (Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal.IConnectionAdapter) have been removed from Kestrel. Replace Connection Adapters with Connection Middleware. Connection Middleware is similar to HTTP Middleware in the ASP.NET Core pipeline but for lower-level connections. HTTPS and connection logging:
Have been moved from Connection Adapters to Connection Middleware.
These extension methods work as in previous versions of ASP.NET Core.
For apps that target earlier versions of ASP.NET Core:
Kestrel adds HTTP/1.1 chunked trailer headers into the request headers collection.
Trailers are available after the request body is read to the end.
This causes some concerns about ambiguity between headers and trailers, so the trailers have been moved to a new collection (RequestTrailerExtensions) in 3.0.
HTTP/2 request trailers are:
Not available in ASP.NET Core 2.2.
Available in 3.0 as RequestTrailerExtensions.
New request extension methods are present to access these trailers. As with HTTP/1.1, trailers are available after the request body is read to the end.
For the 3.0 release, the following RequestTrailerExtensions methods are available:
GetDeclaredTrailers: Gets the request Trailer header that lists which trailers to expect after the body.
SupportsTrailers: Indicates if the request supports receiving trailer headers.
CheckTrailersAvailable: Checks if the request supports trailers and if they're available to be read. This check doesn't assume that there are trailers to read. There might be no trailers to read even if true is returned by this method.
GetTrailer: Gets the requested trailing header from the response. Check SupportsTrailers before calling GetTrailer, or a NotSupportedException may occur if the request doesn't support trailing headers.
AllowSynchronousIO enables or disables synchronous I/O APIs, such as HttpRequest.Body.Read, HttpResponse.Body.Write, and Stream.Flush. These APIs are a source of thread starvation leading to app crashes. In 3.0, AllowSynchronousIO is disabled by default. For more information, see the Synchronous I/O section in the Kestrel article.
If synchronous I/O is needed, it can be enabled by configuring the AllowSynchronousIO option on the server being used (when calling ConfigureKestrel, for example, if using Kestrel). Note that servers (Kestrel, HttpSys, TestServer, etc.) all have their own AllowSynchronousIO option that won't affect other servers. Synchronous I/O can be enabled for all servers on a per-request basis using the IHttpBodyControlFeature.AllowSynchronousIO option:
var syncIOFeature = HttpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
}
If you have trouble with TextWriter implementations or other streams that call synchronous APIs in Dispose, call the new DisposeAsync API instead.
Newtonsoft.Json, XmlSerializer, and DataContractSerializer based output formatters only support synchronous serialization. To allow these formatters to work with the AllowSynchronousIO restrictions of the server, MVC buffers the output of these formatters before writing to disk. As a result of buffering, MVC will include the Content-Length header when responding using these formatters.
System.Text.Json supports asynchronous serialization and consequently the System.Text.Json based formatter does not buffer. Consider using this formatter for improved performance.
In ASP.NET Core 2.1, the contents of Microsoft.AspNetCore.Server.Kestrel.Https.dll were moved to Microsoft.AspNetCore.Server.Kestrel.Core.dll. This was a non-breaking update using TypeForwardedTo attributes. For 3.0, the empty Microsoft.AspNetCore.Server.Kestrel.Https.dll assembly and the NuGet package have been removed.
The default JSON serializer for ASP.NET Core is now System.Text.Json, which is new in .NET Core 3.0. Consider using System.Text.Json when possible. It's high-performance and doesn't require an additional library dependency. However, since System.Text.Json is new, it might currently be missing features that your app needs. For more information, see How to migrate from Newtonsoft.Json to System.Text.Json.
Use Newtonsoft.Json in an ASP.NET Core 3.0 SignalR project
ASP.NET Core 3.0 adds new options for registering MVC scenarios inside Startup.ConfigureServices.
Three new top-level extension methods related to MVC scenarios on IServiceCollection are available. Templates use these new methods instead of AddMvc. However, AddMvc continues to behave as it has in previous releases.
The following example adds support for controllers and API-related features, but not views or pages. The API template uses this code:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
The following example adds support for controllers, API-related features, and views, but not pages. The Web Application (MVC) template uses this code:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
The following example adds support for Razor Pages and minimal controller support. The Web Application template uses this code:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
The new methods can also be combined. The following example is equivalent to calling AddMvc in ASP.NET Core 2.2:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
}
Routing startup code
If an app calls UseMvc or UseSignalR, migrate the app to Endpoint Routing if possible. To improve Endpoint Routing compatibility with previous versions of MVC, we've reverted some of the changes in URL generation introduced in ASP.NET Core 2.2. If you experienced problems using Endpoint Routing in 2.2, expect improvements in ASP.NET Core 3.0 with the following exceptions:
If the app directly accesses RouteData.Routers inside MVC to parse URLs, you can replace this with use of LinkParser.ParsePathByEndpointName.
Define the route with a route name.
Use LinkParser.ParsePathByEndpointName and pass in the desired route name.
Endpoint Routing supports the same route pattern syntax and route pattern authoring features as IRouter. Endpoint Routing supports IRouteConstraint. Endpoint routing supports [Route], [HttpGet], and the other MVC routing attributes.
For most applications, only Startup requires changes.
Migrate Startup.Configure
General advice:
Add UseRouting.
If the app calls UseStaticFiles, place UseStaticFilesbeforeUseRouting.
If the app uses authentication/authorization features such as AuthorizePage or [Authorize], place the call to UseAuthentication and UseAuthorization: after, UseRouting and UseCors, but before UseEndpoints:
If the app uses CORS scenarios, such as [EnableCors], place the call to UseCors before any other middleware that use CORS (for example, place UseCors before UseAuthentication, UseAuthorization, and UseEndpoints).
Replace IHostingEnvironment with IWebHostEnvironment and add a using statement for the Microsoft.AspNetCore.Hosting namespace.
For most apps, calls to UseAuthentication, UseAuthorization, and UseCors must appear between the calls to UseRouting and UseEndpoints to be effective.
Health Checks
Health Checks use endpoint routing with the Generic Host. In Startup.Configure, call MapHealthChecks on the endpoint builder with the endpoint URL or relative path:
Support for authorization and CORS is unified around the middleware approach. This allows use of the same middleware and functionality across these scenarios. An updated authorization middleware is provided in this release, and CORS Middleware is enhanced so that it can understand the attributes used by MVC controllers.
CORS
Previously, CORS could be difficult to configure. Middleware was provided for use in some use cases, but MVC filters were intended to be used without the middleware in other use cases. With ASP.NET Core 3.0, we recommend that all apps that require CORS use the CORS Middleware in tandem with Endpoint Routing. UseCors can be provided with a default policy, and [EnableCors] and [DisableCors] attributes can be used to override the default policy where required.
In the following example:
CORS is enabled for all endpoints with the default named policy.
The MyController class disables CORS with the [DisableCors] attribute.
public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();
app.UseCors("default");
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
[DisableCors]
public class MyController : ControllerBase
{
...
}
Authorization
In earlier versions of ASP.NET Core, authorization support was provided via the [Authorize] attribute. Authorization middleware wasn't available. In ASP.NET Core 3.0, authorization middleware is required. We recommend placing the ASP.NET Core Authorization Middleware (UseAuthorization) immediately after UseAuthentication. The Authorization Middleware can also be configured with a default policy, which can be overridden.
In ASP.NET Core 3.0 or later, UseAuthorization is called in Startup.Configure, and the following HomeController requires a signed in user:
public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
public class HomeController : Controller
{
[Authorize]
public IActionResult BuyWidgets()
{
...
}
}
When using endpoint routing, we recommend against configuring AuthorizeFilter and instead relying on the Authorization middleware. If the app uses an AuthorizeFilter as a global filter in MVC, we recommend refactoring the code to provide a policy in the call to AddAuthorization.
The DefaultPolicy is initially configured to require authentication, so no additional configuration is required. In the following example, MVC endpoints are marked as RequireAuthorization so that all requests must be authorized based on the DefaultPolicy. However, the HomeController allows access without the user signing into the app due to [AllowAnonymous]:
public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute().RequireAuthorization();
});
}
[AllowAnonymous]
public class HomeController : Controller
{
...
}
Authorization for specific endpoints
Authorization can also be configured for specific classes of endpoints. The following code is an example of converting an MVC app that configured a global AuthorizeFilter to an app with a specific policy requiring authorization:
Policies can also be customized. The DefaultPolicy is configured to require authentication:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute().RequireAuthorization();
endpoints.MapRazorPages();
});
}
}
[AllowAnonymous]
public class HomeController : Controller
{
Alternatively, all endpoints can be configured to require authorization without [Authorize] or RequireAuthorization by configuring a FallbackPolicy. The FallbackPolicy is different from the DefaultPolicy. The DefaultPolicy is triggered by [Authorize] or RequireAuthorization, while the FallbackPolicy is triggered when no other policy is set. FallbackPolicy is initially configured to allow requests without authorization.
The following example is the same as the preceding DefaultPolicy example but uses the FallbackPolicy to always require authentication on all endpoints except when [AllowAnonymous] is specified:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
}
public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
[AllowAnonymous]
public class HomeController : Controller
{
...
}
Authorization by middleware works without the framework having any specific knowledge of authorization. For instance, health checks has no specific knowledge of authorization, but health checks can have a configurable authorization policy applied by the middleware.
Additionally, each endpoint can customize its authorization requirements. In the following example, UseAuthorization processes authorization with the DefaultPolicy, but the /healthz health check endpoint requires an admin user:
Protection is implemented for some scenarios. Endpoints Middleware throws an exception if an authorization or CORS policy is skipped due to missing middleware. Analyzer support to provide additional feedback about misconfiguration is in progress.
Custom authorization handlers
If the app uses custom authorization handlers, endpoint routing passes a different resource type to handlers than MVC. Handlers that expect the authorization handler context resource to be of type AuthorizationFilterContext (the resource type provided by MVC filters) will need to be updated to handle resources of type RouteEndpoint (the resource type given to authorization handlers by endpoint routing).
MVC still uses AuthorizationFilterContext resources, so if the app uses MVC authorization filters along with endpoint routing authorization, it may be necessary to handle both types of resources.
SignalR
Mapping of SignalR hubs now takes place inside UseEndpoints.
Map each hub with MapHub. As in previous versions, each hub is explicitly listed.
In the following example, support for the ChatHub SignalR hub is added:
In ASP.NET Core 2.2, you could set the TransportMaxBufferSize and that would effectively control the maximum message size. In ASP.NET Core 3.0, that option now only controls the maximum size before backpressure is observed.
SignalR assemblies in shared framework
ASP.NET Core SignalR server-side assemblies are now installed with the .NET Core SDK. For more information, see Remove obsolete package references in this document.
MVC controllers
Mapping of controllers now takes place inside UseEndpoints.
Add MapControllers if the app uses attribute routing. Since routing includes support for many frameworks in ASP.NET Core 3.0 or later, adding attribute-routed controllers is opt-in.
Replace the following:
MapRoute with MapControllerRoute
MapAreaRoute with MapAreaControllerRoute
Since routing now includes support for more than just MVC, the terminology has changed to make these methods clearly state what they do. Conventional routes such as MapControllerRoute/MapAreaControllerRoute/MapDefaultControllerRoute are applied in the order that they're added. Place more specific routes (such as routes for an area) first.
In the following example:
MapControllers adds support for attribute-routed controllers.
MapAreaControllerRoute adds a conventional route for controllers in an area.
MapControllerRoute adds a conventional route for controllers.
In ASP.NET Core 3.0, ASP.NET Core MVC removes the Async suffix from controller action names. Both routing and link generation are impacted by this new default. For example:
public class ProductsController : Controller
{
public async Task<IActionResult> ListAsync()
{
var model = await _dbContext.Products.ToListAsync();
return View(model);
}
}
Prior to ASP.NET Core 3.0:
The preceding action could be accessed at the Products/ListAsync route.
Link generation required specifying the Async suffix. For example:
This change doesn't affect names specified using the [ActionName] attribute. The default behavior can be disabled with the following code in Startup.ConfigureServices:
There are some differences in link generation (using Url.Link and similar APIs, for example). These include:
By default, when using endpoint routing, casing of route parameters in generated URIs is not necessarily preserved. This behavior can be controlled with the IOutboundParameterTransformer interface.
Generating a URI for an invalid route (a controller/action or page that doesn't exist) will produce an empty string under endpoint routing instead of producing an invalid URI.
Ambient values (route parameters from the current context) are not automatically used in link generation with endpoint routing. Previously, when generating a link to another action (or page), unspecified route values would be inferred from the current routes ambient values. When using endpoint routing, all route parameters must be specified explicitly during link generation.
Razor Pages
Mapping Razor Pages now takes place inside UseEndpoints.
Add MapRazorPages if the app uses Razor Pages. Since Endpoint Routing includes support for many frameworks, adding Razor Pages is now opt-in.
In the following Startup.Configure method, MapRazorPages adds support for Razor Pages:
Using MVC via UseMvc or UseMvcWithDefaultRoute in ASP.NET Core 3.0 requires an explicit opt-in inside Startup.ConfigureServices. This is required because MVC must know whether it can rely on the authorization and CORS Middleware during initialization. An analyzer is provided that warns if the app attempts to use an unsupported configuration.
If the app requires legacy IRouter support, disable EnableEndpointRouting using any of the following approaches in Startup.ConfigureServices:
Health checks can be used as a router-ware with Endpoint Routing.
Add MapHealthChecks to use health checks with Endpoint Routing. The MapHealthChecks method accepts arguments similar to UseHealthChecks. The advantage of using MapHealthChecks over UseHealthChecks is the ability to apply authorization and to have greater fine-grained control over the matching policy.
In the following example, MapHealthChecks is called for a health check endpoint at /healthz:
public void Configure(IApplicationBuilder app)
{
...
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/healthz", new HealthCheckOptions() { });
});
}
HostBuilder replaces WebHostBuilder
The ASP.NET Core 3.0 templates use Generic Host. Previous versions used Web Host. The following code shows the ASP.NET Core 3.0 template generated Program class:
// requires using Microsoft.AspNetCore.Hosting;
// requires using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
The following code shows the ASP.NET Core 2.2 template-generated Program class:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
IWebHostBuilder remains in 3.0 and is the type of the webBuilder seen in the preceding code sample. WebHostBuilder will be deprecated in a future release and replaced by HostBuilder.
The most significant change from WebHostBuilder to HostBuilder is in dependency injection (DI). When using HostBuilder, you can only inject the following into Startup's constructor:
Apps that don't use Razor Pages must call MapRazorPages. See Razor Pages in this document.
Bootstrap 4 is the default UI framework. Set an IdentityUIFrameworkVersion project property to change the default. For more information, see this GitHub announcement.
SignalR
The SignalR JavaScript client has changed from @aspnet/signalr to @microsoft/signalr. To react to this change, change the references in package.json files, require statements, and ECMAScript import statements.
System.Text.Json is the default protocol
System.Text.Json is now the default Hub protocol used by both the client and server.
In Startup.ConfigureServices, call AddJsonProtocol to set serializer options.
Prior to ASP.NET Core 3.0, runtime compilation of views was an implicit feature of the framework. Runtime compilation supplements build-time compilation of views. It allows the framework to compile Razor views and pages (.cshtml files) when the files are modified, without having to rebuild the entire app. This feature supports the scenario of making a quick edit in the IDE and refreshing the browser to view the changes.
In ASP.NET Core 3.0, runtime compilation is an opt-in scenario. Build-time compilation is the only mechanism for view compilation that's enabled by default. The runtime relies on Visual Studio or dotnet-watch in Visual Studio Code to rebuild the project when it detects changes to .cshtml files. In Visual Studio, changes to .cs, .cshtml, or .razor files in the project being run (Ctrl+F5), but not debugged (F5), trigger recompilation of the project.
To enable runtime compilation in your ASP.NET Core 3.0 project:
Libraries often need to support multiple versions of ASP.NET Core. Most libraries that were compiled against previous versions of ASP.NET Core should continue working without issues. The following conditions require the app to be cross-compiled:
The library relies on a feature that has a binary breaking change.
The library wants to take advantage of new features in ASP.NET Core 3.0.
The validation system in .NET Core 3.0 and later treats non-nullable parameters or bound properties as if they had a [Required] attribute. For more information, see [Required] attribute.
Publish
Delete the bin and obj folders in the project directory.
The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, see our contributor guide.
ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Understand and implement middleware in an ASP.NET Core app. Use included middleware like HTTP logging and authentication. Create custom middleware to handle requests and responses.