preface

Recently I have been looking at DDD related materials and the ddD-based architecture design of eShopOnContainers project of Microsoft. In the example service Ordering, it can be seen that the code invocation between different layers is quite different from the traditional invocation method. All the code between the various layers of the project is called by injecting IMediator. F12 can see that the interface belongs to the MediatR component after looking at the source code. To follow suit, let’s look at how to use MediatR in ASP.NET Core projects.

Storage address: github.com/Lanesra712/…

Step by Step

MediatR, described by the author on the Github project homepage, is a.NET implementation based on the intermediary pattern, which is based on in-process data transfer. In other words, this component mainly implements data transfer within one application, which is not suitable for data transfer between multiple applications. As you can see from the author’s github page, he is also the author of the OOM component AutoMapper, PS. If you want to learn how to use AutoMapper in ASP.NET Core projects, you can check out this article I wrote earlier (elevator up). For MediatR, let’s take a look at what the mediator pattern is before we learn how to use it.

What is the intermediary model

For example, the Chinese names of the 23 design patterns used in software development can be easily learned from the Chinese names, just like the intermediary pattern introduced here.

When we are through the code to realize the actual business logic, if it involves the interaction between the multiple object classes, we are often in the form of direct reference, as business logic become more and more complex, for a simple business abstracts the implementation of the method, may be we add on all kinds of judgment logic or for the business logic of the data processing method.

For example, with a simple user login event, we might end up abstracting the following business process implementation.

public bool Login(AppUserLoginDto dto, out string msg)
{
    bool flag = false;
    try
    {
        Check whether the verification code is correct
        flag = _redisLogic.GetValueByKey(dto.VerificationCode);
        if(! flag) { msg ="Verification code error. Please try again.";
            return false;
        }

        // 2. Verify that the account password is correct
        flag = _userLogic.GetAppUser(dto.Account.Trim(), dto.Password.Trim(), out AppUserDetailDto appUser);
        if(! flag) { msg ="Wrong account or password, please try again.";
            return false;
        }

        // 3, verify that the account can log in to the current site (is not locked or has the right to log in to the current system...)
        flag = _authLogic.CheckIsAvailable(appUser);
        if(! flag) { msg ="User is prohibited from logging on to the current system. Please try again.";
            return false;
        }

        // 4. Set the information about the current user
        _authLogic.SetCurrentUser(appUser);

        // 5. Record the login record
        _userLogic.SaveLoginRecord(appUser);

        msg = "";
        return true;
    }
    catch (Exception ex)
    {
        // Record error information
        msg = $" User login failed:{ex.Message}";
        return false; }}Copy the code

We assume that the implementation for login events exists in the UserAppService class, for redis resources in the RedisLogic class, and for user-related resources in the UserLogic class. The resource operations related to permission verification are located in the AuthLogic class.

As you can see, in order to implement the login methods defined in the UserAppService class, we need to rely on at least RedisLogic, UserLogic, and AuthLogic, There may even be some kind of dependency between UserLogic and AuthLogic in some cases, so we can get a dependency between classes as shown in the figure below.

A simple login business did so, if we need to log in to add a new business needs, such as many sites now login and registration is put together, if there is no current judgment when login user information, actually leads to create a new user processes, then, for the original login function implementation, Is there a case for adding more dependencies? At the same time, for many complicated businesses, whether there will be more dependencies between object classes in the final implementation method, affecting the whole body, and whether the cost of late modification test will become higher.

So how does the mediator pattern solve this problem?

As mentioned above, for loanwords, Chinese names are more likely to be based on their actual meanings. Just think about whether we think of real estate agents when we refer to intermediaries in real life. When we come to a new city and are faced with the problem of renting a house, in most cases, we finally need to achieve our goal of renting a house through the intermediary. In the case of renting a house, the real estate agent is actually a broker who accepts our various demands for the house we want to rent and searches for qualified ones from their own housing database. Finally, it connects us with the landlord in the form of a bridge, and finally reaches an agreement on the house renting.

In software development, the intermediary pattern requires us to define an object class containing the interaction relations between various objects according to the actual business. After that, all objects involved in the business are only associated with this one mediation object class, and no other classes are explicitly called. The class dependencies involved in the login function designed with the intermediary pattern are shown below, where AppUserLoginEventHandler is really our mediation class.

Of course, everything has pros and cons, and there is no perfect thing. For example, when we search for a suitable house through a rent agent, we eventually need to pay a fee to the agent as a reward. The code architecture designed by the agent model will also have other problems. The introduction of an intermediary object in our code is bound to increase the complexity of our code, potentially complicating code that would otherwise be easy to implement. At the same time, the original intention of introducing the intermediary pattern is to solve the complex reference relationship between various object classes. For some businesses, it is very complex itself, which will eventually lead to the extremely complex intermediary object.

After all, there is no silver bullet in software development that will solve all our problems.

So, in the sample code for this article, I’ll use the MediatR component to do the user login example above by introducing the idea of the mediator pattern.

Two, component loading

Before using MediatR, here’s a quick look at the sample demo project for this article. The architectural layering of this sample project can be thought of as an architectural layering between a traditional multi-tier architecture and the idea of adopting DDD. Well, you can think of it as four different products of the traditional model of developers migrating to DDD ideas. The specific code layers are explained below.

01 _infrastructure: Infrastructure layer, this layer contains some basic components for configuration or help class code, for each of the new service, the layer of code are almost the same, so for the infrastructure layer code is released to the public or private is the best Nuget warehouse, then we can directly in the project through Nuget to reference.

For projects built with DDD in mind, many people may be used to put the configuration of some entities in the infrastructure layer, my personal understanding is to put the domain layer, for the infrastructure layer, just do some basic components encapsulation. If you find something wrong, feel free to raise it in the comments section.

02 _domain: The Domain layer contains almost all the important parts of the Domain that we divide according to the business, including Domain objects, Value objects, Domain events, and repositories.

Although I created the AggregateModels folder here, in this project I created an anemic model without any business logic. At the same time, everyone will have their own understanding of whether Repository is located in Infrastructure or Domain in the Domain layer, so I prefer to locate it in Domain layer.

03_Application: Application layer, this layer will contain the various actual business logic we encapsulated based on the domain, each encapsulated service application will not call each other.

Sample.Api: Api interface layer, this layer is very simple, mainly through the Api interface to expose the various services we provide externally based on the domain.

The hierarchical structure of the entire sample project is shown below.

In the same way as with other third-party components, we need to add an assembly reference to MediatR in the project via Nuget before using it.

Note here that since we implement the mediator pattern primarily by referring to MediatR, we only need to load MediatR at the domain layer and application layer. In the case of sample. Api, the Web Api project, which requires dependency injection to use the various services we build based on MediatR, So here we need to add MediatR. Extensions. Microsoft. DependencyInjection the assembly to the Sample. The Api.

Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
Copy the code

Iii. Case realization

First, we add the AppUser (user information) class and Address (Address information) class in the AggregateModels folder of sample. Domain. Although the DDD idea is not adopted to divide Domain objects and value objects, We create anemic models with no business logic. However, in the user management business, the contact address information contained by users is actually stateless data. That is to say, for the same address information, because of multiple users will not appear data ambiguity. Therefore, the Address information does not need a unique identifier to distinguish this data, so the Address class here does not need to add a primary key, which is essentially a value object corresponding to the domain modeling.

Here I’m using EF Core as the ORM component of the project. After creating the entities we need to use, we create a SeedWorks folder under the sample. Domain library. Add custom DbContext objects and information classes for writing preset seed data when EF Core first generates the database.

Note that in EF Core, when we write a C# class to create a database table with Code First, our C# class must contain primary key information. As for the Address class, it is more displayed as the attribute information in the AppUser class, so we need to rewrite the process of EF Core generating database tables.

Here we create a new folder named EntityConfigurations under the SeedWorks folder where we store our custom EF Core table creation rules. Create an AppUserConfiguration class that inherits from the IEntityTypeConfiguration interface. In the default Configure method of the interface, we need to write mapping rules. Display the Address class as a field in the AppUser class, and the resulting code looks like this.

public class AppUserConfiguration : IEntityTypeConfiguration<AppUser> {public void Configure(EntityTypeBuilder<AppUser> Builder) builder.ToTable("appuser"); OwnsOne(I => i.arddress, n => { n.Property(p => p.Province).HasMaxLength(50) .HasColumnName("Province") .HasDefaultValue(""); n.Property(p => p.City).HasMaxLength(50) .HasColumnName("City") .HasDefaultValue(""); n.Property(p => p.Street).HasMaxLength(50) .HasColumnName("Street") .HasDefaultValue(""); n.Property(p => p.ZipCode).HasMaxLength(50) .HasColumnName("ZipCode") .HasDefaultValue(""); }); }}Copy the code

Once the mapping rules for creating tables are written, we can override the OnModelCreating method on the UserApplicationDbContext class. In this method, we can apply our custom set of entity mapping rules to let EF Core create the database as we want, and the resulting code is shown below.

public class UserApplicationDbContext : DbContext
{
    public DbSet<AppUser> AppUsers { get; set; }

    public UserApplicationDbContext(DbContextOptions<UserApplicationDbContext> options)
        : base(options)
    {}/// <summary>
    ///
    /// </summary>
    /// <param name="modelBuilder"></param>
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Customize the AppUser table creation rule
        modelBuilder.ApplyConfiguration(newAppUserConfiguration()); }}Copy the code

Once we have created the DbContext, we need to inject it in the ConfigureServices method of the Startup class. In the sample code, I used the MySQL 8.0 database, wrote the configuration file to the appSettings. json file, and finally injected DbContext with the code shown below.

public void ConfigureServices(IServiceCollection services)
{
    // Configures the database connection string
    services.AddDbContext<UserApplicationDbContext>(options =>
        options.UseMySql(Configuration.GetConnectionString("SampleConnection")));
}
Copy the code

The database connection string is configured as follows.

{
  "ConnectionStrings": {
    "SampleConnection": "Server = 127.0.0.1; database=sample.application; user=root; password=123456@sql; port=3306; persistsecurityinfo=True;"}}Copy the code

As mentioned above, in addition to creating a DbContext object, we also created a DbInitializer class to write our preset information into the corresponding database table when EF Core first performs a database creation operation. Here we simply check if there is any data in the AppUser table. If there is no data, we add a new record, and the final code looks like this.

public class DbInitializer
{
    public static void Initialize(UserApplicationDbContext context)
    {
        context.Database.EnsureCreated();

        if (context.AppUsers.Any())
            return;

        AppUser admin = new AppUser()
        {
            Id = Guid.NewGuid(),
            Name = "Mo Mo Mo Mo xiao Yu",
            Account = "danvic.wang",
            Phone = "13912345678",
            Age = 12,
            Password = "123456",
            Gender = true,
            IsEnabled = true,
            Address = new Address("La la la la la Street."."La la la City."."La la la province."."12345"),
            Email = "[email protected]"}; context.AppUsers.Add(admin); context.SaveChanges(); }}Copy the code

When we finish seeding the code, we need to execute our code before the program starts. Therefore, we need to modify the Main method in the Program class to implement seed data implantation before running the Web Program.

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateWebHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            // Perform seed data seeding
            //
            var services = scope.ServiceProvider;
            varcontext = services.GetRequiredService<UserApplicationDbContext>(); DbInitializer.Initialize(context); }}public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}
Copy the code

At this time, when running our project, the program will automatically create the database and write the preset seed data into the database table. The final result is shown in the figure below.

Now that the basic project code is complete, we can begin to learn how to implement the mediator pattern through MediatR. In the example project in this chapter, we’ll use two important interface types in MediatR: IRequest and INotification.

On Github, the author explains these two interfaces as follows, and I will use them according to my understanding. At the same time, in order to prevent any deviation in my understanding, which may affect you, the original reply of the author is posted here.

Requests are for: 1 request to 1 handler. Handler may or may not return a value

Notifications are for: 1 notification to n handlers. Handler may not return a value.

In practical terms, requests are “commands”, notifications are “events”. Command would be directing MediatR to do something like “ApproveInvoiceCommand -> ApproveInvoiceHandler”. Event would be notifications, like “InvoiceApprovedEvent -> SendThankYouEmailToCustomerHandler”

For classes that inherit from the IRequest interface, a request has only one handler for the request (requestHandler), which may return a value or no information;

For classes that inherit from the INotification interface, a notification will correspond to multiple notificationHandlers that do not return any data.

A request is more like a command, while a notification is more like an event. Well, it might seem even more confusing, but JBogard here gives us a case that further explains the difference between request and Notification.

Double tenth one just over, a lot of people are crazy cut hands, for buying big, in order to be able to have better after-sales service, we will certainly expect after buy merchants to provide us with the invoice, here require businesses to provide invoice is one kind of request, and against us this request, businesses will respond, whether we can open out invoice, Merchants should notify us, and the notification to users here is a kind of notification.

For a request to provide an invoice, there is only one way to handle it, regardless of the final outcome; For notification to users, merchants can send notification via SMS, push notification via public account or email. No matter what method is adopted, as long as the notification is completed, the event will be completed.

For user login, the user’s login behavior is actually a request. For this request, we may go to the database to check whether the account exists and determine whether it has the permission to log in to the system, etc. And no matter how much logical judgment we make in the process, there are only two outcomes, login success or login failure. However, after a user logs in to the system, the user may need to set the information of the current login person and record the user login log, which belong to the Notification.

Now that we know the request and Notification division in the user login event, we can implement our functionality in code. I’ll skip over some of the basic components in the sample project here, but if you want to see more specifically how some of the components used here are used, you can refer to my previous article.

First, we create a Commands folder under the sample. Application library to store the user’s requests. Now we create a UserLoginCommand class to map user login requests, which needs to inherit from the IRequest<T&gt generic interface. Since there is only a yes or no for the user to log in to the request, the result of the response to the request is a bool, that is, we should inherit IRequest<bool>.

For a variety of user initiated requests, it actually contains some basic information for this request, but for the user login request class UserLoginCommand, it may only have three information: account number, password, and verification code. The request class code is shown below.

Public class UserLoginCommand: IRequest<bool> {/// <summary> /// Account /// </summary> Public String Account {get; private set; } /// <summary> /// Password /// </summary> public string Password {get; private set; } /// <summary> // VerificationCode /// </summary> public string VerificationCode {get; private set; } / / / < summary > / / / ctor / / / < summary > / / / < param name = "account" > account < param > / / / < param name = "password" > password < param > / / / <param name="verificationCode"> verificationCode </param> public UserLoginCommand(string account, string password, string verificationCode) { Account = account; Password = password; VerificationCode = verificationCode; }}Copy the code

Once we have a class that stores the user login request information, we need to process the user login request. Here, we create a CommandHandlers folder under the sample. Application library to store the handlers for user requests.

Now we create a UserLoginCommandHandler class that inherits from the IRequestHandler interface to handle user login requests. IRequestHandler is a generic interface that requires us to declare the request we need to implement and the return information for that request when we inherit. Therefore, for the UserLoginCommand request, the UserLoginCommandHandler request processing class must inherit from IRequestHandler<UserLoginCommand, bool>.

As mentioned above, we need to Handle the user’s request in the request handler class. In the UserLoginCommandHandler class, we need to perform our judgment logic in the Handle method, where we refer to the repository to retrieve the user’s information. I won’t show you the code in the repository, but the code we eventually implemented looks like this.

public class UserLoginCommandHandler : IRequestHandler<UserLoginCommand, Bool > {#region Initizalize /// <summary> /// </summary> private readonly IUserRepository _userRepository; /// <summary> /// ctor /// </summary> /// <param name="userRepository"></param> public UserLoginCommandHandler(IUserRepository userRepository) { _userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository)); } #endregion Initizalize /// <summary> /// Command Handler /// </summary> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task<bool> Handle(UserLoginCommand request, If (string.IsNullOrEmpty(request.VerificationCode)) return false; / / 2, verify Password is correct var appUser = await _userRepository. GetAppUserInfo (request. The Account. The Trim (), request the Password. The Trim ()); if (appUser == null) return false; return true; }}Copy the code

When we are done with the request processing code, we can provide the user access entry in the Controller. Of course, since we need to use MediatR in the dependency injection way, we need to inject the corresponding handling relationship of the request into the dependency injection container before using it.

To use MediatR through DEPENDENCY injection, we need to inject all events (requests and notifications) into the container, and MediatR will automatically find the corresponding event handler class. In addition, we need to inject the implementation class of the IMediator interface used through dependency injection into the container. In this example project, we mainly use MediatR in sample.domain, sample. Application, and our Web Api project, so we need to inject all the classes that use MediatR in these three projects into the container.

One by one injection will be more trouble, so HERE I still use the specified assembly reflection operation, to obtain the information to load the batch injection operation, the final implementation of the code is as follows.

public static IServiceCollection AddCustomMediatR(this IServiceCollection services, MediatorDescriptionOptions options)
{
    // Get the type of the Startup class
    var mediators = new List<Type> { options.StartupClassType };

    // IRequest
      
        Indicates the type of the interface
      
    var parentRequestType = typeof(IRequest<>);

    // INotification interface type
    var parentNotificationType = typeof(INotification);

    foreach (var item in options.Assembly)
    {
        var instances = Assembly.Load(item).GetTypes();

        foreach (var instance in instances)
        {
            // Determine whether the interface is inherited
            //
            var baseInterfaces = instance.GetInterfaces();
            if (baseInterfaces.Count() == 0| |! baseInterfaces.Any())continue;

            // Determine whether the IRequest
      
        interface is inherited
      
            //
            var requestTypes = baseInterfaces.Where(i => i.IsGenericType
                && i.GetGenericTypeDefinition() == parentRequestType);

            if(requestTypes.Count() ! =0 || requestTypes.Any())
                mediators.Add(instance);

            // Check whether the INotification interface is inherited
            //
            var notificationTypes = baseInterfaces.Where(i => i.FullName == parentNotificationType.FullName);

            if(notificationTypes.Count() ! =0|| notificationTypes.Any()) mediators.Add(instance); }}// Add to the dependency injection container
    services.AddMediatR(mediators.ToArray());

    return services;
}
Copy the code

Since you need to know which assemblies should be reflected, and the Web Api project only uses the IMediator interface through dependency injection, you need to take the form of different parameters to determine which assemblies need to be loaded through reflection.

public class MediatorDescriptionOptions
{
    /// <summary>
    ///Startup Type Type of the class
    /// </summary>
    public Type StartupClassType { get; set; }

    /// <summary>
    ///Contains assemblies that use MediatR components
    /// </summary>
    public IEnumerable<string> Assembly { get; set; }}Copy the code

Finally, we can quickly inject the information from the extension method in the Startup class. The actual code is as follows. Here I put the assembly information to load in the configuration file AppSetting, which you can adjust according to your preference.

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Config mediatr
        services.AddCustomMediatR(new MediatorDescriptionOptions
        {
            StartupClassType = typeof(Startup),
            Assembly = Configuration["Assembly:Mediator"].Split("|", StringSplitOptions.RemoveEmptyEntries) }); }}Copy the code

The configuration information in this sample project is shown below.

{
  "Assembly": {
    "Function": "Sample.Domain"."Mapper": "Sample.Application"."Mediator": "Sample.Application|Sample.Domain"}}Copy the code

When we’re done with the injection, we can use it directly in the Controller. For methods that inherit IRequest, we can call the request information directly through the Send method. MediatR will help us find the corresponding request processing method. Finally, the code in the login action is as follows.

[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
    #region Initizalize

    /// <summary>
    ///
    /// </summary>
    private readonly IMediator _mediator;

    /// <summary>
    /// ctor
    /// </summary>
    /// <param name="mediator"></param>
    public UsersController(IMediator mediator)
    {
        _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
    }

    #endregion Initizalize

    #region APIs

    /// <summary>
    ///The user login
    /// </summary>
    /// <param name="login">The user logs in to the data transfer object</param>
    /// <returns></returns>
    [HttpPost("login")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> Post([FromBody] AppUserLoginDto login)
    {
        // Entity mapping conversion
        var command = new UserLoginCommand(login.Account, login.Password, login.VerificationCode);

        bool flag = await _mediator.Send(command);

        if (flag)
            return Ok(new
            {
                code = 20001,
                msg = $"{login.Account}User login successful",
                data = login
            });
        else
            return Unauthorized(new
            {
                code = 40101,
                msg = $"{login.Account}User login failed",
                data = login
            });
    }

    #endregion APIs
}
Copy the code

After we have finished processing the user login request, we can execute the subsequent “notification class” events. Similar to the request information class for user login, the notification class for user login events only contains some basic information for notification. Under the smaple. Domain library, create a Events file to store our Events. We will create a new AppUserLoginEvent class that inherits from the INotification interface to handle user login Events.

public class AppUserLoginEvent : INotification
{
    /// <summary>
    ///account
    /// </summary>
    public string Account { get; }

    /// <summary>
    /// ctor
    /// </summary>
    /// <param name="account"></param>
    public AppUserLoginEvent(string account)
    { Account = account; }}Copy the code

As mentioned above, there may be several ways to handle a notification event, so we will create a folder to store the actual handling method under the DomainEventHandlers folder of the smaple. Application library.

For notification classes that inherit from the INotification interface, in MediatR we can create classes that inherit from the INotificationHandler interface to handle the corresponding events. Because a Notification can have multiple handlers, we can create multiple NotificationHandler classes to handle the same Notification. An example of the NotificationHandler class is shown below.

public class SetCurrentUserEventHandler : INotificationHandler<AppUserLoginEvent> { #region Initizalize /// <summary> /// /// </summary> private readonly ILogger<SetCurrentUserEventHandler> _logger; /// <summary> /// /// </summary> /// <param name="logger"></param> public SetCurrentUserEventHandler(ILogger<SetCurrentUserEventHandler> logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } #endregion Initizalize /// <summary> /// Notification handler /// </summary> /// <param name="notification"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public Task Handle(AppUserLoginEvent notification, CancellationToken cancellationToken) { _logger.LogInformation($"CurrentUser with Account: {notification.Account} has been successfully setup"); return Task.FromResult(true); }}Copy the code

How to raise this event, a better approach for domain-driven architecture is to add various domain events to the collection of events, and then schedule these domain events immediately before or after committing the transaction. For our project, since this is not the scope of this article, I’m just demonstrating how to use the MediatR component, so I’m going to fire the event directly after the request logic has been processed.

In the UserLoginCommandHandler class, modify our code to trigger our notification event by calling the SetUserLoginRecord method of the AppUser class after confirming a successful login, as shown below.

public class UserLoginCommandHandler : IRequestHandler<UserLoginCommand, Bool > {#region Initizalize /// <summary> /// </summary> private readonly IUserRepository _userRepository; /// <summary> /// /// </summary> private readonly IMediator _mediator; /// <summary> /// ctor /// </summary> /// <param name="userRepository"></param> /// <param name="mediator"></param> public UserLoginCommandHandler(IUserRepository userRepository, IMediator mediator) { _userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository)); _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } #endregion Initizalize /// <summary> /// Command Handler /// </summary> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task<bool> Handle(UserLoginCommand request, If (string.IsNullOrEmpty(request.VerificationCode)) return false; / / 2, verify Password is correct var appUser = await _userRepository. GetAppUserInfo (request. The Account. The Trim (), request the Password. The Trim ()); if (appUser == null) return false; Login / / 3, triggering event appUser. SetUserLoginRecord (_mediator); return true; }}Copy the code

Instead of using Send to call request class, we need to use Publish method to call event notification class inherited from INotification interface. At this point, for a login process designed with an intermediary pattern, the definition of the SetUserLoginRecord method, and the resulting effect we have implemented, are shown below.

public void SetUserLoginRecord(IMediator mediator)
{
    mediator.Publish(new AppUserLoginEvent(Account));
}
Copy the code

conclusion

This chapter mainly introduces how to realize the intermediary mode through MediatR, because I am also the first time to contact this idea, MediatR is the first time to use this component, so just use the case to share the way of the intermediary mode for an explanation. If you want to further understand the specific definition and basic concepts of the intermediary pattern, you may need to find the materials to understand the specific definition. Because of the first contact, there will inevitably be omissions or mistakes, if you find something wrong in the article, welcome to point out in the comments section, thank you in advance.

reference

1. Intermediary Patterns — Graphic Design Patterns

2. How much MediatR knows

Of the pit

Personal Profile: Born in 1996, born in a fourth-tier city in Anhui province, graduated from Top 10 million universities. .NET programmer, gunslinger, cat. It will begin in December 2016. NET programmer career, Microsoft. NET technology stalwart, aspired to be the cloud cat kid programming for Google the best. NET programmer. Personal blog: yuiter.com blog garden blog: www.cnblogs.com/danvic712