1. Create loosely coupled components
1. "decomposition Focus" is a very important feature in the MVC model. We want to create components in the application as independent as possible, so that we can manage less dependencies. Ideally, each component is isolated and does not know the existence of other components. Other fields that process applications only use abstract interfaces. This is called loose coupling, it makes our applications easier to test and modify. A simple example can help us understand that if we want to write an email component, we will name this component MyEmailSender for the time being, and then implement an interface, this interface defines all the functions for sending emails. It is also named IEmailSender. Components of any other application must reference the methods in IEmailSender. For example, the PasswordResetHelper component that resets the password requires an email after the user resets the password to show the relationship between them:
By introducing IEmailSender, we can ensure that there is no direct dependency between PasswordResetHelper and MyEmailSender. For example, we can replace the current MyEmailSender with other mail-sending providers without affecting PasswordResetHelper. Here we can also appreciate the benefits of loose coupling.
Of course, not all components need interfaces to decouple and interact with each other. It depends on the complexity of our applications, the testing required, and the possibility of long-term maintenance. We do not need to perform decoupling on a simple ASP. net mvc program.
2. Use dependency Injection
Interfaces can help us decouple components, but this still faces a problem, that is, C # does not provide an embedded method to easily create objects that implement interfaces, because we can only create an instance of components that implement the interface, such as the MyEmailSender instance. As follows:
Public class PasswordResetHelper
{
Public void ResetPassword ()
{
IEmailSender mySender = new MyEmailSender ();
// Email details
MySender. SendEmail ();
}
}
We only do a part of the loosely coupled work. The PasswordResetHelper class configures and sends emails through IEmailSender, and creates objects through interface implementation. here we need to create an instance of MyEmailSender. In this case, we may make things worse, because the current PasswordResetHelper relies on both IEmailSender and MyEmailSender, as shown in:
In fact, I need a way to get objects (here is the mySender in the code above ), this object implements the given interface, but does not directly create the implementation interface (MyEmailSender here) object itself. The solution to this problem is called dependency injection (DI). It can also be considered as inversion of control (IoC )).
3. DI (dependency injection): It is a loosely coupled design model. It is a very important concept and is the center of efficient MVC development.
DI is divided into two parts: one is to remove any dependencies on specific classes from our components. In our example, we will move the implementation of necessary interfaces to the class constructor, as shown below:
Public class PasswordResetHelper
{
Private IEmailSender emailSender;
Public PasswordResetHelper (IEmailSender emailSenderParam)
{
EmailSender = emailSenderParam;
}
Public void ResetPassword ()
{
// Detailed email configuration...
EmailSender. SendEmail ();
}
}
Www.2cto.com
After doing so, we can find that without MyEmailSender, this means that we break the dependency between PasswordResetHelper and MyEmailSender. The constructor of PasswordResetHelper requires an object as a parameter, which is the implementation of the IEmailSender interface. It does not need to know what the object is, or it does not need to be concerned at all, and you do not have to create it.
4. through the above operations, dependencies are injected into PasswordResetHelper at runtime, which also means that instances of classes that implement the IEmailSender interface will be created, and the constructor passed to PasswordResetHelper during the instantiation. In this way, there is no compile-time dependency between PasswordResetHelper and any classes that implement the interfaces required by it.
PasswordResetHelper uses its Constructor to implement dependency Injection. We call this Constructor Injection (Constructor Injection). I can also implement dependency Injection through its Public attributes, this method is usually called Setter Injection (set Injection ). To learn more about Constructor Injection vs. Setter Injection, click here;
Because the dependency is processed at runtime, we can decide which interface is used for implementation when the program is running. Just like here we can use different Email providers, or forge one for testing only. The above method achieves our goal.
2. A specific example of MVC dependency Injection
Back to my previous bidding. Next, we will apply the dependency injection to our bidding program. Our goal is very simple. Create a controller named AdminController, and we will use MembersRepository for persistence, to solve the coupling between AdminController and MembersRepository, we define an interface IMembersRepository. the specific code is as follows:
Public interface IMembersRepository
{
Void AddMember (Member member );
Member FetchByLoginName (string loginName );
Void SubmitChanges ();
}
Public class MembersRepository: IMembersRepository
{
Public void AddMember (Member member ){...}
Public Member FetchByLoginName (string loginName ){...}
Public void SubmitChanges (){...}
}
Now I write a controller class, which depends on IMembersRepository, as shown below:
Public class AdminController: Controller
{
IMembersRepository membersRepository;
Public AdminController (IMembersRepository repositoryParam)
{
MembersRepository = repositoryParam;
}
Public ActionResult ChangeLoginName (string oldLoginParam, string newLoginParam)
{
Member member = membersRepository. FetchByLoginName (oldLoginParam );
Member. LoginName = newLoginParam;
MembersRepository. SubmitChanges ();
// Used to present some views
}
}
AdminController requires an implementation object of the IMembersRepository interface as the constructor parameter. This will be injected at runtime, and AdminController will be allowed to operate on the instance of the object implemented by the interface without coupling.
Use a dependency injection container
I have solved the dependency problem, because we will inject the constructor dependency when the program is running, but I still need to solve another problem: how do we instantiate interfaces to implement object instances without creating dependencies elsewhere in our programs.
The solution is DI container or IoC container. It is actually a component that acts as a "broker" between dependency and implementation dependency. I understand it as an intermediary proxy or something. Specifically, in our example, it is the "broker" between PasswordResetHelper requirement (Demands) and MyEmailSender ".
We use the DI container to register the interface or abstract class set used by the application. In this way, we need to tell the DI container which appropriate instance of the specific class is selected to satisfy the dependency. Therefore, we should also register IEmailSender through the container, and when the implementation of IEmailSender is required, which MyEmailSender instance will be created, no matter when we need to call IEmailSender, for example, if we create a PasswordResetHelper instance, we will go to the DI container and it will give us a previously registered instance as the default (interface implementation class), which may be a detour here, in our example, MyEmailSender is used.
Maybe you are just like me. You are still very unfamiliar with DI containers. What is it. Don't worry. We don't need to implement DI containers. There are many good open-source implementations that can be used directly. One of them is recommended for Ninject. Maybe you are as unfamiliar with Ninject as I do, and you don't have to worry about it. You can slam here. Of course, there will be special chapters to introduce it later, so we can rest assured. Of course, Microsoft has also created its own DI container Unity. If you want to understand it, you can also slam here and we may feel that the role of DI is not important, then you are wrong, in fact, good DI will have some very "smart" features, such as dependency chain solution, object lifecycle management, and constructor parameter configuration. We do not recommend that you create a DI container by yourself. If you do the experiment by yourself, because Ninject is already doing well and is recommended.
Today's notes are made here. At this point, I have completed the notes of the first four chapters. That is to say, I have learned this place, and I very much hope that I will be guided by the passing Daniel.
I also just learned MVC and took notes to consolidate and deepen my understanding. Of course, I am very happy to be able to help beginners who are just like me a little bit. There will certainly be something wrong with my notes. Please help me with your guidance. Thank you!
Wish the passing friends a smooth job!
Good night!
Author Gabriel Zhang