How is the MVC architecture used in Dotnet

Developing ASP.NET Core MVC apps

  • 19 minutes to read

"You don't have to do everything right the first time, but you have to do it the last time." - Andrew Hunt and David Thomas

ASP.NET Core is a cross-platform open source framework for building modern, cloud-optimized web applications. ASP.NET Core apps are simple and modular. They have built-in support for dependency injection, which improves their testability and manageability. Combined with MVC (a pattern that supports building modern web APIs in addition to view-based apps), ASP.NET Core provides a powerful framework for building enterprise web applications.

MVC and Razor Pages

ASP.NET Core MVC includes many features that are useful for building web-based APIs and apps. MVC means "Model View Controller". This is a user interface pattern that divides responsibility for responding to requests from users. Using this pattern, you can also implement features as so-called Razor Pages in your ASP.NET Core apps. Razor Pages integrate with ASP.NET Core MVC and use the same features for routing, model bindings, filters, and authorization. However, different folders and files for controllers, models or views and no attribute-based routing are used as usual. Razor Pages are stored in a single folder (/ Pages), routing is based on their relative location in that folder, and requests are processed using handlers rather than controller actions. As a result, when working with Razor Pages, all required files and classes are usually provided together and not distributed in the web project.

If you are creating a new ASP.NET Core app, it is important to think carefully beforehand what the app you are creating will do. There are several templates to choose from in Visual Studio. The three project templates “Web API”, “Web Application” and “Web Application (Model View Controller)” are used most frequently. You can only choose a template when you create the project, but the decision is not final. A web API project uses standard MVCs, but does not contain views by default. The default web application template uses Razor Pages, but it also does not contain a folder for views. You can add a corresponding folder to these projects afterwards to support view-based behavior. Web API and Model View Controller projects do not include a Pages folder by default. However, you can add these afterwards to support Razor Pages. These three templates support three different types of standard user interaction: data-based (web API), page-based, and view-based interactions. However, you can combine these templates in a project as required.

What are the advantages of Razor Pages?

Razor Pages are used by default for new web applications in Visual Studio. Razor Pages make it easier for you to create page-based application features (such as non-single-page forms). When controllers and views are used, applications often contain very large controllers that work with multiple dependencies and view models and return many different views. This makes the application more complex, and there are often controllers who do not follow the single responsibility principle or the open-closed principle. Razor Pages addresses this issue by encapsulating the server-side logic for a specific local page in a web application with appropriate Razor markup. A Razor page with no server-side logic can only consist of one Razor file (e.g. "Index.cshtml"). Most non-trivial Razor Pages are assigned a page model class, which is usually named the same as the Razor file, but has the extension ".cs" (e.g. "Index.cshtml.cs").

The page model of a Razor Page combines the responsibility of an MVC controller and a view model. Requests are not processed using controller action methods, but page model handlers such as OnGet () are executed to render the associated page by default. Razor Pages simplifies the creation of individual pages in an ASP.NET Core app, while all of the architectural features of ASP.NET Core MVC can be used. These are well suited for new page-based functions.

When should you use MVC?

If you're building web APIs, the MVC pattern is better than Razor Pages. If your project only exposes web API endpoints, ideally you should start with the web API project template. Otherwise, it's also very easy to add controllers and associated API endpoints to any ASP.NET Core app. Use the view-based MVC approach when you want to migrate an existing application from ASP.NET Core MVC 5 or earlier to ASP.NET Core MVC with little effort. After the migration, you can check whether Razor Pages can be used for new features or as a whole migration.

The performance of your web app is marginally dependent on whether you are using Razor Pages or MVC views, and both methods support features such as dependency injection, filtering, model binding, validation, and so on.

Map requirements to responses

Essentially, ASP.NET Core apps are used to map incoming requests to outgoing responses. At a lower level, middleware is used for this mapping. As a result, ASP.NET Core apps and microservices may consist entirely of custom middleware. Using ASP.NET Core MVC allows you to work on a more general level and Routes, Controller and Actions Add. Each incoming request is compared to the application's routing table, and if a matching route is found, the assigned action method (of the controller) is called to process the request. If no matching route is found, an error handler is called and the result “NotFound” is returned.

ASP.NET Core MVC apps can use either traditional routes or attribute routes, or both at the same time. Conventional routes are defined as code and enter using a syntax as shown below Routing conventions at:

In this example, a route named “Standard” has been added to the routing table. It defines a route template with placeholders for, and. The standard route is set for the placeholders and (respectively and), and the placeholder is optional because they are provided with a question mark ("?"). The convention defined here expresses that the first part of a request should correspond to the name of a controller and the second part of the action. If necessary, a third part can also represent the ID parameter. Conventional routes are usually defined for use at a specific location, e.g. B. in the method in the class.

Attribute routes apply directly to controllers and actions and are not specified globally. This approach has the advantage that they are easier to find by looking at a particular method. On the other hand, this also means that the routing information is not stored in a specific location in the application. You can easily define multiple routes for a specific action with attribute routes, but you can also combine routes between controllers and actions at the same time. For example:

Routes can be specified using [HttpGet] and similar attributes, so there is no need to add separate [Route] attributes. Tokens from attribute routes can also be used as follows so that the names of controllers or actions do not have to be repeated as often:

Razor Pages does not use attribute routing. However, you can include additional information about routing templates in the statement of a Razor Page:

In the previous example, the page matched routes with an integer parameter. The page Products.cshtmlFor example, that is in the root of would have the following route:

After a specific request is associated with a route, but before the action method is called, ASP.NET Core MVC performs model binding and model validation operations on the request. The model binding is necessary in order to convert incoming HTTP data into .NET types that were specified as parameters of the action method to be called. If z. For example, if the action method expects a parameter, the model binding tries to provide that parameter with a value that is part of the request. To do this, model binding looks for provided form values, values ​​in the route itself, and values ​​from query strings. If a value is found, it is converted to an integer before being passed to the action method.

After the model has been bound, but before the action method is called, a model validation is carried out. Model validation uses optional attributes for the model type. In this context, it may be possible to ensure that the model object provided complies with certain data requirements. Certain values ​​may be specified as required, or restricted to a specific length or numeric range. If validation attributes are specified but the model does not meet their requirements, the ModelState.IsValid property is returned as FALSE. Then the faulty validation rules can be sent to the client making the request.

When using model validation, you should always verify that the model is valid before executing any command that could affect status. This will ensure that the app is not damaged by invalid data. You can use a filter so that you don't have to add code for this validation to every action. You can use ASP.NET Core MVC filters to intercept groups of requests so that general guidelines and overarching issues can be targeted. Filters can be applied to individual actions as well as to complete controllers or globally to an entire application.

In terms of web APIs, ASP.NET Core MVC supports the Content negotiation. This allows requests to specify how responses should be formatted. Based on headers included in requests, actions that return data format the responses in XML, JSON, or any other supported format. This feature enables the same API to be used in multiple clients with different data format requirements.

For Web API projects, the attribute should be used, which can be applied to individual controllers, a base controller class, or the entire assembly. This attribute adds automatic model validation and any action that uses an invalid model will return a BadRequest error with details of the validation errors. When this attribute is used, all actions must have an attribute route rather than a conventional route. The attribute also returns more detailed information on errors that have occurred.

Controlling controllers

In the case of page-based applications, Razor Pages ensure that controllers do not become too large. Each individual page receives its own files and classes that are only dedicated to its handlers. Before Razor Pages was introduced, many view-focused applications had large classes of controllers that were responsible for many different actions and views. These classes naturally grow to have many responsibilities and dependencies, making them difficult to manage. If you find that your view-based controllers are getting too big, consider refactoring or introducing an intermediary pattern to use Razor Pages.

The broker design pattern is used to reduce coupling between classes while allowing communication between them. A common use of this pattern in ASP.NET Core MVC applications is to break down controllers into smaller pieces by adding Handler that do the work of action methods. The popular MediatR NuGet package is often used for this. Typically, controllers contain many different methods of action, each of which may require specific dependencies. These dependencies required for an action must be passed to the constructor of the controller. When using MediatR, the only instance of a controller is in an instance of the mediator. Each action then uses the mediator to send a message that is processed by a handler. The handler is specific to a single action and therefore only needs the dependencies required for that action. Here is an example of a controller using MediatR:

During the action, the command to send a message is processed by this class:

The end result of this approach is that controllers are much smaller and mostly focused on routing and model bindings, while individual handlers are responsible for the specific tasks required for a given endpoint. Instead of MediatR, you can also use the ApiEndpoints NuGet package with this approach, which attempts to provide API controllers with the same benefits that Razor Pages view-based controllers offer.

Resources: Mapping Requirements to Responses

Working with dependencies

The dependency injection technique is supported by ASP.NET Core and used internally. It is a technique that enables the loose coupling of different parts of an application. A looser coupling is an advantage as it allows different parts of the application to be tested or replaced in a more isolated manner. It also makes it less likely that changing part of the application could unexpectedly affect the rest of the application. Dependency injection is based on the principle of dependency inversion and is often an important means of enforcing the open / closed principle. When evaluating how the application and its dependencies work, you should avoid poorly structured code in a static context and observe the principle New is Glue.

A static relationship arises when your classes call static methods or access static properties that include side effects or dependencies on the infrastructure. For example, if you have a method that calls a static method that in turn writes to a database, your method will be tightly coupled to the database. Any element that interrupts the database call also interrupts the method. This method is known to be very difficult to test because it either requires commercial test libraries to test static calls or the tests can only be run with an active test database. Static calls that are not dependent on the infrastructure, especially the completely stateless calls, can be called without hesitation and (apart from the coupling code and static calls per se) have no effect on the coupling or testability.

Many developers know the risk of static relationships and global status, but still couple their code closely to specific implementations via direct instantiation. The guiding principle "new is glue" ("new" acts as glue) is intended to remind of this coupling and does not represent a general condemnation of the use of the keyword. Just like static method calls, new instances of types without external dependencies do not couple code closely to the implementation details, nor do they complicate the testing process. However, every time a class is instantiated, consider whether it makes sense to use predefined code for that instance in a particular location, or whether it is better to specify that this instance is queried as a dependency.

Declare your dependencies

ASP.NET Core is designed in such a way that methods and classes declare their respective dependencies and request them as arguments. ASP.NET applications are typically set up in startup classes that are configured to support dependency injection in several places. If your startup class has a constructor, it can use it to request dependencies as follows:

The starting class is important because there are no explicit type requirements on it. It neither inherits from a particular base start class nor does it implement any particular interface. You can optionally assign a constructor to it and specify any number of parameters for it.When the web host you configured for your application starts, it calls the startup class you specified and uses dependency injection to fill in any dependencies the startup class requires. If you request parameters that are not configured in the service containers used by ASP.NET Core, an exception is thrown. However, if you are only referring to the dependencies known to the container, you can send any request you want.

Dependency injection is built into ASP.NET Core apps from the start when you create the launch instance. This also applies to the starting class. You can also request dependencies in the "Configure" method:

The ConfigureServices method is an exception to this behavior, as it only takes one parameter of type IServiceCollection. It does not necessarily have to support Dependency Injection, as it is responsible for adding objects to the service container and has access to all currently configured services via the IServiceCollection parameter. Therefore, throughout the startup class, you can work with dependencies defined in the ASP.NET Core service collection, either by requesting the service you need as a parameter, or by working with IServiceCollection in the ConfigureServices method.

Note

If you need to ensure that certain services are available in your Startup class, you can configure them using WebHostBuilder and the ConfigureServices method within the call to CreateDefaultBuilder.

The Starter Class is a model for how you structure other parts of your ASP.NET Core application - from controllers to middleware to filtering your own services. In any case, you should observe the principle of explicit dependencies and it is better to request your dependencies than to create them directly and use dependency injection throughout the application. Pay attention to where and how you instantiate implementations directly, especially when it comes to services and objects that work with the infrastructure and have side effects. It is better to work with abstractions that are defined in the application core and passed as arguments to predefined references to specific implementation types.

Structure the application

Monolithic applications usually have a fixed entry point. For ASP.NET Core web applications, the ASP.NET Core web project is the entry point. However, that does not mean that the solution should consist of only one project. You should divide the application into different layers in order to follow the principle of "Separation of Concerns". When you do this, consider creating folders to separate projects for better encapsulation. The best approach to achieving these goals with an ASP.NET Core application is to modify the so-called “clean architecture” discussed in Chapter 5. If you follow this approach, this application's solution will consist of separate libraries for the user interface, infrastructure, and the ApplicationCore project.

In addition to these projects, the application also contains test projects (information on testing can be found in Chapter 9).

The application's object model and interfaces should be included in the ApplicationCore project. Then the project has as few dependencies as possible that the other projects in the solution refer to. Both business entities that should be retained and services that are not directly dependent on the infrastructure are defined in the ApplicationCore project.

The infrastructure project contains information on implementation. These include instructions on how to proceed with regard to persistence and the possible sending of notifications to the user. This project then refers to implementation-specific packages such as Entity Framework Core, but should not make any information about these implementations available outside of the project. Infrastructure services and repositories should implement interfaces that are defined in the ApplicationCore project. Entities implemented in it are retrieved and stored via the persistence implementations of this project.

While the ASP.NET Core UI project is responsible for everything related to the UI layer, it shouldn't contain any business logic or information about infrastructure. At best, it should even be independent of the infrastructure project. This ensures that a dependency between two projects is not accidentally created. You can do this by using a third-party dependency injection container like Autofac. This allows you to define rules for the individual projects for dependency injection in module classes.

Alternatively, you can choose to have the application call microservices, which may be deployed in custom Docker containers, to decouple the application from implementation information. This means that the principle of “Separation of Concerns” is adhered to even more clearly, and it is better decoupled than when using Dependency Injection between two projects. However, this method is also more complex.

Organize features

By default, ASP.NET Core applications create their own folder structure that includes Controllers, Views, and often ViewModels. Client-side code that is supposed to support these structures on the server side is usually stored separately in the wwwroot folder. However, larger applications may experience problems with this folder structure as you often have to switch between these folders when working on a particular feature. The more files and subfolders that are stored in a folder, the more difficult it becomes, and the more you have to scroll in Solution Explorer. If you want to avoid this problem, you can search by application code instead of by file type feature organize. This structure is often referred to as a feature folder or feature slices (see also: Vertical Slices).

In this regard, ASP.NET Core MVC supports the use of different areas. If you are using different areas, you can create separate folders for controllers and views (and any associated models) in each area folder. Figure 7-1 shows a folder structure in which areas are used.

Figure 7-1. Example structure with areas

If you're using scopes, you'll need to use attributes to label your controllers with the names of the scopes they belong to:

Also, you need to add the area support to your routes:

In addition to the already built-in support of areas, you can also use your own folder structure and conventions instead of attributes and user-defined routes. This allows you to have feature folders that don't have separate folders for views, controllers, and so on. This keeps the hierarchy flat and shows all related files for a feature in one place at once.

ASP.NET Core uses built-in convention types to control the behavior of this framework. You can change or replace these conventions. For example, you can create a convention that automatically gets the feature name for a particular controller based on the namespace, which typically correlates with the folder in which the controller is placed:

Then include this convention as an option when adding MVC support to your application in ConfigureServices:

ASP.NET Core MVC also uses a convention to find views. You can override this convention with a custom one by using the feature name listed under FeatureConvention above so that views are found in your feature folders. For more information on this approach, see the MSDN Magazine article ASP.NET Core: Feature Slices for ASP.NET Core MVC. You can also download an example from this page.

APIs and Blazor Applications

Ideally, if your application contains multiple web APIs that need to be protected, they should be configured as a separate project from your View or Razor Pages application. Separating APIs (especially public APIs) from the server-side web application has several advantages. These applications often have unique delivery and load characteristics. It is also very likely that different security mechanisms will be used, with standard forms-based applications using cookie-based authentication and APIs that are likely to use token-based authentication.

In addition, Blazor applications should be built as separate projects independently from using Blazor Server or Blazor WebAssembly. The applications have different runtime properties and security models. You will likely use common common types for the server-side web application (or API project), and these types should be defined in a common shared project.

Adding a Blazor WebAssembly admin user interface to eShopOnWeb also requires adding several new projects. This includes the Blazor WebAssembly project itself. Several new API endpoints are defined in the project that are used by and configured to use token-based authentication. Certain shared types that are used by both projects are saved in the new project.

Now the question may arise, why add a separate project when there is already a common project that can be used to share all of the types required for and. This is because this project contains the entire business logic of the application and is therefore much larger than necessary, which is why protection on the server is also necessary. Note that any referenced library will be downloaded when the Blazor application is loaded from users' browsers.

Depending on whether the "Back-Ends for Front-Ends" (BFF) pattern is used, the APIs used by the Blazor WebAssembly app are not allowed to share their types with Blazor. In particular, a public API that is to be used by many different clients can define their own request and result types instead of releasing them in a client-specific shared project. The eShopOnWeb example assumes that the project is actually hosting a public API, so not all request and response types come from the project.

Overarching issues

The larger the applications, the more important it is to avoid overlapping issues in order to avoid duplication and ensure consistency. General issues for ASP.NET Core applications include authentication, model validation rules, output caching and error handling. In ASP.Net Core MVC, filters allow you to run code before or after certain steps in the request processing pipeline. For example, a filter can be run both before and after the model binding, an action, or the result of an action. You can also use an authorization filter to control access to the rest of the pipeline. Figure 7-2 shows how you can request execution flows through any configured filters.

Figure 7-2. Request execution through filters and request pipeline

Filters are typically implemented as attributes so that you can apply them to controllers or actions (or even globally). When filters are added in this way, the filters specified at the action level either override the filters specified at the controller level, or they build on these filters, which themselves override global filters. For example, the [Route] attribute can be used to create routes between controllers and actions. In the same way, the authorization can be configured at the controller level and then overwritten by individual actions. This is shown in the following example:

The first method (login) uses the AllowAnonymous filter (attribute) to override the Authorize filter set at the controller level. The ForgotPassword action, as well as any other action in the class that does not have an AllowAnonymous attribute, requires an authenticated request.

Filters can be used to avoid duplication in the form of general error handling guidelines for APIs. For example, a typical API policy would be to return a NotFound response to requests that refer to keys that do not exist. On the other hand, if the model validation fails, a BadRequest should be returned. The following example illustrates the use of these two directives:

Avoid cluttering your action methods with conditional code like this one. Instead, pull the policies into filters that can be applied as needed. In this example, the model validation that should be done every time a command is sent to the API can be replaced with the following attribute:

You can add a NuGet dependency to your project by including the Ardalis.ValidateModel package. For APIs, you can use the attribute to force this behavior without using a separate filter.

Likewise, a filter can be used to check for the presence of a record and return a 404 error before taking the action. Therefore, these checks no longer have to be carried out within the action. Once you've removed commonly used conventions and organized your solution so that infrastructure code and business logic are separated from your user interface, your MVC action methods should be very thin:

For more information on implementing filters and a working example to download, see the MSDN Magazine article ASP.NET Core - ASP.NET Core MVC Filters in Action.

Resources: Structuring Applications

security

Securing web applications is a long and complex topic. Basically, when it comes to security, you need to make sure you know who is sending a request. Then you need to make sure that the request only has access to the resources that it should have access to. Authentication compares credentials that are provided with a request for the credentials in a trusted data store to verify that the request should be treated as if it were sent by a known entity. Authorization restricts access to certain resources based on user identity. Another security aspect is protecting requests from third-party eavesdropping. To do this, make sure that your application is using at least SSL.

Identity

ASP.NET Core Identity is a membership system that you can use to aid the login features for your application. This system supports both local user accounts and the support of external protocol providers such as Microsoft Account, Twitter, Facebook and Google. In addition to ASP.NET Core Identity, your application can also use Windows authentication or a third-party identity provider such as Identity Server.

ASP.NET Core Identity is included in new project templates when the "Individual user accounts" option is enabled. This template includes support for registration, login, external login, forgotten passwords, and additional features.

Figure 7-3. Select individual user accounts to pre-configure Identity.

Identity support is configured under "Startup" and both in the ConfigureServices method and in Configure:

It is important that UseIdentity appears before UseMvc in the Configure method. When configuring Identity in ConfigureServices, AddDefaultTokenProviders should be called. This has nothing to do with tokens that might be used to secure web communications. Instead, it refers to providers who create prompts that can be sent via SMS or email to users so that they can verify their identity.

For more information on configuring two-factor authentication and allowing external login providers, see the official ASP.NET Core documentation.

Authentication

Authentication is the process of determining who is accessing the system. When you use ASP.NET Core Identity and the configuration methods shown in the previous section, the application automatically configures some default authentication settings.However, you can also manually configure these default settings or override those set by AddIdentity. When using an identity, cookie-based authentication is used as a Standard scheme configured.

With web-based authentication, there are typically up to five actions that might be performed during the process of authenticating a system client. These are:

  • Authenticate: Use the information provided by the client to create an identity that can be used in the application.
  • Query: This action is used to identify the clients.
  • Prohibit: Inform the client that an action is prohibited.
  • Log in: keep the existing client in some way.
  • Log off: Remove the client from the persistence store.

There are a number of general techniques for performing authentication in web applications. These are called schemas. A particular scheme defines actions for some or all of the options listed above. Some schemes only support some actions and may require a separate scheme to run unsupported schemes. For example, the OpenId-Connect (OIDC) scheme does not support login or logout, but is often configured to use cookie authentication for this persistence.

In your ASP.NET Core application, you can configure a property and optional specific schemes for each of the actions described above. Examples include and. Calling configures a number of aspects of the application and adds many required services. It also includes this call to configure the authentication scheme:

These schemes use cookies for persistence and redirection to login pages, which are used for authentication by default. These schemas are appropriate for web applications that interact with users through web browsers, but are not recommended for APIs. Instead, APIs usually use a different form of authentication (e.g. JWT bearer tokens).

Web APIs are used by code such as in .NET applications and equivalent types in other frameworks. These clients expect a usable response from an API call or a status code that may indicate what problem has occurred. These clients do not interact through a browser and cannot render or interact with any HTML returned by an API. Therefore, in the context of API endpoints, it is not appropriate to redirect their clients to login pages if they are not authenticated. Another scheme is more suitable.

You can set up authentication as follows to configure authentication for APIs used by the project in the eShopOnWeb reference application:

While it is possible to configure several different authentication schemes within a single project, it is much easier to configure a single standard scheme. For this reason, the eShopOnWeb reference application separates its APIs into, among other things, its own project, which is separate from the main project and contains the views and razor pages of the application.

Authentication in Blazor apps

Blazor Server applications can use the same authentication features as any other ASP.NET Core application. Blazor WebAssembly applications cannot use the built-in identity and authentication providers because they run in the browser. Blazor WebAssembly applications can store user authentication status locally and access claims to determine what actions users can take. However, regardless of the logic implemented in the Blazor WebAssembly app, all authentication and authorization checks should be run on the server because users can easily bypass the app and interact directly with the APIs.

References: authentication

Authorization

The simplest type of authentication involves restricting access to anonymous users. You can set up this functionality by applying the [Authorize] attribute to certain controllers or actions. If roles are used, the attribute can be expanded as shown below to restrict access to users who are assigned certain roles:

In this case, users with the role or (or both roles) have access to “SalaryController”. If you want to require that a user belong to multiple roles instead of just one of multiple roles, you can apply the attribute multiple times, each specifying a required role.

If you define certain roles as strings in many different controllers and actions, this can lead to unwanted repetitions. At a minimum, define constants for these string literals and use the constants needed to specify the string. You can also configure authorization policies that encapsulate authorization rules and then specify the policy instead of individual roles when you apply the [Authorize] attribute:

By using policies in this way, you can break down the types of actions that are limited to specific roles and rules that apply to them. If you later create a new role that needs access to certain resources, you can simply update a policy instead of having to update every list of roles for each [Authorize] attribute.

Expectations

Claims are name-value pairs that represent properties of an authenticated user. You may want to save a user's personnel number as an entitlement. Claims can be used as part of authorization policies. Therefore, you can create a policy named EmployeeOnly that requires a claim named EmployeeNumber, as shown in the following example:

This policy can then be used with the [Authorize] attribute to protect a controller and / or an action as described above.

Securing web APIs

Most web APIs should implement a token-based authentication system. Token authentication is stateless and scalable. In a token-based authentication system, the client must first authenticate itself with the authentication provider. If this is successful, a token is issued to the client, which is a cryptographic, meaningful string. The most common format for tokens is JSON Web Token or JWT (often pronounced “Jot”). Then, when the client needs to make a request to an API, it adds this token as a header to the request. The server then checks the token found in the request header before completing the request. Figure 7-4 shows this process.

Figure 7-4. Token-based authentication for web APIs.

You can create your own authentication service, integrate your API with Azure AD and OAuth, or use an open source tool such as B. Implement IdentityServer.

JWT tokens can embed user-associated claims that can be read on the client or server. You can use a tool like jwt.io to view the contents of a JWT token. Do not store sensitive data such as passwords or keys in a JTW token, as its contents are easy to read.

If you are using JWT token with single-page web application or Blazor WebAssembly applications, you must store the token somewhere on the client and then add it to each API call. This activity usually takes place as a header. The following code demonstrates this:

After calling the above method, the token is embedded in the request headers for requests that are executed so that the server-side API can authenticate and authorize the request.

Custom security

Be especially careful when implementing your own cryptography, user membership, or token generation systems. There are many commercially available or open source alternatives that are more secure than a custom implementation in most cases.

Resources: Security

Client communication

In addition to serving pages and responding to requests for data through web APIs, ASP.NET Core apps can also communicate directly with connected clients. Various transport technologies can be used for this outbound communication, the most common of which is WebSockets. ASP.NET Core SignalR is a library that allows you to easily add functionality for real-time communication between server and client to your applications. SignalR supports a variety of transport technologies, including WebSockets, and relieves the developer of much of the implementation details.

Real-time client communication is useful in many application scenarios. It makes no difference whether you use WebSockets or other methods. Examples:

  • Applications for live chat

  • Monitoring Applications

  • Order status updates

  • Notifications

  • Interactive Forms Applications

When you integrate client communications into your applications, there are typically two components:

  • A server-side connection manager (SignalR-Hub, WebSocketManager and WebSocketHandler)

  • A client-side library

Clients aren't limited to browsers, as mobile apps, console apps, and other native apps can also communicate using SignalR / WebSockets. The following simple program is part of a WebSocketManager sample application and passes any content sent to a chat application to the console:

Consider ways that applications can communicate directly with client applications and see if real-time communication would improve the app's usability.

Resources: Client Communication

Should you use the Domain-Driven Design method?

Domain-Driven Design (DDD) is a useful approach to creating software where the developer can rely on the Business domain concentrated. There is also a focus on communication and interaction with business domain experts who can explain to developers how the system should work in practice. For example, if you are creating a system for stock trading, you should seek help from a seasoned stockbroker as an expert in the business domain. DDD is designed to address large, complex business problems and is often unsuitable for smaller, simpler applications because it is not worth investing a lot of time in understanding and modeling the domain.

If you are creating software using DDD technology, your team (which includes project participants and non-technical contributors) should have a ubiquitous language develop for the problem area. This means that the same terminology should be used for the practice-oriented concept that is being modeled, its software equivalent, and any other structures that may contribute to the concept (e.g. data tables). The concepts described in ubiquitous language are meant to be the basis for your Domain model represent.

Your domain model is made up of objects that interact with each other to represent the behavior of the system. These objects can be divided into the following categories:

  • Entities that represent objects with threads of identity. Entities are usually stored permanently with a key that can be used to retrieve them at a later point in time.

  • Aggregates, which represent groups of objects that should be kept as a unit.

  • Value objects that represent concepts that can be compared based on the sum of their property values. For example the DateRange object, which consists of a start date and an end date.

  • Domain events that represent operations that occur within the system and that are relevant to other parts of the system.

A DDD domain model must encapsulate complex behavior within the model. Entities, in particular, shouldn't just be collections of properties. When the domain model has no behavior and only represents the state of the system, it is known as the anemic model. These types of models should not occur in connection with DDD.

In addition to these types of models, DDD typically uses different patterns:

  • Repository for summarizing information on persistence.

  • Factory for encapsulating the creation of complex objects.

  • Services for encapsulating complex behavior and / or information for implementing the infrastructure.

  • Command to decouple issuing commands and execute the command itself.

  • Specification for decoupling query details.

For DDD, it is also recommended to use the aforementioned clean architecture, which includes loose coupling, decoupling, and code that can be easily verified using unit tests.

Recommended use of DDD

DDD is particularly suitable for large applications with considerable complexity in the business area (not only in the technical area). Domain expert knowledge is then required to build the application. The domain model itself should have meaningful behavior in that it represents business rules and interactions, rather than just storing and querying the current status of various records from data stores.

Not recommended use of DDD

If you want to use the DDD method, you have to invest a lot of time in modeling, architecture and communication. This is not worthwhile for smaller applications or CRUD applications (create / read / update / delete = create / read / update / delete). If you choose to take this approach but then find that your domain model is an anemic, non-behavioral model, you should reconsider your choice. Either the DDD method is not necessary for this application, or you may need assistance refactoring your application to encapsulate the business logic in the domain model instead of in your database or user interface.

You can also take a hybrid approach and use DDD only for transactional areas or more complex areas of the application, and use a different method for the CRUD parts or the read-only parts of the application. For example, you don't need the constraints of an aggregate when querying data to query a report or visualizing data for a dashboard. Then it is perfectly acceptable to use a separate, simple reading model for such requests.

Resources: Domain-Driven Design

Provision

Regardless of where the ASP.NET Core application is hosted, there are a few steps to deploying it. The first thing to do is publish the application. You can use the CLI command for this. This step compiles the application and places all of the files necessary for the application to run in the specified folder. If you are deploying through Visual Studio, this step will be done for you automatically. The publish folder contains EXE and DLL files for the application and its dependencies. Standalone applications also include a version of the .NET runtime. ASP.NET Core applications also contain configuration files, static client objects, and MVC views.

ASP.NET Core applications are console applications that must be started when the server starts and restarted when the application or server crashes. If you want to automate this process, you can use a process manager. The most commonly used process managers for ASP.NET Core are Nginx and Apache on Linux and IIS or Windows Service on Windows.

In addition to a process manager, ASP.NET Core applications can use a reverse proxy server. A reverse proxy server receives HTTP requests from the Internet and forwards them to Kestrel after preparatory processing. Reverse proxy servers provide a layer of security to the application. Kestrel does not support hosting multiple applications on one port. Therefore, techniques such as host headers cannot be used to allow multiple applications to be hosted on one port and one IP address.

Figure 7-5. ASP.NET hosted in Kestrel behind a reverse proxy server

A reverse proxy server can also be useful for securing multiple applications using SSL / HTTPS. In this context, SSL only needs to be configured for the reverse proxy. As shown in Figure 7-6, communication between the reverse proxy server and Kestrel can be established using HTTP.

Figure 7-6. ASP.NET hosted behind a reverse proxy server secured via HTTPS

An increasingly popular approach is to host your ASP.NET Core application in a Docker container, which can then be hosted locally or deployed on Azure for cloud-based hosting. Then, as shown above, the Docker container can hold your application code running on Kestrel and deployed behind a reverse proxy server.

When you host your application on Azure, you can use Microsoft Azure Application Gateway as a reserved virtual application to provide various services. Application Gateway not only serves as a reverse proxy for individual applications, but also offers the following features:

  • HTTP load balancing

  • SSL offload (SSL only for internet)

  • End-to-end SSL

  • Routing across multiple websites (consolidating up to 20 websites for one Application Gateway version)

  • Web application firewall

  • WebSocket support

  • Extended diagnosis

For more information on the deployment options for Azure, see Chapter 10.

Resources: Deployment