{"id":360,"date":"2023-10-20T13:50:15","date_gmt":"2023-10-20T05:50:15","guid":{"rendered":"https:\/\/miie.net\/?p=360"},"modified":"2023-10-20T13:50:15","modified_gmt":"2023-10-20T05:50:15","slug":"pro-c10-chapter-31-diving-into-asp-net-core","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=360","title":{"rendered":"Pro C#10 CHAPTER 31 Diving Into ASP.NET Core"},"content":{"rendered":"<p>CHAPTER 31<\/p>\n<p>Diving Into ASP.NET Core<\/p>\n<p>This chapter takes a deep look into the new features in ASP.NET Core. As you learn about the features, you will add them to the projects created in the previous chapter, Introducing ASP.NET Core.<\/p>\n<p>What\u2019s New in ASP.NET Core<br \/>\nIn addition to supporting the base functionality of ASP.NET MVC and ASP.NET Web API, Microsoft has added a host of new features and improvements over the previous frameworks. In addition to the unification of frameworks and controllers, a new style of web applications is now supported using Razor pages. Listed here are some additional improvements and innovations:<br \/>\n\u2022Razor Page based web applications<br \/>\n\u2022Environment awareness<br \/>\n\u2022Minimal templates with top level statements<br \/>\n\u2022Cloud-ready, environment-based configuration system<br \/>\n\u2022Built-in dependency injection<br \/>\n\u2022The Options pattern<br \/>\n\u2022The HTTP Client Factory<br \/>\n\u2022Flexible development and deployment models<br \/>\n\u2022Lightweight, high-performance, and modular HTTP request pipeline<br \/>\n\u2022Tag helpers (covered in a later chapter)<br \/>\n\u2022View components (covered in a later chapter)<br \/>\n\u2022Vast improvements in performance<br \/>\n\u2022Integrated logging<\/p>\n<p>Razor Pages<br \/>\nAnother option for creating web applications with ASP.NET Core is using Razor pages. Instead of using the MVC pattern, Razor page applications are (as the name suggests) page-centric. Each Razor page consists of two files, the page file (e.g. Index.cshtml) is the view, and the PageModel C# class (e.g. Index.cshtml.cs), which serves as the code behind file for the page file.<\/p>\n<p>\u00a9 Andrew Troelsen, Phil Japikse 2022<br \/>\nA. Troelsen and P. Japikse, Pro C# 10 with .NET 6, <a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_31\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_31\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_31\">https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_31<\/a><\/a><\/a><\/p>\n<p>1355<\/p>\n<p>\u25a0Note Razor page based web applications also support partial and layout views, which will be covered in detail in later chapters.<\/p>\n<p>The Razor Page File<br \/>\nJust like MVC based web applications, the Razor page file is responsible for rendering the content of the page, and receiving input from the user. It should be lightweight, and hand off work to the PageModel class. Razor page files will be explored in depth in Chapter 34.<\/p>\n<p>The PageModel Class<br \/>\nLike the Controller class for MVC style applications, the PageModel class provides helper methods for Razor page based web applications. Razor pages derive from the PageModel class, and are typically named with the Model suffix, like CreateModel. The Model suffix is dropped when routing to a page. Table 31-1 lists the most commonly used methods and Table 31-2 lists the HTTP Status Code Helpers.<\/p>\n<p>Table 31-1. Some of the Helper Methods Provided by the Controller Class<\/p>\n<p>Helper Method   Meaning in Life<br \/>\nHttpContext Returns the HttpContext for the currently executing action.<br \/>\nRequest Returns the HttpRequest for the currently executing action.<br \/>\nResponse    Returns the HttpResponse for the currently executing action.<br \/>\nRouteData   Returns the RouteData for the currently executing action (routing is covered later in this chapter).<br \/>\nModelState  Returns the state of the model in regard to model binding and validation (both covered later in this chapter).<br \/>\nUrl Returns an instance of the IUrlHelper, providing access to building URLs for ASP.NET Core MVC applications and services.<br \/>\nViewData TempData   Provide data to the view through the ViewDataDictionary and<br \/>\nTempDataDictionary<br \/>\nPage    Returns a PageResult (derived from ActionResult) as the HTTP response.<br \/>\nPartialView Returns a PartialViewResult to the response pipeline.<br \/>\nViewComponent   Returns a ViewComponentResult to the response pipeline.<br \/>\nOnPageHandlerSelected   Executes when a page handler method is selected but before model binding.<br \/>\nOnPageHandlerExecuting  Executes before a page handler method executes.<br \/>\nOnPageHandlerExecutionAsync Async version of OnPageHandlerExecuting.<br \/>\nOnPageHandlerExecuted   Executes after a page handler method executes.<br \/>\n(continued)<\/p>\n<p>Table 31-1. (continued)<\/p>\n<p>Helper Method   Meaning in Life<br \/>\nOnPageHandlerSelectionAsync Async version of OnPageHandlerSelected.<br \/>\nUser    Returns the ClaimsPrincipal user.<br \/>\nContent Returns a ContentResult to the response. Overloads allow for adding a content type and encoding definition.<br \/>\nFile    Returns a FileContentResult to the response.<br \/>\nRedirect    A series of methods that redirect the user to another URL by returning a<br \/>\nRedirectResult.<br \/>\nLocalRedirect   A series of methods that redirect the user to another URL only if the URL is local. More secure than the generic Redirect methods.<br \/>\nRedirectToAction RedirectToPage RedirectToRoute A series of methods that redirect to another action method, Razor Page, or named route. Routing is covered later in this chapter.<br \/>\nTryUpdateModelAsync Used for explicit model binding.<br \/>\nTryValidateModel    Used for explicit model validation.<\/p>\n<p>Table 31-2. Some of the HTTP Status Code Helper Methods Provided by the PageModelClass<\/p>\n<p>Helper Method   HTTP Status Code Action Result  Status Code<br \/>\nNotFound    NotFoundResult  404<br \/>\nForbid  ForbidResult    403<br \/>\nBadRequest  BadRequestResult    400<br \/>\nStatusCode(int)StatusCode(int, object)  StatusCodeResultObjectResult    Defined by the int parameter.<\/p>\n<p>You might be surprised to see some familiar methods from the Controller class. Razor page based applications share many of the features of MVC style applications as you have seen and will continue to see throughout these chapters.<\/p>\n<p>Page Handler Methods<br \/>\nAs discussed in the routing section, Razor pages define handler methods to handle HTTP Get and Post requests. The PageModel class supports both synchronous and asynchronous handler methods. The verb handled is based on the name of the method, with OnPost()\/OnPostAsync() handling HTTP post requests, and OnGet()\/OnGetAsync() handling HTTP get requests. The async versions are listed here:<\/p>\n<p>public class DeleteModel : PageModel<br \/>\n{<br \/>\npublic async Task<IActionResult> OnGetAsync(int? id)<br \/>\n{<br \/>\n\/\/handle the get request here<br \/>\n}<\/p>\n<p>public async Task<IActionResult> OnPostAsync(int? id)<br \/>\n{<br \/>\n\/\/handle the post request here<br \/>\n}<br \/>\n}<\/p>\n<p>The names of the handler methods can be changed, and multiple handler methods for each HTTP verb can exist, however overloaded versions with the same name are not permitted. This will be covered in Chapter 34.<\/p>\n<p>Environmental Awareness<br \/>\nASP.NET Core applications\u2019 awareness of their execution environment includes host environment variables and file locations through an instance of IWebHostEnvironment, which implement the IHostEnvironment interface. Table 31-3 shows the properties available through these interfaces.<\/p>\n<p>Table 31-3. The IWebHostEnvironment Properties<\/p>\n<p>Property    Functionality Provided<br \/>\nApplicationName Gets or sets the name of the application. Defaults to the name of the entry assembly.<br \/>\nContentRootPath Gets or sets the absolute path to the directory that contains the application content files.<br \/>\nContentRootFileProvider Gets or sets an IFileProvider pointing to the ContentRootPath.<br \/>\nEnvironmentName Gets or sets the name of the environment. Sets to the value of the ASPNETCORE_ ENVIRONMENT environment variable.<br \/>\nWebRootFileProvider Gets or sets an IFileProvider pointing at the WebRootPath.<br \/>\nWebRootPath Gets or sets the absolute path to the directory that contains the web-servable application content files.<\/p>\n<p>In addition to accessing the relevant file paths, IWebHostEnvironment is used to determine the runtime environment.<\/p>\n<p>Determining the Runtime Environment<br \/>\nASP.NET Core automatically reads the value of the environment variable named ASPNETCORE_ENVIRONMENT<br \/>\nto set the runtime environment. If the ASPNETCORE_ENVIRONMENT variable is not set, ASP.NET Core sets the value to Production. The value set is accessible through the EnvironmentName property on the IWebHostEnvironment.<br \/>\nWhile developing ASP.NET Core applications, this variable is typically set using the launchSettings. json file or the command line. Downstream environments (staging, production, etc.) typically use standard operating system environment variables.<br \/>\nYou are free to use any name for the environment or the three that are supplied by the Environments<br \/>\nstatic class.<\/p>\n<p>public static class Environments<br \/>\n{<br \/>\npublic static readonly string Development = &quot;Development&quot;; public static readonly string Staging = &quot;Staging&quot;;<br \/>\npublic static readonly string Production = &quot;Production&quot;;<br \/>\n}<\/p>\n<p>The HostEnvironmentEnvExtensions class provides extensions methods on the IHostEnvironment for working with the environment name property. Table 31-4 lists the convenience methods available.<\/p>\n<p>Table 31-4. The HostEnvironmentEnvExtensions Methods<\/p>\n<p>Method  Functionality Provided<br \/>\nIsProduction    Returns true if the environment variable is set to Production (case insensitive)<br \/>\nIsStaging   Returns true if the environment variable is set to Staging (case insensitive)<br \/>\nIsDevelopment   Returns true if the environment variable is set to Development (case insensitive)<br \/>\nIsEnvironment   Returns true if the environment variable matches the string passed into the method (case insensitive)<\/p>\n<p>These are some examples of using the environment setting:<br \/>\n\u2022Determining which configuration files to load<br \/>\n\u2022Setting debugging, error, and logging options<br \/>\n\u2022Loading environment-specific JavaScript and CSS files<br \/>\nExamine the Program.cs file in the AutoLot.Mvc project. Near the end of the file, the environment is checked to determine if the standard exception handler and HSTS (HTTP Strict Transport Security Protocol) should be used:<\/p>\n<p>if (!app.Environment.IsDevelopment())<br \/>\n{<br \/>\napp.UseExceptionHandler(&quot;\/Home\/Error&quot;); app.UseHsts();<br \/>\n}<\/p>\n<p>Update this block to flip it around:<\/p>\n<p>if (app.Environment.IsDevelopment())<br \/>\n{<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\napp.UseExceptionHandler(&quot;\/Home\/Error&quot;); app.UseHsts();<br \/>\n}<\/p>\n<p>Next, add the developer exception page into the pipeline when the app is running development. This provides debugging details like the stack trace, detailed exception information, etc. The standard exception handler renders a simple error page.<\/p>\n<p>if (app.Environment.IsDevelopment())<br \/>\n{<br \/>\napp.UseDeveloperExceptionPage();<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\napp.UseExceptionHandler(&quot;\/Home\/Error&quot;); app.UseHsts();<br \/>\n}<\/p>\n<p>Update the AutoLot.Web project block to the following (notice the different route for the error handle in the Razor page based application):<\/p>\n<p>if (app.Environment.IsDevelopment())<br \/>\n{<br \/>\napp.UseDeveloperExceptionPage();<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\napp.UseExceptionHandler(&quot;\/Error&quot;); app.UseHsts();<br \/>\n}<\/p>\n<p>The AutoLot.Api project is a little different. It is checking for the development environment, and if it is running in development, Swagger and SwaggerUI are added into the pipeline. For this app, we are going to move the Swagger code out of the if block so it will always be available (leave the if block there, as it will be used later in this chapter):<\/p>\n<p>if (app.Environment.IsDevelopment())<br \/>\n{<br \/>\n\/\/more code to be placed here later<br \/>\n}<br \/>\napp.UseSwagger(); app.UseSwaggerUI();<\/p>\n<p>Since there isn\u2019t a UI associated with RESTful services, there isn\u2019t any need for the developer exception page. Swagger will be covered in depth in the next chapter.<\/p>\n<p>\u25a0Note  Whether the Swagger pages are available outside of the development environment is a business decision. We are moving the Swagger code to run in all environments so that the Swagger page is always available as you work through this book. Swagger will be covered in depth in the next chapter.<\/p>\n<p>You will see many more uses for environment awareness as you build the ASP.NET Core applications in this book.<\/p>\n<p>The WebAppBuilder and WebApp<br \/>\nUnlike classic ASP.NET MVC or ASP.NET Web API applications, ASP.NET Core applications are .NET console applications that create and configure a WebApplication, which is an instance of IHost. The creation of configuration of the IHost is what sets the application up to listen and respond to HTTP requests.<br \/>\nThe default templates for ASP.NET Core 6 MVC, Razor, and Service application are minimal. These files will be added to as you progress through the ASP.NET Core chapters.<\/p>\n<p>\u25a0Note  prior to .net 6 and C# 10, a WebHost was created in the Main() method of the Program.cs file and configured for your application in the Startup.cs file. With the release of .net 6 and C# 10, the aSp.net Core template uses top level statements in the Program.cs file for creation and configuration and doesn\u2019t have a Startup.cs file.<\/p>\n<p>The Program.cs File with RESTful Services<br \/>\nOpen the Program.cs class in the AutoLot.Api application, and examine the contents, shown here for reference:<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<br \/>\n\/\/ Add services to the container. builder.Services.AddControllers();<br \/>\n\/\/ Learn more about configuring Swagger\/OpenAPI at <a href=\"https:\/\/aka.ms\/aspnetcore\/swashbuckle\"><a href=\"https:\/\/aka.ms\/aspnetcore\/swashbuckle\"><a href=\"https:\/\/aka.ms\/aspnetcore\/swashbuckle\">https:\/\/aka.ms\/aspnetcore\/swashbuckle<\/a><\/a><\/a> builder.Services.AddEndpointsApiExplorer();<br \/>\nbuilder.Services.AddSwaggerGen(); var app = builder.Build();<br \/>\n\/\/ Configure the HTTP request pipeline. if (app.Environment.IsDevelopment())<br \/>\n{<br \/>\n\/\/more code to be placed here later<br \/>\n}<br \/>\napp.UseSwagger(); app.UseSwaggerUI();<\/p>\n<p>app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers();<\/p>\n<p>app.Run();<\/p>\n<p>If you are coming from a previous version of ASP.NET Core, the preceding code was split between the Program.cs file and the Startup.cs file. With ASP.NET Core 6, those file are combined into top level statements in the Program.cs file. The code before the call to builder.Build() was contained in the<br \/>\nProgram.cs file and the ConfigureServices() method in the Startup.cs file and is responsible for creating<\/p>\n<p>the WebApplicationBuilder and registering services in the dependency injection container. The code including the builder.Build() call and the rest of the code in the file was contained in the Configure() method and is responsible for the configuration of the HTTP pipeline.<br \/>\nThe CreateBuilder() method compacts the most typical application setup into one method call. It configures the app (using environment variables and appsettings JSON files), it configures the default logging provider, and it sets up the dependency injection container. The returned WebApplicationBuilder is used to register services, add additional configuration information, logging support, etc.<br \/>\nThe next set of methods adds the necessary base services into the dependency injection container for building RESTful services. The AddControllers() method adds in support for using controllers and action methods, the AddEndpointsApiExplorer() method provides information about the API (and is used by Swagger), and AddSwaggerGen() creates the basic OpenAPI support.<\/p>\n<p>\u25a0Note When adding services into the Dependency injection container, make sure to add them into the top level statements using the comment \/\/Add services to the container.they must be added before the builder.Build() method is called.<\/p>\n<p>The builder.Build() method generates the WebApplication and sets up the next group of method calls to configure the HTTP pipeline. The Environment section was discussed previously. The next set of calls ensures all requests use HTTPS, enables the authorization middleware, and maps the controllers to their endpoints. Finally, the Run() method starts the application and gets everything ready to receive web requests and respond to them.<\/p>\n<p>The Program.cs File with MVC Style Applications<br \/>\nOpen the Program.cs class in the AutoLot.Mvc application, and examine the contents, shown here for reference (with differences from the AutoLot.Api application\u2019s file in bold):<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<\/p>\n<p>\/\/ Add services to the container. builder.Services.AddControllersWithViews(); var app = builder.Build();<br \/>\n\/\/ Configure the HTTP request pipeline.<br \/>\nif (app.Environment.IsDevelopment())<br \/>\n{<br \/>\napp.UseDeveloperExceptionPage();<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\napp.UseExceptionHandler(&quot;\/Home\/Error&quot;); app.UseHsts();<br \/>\n}<\/p>\n<p>app.UseHttpsRedirection();<br \/>\napp.UseStaticFiles();<\/p>\n<p>app.UseRouting();<\/p>\n<p>app.UseAuthorization();<\/p>\n<p>app.MapControllerRoute( name: &quot;default&quot;,<br \/>\npattern: &quot;{controller=Home}\/{action=Index}\/{id?}&quot;);<\/p>\n<p>app.Run();<\/p>\n<p>The first difference is the call to AddControllersWithViews(). ASP.NET Core RESTful services use the same controller\/action method pattern as MVC style applications, just without views. This adds support for views into the application. The calls for Swagger and the API Explorer are omitted, as they are used by API service.<br \/>\nThe next difference is the call to UserExceptionHandler() when the application is not running in a development environment. This is a user friendly exception handler that displays simple information<br \/>\n(and no technical debugging data). That is followed by the UseHsts() method, which turns on HTTP Strict Transport Security, preventing users to switch back to HTTP once they have connected. It also prevents them from clicking through warnings regarding invalid certificates.<br \/>\nThe call to UseStaticFiles() enables static content (images, JavaScript files, CSS files, etc.) to be rendered through the application. This call is not in the API style applications, as they don\u2019t typically have a need to render static content. The final changes add end point routing support and the default route to the application.<\/p>\n<p>The Program.cs File with Razor Page Based Applications<br \/>\nOpen the Program.cs class in the AutoLot.Web application, and examine the contents, shown here for reference (with the differences from the AutoLot.Mvc application shown in bold):<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<br \/>\n\/\/ Add services to the container.<\/p>\n<p>builder.Services.AddRazorPages();<br \/>\nvar app = builder.Build();<\/p>\n<p>\/\/ Configure the HTTP request pipeline. if (app.Environment.IsDevelopment())<br \/>\n{<br \/>\napp.UseDeveloperExceptionPage();<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\napp.UseExceptionHandler(&quot;\/Error&quot;); app.UseHsts();<br \/>\n}<br \/>\napp.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapRazorPages(); app.Run();<\/p>\n<p>There are only three differences between this file and the initial file for the AutoLot.Mvc application. Instead of adding support for controllers with views, there is a call to AddRazorPages() to add support for Razor pages, add routing for razor pages with the call to the MapRazorPages() method, and there isn\u2019t a default route configured.<\/p>\n<p>Application Configuration<br \/>\nPrevious versions of ASP.NET used the web.config file to configure services and applications, and developers accessed the configuration settings through the System.Configuration class. Of course, all configuration settings for the site, not just application-specific settings, were dumped into the web.config file making it a (potentially) complicated mess.<br \/>\nASP.NET Core leverages the new .NET JSON-based configuration system first introduced in Chapter 16.<br \/>\nAs a reminder, it\u2019s based on simple JSON files that hold configuration settings. The default file for configuration is the appsettings.json file. The initial version of appsettings.json file (created by the ASP. NET Core web application and API service templates) simply contains configuration information for the logging, as well as allowing all hosts (e.g. <a href=\"https:\/\/localhost:xxxx\"><a href=\"https:\/\/localhost:xxxx\"><a href=\"https:\/\/localhost:xxxx\">https:\/\/localhost:xxxx<\/a><\/a><\/a>) to bind to the app:<\/p>\n<p>{<br \/>\n&quot;Logging&quot;: {<br \/>\n&quot;LogLevel&quot;: {<br \/>\n&quot;Default&quot;: &quot;Information&quot;, &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;,<br \/>\n}<br \/>\n},<br \/>\n&quot;AllowedHosts&quot;: &quot;*&quot;<br \/>\n}<\/p>\n<p>The template also creates an appsettings.Development.json file. The configuration system works in conjunction with the runtime environment awareness to load additional configuration files based<br \/>\non the runtime environment. This is accomplished by instructing the configuration system to load a file named appsettings.{environmentname}.json after the appSettings.json file. When running under Development, the appsettings.Development.json file is loaded after the initial settings file. If the environment is Staging, the appsettings.Staging.json file is loaded, etc. It is important to note that when more than one file is loaded, any settings that appear in both files are overwritten by the last file loaded; they are not additive.<br \/>\nFor each of the web application projects, add the following connection string information (updating the actual connection string to match your environment from Chapter 23 into the appsettings.Development. json files:<\/p>\n<p>&quot;ConnectionStrings&quot;: {<br \/>\n&quot;AutoLot&quot;: &quot;Server=.,5433;Database=AutoLot;User ID=sa;Password=P@ssw0rd;&quot;<br \/>\n}<\/p>\n<p>\u25a0Note  each item in the JSon must be comma delimited. When you add in the ConnectionStrings item, make sure to add a comma after the curly brace that proceeds the item you are adding.<\/p>\n<p>Next, copy each of the appsettings.Development.json files to a new file named appsettings.<br \/>\nProduction.json into each of the web application projects. Update the connection string entries to the following:<\/p>\n<p>&quot;ConnectionStrings&quot;: { &quot;AutoLot&quot;: &quot;It\u2019s a secret&quot;<br \/>\n},<\/p>\n<p>This shows part of the power of the new configuration system. The developers don\u2019t have access to the secret production information (like connection strings), just the non-secret information, yet everything can still be checked into source control.<\/p>\n<p>\u25a0Note  in production scenarios using this pattern, the secrets are typically tokenized. the build and release process replaces the tokens with the production information.<\/p>\n<p>All configuration values are accessible through an instance of IConfiguration, which is available through the ASP.NET Core dependency injection system. In the top level statements prior to the web application being built, the configuration is available from the WebApplicationBuilder like this:<\/p>\n<p>var config = builder.Configuration;<\/p>\n<p>After the web application is built, the IConfiguration instance is available from the WebApplication<br \/>\ninstance:<\/p>\n<p>var config = app.Configuration;<\/p>\n<p>Settings can be accessed using the traditional methods covered in Chapter 16. There is also a shortcut for getting application connection strings.<\/p>\n<p>config.GetConnectionString(&quot;AutoLot&quot;)<\/p>\n<p>Additional configuration features, including the Options pattern, will be discussed later in this chapter.<\/p>\n<p>Built-in Dependency Injection<br \/>\nDependency injection (DI) is a mechanism to support loose coupling between objects. Instead of directly creating dependent objects or passing specific implementations into classes and\/or methods, parameters are defined as interfaces. That way, any implementation of the interface can be passed into the classes or methods and classes, dramatically increasing the flexibility of the application.<br \/>\nDI support is one of the main tenets in the rewrite ASP.NET Core. Not only do the top level statements in the Program.cs file (covered later in this chapter) accept all the configuration and middleware services through dependency injection, your custom classes can (and should) also be added to the service container to be injected into other parts of the application. When an item is configured into the ASP.NET Core DI container, there are three lifetime options, as shown in Table 31-5.<\/p>\n<p>Table 31-5. Lifetime Options for Services<\/p>\n<p>Lifetime Option Functionality Provided<br \/>\nTransient   Created each time they are needed.<br \/>\nScoped  Created once for each request. Recommended for Entity Framework DbContext objects.<br \/>\nSingleton   Created once on first request and then reused for the lifetime of the object. This is the recommended approach versus implementing your class as a Singleton.<\/p>\n<p>\u25a0Note  if you want to use a different dependency injection container, aSp.net Core was designed with that flexibility in mind. Consult the docs to learn how to plug in a different container: <a href=\"https:\/\/docs.microsoft\"><a href=\"https:\/\/docs.microsoft\"><a href=\"https:\/\/docs.microsoft\">https:\/\/docs.microsoft<\/a><\/a><\/a>. com\/en-us\/aspnet\/core\/fundamentals\/dependency-injection.<\/p>\n<p>Services are added into the DI container by adding them to the IServiceCollection for the application.<br \/>\nWhen using the top level statements template in .NET 6 web applications, the IServiceCollection is instantiated by the WebApplicationBuilder, and this instance is used to add services into the container.<br \/>\nWhen adding services to the DI container, make sure to add them before the line that builds the<br \/>\nWebApplication object:<\/p>\n<p>var app = builder.Build();<\/p>\n<p>\u25a0Note in pre-.net 6 web applications, the startup process involved both a Program.cs class and another class named Startup.cs (by convention). in .net 6, all of the configuration is done in top level statements in the Program.cs file. this will be covered in the section Configuring the Web application.<\/p>\n<p>Adding Web App Support To The Dependency Injection Container<br \/>\nWhen creating MVC based web applications, the AddControllersWithView() method adds in the necessary services to support the MVC pattern in ASP.NET Core. The following code (in the Program.cs file of the AutoLot.Mvc project) accesses the IServiceCollection of the WebApplicationBuilder and adds the required DI support for controllers and views:<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<br \/>\n\/\/ Add services to the container. builder.Services.AddControllersWithViews();<\/p>\n<p>RESTful service web applications don\u2019t use views but do need controller support, so they use the AddControllers() method. The API template for ASP.NET Core also adds in support for Swagger (a .NET implementation of OpenAPI) and the ASP.NET Core end point explorer:<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<br \/>\n\/\/ Add services to the container. builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen();<\/p>\n<p>\u25a0Note  Swagger and openapi will be covered in the next chapter.<\/p>\n<p>Finally, Razor page based applications must enable Razor page support with the<br \/>\nAddRazorPages() method:<\/p>\n<p>var builder = WebApplication.CreateBuilder(args);<br \/>\n\/\/ Add services to the container. builder.Services.AddRazorPages();<\/p>\n<p>Adding Derived DbContext Classes into the DI Container<br \/>\nRegistering a derived DbContext class in an ASP.NET Core application allows the DI container to handle the initialization and lifetime of the context. The AddDbContext&lt;&gt;()\/AddDbContextPool() methods add a properly configured context class into the DI container. The AddDbContextPool() version creates a pool of instances that are cleaned between requests, ensuring that there isn\u2019t any data contamination between<br \/>\nrequests. This can improve performance in your application, as it eliminates the startup cost for creating a context once it is added to the pool.<br \/>\nStart by adding the following global using statements into the GlobalUsings.cs for the AutoLot.Api, AutoLot.Mvc, AutoLot.Web projects:<\/p>\n<p>global using AutoLot.Dal.EfStructures; global using AutoLot.Dal.Initialization; global using Microsoft.EntityFrameworkCore;<\/p>\n<p>The following code (which must be added into each of the Program.cs files in the web projects) gets the connection string from the JSON configuration file and adds the ApplicationDbContext as a pooled resource into the services collection:<\/p>\n<p>var connectionString = builder.Configuration.GetConnectionString(&quot;AutoLot&quot;); builder.Services.AddDbContextPool<ApplicationDbContext>(<br \/>\noptions =&gt; options.UseSqlServer(connectionString,<br \/>\nsqlOptions =&gt; sqlOptions.EnableRetryOnFailure().CommandTimeout(60)));<\/p>\n<p>Now, whenever an instance of the ApplicationDbContext is needed, the dependency injection (DI) system will take care of the creation (or getting it from the pool) and recycling of the instance (or returning it to the pool).<br \/>\nEF Core 6 introduced a set of minimal APIs for adding derived DbContext classes into the services collection. Instead of the previous code, you can use the following short cut:<\/p>\n<p>builder.Services.AddSqlServer<ApplicationDbContext>(connectionString, options =&gt;<br \/>\n{<br \/>\noptions.EnableRetryOnFailure().CommandTimeout(60);<br \/>\n});<\/p>\n<p>Note that the minimal API doesn\u2019t have the same level of capabilities. Several features, like DbContext pooling are not supported. For more information on the minimal APIs, refer to the documentation located here: <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/what-is-new\/ef-core-6.0\/\"><a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/what-is-new\/ef-core-6.0\/\"><a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/what-is-new\/ef-core-6.0\/\">https:\/\/docs.microsoft.com\/en-us\/ef\/core\/what-is-new\/ef-core-6.0\/<\/a><\/a><\/a> whatsnew#miscellaneous-improvements.<\/p>\n<p>Adding Custom Services To The Dependency Injection Container<br \/>\nApplication services (like the repositories in the AutoLot.Dal project) can be added into the DI container using one of the lifetime options from Table 31-5. For example, to add the CarRepo as a service into the DI container, you would use the following<\/p>\n<p>services.AddScoped&lt;ICarRepo, CarRepo&gt;();<\/p>\n<p>The previous example added a scoped service (AddScoped&lt;&gt;()) into the DI container specifying the service type (ICarRepo) and the concrete implementation (CarRepo) to inject. You could add all of the repos into all of the web applications in the Program.cs file directly, or you can create an extension method to encapsulate the calls. This process keeps the Program.cs file cleaner.<br \/>\nBefore creating the extension method, update the GlobalUsings.cs file in the AutoLot.Services project to the following:<\/p>\n<p>global using AutoLot.Dal.Repos; global using AutoLot.Dal.Repos.Base;<br \/>\nglobal using AutoLot.Dal.Repos.Interfaces;<\/p>\n<p>global using AutoLot.Models.Entities; global using AutoLot.Models.Entities.Base;<\/p>\n<p>global using Microsoft.AspNetCore.Builder;<\/p>\n<p>global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.Hosting;<br \/>\nglobal using Microsoft.Extensions.Logging;<\/p>\n<p>global using Serilog;<br \/>\nglobal using Serilog.Context;<br \/>\nglobal using Serilog.Core.Enrichers; global using Serilog.Events;<br \/>\nglobal using Serilog.Sinks.MSSqlServer;<\/p>\n<p>global using System.Data;<br \/>\nglobal using System.Runtime.CompilerServices;<\/p>\n<p>Next, create a new folder named DataServices in the AutoLot.Services project. In this folder, create a new public static class named DataServiceConfiguration. Update this class to the following:<br \/>\nnamespace AutoLot.Services.DataServices; public static class DataServiceConfiguration<br \/>\n{<br \/>\npublic static IServiceCollection AddRepositories(this IServiceCollection services)<br \/>\n{<br \/>\nservices.AddScoped&lt;ICarDriverRepo, CarDriverRepo&gt;(); services.AddScoped&lt;ICarRepo, CarRepo&gt;(); services.AddScoped&lt;ICreditRiskRepo, CreditRiskRepo&gt;();<br \/>\nservices.AddScoped&lt;ICustomerOrderViewModelRepo, CustomerOrderViewModelRepo&gt;();<\/p>\n<p>services.AddScoped&lt;ICustomerRepo, CustomerRepo&gt;(); services.AddScoped&lt;IDriverRepo, DriverRepo&gt;(); services.AddScoped&lt;IMakeRepo, MakeRepo&gt;(); services.AddScoped&lt;IOrderRepo, OrderRepo&gt;(); services.AddScoped&lt;IRadioRepo, RadioRepo&gt;(); return services;<br \/>\n}<br \/>\n}<\/p>\n<p>Next, add the following to the GlobalUsings.cs file in each web application (AutoLot.Api, AutoLot.Mvc, AutoLot.Web):<\/p>\n<p>global using AutoLot.Services.DataServices;<\/p>\n<p>Finally, add the following code to the top level statements in each of the web applications, making sure to add them above the line that builds the WebApplication:<\/p>\n<p>builder.Services.AddRepositories();<\/p>\n<p>Dependency Hierarchies<br \/>\nWhen there is a chain of dependencies, all dependencies must be added into the DI container, or a run- time error will occur when the DI container tries to instantiate the concrete class. If you recall from our repositories, they each had a public constructor that took an instance of the ApplicationDbContext, which was added into the DI container before adding in the repositories. If ApplicationDbContext was not in the DI container, then the repositories that depend on it can\u2019t be constructed.<\/p>\n<p>Injecting Dependencies<br \/>\nServices in the DI container can be injected into class constructors and methods, into Razor views, and Razor pages and PageModel classes. When injecting into the constructor of a controller or PageModel class, add the type to be injected into the constructor, like this:<\/p>\n<p>\/\/Controller<br \/>\npublic class CarsController : Controller<br \/>\n{<br \/>\nprivate readonly ICarRep _repo; public CarsController(ICarRepo repo)<br \/>\n{<br \/>\n_repo = repo;<br \/>\n}<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>\/\/PageModel<br \/>\npublic class CreateModel : PageModel<br \/>\n{<br \/>\nprivate readonly ICarRepo _repo;<\/p>\n<p>public CreateModel(ICarRepo repo)<br \/>\n{<br \/>\n_repo = repo;<br \/>\n}<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>Method injection is supported for action methods and page handler methods. To distinguish between a binding target and a service from the DI container, the FromServices attribute must be used:<\/p>\n<p>\/\/Controller<br \/>\npublic class CarsController : Controller<br \/>\n{<br \/>\n[HttpPost] [ValidateAntiForgeryToken]<br \/>\npublic async Task<IActionResult> CreateAsync([FromServices]ICarRepo repo)<br \/>\n{<br \/>\n\/\/do something<br \/>\n}<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>\/\/PageModel<br \/>\npublic class CreateModel : PageModel<br \/>\n{<br \/>\npublic async Task<IActionResult> OnPostAsync([FromServices]ICarRepo repo)<br \/>\n{<br \/>\n\/\/do somethng<br \/>\n}<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>\u25a0Note You might be wondering when you should use constructor injection vs method injection, and the answer, of course, is \u201cit depends\u201d. i prefer to use constructor injection for services used throughout the class and method injection for more focused scenarios.<\/p>\n<p>To inject into an MVC view or a Razor page view, use the @inject command:<\/p>\n<p>@inject ICarRepo Repo<\/p>\n<p>Getting Dependencies in Program.cs<br \/>\nYou might be wondering how to get dependencies out of the DI container when you are in the top level statements in the Program.cs file. For example, if you want to initialize the database, you need an instance of the ApplicationDbContext. There isn\u2019t a constructor, action method, page handler method, or view to inject the instance into.<\/p>\n<p>In addition to the traditional DI techniques, services can also be retrieved directly from the application\u2019s ServiceProvider. The WebApplication exposes the configured ServiceProvider through the Services property. To get a service, first create an instance of the IServiceScope. This provides a lifetime container<br \/>\nto hold the service. Then get the ServiceProvider from the IServiceScope, which will then provide the services within the current scope.<br \/>\nSuppose you want to selectively clear and reseed the database when running in the development environment. To set this up, first add the following line to the appsettings.Development.json files in each of the web projects:<\/p>\n<p>&quot;RebuildDatabase&quot;: true,<\/p>\n<p>Next, add the following line to each of the appsettings.Production.json files in each of the web projects:<\/p>\n<p>&quot;RebuildDatabase&quot;: false,<\/p>\n<p>In the development block of the if statement in Program.cs, if the configured value for RebuildDatabase is true, then create a new IServiceScope to the an instance of the ApplicationDbContext and use that to call the ClearAndReseedDatabase() method (example shown from the AutoLot.Mvc project):<\/p>\n<p>if (env.IsDevelopment())<br \/>\n{<br \/>\napp.UseDeveloperExceptionPage();<br \/>\n\/\/Initialize the database<br \/>\nif (app.Configuration.GetValue<bool>(&quot;RebuildDatabase&quot;))<br \/>\n{<br \/>\nusing var scope = app.Services.CreateScope();<br \/>\nvar dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); SampleDataInitializer.ClearAndReseedDatabase(dbContext);<br \/>\n}<br \/>\n}<\/p>\n<p>Make the exact same changes to the Program.cs file in the AutoLot.Web project. The Program.cs file update in the AutoLot.Api project is shown here:<\/p>\n<p>if (app.Environment.IsDevelopment())<br \/>\n{<br \/>\n\/\/Initialize the database<br \/>\nif (app.Configuration.GetValue<bool>(&quot;RebuildDatabase&quot;))<br \/>\n{<br \/>\nusing var scope = app.Services.CreateScope();<br \/>\nvar dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); SampleDataInitializer.ClearAndReseedDatabase(dbContext);<br \/>\n}<br \/>\n}<\/p>\n<p>Build the Shared Data Services<br \/>\nTo wrap up the discussion on dependency injection, we are going to build a set of data services that will be used by both the AutoLot.Mvc and AutoLot.Web projects. The goal of the services is to present a single set of interfaces for all data access. There will be two concrete implementations of the interfaces, one that will call into the AutoLot.Api project and another one that will call into the AutoLot.Dal code directly. The concrete implementations that are added into the DI container will be determined by a project configuration setting.<\/p>\n<p>The Interfaces<br \/>\nStart by creating a new directory named Interfaces in the DataServices directory of the AutoLot.Services project. Next, add the following IDataServiceBase<T> interface into the Interfaces directory:<\/p>\n<p>namespace AutoLot.Services.DataServices.Interfaces;<\/p>\n<p>public interface IDataServiceBase<TEntity> where TEntity : BaseEntity, new()<br \/>\n{<br \/>\nTask&lt;IEnumerable<TEntity>&gt; GetAllAsync(); Task<TEntity> FindAsync(int id);<br \/>\nTask<TEntity> UpdateAsync(TEntity entity, bool persist = true); Task DeleteAsync(TEntity entity, bool persist = true); Task<TEntity> AddAsync(TEntity entity, bool persist = true);<br \/>\n\/\/implemented ghost method since it won\u2019t be used by the API data service void ResetChangeTracker() {}<br \/>\n}<\/p>\n<p>The IMakeDataService interface simply implements the IDataServiceBase<Make> interface:<\/p>\n<p>namespace AutoLot.Services.DataServices.Interfaces;<\/p>\n<p>public interface IMakeDataService : IDataServiceBase<Make><br \/>\n{<br \/>\n}<\/p>\n<p>The ICarDataService interface implements the IDataServiceBase<Car> interface and adds a method to get Car records by Make Id:<br \/>\nnamespace AutoLot.Services.DataServices.Interfaces; public interface ICarDataService : IDataServiceBase<Car><br \/>\n{<br \/>\nTask&lt;IEnumerable<Car>&gt; GetAllByMakeIdAsync(int? makeId);<br \/>\n}<\/p>\n<p>Add the following global using statement into the GlobalUsing.cs file:<\/p>\n<p>global using AutoLot.Services.DataServices.Interfaces;<\/p>\n<p>The AutoLot.Dal Direct Implementation<br \/>\nStart by creating a new directory named Dal in the DataServices directory, and add a new directory named Base in the Dal directory. Add a new class named DalServiceBase, make it public abstract and implement the IDataServiceBase<T> interface:<\/p>\n<p>namespace AutoLot.Services.DataServices.Dal.Base;<\/p>\n<p>public abstract class DalDataServiceBase<TEntity> : IDataServiceBase<TEntity> where TEntity : BaseEntity, new()<br \/>\n{<br \/>\n\/\/implementation goes here<br \/>\n}<\/p>\n<p>Add a constructor that takes in an instance of the IBaseRepo<T> and assign it to a class level variable:<\/p>\n<p>protected readonly IBaseRepo<TEntity> MainRepo;<br \/>\nprotected DalDataServiceBase(IBaseRepo<TEntity> mainRepo)<br \/>\n{<br \/>\nMainRepo = mainRepo;<br \/>\n}<\/p>\n<p>Recall that all of the create, read, update, and delete (CRUD) methods on the base interface were defined as Task or Task<T>. They are defined this way because calls to a RESTful service are asynchronous calls. Also recall that the repo methods that were built with the AutoLot.Dal are not async. The reason for that was mostly for teaching purposes, and not to introduce additional friction into learning EF Core. As the rest of the data services implementation is complete, you can either leave the repo methods synchronous (as they will be shown here) or refactor the repos to add in async versions of the methods.<br \/>\nThe base methods call the related methods in the MainRepo:<\/p>\n<p>public async Task&lt;IEnumerable<TEntity>&gt; GetAllAsync()<br \/>\n=&gt; MainRepo.GetAllIgnoreQueryFilters();<br \/>\npublic async Task<TEntity> FindAsync(int id) =&gt; MainRepo.Find(id);<br \/>\npublic async Task<TEntity> UpdateAsync(TEntity entity, bool persist = true)<br \/>\n{<br \/>\nMainRepo.Update(entity, persist); return entity;<br \/>\n}<br \/>\npublic async Task DeleteAsync(TEntity entity, bool persist = true)<br \/>\n=&gt; MainRepo.Delete(entity, persist);<br \/>\npublic async Task<TEntity> AddAsync(TEntity entity, bool persist = true)<br \/>\n{<br \/>\nMainRepo.Add(entity, persist); return entity;<br \/>\n}<\/p>\n<p>The final method resets the ChangeTracker on the context, clearing it out for reuse:<\/p>\n<p>public void ResetChangeTracker()<br \/>\n{<br \/>\nMainRepo.Context.ChangeTracker.Clear();<br \/>\n}<\/p>\n<p>Add the following global using statements into the GlobalUsings.cs file:<\/p>\n<p>global using AutoLot.Services.DataServices.Dal; global using AutoLot.Services.DataServices.Dal.Base;<\/p>\n<p>Add a new class named MakeDalDataService.cs in the Dal directory and update it to match the following:<\/p>\n<p>namespace AutoLot.Services.DataServices.Dal;<\/p>\n<p>public class MakeDalDataService : DalDataServiceBase<Make>,IMakeDataService<br \/>\n{<br \/>\npublic MakeDalDataService(IMakeRepo repo):base(repo) { }<br \/>\n}<\/p>\n<p>Finally, add a class named CarDalDataService.cs and update it to match the following, which implements the one extra method from the interface:<\/p>\n<p>namespace AutoLot.Services.DataServices.Dal;<\/p>\n<p>public class CarDalDataService : DalDataServiceBase<Car>,ICarDataService<br \/>\n{<br \/>\nprivate readonly ICarRepo _repo;<br \/>\npublic CarDalDataService(ICarRepo repo) : base(repo)<br \/>\n{<br \/>\n_repo = repo;<br \/>\n}<br \/>\npublic async Task&lt;IEnumerable<Car>&gt; GetAllByMakeIdAsync(int? makeId) =&gt; makeId.HasValue<br \/>\n? _repo.GetAllBy(makeId.Value)<br \/>\n: MainRepo.GetAllIgnoreQueryFilters();<br \/>\n}<\/p>\n<p>The API Initial Implementation<br \/>\nMost of the implementation for the API version of the data services will be completed after you create the HTTP client factory in the next section. This section will just create the classes to illustrate toggling<br \/>\nimplementations for interfaces. Start by creating a new directory named Api in the DataServices directory, and add a new directory named Base in the Api directory. Add a new class named ApiServiceBase, make it public abstract and implement the IDataServiceBase<T> interface:<\/p>\n<p>namespace AutoLot.Services.DataServices.Api.Base;<\/p>\n<p>public abstract class ApiDataServiceBase<TEntity> : IDataServiceBase<TEntity> where TEntity : BaseEntity, new()<br \/>\n{<br \/>\nprotected ApiDataServiceBase()<br \/>\n{<br \/>\n}<\/p>\n<p>public async Task&lt;IEnumerable<TEntity>&gt; GetAllAsync()<br \/>\n=&gt; throw new NotImplementedException();<br \/>\npublic async Task<TEntity> FindAsync(int id) =&gt; throw new NotImplementedException(); public async Task<TEntity> UpdateAsync(TEntity entity, bool persist = true)<br \/>\n{<br \/>\nthrow new NotImplementedException();<br \/>\n}<\/p>\n<p>public async Task DeleteAsync(TEntity entity, bool persist = true)<br \/>\n=&gt; throw new NotImplementedException();<br \/>\npublic async Task<TEntity> AddAsync(TEntity entity, bool persist = true)<br \/>\n{<br \/>\nthrow new NotImplementedException();<br \/>\n}<br \/>\n}<\/p>\n<p>Add the following global using statement into the GlobalUsings.cs file:<\/p>\n<p>global using AutoLot.Services.DataServices.Api; global using AutoLot.Services.DataServices.Api.Base;<\/p>\n<p>Add a new class named MakeDalDataService.cs in the Dal directory and update it to match the following:<\/p>\n<p>namespace AutoLot.Services.DataServices.Api;<\/p>\n<p>public class MakeApiDataService : ApiDataServiceBase<Make>, IMakeDataService<br \/>\n{<br \/>\npublic MakeApiDataService():base()<br \/>\n{<br \/>\n}<br \/>\n}<\/p>\n<p>Finally, add a class named CarDalDataService.cs and update it to match the following, which implements the one extra method from the interface:<\/p>\n<p>namespace AutoLot.Services.DataServices.Api;<\/p>\n<p>public class CarApiDataService : ApiDataServiceBase<Car>, ICarDataService<br \/>\n{<br \/>\npublic CarApiDataService() :base()<br \/>\n{<br \/>\n}<\/p>\n<p>public async Task&lt;IEnumerable<Car>&gt; GetAllByMakeIdAsync(int? makeId) =&gt; throw new NotImplementedException();<br \/>\n}<\/p>\n<p>Adding the Data Services to the DI Container<br \/>\nThe final step is to add the ICarDataService and IMakeDataService into the services collection. Start by adding the following line into the appsettings.Development.json and appsettings.Production.json files in the AutoLot.Mvc and AutoLot.Web project:<\/p>\n<p>&quot;RebuildDatabase&quot;: false,<br \/>\n&quot;UseApi&quot;: false,<\/p>\n<p>Add the following global using statements to the GlobalUsings.cs file in both the AutoLot.Mvc and AutoLot.Web projects:<\/p>\n<p>global using AutoLot.Services.DataServices.Api; global using AutoLot.Services.DataServices.Dal;<\/p>\n<p>Finally, add a new public static method named AddDataServices() to the DataServiceConfiguration class. In this method, check the value of the UseApi configuration flag, and if it is set to true, add the API versions of the data services classes into the services collection. Otherwise use the data access layer versions:<\/p>\n<p>public static IServiceCollection AddDataServices( this IServiceCollection services, ConfigurationManager config)<br \/>\n{<br \/>\nif (config.GetValue<bool>(&quot;UseApi&quot;))<br \/>\n{<br \/>\nservices.AddScoped&lt;ICarDataService, CarApiDataService&gt;(); services.AddScoped&lt;IMakeDataService, MakeApiDataService&gt;();<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nservices.AddScoped&lt;ICarDataService, CarDalDataService&gt;(); services.AddScoped&lt;IMakeDataService, MakeDalDataService&gt;();<br \/>\n}<br \/>\nreturn services;<br \/>\n}<\/p>\n<p>Call the new extension method in the top level statements in Program.cs in the AutoLot.Mvc and AutoLot.Web projects:<\/p>\n<p>builder.Services.AddRepositories();<br \/>\nbuilder.Services.AddDataServices(builder.Configuration);<\/p>\n<p>The Options Pattern in ASP.NET Core<br \/>\nThe options pattern provides a mechanism to instantiate classes from configured settings and inject the configured classes into other classes through dependency injection. The classes are injected into another class using one of the versions of IOptions<T>. There are several versions of this interface, as shown in Table 31-6.<\/p>\n<p>Table 31-6. Some of the IOptions Interfaces<\/p>\n<p>Interface   Description<br \/>\nIOptionsMonitor<T>  Retrieves options and supports the following: notification of changes (with OnChange), configuration reloading, named options (with Get and CurrentValue), and selective options invalidation.<br \/>\nIOptionsMonitorCache<T> Caches instances of T with support for full\/partial invalidation\/reload.<br \/>\nIOptionsSnaphot<T>  Recomputes options on every request.<br \/>\nIOptionsFactory<T>  Creates new instances of T.<br \/>\nIOptions<T> Root interface. Doesn\u2019t support IOptionsMonitor<T>. Left in for backward compatibility.<\/p>\n<p>Using the Options Pattern<br \/>\nA simple example is to retrieve car dealer information from the configuration, configure a class with that data, and inject it into a controller\u2019s action method for display. By placing the information into the settings file, the data is customizable without having to redeploy the site.<br \/>\nStart by adding the dealer information into the appsettings.json files for the AutoLot.Mvc and AutoLot.Web projects:<\/p>\n<p>{<br \/>\n&quot;Logging&quot;: {<br \/>\n&quot;LogLevel&quot;: {<br \/>\n&quot;Default&quot;: &quot;Information&quot;, &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;<br \/>\n}<br \/>\n},<br \/>\n&quot;AllowedHosts&quot;: &quot;*&quot;,<br \/>\n&quot;DealerInfo&quot;: {<br \/>\n&quot;DealerName&quot;: &quot;Skimedic's Used Cars&quot;, &quot;City&quot;: &quot;West Chester&quot;,<br \/>\n&quot;State&quot;: &quot;Ohio&quot;<br \/>\n}<br \/>\n}<\/p>\n<p>Next, we need to create a view model to hold dealer information. Create a new folder named ViewModels in the AutoLot.Services project. In that folder, add a new class named DealerInfo.cs. Update the class to the following:<\/p>\n<p>namespace AutoLot.Services.ViewModels; public class DealerInfo<br \/>\n{<br \/>\npublic string DealerName { get; set; } public string City { get; set; } public string State { get; set; }<br \/>\n}<\/p>\n<p>\u25a0Note the class to be configured must have a public parameterless constructor and be non-abstract. properties are bound but fields are not. Default values can be set on the class properties.<\/p>\n<p>Next, add the following global using statements to the AutoLot.Mvc and AutoLot.Web GlobalUsings. cs files:<\/p>\n<p>global using AutoLot.Services.ViewModels; global using Microsoft.Extensions.Options;<\/p>\n<p>The Configure&lt;&gt;() method of the IServiceCollection maps a section of the configuration files to a specific type. That type can then be injected into classes and views using the options pattern. In the<br \/>\nProgram.cs files for the AutoLot.Mvc and AutoLot.Web Program.cs files, add the following after the line used to configure the repositories:<\/p>\n<p>builder.Services.Configure<DealerInfo>(builder.Configuration.GetSection(nameof(Deal erInfo)));<\/p>\n<p>Now that the DealerInfo is configured, instances are retrieved by injection one of the IOptions interfaces into the class constructor, controller action method, or Razor page handler method. Notice that what is injected in is not an instance of the DealerInfo class, but an instance of the IOptions<T> interface. To get the configured instance, the CurrentValue (IOptionsMonitor<T>) or Value<br \/>\n(IOptionsSnapshot<T>) must be used. The following example uses method injection to pass an instance if IOptionsMonitor<DealerInfo> into the HomeController\u2019s Index method in the AutoLot.Mvc project, then gets the CurrentValue and passes the configured instance of the DealerInfo class to the view (although the view doesn\u2019t do anything with it yet).<\/p>\n<p>public IActionResult Index([FromServices] IOptionsMonitor<DealerInfo> dealerMonitor)<br \/>\n{<br \/>\nvar vm = dealerMonitor.CurrentValue;<br \/>\nreturn View(vm);<br \/>\n}<\/p>\n<p>The following example replicates the process for the Index Razor page in the Pages folder in the AutoLot.Web project. Instead of passing the instance to the view, it is assigned to a property on the page. Update the Index.cshtml.cs by adding the same injection into the OnGet() method:<\/p>\n<p>public DealerInfo DealerInfoInstance { get; set; }<br \/>\npublic void OnGet([FromServices]IOptionsMonitor<DealerInfo> dealerOptions)<br \/>\n{<br \/>\nDealerInfoInstance = dealerOptions.CurrentValue;<br \/>\n}<\/p>\n<p>\u25a0Note For more information on the options pattern in aSp.net Core, consult the documentation: <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/configuration\/\"><a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/configuration\/\"><a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/configuration\/\">https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/configuration\/<\/a><\/a><\/a> options?view=aspnetcore-6.0.<\/p>\n<p>The HTTP Client Factory<br \/>\nASP.NET Core 2.1 introduced the IHTTPClientFactory, which can be used to create and configure HttPClient instances. The factory manages the pooling and lifetime of the underlying HttpClientMessageHandler instance, abstracting that away from the developer. It provides four mechanisms of use:<br \/>\n\u2022Basic usage<br \/>\n\u2022Named clients,<br \/>\n\u2022Typed clients, and<br \/>\n\u2022Generated clients.<br \/>\nAfter exploring the basic, named client, and typed client usages, we will build the API service wrappers that will be utilized by the data services built earlier in this chapter.<\/p>\n<p>\u25a0Note  For information on generating clients, consult the documentation: <a href=\"https:\/\/docs.microsoft\"><a href=\"https:\/\/docs.microsoft\"><a href=\"https:\/\/docs.microsoft\">https:\/\/docs.microsoft<\/a><\/a><\/a>. com\/en-us\/aspnet\/core\/fundamentals\/http-requests?view=aspnetcore-6.0.<\/p>\n<p>Basic Usage<br \/>\nThe basic usage registers the IHttpClientFactory with the services collection, and then uses the injected factory instance to create HttPClient instances. This method is a convenient way to refactor an existing application that is creating HttPClient instances. To implement this basic usage, add the following line into the top level statements in Program.cs (no need to actually do this, as the projects will use Typed clients):<\/p>\n<p>builder.Services.AddHttpClient();<\/p>\n<p>Then in the class that needs an HttpClient, inject the IHttpClientFactory into the constructor, and then call CreateClient():<br \/>\nnamespace AutoLot.Services.DataServices.Api.Examples; public class BasicUsageWithIHttpClientFactory<br \/>\n{<br \/>\nprivate readonly IHttpClientFactory _clientFactory;<br \/>\npublic BasicUsageWithIHttpClientFactory(IHttpClientFactory clientFactory)<br \/>\n{<br \/>\n_clientFactory = clientFactory;<br \/>\n}<br \/>\npublic async Task DoSomethingAsync()<br \/>\n{<br \/>\nvar client = _clientFactory.CreateClient();<br \/>\n\/\/do something interesting with the client<br \/>\n}<br \/>\n}<\/p>\n<p>Named Clients<br \/>\nNamed clients are useful when your application uses distinct HttpClient instances, especially when they are configured differently. When registering the IHttpClientFactory, a name is provided along with any specific configuration for that HttpClient usage. To create a client named AutoLotApi, add the following into the top level statements in Program.cs (no need to actually do this, as the projects will use Typed clients):<\/p>\n<p>using AutoLot.Services.DataServices.Api.Examples; builder.Services.AddHttpClient(NamedUsageWithIHttpClientFactory.API_NAME, client =&gt;<br \/>\n{<br \/>\n\/\/add any configuration here<br \/>\n});<\/p>\n<p>Then in the class that needs an HttpClient, inject the IHttpClientFactory into the constructor, and then call CreateClient() passing in the name of the client to be created. The configuration from the AddHttpClient() call is used to create the new instance:<br \/>\nnamespace AutoLot.Services.DataServices.Api.Examples; public class NamedUsageWithIHttpClientFactory<br \/>\n{<br \/>\npublic const string API_NAME = &quot;AutoLotApi&quot;; private readonly IHttpClientFactory _clientFactory;<br \/>\npublic NamedUsageWithIHttpClientFactory(IHttpClientFactory clientFactory)<br \/>\n{<br \/>\n_clientFactory = clientFactory;<br \/>\n}<br \/>\npublic async Task DoSomethingAsync()<br \/>\n{<br \/>\nvar client = _clientFactory.CreateClient(API_NAME);<br \/>\n\/\/do something interesting with the client<br \/>\n}<br \/>\n}<\/p>\n<p>Typed Clients<br \/>\nTyped clients are classes that accept an HttpClient instance through injection into its constructor. Since the typed client is a class, it can be added into the service collection and injected into other classes,<br \/>\nfully encapsulating the calls using an HttpClient. As an example, suppose you have a class named<br \/>\nApiServiceWrapper that takes in an HttpClient and implements IApiServiceWrapper as follows:<br \/>\nnamespace AutoLot.Services.DataServices.Api.Examples; public interface IApiServiceWrapperExample<br \/>\n{<br \/>\n\/\/interesting methods places here<br \/>\n}<\/p>\n<p>public class ApiServiceWrapperExample : IApiServiceWrapperExample<br \/>\n{<br \/>\nprotected readonly HttpClient Client;<br \/>\npublic ApiServiceWrapperExample(HttpClient client)<br \/>\n{<br \/>\nClient = client;<br \/>\n\/\/common client configuration goes here<br \/>\n}<br \/>\n\/\/interesting methods implemented here<br \/>\n}<\/p>\n<p>With the interface and the class in place, they can be added to the service collection as follows:<\/p>\n<p>builder.Services.AddHttpClient&lt;IApiServiceWrapperExample,ApiServiceWrapperExample&gt;();<\/p>\n<p>Inject the IApiServiceWrapper interface into the class that needs to make calls to the API and use the methods on the class instance to call to the API. This pattern completely abstracts the HttpClient away from the calling code:<br \/>\nnamespace AutoLot.Services.DataServices.Api.Examples; public class TypedUsageWithIHttpClientFactory<br \/>\n{<br \/>\nprivate readonly IApiServiceWrapperExample _serviceWrapper;<\/p>\n<p>public TypedUsageWithIHttpClientFactory(IApiServiceWrapperExample serviceWrapper)<br \/>\n{<br \/>\n_serviceWrapper = serviceWrapper;<br \/>\n}<br \/>\npublic async Task DoSomethingAsync()<br \/>\n{<br \/>\n\/\/do something interesting with the service wrapper<br \/>\n}<br \/>\n}<\/p>\n<p>In addition to the configuration options in the constructor of the class, the call to AddHttpClient() can also configure the client:<\/p>\n<p>builder.Services.AddHttpClient&lt;IApiServiceWrapperExample,ApiServiceWrapperExample&gt;(client=&gt;<br \/>\n{<br \/>\n\/\/configuration goes here<br \/>\n});<\/p>\n<p>The AutoLot API Service Wrapper<br \/>\nThe AutoLot API service wrapper uses a typed base client and entity specific typed clients to encapsulate all of the calls to the AutoLot.Api project. Both the AutoLot.Mvc and AutoLot.Web projects will use the service wrapper through the data services classes stubbed out earlier in this chapter and completed at the end of this section. The AutoLot.Api project will be finished in the next chapter.<\/p>\n<p>Update the Application Configuration<br \/>\nThe AutoLot.Api application endpoints will vary based on the environment. For example, when developing on your workstation, the base URI is <a href=\"https:\/\/localhost:5011\"><a href=\"https:\/\/localhost:5011\"><a href=\"https:\/\/localhost:5011\">https:\/\/localhost:5011<\/a><\/a><\/a>. In your integration environment, the URI might be <a href=\"https:\/\/mytestserver.com\"><a href=\"https:\/\/mytestserver.com\"><a href=\"https:\/\/mytestserver.com\">https:\/\/mytestserver.com<\/a><\/a><\/a>. The environmental awareness, in conjunction with the updated configuration system, will be used to add these different values.<br \/>\nThe appsettings.Development.json file will add the service information for the local machine. As code moves through different environments, the settings would be updated in each environment\u2019s specific file to match the base URI and endpoints for that environment. In this example, you only update the settings for the development environment. In the appsettings.Development.json files in the AutoLot.Mvc and AutoLot.Web projects, add the following after the ConnectionStrings entry (changes in bold):<\/p>\n<p>&quot;ConnectionStrings&quot;: {<br \/>\n&quot;AutoLot&quot;: &quot;Server=.,5433;Database=AutoLot;User ID=sa;Password=P@ssw0rd;&quot;<br \/>\n},<br \/>\n&quot;ApiServiceSettings&quot;: {<br \/>\n&quot;Uri&quot;: &quot;<a href=\"https:\/\/localhost:5011\/\"><a href=\"https:\/\/localhost:5011\/\"><a href=\"https:\/\/localhost:5011\/\">https:\/\/localhost:5011\/<\/a><\/a><\/a>&quot;, &quot;UserName&quot;: &quot;AutoLotUser&quot;, &quot;Password&quot;: &quot;SecretPassword&quot;, &quot;CarBaseUri&quot;: &quot;api\/v1\/Cars&quot;, &quot;MakeBaseUri&quot;: &quot;api\/v1\/Makes&quot;, &quot;MajorVersion&quot;: 1,<br \/>\n&quot;MinorVersion&quot;: 0, &quot;Status&quot;: &quot;&quot;<br \/>\n}<\/p>\n<p>\u25a0Note Make sure the port number matches your configuration for autoLot.api.<\/p>\n<p>Create the ApiServiceSettings Class<br \/>\nThe service settings will be populated from the settings the same way the dealer information was populated. In the AutoLot.Service project, create a new folder named ApiWrapper and in that folder, create a new folder named Models. In this folder, add a class named ApiServiceSettings.cs. The property names of the class need to match the property names in the JSON ApiServiceSettings section. The class is listed here:<br \/>\nnamespace AutoLot.Services.ApiWrapper.Models; public class ApiServiceSettings<br \/>\n{<br \/>\npublic ApiServiceSettings() { } public string UserName { get; set; } public string Password { get; set; } public string Uri { get; set; }<br \/>\npublic string CarBaseUri { get; set; } public string MakeBaseUri { get; set; } public int MajorVersion { get; set; } public int MinorVersion { get; set; } public string Status { get; set; }<\/p>\n<p>public string ApiVersion<br \/>\n=&gt; $&quot;{MajorVersion}.{MinorVersion}&quot; + (!string.IsNullOrEmpty(Status)?$&quot;-<br \/>\n{Status}&quot;:string.Empty);<br \/>\n}<\/p>\n<p>\u25a0Note api versioning will be covered in depth in Chapter 32.<\/p>\n<p>Add the following global using statement to the GlobalUsings.cs file in the AutoLot.Service project:<\/p>\n<p>global using AutoLot.Services.ApiWrapper.Models;<\/p>\n<p>Register the ApiServiceSettings Class<br \/>\nWe are once again going to use an extension method to register everything needed for the API service wrapper, including configuring the ApiServiceSettings.cs using the Options pattern. Create a new folder named Configuration under the ApiWrapper folder, and in that folder create a new public static class named ServiceConfiguration, as shown here:<br \/>\nnamespace AutoLot.Services.ApiWrapper.Configuration; public static class ServiceConfiguration<br \/>\n{<br \/>\npublic static IServiceCollection ConfigureApiServiceWrapper(this IServiceCollection services, IConfiguration config)<br \/>\n{<br \/>\nservices.Configure<ApiServiceSettings>(config.GetSection(nameof(ApiServiceSettings))); return services;<br \/>\n}<br \/>\n}<\/p>\n<p>Add the following global using statement to the GlobalUsings.cs file for both the AutoLot.Mvc and AutoLot.Web projects:<\/p>\n<p>global using AutoLot.Services.ApiWrapper.Configuration;<\/p>\n<p>Add the following to the top level statements in Program.cs (in both AutoLot.Mvc and AutoLot.Web) before the call to builder.Build():<\/p>\n<p>builder.Services.ConfigureApiServiceWrapper(builder.Configuration);<\/p>\n<p>The API Service Wrapper Base Class and Interface<br \/>\nThe ApiServiceWrapperBase class is a generic base class that performs create, read, update, and delete (CRUD) operations against the AutoLot.Api RESTful service. This centralizes communication with the service, configuration of the HTTP client, error handling, and so on. Entity specific classes will inherit from this base class, and those will be added into the services collection.<\/p>\n<p>The  IApiServiceWrapperBase  Interface<br \/>\nThe AutoLot service wrapper interface contains the common methods to call into the AutoLot.Api service. Create a directory named Interfaces in the ApiWrapper directory. Add a new interface named IApiServiceWrapper.cs and update the interface to the code shown here:<\/p>\n<p>namespace AutoLot.Services.ApiWrapper.Interfaces;<\/p>\n<p>public interface IApiServiceWrapperBase<TEntity> where TEntity : BaseEntity, new()<br \/>\n{<br \/>\nTask&lt;IList<TEntity>&gt; GetAllEntitiesAsync(); Task<TEntity> GetEntityAsync(int id); Task<TEntity> AddEntityAsync(TEntity entity); Task<TEntity> UpdateEntityAsync(TEntity entity); Task DeleteEntityAsync(TEntity entity);<br \/>\n}<\/p>\n<p>Add the following global using statement to the GlobalUsings.cs file for the AutoLot.Services project:<\/p>\n<p>global using AutoLot.Services.ApiWrapper.Interfaces;<\/p>\n<p>The  ApiServiceWrapperBase  Class<br \/>\nBefore building the base class, add the following global using statements to the GlobalUsings.cs file:<\/p>\n<p>global using AutoLot.Services.ApiWrapper.Base; global using Microsoft.Extensions.Options; global using System.Net.Http.Headers;<br \/>\nglobal using System.Net.Http.Json; global using System.Text;<br \/>\nglobal using System.Text.Json;<\/p>\n<p>Create a new folder named Base in the ApiWrapper directory of the AutoLot.Services project and add a class named ApiServiceWrapperBase. Make the class public and abstract and implement the<br \/>\nIApiServiceWrapperBase interface. Add a protected constructor that takes an instance of HttpClient and IOptionsMonitor<ApiServiceSettings> and a string for the entity specific endpoint. Add a protected readonly string to hold the ApiVersion from the settings and initialize them all from the constructor<br \/>\nlike this:<\/p>\n<p>namespace AutoLot.Services.ApiWrapper.Base;<\/p>\n<p>public abstract class ApiServiceWrapperBase<TEntity><br \/>\n: IApiServiceWrapperBase<TEntity> where TEntity : BaseEntity, new()<br \/>\n{<br \/>\nprotected readonly HttpClient Client; private readonly string _endPoint;<br \/>\nprotected readonly ApiServiceSettings ApiSettings; protected readonly string ApiVersion;<\/p>\n<p>protected ApiServiceWrapperBase(<br \/>\nHttpClient client, IOptionsMonitor<ApiServiceSettings> apiSettingsMonitor, string endPoint)<br \/>\n{<br \/>\nClient = client;<br \/>\n_endPoint = endPoint;<br \/>\nApiSettings = apiSettingsMonitor.CurrentValue; ApiVersion = ApiSettings.ApiVersion;<br \/>\n}<br \/>\n}<\/p>\n<p>Configuring the HttpClient in the Constructor<br \/>\nThe constructor adds the standard configuration to the HttpClient that will be used by all methods, including an AuthorizationHeader that uses basic authentication. Basic authentication will be covered in depth in the next chapter, Restful Services with ASP.NET Core, so for now just understand that basic authentication takes a username and password, concatenates them together (separated by a colon) and Base64 encodes them. The rest of the code sets the BaseAddress for the HttpClient and specifies that the client is expecting JSON.<\/p>\n<p>public ApiServiceWrapperBase(<br \/>\nHttpClient client, IOptionsMonitor<ApiServiceSettings> apiSettingsMonitor, string endPoint)<br \/>\n{<br \/>\nClient = client;<br \/>\n_endPoint = endPoint;<br \/>\nApiSettings = apiSettingsMonitor.CurrentValue; ApiVersion = ApiSettings.ApiVersion; Client.BaseAddress = new Uri(ApiSettings.Uri);<br \/>\nclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application\/ json&quot;));<br \/>\nvar authToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($&quot;{apiSettingsMonitor. CurrentValue.UserName}:{apiSettingsMonitor.CurrentValue.Password}&quot;)); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(&quot;basic&quot;, authToken);<br \/>\n}<\/p>\n<p>The Internal Support Methods<br \/>\nThe class contains four support methods that are used by the public methods.<\/p>\n<p>The Post and Put Helper Methods<br \/>\nThese methods wrap the related HttpClient methods.<\/p>\n<p>internal async Task<HttpResponseMessage> PostAsJsonAsync(string uri, string json)<br \/>\n{<br \/>\nreturn await Client.PostAsync(uri, new StringContent(json, Encoding.UTF8, &quot;application\/ json&quot;));<br \/>\n}<\/p>\n<p>internal async Task<HttpResponseMessage> PutAsJsonAsync(string uri, string json)<br \/>\n{<br \/>\nreturn await Client.PutAsync(uri, new StringContent(json, Encoding.UTF8, &quot;application\/ json&quot;));<br \/>\n}<\/p>\n<p>The HTTP Delete Helper Method Call<br \/>\nThe final helper method is used for executing an HTTP delete. The HTTP 1.1 specification (and later) allows for passing a body in a delete statement, but there isn\u2019t yet an extension method of the HttpClient for doing this. The HttpRequestMessage must be built up from scratch.<br \/>\nThe first step is to then create a request message using object initialization to set Content, Method, and RequestUri. Once this is complete, the message is sent, and the response is returned to the calling code. The method is shown here:<\/p>\n<p>internal async Task<HttpResponseMessage> DeleteAsJsonAsync(string uri, string json)<br \/>\n{<br \/>\nHttpRequestMessage request = new HttpRequestMessage<br \/>\n{<br \/>\nContent = new StringContent(json, Encoding.UTF8, &quot;application\/json&quot;), Method = HttpMethod.Delete,<br \/>\nRequestUri = new Uri(uri)<br \/>\n};<br \/>\nreturn await Client.SendAsync(request);<br \/>\n}<\/p>\n<p>The HTTP Get Calls<br \/>\nThere are two Get calls: one to get all records and one to get a single record. They both follow the same pattern. The GetAsync() method is called to return an HttpResponseMessage. The success or failure of the call is checked with the EnsureSuccessStatusCode() method, which throws an exception if the call did not return a successful status code. Then the body of the response is serialized back into the property type<br \/>\n(either as an entity or a list of entities) and returned to the calling code. The endpoint is constructed from the settings, and the ApiVersion is appended as a query string value. Each of these methods is shown here:<\/p>\n<p>public async Task&lt;IList<TEntity>&gt; GetAllEntitiesAsync()<br \/>\n{<br \/>\nvar response = await Client.GetAsync($&quot;{ApiSettings.Uri}{_endPoint}?v={ApiVersion}&quot;); response.EnsureSuccessStatusCode();<br \/>\nvar result = await response.Content.ReadFromJsonAsync&lt;IList<TEntity>&gt;(); return result;<br \/>\n}<\/p>\n<p>public async Task<TEntity> GetEntityAsync(int id)<br \/>\n{<br \/>\nvar response = await Client.GetAsync($&quot;{ApiSettings.Uri}{_endPoint}\/{id}?v={ApiVersion}&quot;); response.EnsureSuccessStatusCode();<br \/>\nvar result = await response.Content.ReadFromJsonAsync<TEntity>(); return result;<br \/>\n}<\/p>\n<p>Notice the improvement in serializing the response body into an item (or list of items). Prior versions of ASP.NET Core would have to use the following code instead of the shorter ReadFromJsonAsync() method:<\/p>\n<p>var result = await JsonSerializer.DeserializeAsync&lt;IList<TEntity>&gt;(await response.Content. ReadAsStreamAsync());<\/p>\n<p>The HTTP Post Call<br \/>\nThe method to add a record uses an HTTP Post request. It uses the helper method to post the entity as JSON and then returns the record from the response body. The method is listed here:<\/p>\n<p>public async Task<TEntity> AddEntityAsync(TEntity entity)<br \/>\n{<br \/>\nvar response = await PostAsJsonAsync($&quot;{ApiSettings.Uri}{_endPoint}?v={ApiVersion}&quot;, JsonSerializer.Serialize(entity));<br \/>\nif (response == null)<br \/>\n{<br \/>\nthrow new Exception(&quot;Unable to communicate with the service&quot;);<br \/>\n}<\/p>\n<p>var location = response.Headers?.Location?.OriginalString;<br \/>\nreturn await response.Content.ReadFromJsonAsync<TEntity>() ?? await GetEntityAsync(entity.Id);<br \/>\n}<\/p>\n<p>There are two lines of note. The first is the line getting the location. Typically, when an HTTP Post call is successful, the service returns an HTTP 201 (Created at) status code. The service will also add the URI of the newly created resource in the Location header. The preceding code demonstrates getting the Location header but isn\u2019t using the location in the code process.<br \/>\nThe second line of note is reading the response content and creating an instance of the updated record. The service wrapper method needs to get the updated instance from the service to guarantee server generated values (like Id and TimeStamp) and are updated in the client app. Returning the updated entity in the response from HTTP post calls is not a guaranteed function of a service. If the service doesn\u2019t return the entity, then the method uses the GetEntityAsync() method. It could also use the URI from the location if<br \/>\nnecessary, but since the GetEntityAsync() supplies everything needed, getting the location value is merely<br \/>\nfor demo purposes.<\/p>\n<p>The HTTP Put Call<br \/>\nThe method to update a record uses an HTTP Put request by way of the PutAsJsonAsync() helper method. This method also assumes that the updated entity is in the body of the response, and if it isn\u2019t, calls the GetEntityAsync() to refresh the server generated values.<\/p>\n<p>public async Task<TEntity> UpdateEntityAsync(TEntity entity)<br \/>\n{<br \/>\nvar response = await PutAsJsonAsync($&quot;{ApiSettings.Uri}{_endPoint}\/{entity. Id}?v={ApiVersion}&quot;,<br \/>\nJsonSerializer.Serialize(entity)); response.EnsureSuccessStatusCode();<br \/>\nreturn await response.Content.ReadFromJsonAsync<TEntity>() ?? await GetEntityAsync(entity.Id);<br \/>\n}<\/p>\n<p>The HTTP Delete Call<br \/>\nThe final method to add, is for executing an HTTP Delete. The pattern follows the rest of the methods: use the helper method and check the response for success. There isn\u2019t anything to return to the calling code since the entity was deleted. The method is shown here:<\/p>\n<p>public async Task DeleteEntityAsync(TEntity entity)<br \/>\n{<br \/>\nvar response = await DeleteAsJsonAsync($&quot;{ApiSettings.Uri}{_endPoint}\/{entity. Id}?v={ApiVersion}&quot;,<br \/>\nJsonSerializer.Serialize(entity)); response.EnsureSuccessStatusCode();<br \/>\n}<\/p>\n<p>The Entity Specific Interfaces<br \/>\nIn the Interfaces directory, create two new interfaces named ICarApiServiceWrapper.cs and<br \/>\nIMakeApiServiceWrapper.cs. Update the interfaces to the following:<\/p>\n<p>\/\/ICarApiServiceWrapper.cs<br \/>\nnamespace AutoLot.Services.ApiWrapper.Interfaces;<\/p>\n<p>public interface ICarApiServiceWrapper : IApiServiceWrapperBase<Car><br \/>\n{<br \/>\nTask&lt;IList<Car>&gt; GetCarsByMakeAsync(int id);<br \/>\n}<\/p>\n<p>\/\/IMakeApiServiceWrapper.cs<br \/>\nnamespace AutoLot.Services.ApiWrapper.Interfaces;<\/p>\n<p>public interface IMakeApiServiceWrapper : IApiServiceWrapperBase<Make><br \/>\n{<br \/>\n}<\/p>\n<p>The Entity Specific Classes<br \/>\nIn the ApiWrapper directory, create two new classes named CarApiServiceWrapper.cs and MakeApiServiceWrapper.cs. The constructor for each class takes an instance of HttpClient and IOptions<ApiServiceSettings> and passes them into the base class along with the entity specific end point:<\/p>\n<p>\/\/CarApiServiceWrapper.cs<br \/>\nnamespace AutoLot.Services.ApiWrapper;<\/p>\n<p>public class CarApiServiceWrapper : ApiServiceWrapperBase<Car>, ICarApiServiceWrapper<br \/>\n{<br \/>\npublic CarApiServiceWrapper(HttpClient client, IOptionsMonitor<ApiServiceSettings> apiSettingsMonitor)<br \/>\n: base(client, apiSettingsMonitor, apiSettingsMonitor.CurrentValue.CarBaseUri) { }<br \/>\n}<\/p>\n<p>\/\/MakeApiServiceWrapper.cs<br \/>\nnamespace AutoLot.Services.ApiWrapper;<\/p>\n<p>public class MakeApiServiceWrapper : ApiServiceWrapperBase<Make>, IMakeApiServiceWrapper<br \/>\n{<br \/>\npublic MakeApiServiceWrapper(HttpClient client, IOptionsMonitor<ApiServiceSettings> apiSettingsMonitor)<br \/>\n: base(client, apiSettingsMonitor, apiSettingsMonitor.CurrentValue.MakeBaseUri) { }<br \/>\n}<\/p>\n<p>The MakeApiServiceWrapper only needs the methods exposed in the ApiServiceWrapperBase to do its job. The CarApiServiceWrapper class has one additional method to get the list of Car records by Make. The method follows the same pattern as the base class\u2019s HTTP Get methods but uses a unique end point:<\/p>\n<p>public async Task&lt;IList<Car>&gt; GetCarsByMakeAsync(int id)<br \/>\n{<br \/>\nvar response = await Client.GetAsync($&quot;{ApiSettings.Uri}{ApiSettings.CarBaseUri}\/bymake\/<br \/>\n{id}?v={ApiVersion}&quot;); response.EnsureSuccessStatusCode();<br \/>\nvar result = await response.Content.ReadFromJsonAsync&lt;IList<Car>&gt;(); return result;<br \/>\n}<\/p>\n<p>As a last step, register the two typed clients by updating the ConfigureApiServiceWrapper method in the ServiceConfiguration class to the following:<\/p>\n<p>public static IServiceCollection ConfigureApiServiceWrapper(this IServiceCollection services, IConfiguration config)<br \/>\n{<br \/>\nservices.Configure<ApiServiceSettings>(config.GetSection(nameof(ApiServiceSettings))); services.AddHttpClient&lt;ICarApiServiceWrapper, CarApiServiceWrapper&gt;(); services.AddHttpClient&lt;IMakeApiServiceWrapper, MakeApiServiceWrapper&gt;();<br \/>\nreturn services;<br \/>\n}<\/p>\n<p>Complete the API Data Services<br \/>\nNow that the API service wrappers are complete, it\u2019s time to revisit the API data services and complete the class implementations.<\/p>\n<p>Complete the ApiDataServiceBase Class<br \/>\nThe first step to complete the base class is to update the constructor to receive an instance of the IApiServic eWrapperBase<TEntity> interface and assign it to a protected field:<\/p>\n<p>protected readonly IApiServiceWrapperBase<TEntity> ServiceWrapper;<br \/>\nprotected ApiDataServiceBase(IApiServiceWrapperBase<TEntity>  serviceWrapperBase)<br \/>\n{<br \/>\nServiceWrapper = serviceWrapperBase;<br \/>\n}<\/p>\n<p>The implementation for each of the CRUD methods calls the related method on the ServiceWrapper:<\/p>\n<p>public async Task&lt;IEnumerable<TEntity>&gt; GetAllAsync()<br \/>\n=&gt; await ServiceWrapper.GetAllEntitiesAsync();<br \/>\npublic async Task<TEntity> FindAsync(int id)<br \/>\n=&gt; await ServiceWrapper.GetEntityAsync(id);<br \/>\npublic async Task<TEntity> UpdateAsync(TEntity entity, bool persist = true)<br \/>\n{<br \/>\nawait ServiceWrapper.UpdateEntityAsync(entity); return entity;<br \/>\n}<\/p>\n<p>public async Task DeleteAsync(TEntity entity, bool persist = true)<br \/>\n=&gt; await ServiceWrapper.DeleteEntityAsync(entity);<\/p>\n<p>public async Task<TEntity> AddAsync(TEntity entity, bool persist = true)<br \/>\n{<br \/>\nawait ServiceWrapper.AddEntityAsync(entity); return entity;<br \/>\n}<\/p>\n<p>Complete the Entity Specific Classes<br \/>\nThe CarApiDataService and MakeApiDataService classes need their constructors updated to receive their entity specific derived instance of the IApiServiceWrapperBase<TEntity> interface and pass it to the<br \/>\nbase class:<\/p>\n<p>public class CarApiDataService : ApiDataServiceBase<Car>, ICarDataService<br \/>\n{<br \/>\npublic CarApiDataService(ICarApiServiceWrapper serviceWrapper) : base(serviceWrapper) { }<br \/>\n}<\/p>\n<p>public class MakeApiDataService : ApiDataServiceBase<Make>, IMakeDataService<br \/>\n{<br \/>\npublic MakeApiDataService(IMakeApiServiceWrapper serviceWrapper):base(serviceWrapper) { }<br \/>\n}<\/p>\n<p>The GetAllByMakeIdIdAsync() method determines if a value was passed in for the makeId parameter.<br \/>\nIf there is a value, the relevant method on the ICarApiServiceWrapper is called. Otherwise, the base<br \/>\nGetAllAsync() is called:<\/p>\n<p>public async Task&lt;IEnumerable<Car>&gt; GetAllByMakeIdAsync(int? makeId)<br \/>\n=&gt; makeId.HasValue<br \/>\n? await ((ICarApiServiceWrapper)ServiceWrapper). GetCarsByMakeAsync(makeId.Value)<br \/>\n: await GetAllAsync();<\/p>\n<p>Deploying ASP.NET Core Applications<br \/>\nPrior versions of ASP.NET applications could only be deployed to Windows servers using IIS as the web server. ASP.NET Core can be deployed to multiple operating systems in multiple ways, using a variety of web servers. ASP.NET Core applications can also be deployed outside of a web server. The high-level options are as follows:<br \/>\n\u2022On a Windows server (including Azure) using IIS<br \/>\n\u2022On a Windows server (including Azure app services) outside of IIS<br \/>\n\u2022On a Linux server using Apache or NGINX<br \/>\n\u2022On Windows or Linux in a container<br \/>\nThis flexibility allows organizations to decide the deployment platform that makes the most sense for them, including popular container-based deployment models (such as using Docker), as opposed to being locked into Windows servers.<\/p>\n<p>Lightweight and Modular HTTP Request Pipeline<br \/>\nFollowing along with the principles of .NET, you must opt in for everything in ASP.NET Core. By default, nothing is loaded into an application. This enables applications to be as lightweight as possible, improving performance, and minimizing the surface area and potential risk.<\/p>\n<p>Logging<br \/>\nLogging in ASP.NET Core is based on an ILoggerFactory. This enables different logging providers to hook into the logging system to send log messages to different locations, such as the Console. The ILoggerFactory is used to create an instance of ILogger<T>, which provides the following methods for logging by way of the LoggerExtensions class:<\/p>\n<p>public static class LoggerExtensions<br \/>\n{<br \/>\npublic static void LogDebug(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)<br \/>\npublic static void LogDebug(this ILogger logger, EventId eventId, string message, params object[] args)<br \/>\npublic static void LogDebug(this ILogger logger, Exception exception, string message, params object[] args)<br \/>\npublic static void LogDebug(this ILogger logger, string message, params object[] args)<\/p>\n<p>public static void LogTrace(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)<br \/>\npublic static void LogTrace(this ILogger logger, EventId eventId, string message, params object[] args)<br \/>\npublic static void LogTrace(this ILogger logger, Exception exception, string message, params object[] args)<br \/>\npublic static void LogTrace(this ILogger logger, string message, params object[] args)<\/p>\n<p>public static void LogInformation(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)<br \/>\npublic static void LogInformation(this ILogger logger, EventId eventId, string message, params object[] args)<br \/>\npublic static void LogInformation(this ILogger logger, Exception exception, string message, params object[] args)<br \/>\npublic static void LogInformation(this ILogger logger, string message, params object[] args)<\/p>\n<p>public static void LogWarning(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)<br \/>\npublic static void LogWarning(this ILogger logger, EventId eventId, string message, params object[] args)<br \/>\npublic static void LogWarning(this ILogger logger, Exception exception, string message, params object[] args)<br \/>\npublic static void LogWarning(this ILogger logger, string message, params object[] args)<\/p>\n<p>public static void LogError(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)<br \/>\npublic static void LogError(this ILogger logger, EventId eventId, string message, params object[] args)<br \/>\npublic static void LogError(this ILogger logger, Exception exception, string message, params object[] args)<br \/>\npublic static void LogError(this ILogger logger, string message, params object[] args)<\/p>\n<p>public static void LogCritical(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args)<br \/>\npublic static void LogCritical(this ILogger logger, EventId eventId, string message, params object[] args)<br \/>\npublic static void LogCritical(this ILogger logger, Exception exception, string message, params object[] args)<br \/>\npublic static void LogCritical(this ILogger logger, string message, params object[] args)<\/p>\n<p>public static void Log(this ILogger logger, LogLevel logLevel, string message, params object[] args)<br \/>\npublic static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, string message, params object[] args)<br \/>\npublic static void Log(this ILogger logger, LogLevel logLevel, Exception exception, string message, params object[] args)<br \/>\npublic static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, string message, params object[] args)<br \/>\n}<\/p>\n<p>Add Logging with Serilog<br \/>\nAny provider that supplies an ILoggerFactory extension can be used for logging in ASP.NET Core, and Serilog is one such logging framework. The next sections cover creating a logging infrastructure based on Serilog and configuring the ASP.NET Core applications to use the new logging code.<\/p>\n<p>The Logging Settings<br \/>\nTo configure Serilog, we are going to use the application configuration files in conjunction with a C# class. Start by adding a new folder named Logging to the AutoLot.Service project. Create a new folder named Settings in the Logging folder, and in that new folder, add a class named AppLoggingSettings. Update the code to the following:<br \/>\nnamespace AutoLot.Services.Logging.Settings; public class AppLoggingSettings<br \/>\n{<br \/>\npublic GeneralSettings General { get; set; } public FileSettings File { get; set; }<br \/>\npublic SqlServerSettings MSSqlServer { get; set; }<\/p>\n<p>public class GeneralSettings<br \/>\n{<br \/>\npublic string RestrictedToMinimumLevel { get; set; }<br \/>\n}<br \/>\npublic class SqlServerSettings<br \/>\n{<br \/>\npublic string TableName { get; set; } public string Schema { get; set; }<br \/>\npublic string ConnectionStringName { get; set; }<br \/>\n}<\/p>\n<p>public class FileSettings<br \/>\n{<br \/>\npublic string Drive { get; set; } public string FilePath { get; set; } public string FileName { get; set; } public string FullLogPathAndFileName =&gt;<\/p>\n<p>$&quot;{Drive}{Path.VolumeSeparatorChar}{Path.DirectorySeparatorChar}{FilePath}{Path. DirectorySeparatorChar}{FileName}&quot;;<br \/>\n}<br \/>\n}<\/p>\n<p>Add the following global using statement to the GlobalUsings.cs file in the AutoLot.Service project.<\/p>\n<p>global using AutoLot.Services.Logging.Settings;<\/p>\n<p>Next, use the following JSON to replace the scaffolded Logging details in the appsettings.<br \/>\nDevelopment.json files for the AutoLot.Api, AutoLot.Mvc, and AutoLot.Web projects:<\/p>\n<p>&quot;AppLoggingSettings&quot;: { &quot;MSSqlServer&quot;: {<br \/>\n&quot;TableName&quot;: &quot;SeriLogs&quot;, &quot;Schema&quot;: &quot;Logging&quot;, &quot;ConnectionStringName&quot;: &quot;AutoLot&quot;<br \/>\n},<\/p>\n<p>&quot;File&quot;: { &quot;Drive&quot;: &quot;c&quot;,<br \/>\n&quot;FilePath&quot;: &quot;temp&quot;, &quot;FileName&quot;: &quot;log_AutoLot.txt&quot;<br \/>\n},<br \/>\n&quot;General&quot;: {<br \/>\n&quot;RestrictedToMinimumLevel&quot;: &quot;Information&quot;<br \/>\n}<br \/>\n},<\/p>\n<p>Next, add the following AppName node to each of the files, customized for each app:<\/p>\n<p>\/\/AutoLot.Api &quot;AppName&quot;:&quot;AutoLot.Api - Dev&quot;<\/p>\n<p>\/\/AutoLot.Mvc &quot;AppName&quot;:&quot;AutoLot.Mvc - Dev&quot;<\/p>\n<p>\/\/AutoLot.Web &quot;AppName&quot;:&quot;AutoLot.Web - Dev&quot;<\/p>\n<p>For reference, here is the complete listing for each project (notice the extra line at the beginning of the AutoLot.Web file \u2013 that will be covered in chapter 34):<\/p>\n<p>\/\/AutoLot.Api<br \/>\n{<br \/>\n&quot;AppLoggingSettings&quot;: { &quot;MSSqlServer&quot;: {<br \/>\n&quot;TableName&quot;: &quot;SeriLogs&quot;, &quot;Schema&quot;: &quot;Logging&quot;, &quot;ConnectionStringName&quot;: &quot;AutoLot&quot;<br \/>\n},<br \/>\n&quot;File&quot;: { &quot;Drive&quot;: &quot;c&quot;,<br \/>\n&quot;FilePath&quot;: &quot;temp&quot;, &quot;FileName&quot;: &quot;log_AutoLot.txt&quot;<br \/>\n},<br \/>\n&quot;General&quot;: {<br \/>\n&quot;RestrictedToMinimumLevel&quot;: &quot;Information&quot;<br \/>\n}<br \/>\n},<br \/>\n&quot;ConnectionStrings&quot;: {<br \/>\n&quot;AutoLot&quot;: &quot;Server=.,5433;Database=AutoLot;User ID=sa;Password=P@ssw0rd;&quot;<br \/>\n},<br \/>\n&quot;RebuildDatabase&quot;: true, &quot;AppName&quot;: &quot;AutoLot.Api - Dev&quot;<br \/>\n}<\/p>\n<p>\/\/AutoLot.Mvc<br \/>\n{<br \/>\n&quot;AppLoggingSettings&quot;: { &quot;MSSqlServer&quot;: {<\/p>\n<p>&quot;TableName&quot;: &quot;SeriLogs&quot;, &quot;Schema&quot;: &quot;Logging&quot;, &quot;ConnectionStringName&quot;: &quot;AutoLot&quot;<br \/>\n},<br \/>\n&quot;File&quot;: { &quot;Drive&quot;: &quot;c&quot;,<br \/>\n&quot;FilePath&quot;: &quot;temp&quot;, &quot;FileName&quot;: &quot;log_AutoLot.txt&quot;<br \/>\n},<br \/>\n&quot;General&quot;: {<br \/>\n&quot;RestrictedToMinimumLevel&quot;: &quot;Information&quot;<br \/>\n}<br \/>\n},<br \/>\n&quot;ConnectionStrings&quot;: {<br \/>\n&quot;AutoLot&quot;: &quot;Server=.,5433;Database=AutoLot;User ID=sa;Password=P@ssw0rd;&quot;<br \/>\n},<br \/>\n&quot;RebuildDatabase&quot;: true, &quot;UseApi&quot;: false, &quot;ApiServiceSettings&quot;: {<br \/>\n&quot;Uri&quot;: &quot;<a href=\"https:\/\/localhost:5011\/\"><a href=\"https:\/\/localhost:5011\/\"><a href=\"https:\/\/localhost:5011\/\">https:\/\/localhost:5011\/<\/a><\/a><\/a>&quot;, &quot;UserName&quot;: &quot;AutoLotUser&quot;, &quot;Password&quot;: &quot;SecretPassword&quot;, &quot;CarBaseUri&quot;: &quot;api\/v1\/Cars&quot;, &quot;MakeBaseUri&quot;: &quot;api\/v1\/Makes&quot;<br \/>\n},<br \/>\n&quot;AppName&quot;: &quot;AutoLot.Mvc - Dev&quot;<br \/>\n}<\/p>\n<p>\/\/AutoLot.Web<br \/>\n{<br \/>\n&quot;DetailedErrors&quot;: true, &quot;AppLoggingSettings&quot;: {<br \/>\n&quot;MSSqlServer&quot;: { &quot;TableName&quot;: &quot;SeriLogs&quot;, &quot;Schema&quot;: &quot;Logging&quot;,<br \/>\n&quot;ConnectionStringName&quot;: &quot;AutoLot&quot;<br \/>\n},<br \/>\n&quot;File&quot;: { &quot;Drive&quot;: &quot;c&quot;,<br \/>\n&quot;FilePath&quot;: &quot;temp&quot;, &quot;FileName&quot;: &quot;log_AutoLot.txt&quot;<br \/>\n},<br \/>\n&quot;General&quot;: {<br \/>\n&quot;RestrictedToMinimumLevel&quot;: &quot;Information&quot;<br \/>\n}<br \/>\n},<br \/>\n&quot;ConnectionStrings&quot;: {<br \/>\n&quot;AutoLot&quot;: &quot;Server=.,5433;Database=AutoLot;User ID=sa;Password=P@ssw0rd;&quot;<br \/>\n},<br \/>\n&quot;RebuildDatabase&quot;: true, &quot;UseApi&quot;: false,<\/p>\n<p>&quot;ApiServiceSettings&quot;: {<br \/>\n&quot;Uri&quot;: &quot;<a href=\"https:\/\/localhost:5011\/\"><a href=\"https:\/\/localhost:5011\/\"><a href=\"https:\/\/localhost:5011\/\">https:\/\/localhost:5011\/<\/a><\/a><\/a>&quot;, &quot;UserName&quot;: &quot;AutoLotUser&quot;, &quot;Password&quot;: &quot;SecretPassword&quot;, &quot;CarBaseUri&quot;: &quot;api\/v1\/Cars&quot;, &quot;MakeBaseUri&quot;: &quot;api\/v1\/Makes&quot;<br \/>\n},<br \/>\n&quot;AppName&quot;: &quot;AutoLot.Web - Dev&quot;<br \/>\n}<\/p>\n<p>The final step is to clear out the Logging section of each of the appsettings.json files, leaving just the AllowedHosts entry in the AutoLot.Api project, and the AllowedHosts and the DealerInfo in the AutoLot. Mvc and AutoLot.Web projects:<\/p>\n<p>\/\/AutoLot.Api<br \/>\n{<br \/>\n&quot;AllowedHosts&quot;: &quot;*&quot;<br \/>\n}<\/p>\n<p>\/\/AutoLot.Mvc<br \/>\n{<br \/>\n&quot;AllowedHosts&quot;: &quot;*&quot;, &quot;DealerInfo&quot;: {<br \/>\n&quot;DealerName&quot;: &quot;Skimedic's Used Cars&quot;, &quot;City&quot;: &quot;West Chester&quot;,<br \/>\n&quot;State&quot;: &quot;Ohio&quot;<br \/>\n}<br \/>\n}<\/p>\n<p>\/\/AutoLot.Web<br \/>\n{<br \/>\n&quot;AllowedHosts&quot;: &quot;*&quot;, &quot;DealerInfo&quot;: {<br \/>\n&quot;DealerName&quot;: &quot;Skimedic's Used Cars&quot;, &quot;City&quot;: &quot;West Chester&quot;,<br \/>\n&quot;State&quot;: &quot;Ohio&quot;<br \/>\n}<br \/>\n}<\/p>\n<p>The Logging Configuration<br \/>\nThe next step is to configure Serilog. Get started by adding a new folder named Configuration in the Logging folder of the AutoLot.Service project. In this folder add a new class named LoggingConfiguration. Make the class public and static, as shown here:<br \/>\nnamespace AutoLot.Services.Logging.Configuration; public static class LoggingConfiguration<br \/>\n{<br \/>\n}<\/p>\n<p>Serilog uses sinks to write to different logging targets. With this mechanism, a single call to SeriLog will write to many places. The targets we will use for logging in the ASP.NET Core apps are a text file, the<br \/>\ndatabase, and the console. The text file and database sinks require configuration, an output template for the text file sink, and a list of fields for the database sink.<br \/>\nTo set up the file template, create the following static readonly string:<\/p>\n<p>internal static readonly string OutputTemplate =<br \/>\n@&quot;[{Timestamp:yy-MM-dd HH:mm:ss} {Level}]{ApplicationName}:{SourceContext}{NewLine} Message:{Message}{NewLine}in method {MemberName} at {FilePath}:{LineNumber}{NewLine}<br \/>\n{Exception}{NewLine}&quot;;<\/p>\n<p>The SQL Server sink needs a list of columns identified using the SqlColumn type. Add the following code to configure the database columns:<\/p>\n<p>internal static readonly ColumnOptions ColumnOptions = new ColumnOptions<br \/>\n{<br \/>\nAdditionalColumns = new List<SqlColumn><br \/>\n{<br \/>\nnew SqlColumn {DataType = SqlDbType.VarChar, ColumnName = &quot;ApplicationName&quot;}, new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = &quot;MachineName&quot;},<br \/>\nnew SqlColumn {DataType = SqlDbType.VarChar, ColumnName = &quot;MemberName&quot;}, new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = &quot;FilePath&quot;}, new SqlColumn {DataType = SqlDbType.Int, ColumnName = &quot;LineNumber&quot;},<br \/>\nnew SqlColumn {DataType = SqlDbType.VarChar, ColumnName = &quot;SourceContext&quot;}, new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = &quot;RequestPath&quot;}, new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = &quot;ActionName&quot;},<br \/>\n}<br \/>\n};<\/p>\n<p>Swapping the default logger with Serilog is a three-step process. The first is to clear the existing provider, the second is to add Serilog into the WebApplicationBuilder, and the third is to finish configuring Serilog. Add a new method named ConfigureSerilog(), which is an extension method for the WebApplicationBuilder. The first line clears out the default loggers, and the last line adds the fully configured Serilog framework into the WebApplicationBuilder\u2018s logging framework.<\/p>\n<p>public static void ConfigureSerilog(this WebApplicationBuilder builder)<br \/>\n{<br \/>\nbuilder.Logging.ClearProviders(); var config = builder.Configuration;<br \/>\nvar settings = config.GetSection(nameof(AppLoggingSettings)).Get<AppLoggingSettings>(); var connectionStringName = settings.MSSqlServer.ConnectionStringName;<br \/>\nvar connectionString = config.GetConnectionString(connectionStringName); var tableName = settings.MSSqlServer.TableName;<br \/>\nvar schema = settings.MSSqlServer.Schema;<br \/>\nstring restrictedToMinimumLevel = settings.General.RestrictedToMinimumLevel; if (!Enum.TryParse<LogEventLevel>(restrictedToMinimumLevel, out var logLevel))<br \/>\n{<br \/>\nlogLevel = LogEventLevel.Debug;<br \/>\n}<br \/>\nvar sqlOptions = new MSSqlServerSinkOptions<br \/>\n{<br \/>\nAutoCreateSqlTable = false,<\/p>\n<p>SchemaName = schema, TableName = tableName,<br \/>\n};<br \/>\nif (builder.Environment.IsDevelopment())<br \/>\n{<br \/>\nsqlOptions.BatchPeriod = new TimeSpan(0, 0, 0, 1);<br \/>\nsqlOptions.BatchPostingLimit = 1;<br \/>\n}<\/p>\n<p>var log = new LoggerConfiguration()<br \/>\n.MinimumLevel.Is(logLevel)<br \/>\n.Enrich.FromLogContext()<br \/>\n.Enrich.With(new PropertyEnricher(&quot;ApplicationName&quot;, config.GetValue<string>(&quot;Applicati onName&quot;)))<br \/>\n.Enrich.WithMachineName()<br \/>\n.WriteTo.File(<br \/>\npath: builder.Environment.IsDevelopment()? settings.File.FileName : settings.File. FullLogPathAndFileName,<br \/>\nrollingInterval: RollingInterval.Day, restrictedToMinimumLevel: logLevel, outputTemplate: OutputTemplate)<br \/>\n.WriteTo.Console(restrictedToMinimumLevel: logLevel)<br \/>\n.WriteTo.MSSqlServer( connectionString: connectionString, sqlOptions, restrictedToMinimumLevel: logLevel, columnOptions: ColumnOptions);<br \/>\nbuilder.Logging.AddSerilog(log.CreateLogger(),false);<br \/>\n}<\/p>\n<p>With everything in place, it\u2019s time to create the logging framework that will use Serilog.<\/p>\n<p>The AutoLot Logging Framework<br \/>\nThe AutoLot logging framework leverages the built-in logging capabilities of ASP.NET Core to simplify using Serilog. It starts with the IAppLogging interface.<\/p>\n<p>The IAppLogging Interface<br \/>\nThe IApplogging<T> interface holds the logging methods for the custom logging system. Add new directory named Interfaces in the Logging directory in the AutoLot.Service project. In this directory, add a new interface named IAppLogging<T>. Update the code in this interface to match the following:<br \/>\nnamespace AutoLot.Services.Logging.Interfaces; public interface IAppLogging<T><br \/>\n{<br \/>\nvoid LogAppError(Exception exception, string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<\/p>\n<p>void LogAppError(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<\/p>\n<p>void LogAppCritical(Exception exception, string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<\/p>\n<p>void LogAppCritical(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<\/p>\n<p>void LogAppDebug(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<\/p>\n<p>void LogAppTrace(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<\/p>\n<p>void LogAppInformation(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<\/p>\n<p>void LogAppWarning(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0);<br \/>\n}<\/p>\n<p>The attributes CallerMemberName, CallerFilePath, and CallerLineNumber inspect the call stack to get the values they are named for from the calling code. For example, if the line that calls LogAppWarning() is in the DoWork() function, in a file named MyClassFile.cs, and resides on line number 36, then the call:<\/p>\n<p>_appLogger.LogAppError(ex, &quot;ERROR!&quot;);<\/p>\n<p>is converted into the equivalent of this:<\/p>\n<p>_appLogger.LogAppError(ex,&quot;ERROR&quot;,&quot;DoWork &quot;,&quot;c:\/myfilepath\/MyClassFile.cs&quot;,36);<\/p>\n<p>If values are passed into the method call for any of the attributed parameters, the values passed in are used instead of the values from the attributes.<br \/>\nAdd the following global using statement to the GlobalUsings.cs file in the AutoLot.Services project:<\/p>\n<p>global using AutoLot.Services.Logging.Interfaces;<\/p>\n<p>The AppLogging Class<br \/>\nThe AppLogging class implements the IAppLogging interface. Add a new class named AppLogging to the Logging directory. Make the class public and implement IAppLogging<T> and add a constructor that takes an instance of ILogger<T> and stores it in a class level variable.<\/p>\n<p>namespace AutoLot.Services.Logging;<\/p>\n<p>public class AppLogging<T> : IAppLogging<T><br \/>\n{<br \/>\nprivate readonly ILogger<T> _logger;<\/p>\n<p>public AppLogging(ILogger<T> logger)<br \/>\n{<br \/>\n_logger = logger;<br \/>\n}<br \/>\n}<\/p>\n<p>Serilog enables adding additional properties into the standard logging process by pushing them onto the LogContext. Add an internal method to log an event with an exception and push the MemberName, FilePath, and LineNumber properties. The PushProperty() method returns an IDisposable, so the method disposes everything before leaving the method.<\/p>\n<p>internal static void LogWithException(string memberName,<br \/>\nstring sourceFilePath, int sourceLineNumber, Exception ex, string message, Action&lt;Exception, string, object[]&gt; logAction)<br \/>\n{<br \/>\nvar list = new List<IDisposable><br \/>\n{<br \/>\nLogContext.PushProperty(&quot;MemberName&quot;, memberName), LogContext.PushProperty(&quot;FilePath&quot;, sourceFilePath), LogContext.PushProperty(&quot;LineNumber&quot;, sourceLineNumber),<br \/>\n};<br \/>\nlogAction(ex,message,null); foreach (var item in list)<br \/>\n{<br \/>\nitem.Dispose();<br \/>\n}<br \/>\n}<\/p>\n<p>Repeat the process for log events that don\u2019t include an exception:<\/p>\n<p>internal static void LogWithoutException(string memberName, string sourceFilePath, int sourceLineNumber, string message, Action&lt;string, object[]&gt; logAction)<br \/>\n{<br \/>\nvar list = new List<IDisposable><br \/>\n{<br \/>\nLogContext.PushProperty(&quot;MemberName&quot;, memberName), LogContext.PushProperty(&quot;FilePath&quot;, sourceFilePath), LogContext.PushProperty(&quot;LineNumber&quot;, sourceLineNumber),<br \/>\n};<\/p>\n<p>logAction(message, null); foreach (var item in list)<br \/>\n{<br \/>\nitem.Dispose();<br \/>\n}<br \/>\n}<\/p>\n<p>For each type of logging event, call the appropriate help method to write to the logs:<\/p>\n<p>public void LogAppError(Exception exception, string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithException(memberName, sourceFilePath, sourceLineNumber, exception, message, _ logger.LogError);<br \/>\n}<\/p>\n<p>public void LogAppError(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithoutException(memberName, sourceFilePath, sourceLineNumber, message, _logger. LogError);<br \/>\n}<\/p>\n<p>public void LogAppCritical(Exception exception, string message, [CallerMemberName] string memberName = &quot;&quot;,<br \/>\n[CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithException(memberName, sourceFilePath, sourceLineNumber, exception, message, _ logger.LogCritical);<br \/>\n}<\/p>\n<p>public void LogAppCritical(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithoutException(memberName, sourceFilePath, sourceLineNumber, message, _logger. LogCritical);<br \/>\n}<\/p>\n<p>public void LogAppDebug(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithoutException(memberName, sourceFilePath, sourceLineNumber, message, _logger.LogDebug);<br \/>\n}<\/p>\n<p>public void LogAppTrace(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithoutException(memberName, sourceFilePath, sourceLineNumber, message, _logger. LogTrace);<br \/>\n}<\/p>\n<p>public void LogAppInformation(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithoutException(memberName, sourceFilePath, sourceLineNumber, message, _logger. LogInformation);<br \/>\n}<\/p>\n<p>public void LogAppWarning(string message, [CallerMemberName] string memberName = &quot;&quot;, [CallerFilePath] string sourceFilePath = &quot;&quot;, [CallerLineNumber] int sourceLineNumber = 0)<br \/>\n{<br \/>\nLogWithoutException(memberName, sourceFilePath, sourceLineNumber, message, _logger. LogWarning);<br \/>\n}<\/p>\n<p>Final Configuration<br \/>\nThe final configuration is to add the IAppLogging&lt;&gt; interface into the DI container and call the extension method to add SeriLog into the WebApplicationBuilder. Start by creating a new extension method in the LoggingConfiguration class:<\/p>\n<p>public static IServiceCollection RegisterLoggingInterfaces(this IServiceCollection services)<br \/>\n{<br \/>\nservices.AddScoped(typeof(IAppLogging&lt;&gt;), typeof(AppLogging&lt;&gt;)); return services;<br \/>\n}<\/p>\n<p>Next, add the following global using statement to the GlobalUsings.cs file in each web project:<\/p>\n<p>global using AutoLot.Services.Logging.Configuration; global using AutoLot.Services.Logging.Interfaces;<\/p>\n<p>Next, add both of the extension methods to the top level statements in the Program.cs file. Note that the ConfigureSerilog() method extends off of the WebAppBuilder (the builder variable) and the RegisterLoggingInterfaces() method extends off of the IServiceCollection:<\/p>\n<p>\/\/Configure logging builder.ConfigureSerilog(); builder.Services.RegisterLoggingInterfaces();<\/p>\n<p>Add Logging to the Data Services<br \/>\nWith Serilog and the AutoLot logging system in place, it\u2019s time to update the data services to add logging capabilities.<\/p>\n<p>Update the Base Classes<br \/>\nStarting with the ApiDataServiceBase and DalDataServiceBase classes, update the generic definition to also accept a class that implements IDataServiceBase<TEntity>. This is to allow for strongly typing the IAppLogging<TDataService> interface to each of the derived class. Here are the updated classes definitions:<\/p>\n<p>\/\/ApiDataServiceBase.cs<br \/>\npublic abstract class ApiDataServiceBase&lt;TEntity, TDataService&gt; : IDataServiceBase<TEntity> where TEntity : BaseEntity, new()<br \/>\nwhere TDataService : IDataServiceBase<TEntity><br \/>\n{<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>\/\/DalDataServiceBase.cs<br \/>\npublic abstract class DalDataServiceBase&lt;TEntity, TDataService&gt; : IDataServiceBase<TEntity> where TEntity : BaseEntity, new()<br \/>\nwhere TDataService : IDataServiceBase<TEntity><br \/>\n{<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>Next, update each of the constructors to take an instance of IAppLogging<TDataService> and assign it to a protected class field:<\/p>\n<p>\/\/CarApiDataService.cs<br \/>\nprotected readonly IApiServiceWrapperBase<TEntity> ServiceWrapper;<br \/>\nprotected readonly IAppLogging<TDataService> AppLoggingInstance;<\/p>\n<p>protected ApiDataServiceBase(IAppLogging<TDataService>  appLogging,<br \/>\nIApiServiceWrapperBase<TEntity> serviceWrapperBase)<br \/>\n{<br \/>\nServiceWrapper = serviceWrapperBase;<br \/>\nAppLoggingInstance = appLogging;<br \/>\n}<\/p>\n<p>\/\/MakeApiDataService.cs<br \/>\nprotected readonly IBaseRepo<TEntity> MainRepo;<br \/>\nprotected readonly IAppLogging<TDataService> AppLoggingInstance;<\/p>\n<p>protected DalDataServiceBase(IAppLogging<TDataService> appLogging, IBaseRepo<TEntity> mainRepo)<br \/>\n{<br \/>\nMainRepo = mainRepo;<br \/>\nAppLoggingInstance = appLogging;<br \/>\n}<\/p>\n<p>Update the Entity Specific Data Services Classes<br \/>\nEach of the entity specific classes need to change their inheritance signature to use the new generic parameter and also take an instance of IAppLogging in the constructor and pass it to the base class. Here is the code for the updated classes:<\/p>\n<p>\/\/CarApiDataService.cs<br \/>\npublic class CarApiDataService : ApiDataServiceBase&lt;Car,CarApiDataService&gt;, ICarDataService<br \/>\n{<br \/>\npublic CarApiDataService( IAppLogging<CarApiDataService> appLogging, ICarApiServiceWrapper serviceWrapper)<br \/>\n: base(appLogging, serviceWrapper) { }<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>\/\/MakeApiDataService.cs<br \/>\npublic class MakeApiDataService<br \/>\n: ApiDataServiceBase&lt;Make,MakeApiDataService&gt;, IMakeDataService<br \/>\n{<br \/>\npublic MakeApiDataService( IAppLogging<MakeApiDataService> appLogging, IMakeApiServiceWrapper serviceWrapper)<br \/>\n: base(appLogging,serviceWrapper) { }<br \/>\n}<\/p>\n<p>\/\/CarDalDataService.cs<br \/>\npublic class CarDalDataService : DalDataServiceBase&lt;Car,CarDalDataService&gt;,ICarDataService<br \/>\n{<br \/>\nprivate readonly ICarRepo _repo;<br \/>\npublic CarDalDataService(IAppLogging<CarDalDataService> appLogging, ICarRepo repo) : base(appLogging, repo)<br \/>\n{<br \/>\n_repo = repo;<br \/>\n}<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>\/\/MakeApiDataService.cs<br \/>\npublic class MakeDalDataService : DalDataServiceBase&lt;Make,MakeDalDataService&gt;,IMakeD ataService<br \/>\n{<br \/>\npublic MakeDalDataService(IAppLogging<MakeDalDataService> appLogging,IMakeRepo repo)<br \/>\n: base(appLogging, repo) { }<br \/>\n}<\/p>\n<p>Next, update the constructors to take the instance of IAppLogging<TDataService> and assign it to a protected class field:<\/p>\n<p>Test-Drive the Logging Framework<br \/>\nTo close out the chapter, let\u2019s test the logging framework. The first step is to update the HomeController in the AutoLot.Mvc project to use the new logging framework. Replace the ILogger<HomeController> parameter with IAppLogging<HomeController> and update the type of the field, like this:<\/p>\n<p>public class HomeController : Controller<br \/>\n{<br \/>\nprivate readonly IAppLogging<HomeController> _logger; public HomeController(IAppLogging<HomeController> logger)<br \/>\n{<br \/>\n_logger = logger;<br \/>\n}<br \/>\n\/\/omitted for brevity<br \/>\n}<\/p>\n<p>With this in place, log a test error in the Index() method:<\/p>\n<p>public IActionResult Index([FromServices]IOptionsMonitor<DealerInfo> dealerMoitor)<br \/>\n{<br \/>\n_logger.LogAppError(&quot;Test error&quot;); var vm = dealerMonitor.CurrentValue; return View(vm);<br \/>\n}<\/p>\n<p>Run the AutoLot.Mvc project. When the app starts, a record will be logged in the SeriLogs table as well as written to a file named log_AutoLotYYYYMMDD.txt.<br \/>\nWhen you open the log file, you might be surprised to see that there are a lot of additional entries that didn\u2019t come from the one call to the logger. That is because EF Core and ASP.NET Core emit very verbose logging when the log level is set to Information. To eliminate the noise, update the appsettings. Development.json files in the AutoLot.Api, AutoLot,Mvc, and AutoLot.Web projects so that the log level is Warning, like this:<\/p>\n<p>&quot;RestrictedToMinimumLevel&quot;: &quot;Warning&quot;<\/p>\n<p>String Utilities<br \/>\nRecall that one of the conventions in ASP.NET Core removes the Controller suffix from controllers and the Async suffix from action methods when routing controllers and actions. When manually building up routes, it\u2019s common to have to remove the suffix through code. While the code is simple to write, it can be repetitive. To cut down on repeating the string manipulation code, the next step will create two string extension methods.<br \/>\nAdd a new directory named Utilities in the AutoLot.Services project, and in that directory, create a new public static class named StringExtensions. In that class, add the following two extension methods:<br \/>\nnamespace AutoLot.Services.Utilities public static class StringExtensions<br \/>\n{<br \/>\npublic static string RemoveControllerSuffix(this string original)<br \/>\n=&gt; original.Replace(&quot;Controller&quot;, &quot;&quot;, StringComparison.OrdinalIgnoreCase);<\/p>\n<p>public static string RemoveAsyncSuffix(this string original)<br \/>\n=&gt; original.Replace(&quot;Async&quot;, &quot;&quot;, StringComparison.OrdinalIgnoreCase); public static string RemovePageModelSuffix(this string original)<br \/>\n=&gt; original.Replace(&quot;PageModel&quot;, &quot;&quot;, StringComparison.OrdinalIgnoreCase);<br \/>\n}<\/p>\n<p>Next, add the following global using statement to the GlobalUsings.cs file in the AutoLot.Services project and all three web applications:<\/p>\n<p>global using AutoLot.Services.Utilities;<\/p>\n<p>Summary<br \/>\nThis chapter dove into the new features introduced in ASP.NET Core and began the process of updating the three ASP.NET Core applications. In the next chapter, you will finish the AutoLot.Api application.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>CHAPTER 31 Diving Into ASP.NET Core This chapter takes a deep look into the new features in ASP.NET Core. As you learn about the features, you will add them to the projects created in the previous chapter, Introducing ASP.NET Core. What\u2019s New in ASP.NET Core In addition to supporting the base functionality of ASP.NET MVC [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[22],"class_list":["post-360","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-pro-csharp10-with-net6"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/360","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=360"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/360\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=360"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=360"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=360"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}