Implementation of dependency injection instance based on WinForm on net core 3.1

Time:2020-1-28

Catalog

  • Implementation of dependency injection instance based on WinForm on net core 3.1
  • 1. background
  • 2. Dependency injection
    • 2.1 what is dependency injection?
    • 2.1 purpose of dependency injection
    • 2.2 benefits of dependency injection
      • 2.2.1 life cycle control
      • 2.2.2 decouples the presentation layer (caller) from the service class
      • 2.2.3 developers no longer need to consider the relationship between dependencies
    • 2.3 design pattern for dependency injection
      • 2.3.1 agency mode
      • 2.3.2 factory mode
  • 3. Implement dependency injection based on WinForm on net core 3.1
    • 3.1 WinForm support in net core 3.1.
    • 3.2 what is the difference between WinForm dependency injection and net core MVC?
    • 3.4 code implementation
      • 3.4.1 establish static method of service registration in program.cs
      • 3.4.2 create service container object
      • 3.4.3 add service registration
      • 3.4.4 build serviceprovider object
      • 3.4.5 running MainForm service
      • 3.4.6 calling Di instance with constructor method
    • 3.5 demonstration effect
    • 3.6 how to call the child form in the parent form by using dependency injection.
      • 3.6.1 injection subform
      • 3.6.2 set global serviceprovider Container Service Manager
      • Calling Form1 in 3.6.3 MainForm
      • 3.6.4 calling Di instance with constructor method
      • 3.6.5 effect
  • 4. last

Implementation of dependency injection instance based on WinForm on net core 3.1

1. background

Net core 3.1 is a long-term 3-year supported version of Microsoft LTS, officially released on December 3, 2019, and supports winfrom and WPF desktop applications on the windows platform. This paper introduces the first step when using Winform, and applies the dependency of DBconfig and storage layer involved in the application layer and ORM to the container, and invokes instances from the container through the constructor function to provide the use of each form control.
Note: the explanation of dependency injection in this article is based on the native Di of Microsoft, which can be imitated by ninject or autoface, and the principles are the same.

2. Dependency injection

2.1 what is dependency injection?

Dependency injection is controlled by inversion of control (IOC). The design mode belongs to agent mode + factory mode, which is called by serviceprovider according to instance interface or instance type. During injection, the life cycle is set to control instantiation and configuration of instance life cycle, and the instance is returned to the programmer for calling, so as to liberate the programmer’s productivity. There is no need to go to new instances or new instances Consider the dependencies between instances and the life cycle of instances. The implementation is divided into three stages. First, the programmer injects the service into the service container, the second programmer calls the di instance, and the third serviceprovider service manager returns the corresponding instance of the program and configures the life cycle of the instance according to the configuration at the time of injection.

One diagram can understand the call process of dependency injection instance
Implementation of dependency injection instance based on WinForm on net core 3.1

Thanks to the author.

Here I will explain to the reader that servicecollection is a service container, serviceprovider is a service manager, and manages the service container. When a program sends an abstract interface or type, serviceprovider will return the required instance and configure the instance life cycle for the programmer according to the set life cycle.

2.1 purpose of dependency injection

Through the control inversion of the agent mode serviceprovider, it will hold the control right, reflect all the required interfaces, types, corresponding instances, instantiate and set the life cycle of the instance, and then return the control right to the programmer. It does not need to go to new instances, consider the dependency between instances, or consider the life cycle of the instance The ultimate goal is to liberate the productivity of programmers and make them write programs more easily.

2.2 benefits of dependency injection

2.2.1 life cycle control

The following three life cycles can be set at the same time of injection:

  • Transient
    At each injection, a new instance is re created.
  • Scoped
    Each request renews a new instance. The same request uses the same instance no matter how many pipelines it passes.
  • Singleton
    After being instantiated, it will not disappear. There will only be one instance during the running of the program.

    2.2.1.1 life cycle test example

  • Define three different life cycle interfaces for the same example
public interface ISample
{
    int Id { get; }
}

public interface ISampleTransient : ISample
{
}

public interface ISampleScoped : ISample
{
}

public interface ISampleSingleton : ISample
{
}

public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
    private static int _counter;
    private int _id;

    public Sample()
    {
        _id = ++_counter;
    }

    public int Id => _id;
}
  • Register the corresponding service and interface into the container
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient();
        services.AddScoped();
        services.AddSingleton();
        //Singleton can also be registered in the following ways
        // services.AddSingleton(new Sample());
    }
}
  • Get the hashcode of the corresponding Di instance in the controller
public class HomeController : Controller
{
    private readonly ISample _transient;
    private readonly ISample _scoped;
    private readonly ISample _singleton;

    public HomeController(
        ISampleTransient transient,
        ISampleScoped scoped,
        ISampleSingleton singleton)
    {
        _transient = transient;
        _scoped = scoped;
        _singleton = singleton;
    }

    public IActionResult Index() {
        ViewBag.TransientId = _transient.Id;
        ViewBag.TransientHashCode = _transient.GetHashCode();

        ViewBag.ScopedId = _scoped.Id;
        ViewBag.ScopedHashCode = _scoped.GetHashCode();

        ViewBag.SingletonId = _singleton.Id;
        ViewBag.SingletonHashCode = _singleton.GetHashCode();
        return View();
    }
}
  • Vewbag display component
Cotroller
    LifetimesIdHash Code
    [email protected]@ViewBag.TransientHashCode
    [email protected]@ViewBag.ScopedHashCode
    [email protected]@ViewBag.SingletonHashCode

You can do your own test. Please refer to this blog for details

2.2.2 decouples the presentation layer (caller) from the service class

As mentioned above, the instance is called through the interface in the homecontroller, so the modifier only needs to be modified in the instance, not in the call layer.
This conforms to the dependency inversion principle in the six programming principles:
1. The high-level module should not rely on the low-level module, and both should rely on its abstraction
The presentation layer controller does not rely on the model layer sample class. Both depend on the sample interface abstraction isample, isampletransient, isamplescoped, isamplesingleton
2. Abstraction should not depend on details
The interface layer only defines the specification, no details.

public interface ISample
{
    int Id { get; }
}

public interface ISampleTransient : ISample
{
}

public interface ISampleScoped : ISample
{
}

public interface ISampleSingleton : ISample
{
}

3. Details should depend on abstraction
Instance fetching in di depends on interface:

ISampleTransient transient;

The implementation of the service class also depends on the interface:

public class Sample : ISampleTransient, ISampleScoped, ISampleSingleton
{
    private static int _counter;
    private int _id;

    public Sample()
    {
        _id = ++_counter;
    }

    public int Id => _id;
}

2.2.3 developers no longer need to consider the relationship between dependencies

So that programmers no longer need to consider the dependency between di instances, as well as many interdependent instances of new.

2.3 design pattern for dependency injection

2.3.1 agency mode

At the place where the service call depends on the injection, the container manager serviceprovider takes control from the programmer, controls the required service instantiation and sets its life cycle, and then returns it to the programmer.

2.3.2 factory mode

According to the life cycle settings of Di, instances of various life cycles are produced according to the interface or type. It should be noted that there may be the same instance (in a single request of scope, or in the transient life cycle). Every time transient generates a new instance.

3. Implement dependency injection based on WinForm on net core 3.1

3.1 WinForm support in net core 3.1.

I found that in the latest vs distribution, we can create WinForm project, but we can’t open the designer or the toolbox of WinForm. What should I do?
It is mentioned in the official blog of Microsoft that WinForm designer is supported in the vs16.5 preview. According to the blog, you need to download the vs16.5 preview here.

The screenshot of netcore3.1 WinForm is as follows:

Implementation of dependency injection instance based on WinForm on net core 3.1

It can be seen that the controls are much better than those based on the dot net framework. At the same time, there are few controls in the toolbox. Microsoft has deleted some old controls that have already been replaced, and will slowly add some necessary controls in the future.

3.2 what is the difference between WinForm dependency injection and net core MVC?

Net core MVC container is created automatically. You only need to configure the service in the configureservices method. After creating WinForm project on net core 3.1, the form is a new instance, running as a single instance. You need to create the configuration of the container by yourself.

static class Program
    {
        /// 
        ///  The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

If you need to inject services into the form form, you need to pass in the arguments when the new instance occurs.

[STAThread]
  static void Main()
  {
      Application.SetHighDpiMode(HighDpiMode.SystemAware);
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
 
      var services = new ServiceCollection();
 
      ConfigureServices(services);
 
      using (ServiceProvider serviceProvider = services.BuildServiceProvider())
      {
          var logg = services.BuildServiceProvider().GetRequiredService>();
 
          var businessObject = services.BuildServiceProvider().GetRequiredService();
 
          Application.Run(new Form1(logg, businessObject));
      }
  }

When calling, use the form’s constructor to call the service interface.

public partial class Form1 : Form
    {
        private readonly ILogger _logger;
 
        private readonly IBusinessLayer _business;
        public Form1(ILogger logger, IBusinessLayer business)
        {
            _logger = logger;
            _business = business;
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                _logger.LogInformation("Form1 {BusinessLayerEvent} at {dateTime}", "Started", DateTime.UtcNow);
 
                // Perform Business Logic here 
                _business.PerformBusiness();
 
                MessageBox.Show("Hello .NET Core 3.0 . This is First Forms app in .NET Core");
 
                _logger.LogInformation("Form1 {BusinessLayerEvent} at {dateTime}", "Ended", DateTime.UtcNow);
 
            }
            catch (Exception ex)
            {
                //Log technical exception 
                _logger.LogError(ex.Message);
                //Return exception repsponse here
                throw;
 
            }
 
        }
    }

This method is excerpted from this paper

There are at least two disadvantages:

  1. The dependency injection instance call of the constructor in form1 is leaked in its call layer, which does not conform to the dependency inversion principle in the six programming principles;
  2. When interface instance calls need to be added from Di in form1, corresponding arguments need to be added in the following call code. And there are many arguments, which will be very lengthy.
    Application.Run(new Form1(logg, businessObject));

    3.3 solution to 3.2

    The form type is also injected into the container in the form of singleton. When calling, the service of MainForm type is obtained. In this way, this service instance depends on other services. The service provider container manager will automatically solve the dependency relationship between services, instantiate the corresponding services and set them according to the life cycle, and give them to the programmers for use. The problem is solved perfectly.

In view of the following two articles
Microsoft MSDN
stackoverflow
Here I would like to highlight stackoverflow, a world-class programmer’s forum. When I encounter a lot of difficult and helpless problems, he will give me ideas, directions and even solutions. Once again, I salute stackoverflow and thank Google.

3.4 code implementation

3.4.1 establish static method of service registration in program.cs

private static void ConfigureServices(ServiceCollection services)
        {
            //App
            services.ApplicationServiceIoC();
            //Infra

            //Repo
            services.InfrastructureORM();


            //Presentation other forms can also be injected here
            services.AddSingleton(typeof(MainForm));
        }

It should be noted that the IOC here is the application layer, presentation layer and storage layer. Each layer has written the static method of the servicecollection service container, so the service can be injected at each layer, and the reader can inject his own service here without investigation.
Layered injection:

Implementation of dependency injection instance based on WinForm on net core 3.1

Simple implementation of layered injection
Cameradm service is registered in applicationserviceioc, and applicationserviceioc is registered in configureservices. That’s what I just said about layer by layer dependency injection.

public static class ServicesIoC
    {
        public static void ApplicationServiceIoC(this IServiceCollection services)
        {
            services.AddScoped(typeof(IServiceBase<>), typeof(ServiceBase<>));
            services.AddScoped();
        }
    }

Focus on
Inject the form type. Of course, other forms can be added later in the same way.

services.AddSingleton(typeof(MainForm));

3.4.2 create service container object

var services = new ServiceCollection();

3.4.3 add service registration

ConfigureServices(services);

This step calls the method in 3.4.1.

3.4.4 build serviceprovider object

var serviceProvider = services.BuildServiceProvider();

3.4.5 running MainForm service

Request the instance service of MainForm type from the service manager. See 2.1 for the specific calling process.

Application.Run(serviceProvider.GetService());

This step is the key point. It is also the difference between WinForm and MVC, but the essence is the same. Serviceprovider manages WPF, WinForm or MVC instances and their corresponding types, but MVC container has been created, and serviceprovider, container manager, has been created. Just add services to the container directly. WinForm, WPF, net core console The program requires us to add the registration service to the container and create the container manager serviceprovider. Because the servicecollection container is dead, only when the agent role of serviceprovider container manager is created can the container embody its value. But there is only serviceprovider, and there is no service in servicecollection.

3.4.1 to 3.4.5 the overall code is as follows:

static class Program
    {
        /// 
        ///  The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Create service container object
            var services = new ServiceCollection();

            //Add service registration
            ConfigureServices(services);
            //Build serviceprovider object
            var serviceProvider = services.BuildServiceProvider();
            //Request instance service of MainForm type from service manager
            Application.Run(serviceProvider.GetService());    
        }
        private static void ConfigureServices(ServiceCollection services)
        {
            //App
            services.ApplicationServiceIoC();
            //Infra

            //Repo
            services.InfrastructureORM();


            //Presentation other forms can also be injected here
            services.AddSingleton(typeof(MainForm));
        }
    }

3.4.6 calling Di instance with constructor method

public partial class MainForm : Form
    {
        ICameraDM_Service _cameraDM_Service;
        public MainForm(ICameraDM_Service cameraDM_Service)
        {
            _cameraDM_Service = cameraDM_Service;
            InitializeComponent();          
        }
        private async void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show(_cameraDM_Service.GetAllCameraInfo().ToList().Count().ToString());
            var _camera  =await _cameraDM_Service.GetAllIncludingTasksAsync();
            //textBox1.Text = _camera.ToList().Count().ToString();
            var _cameraNo3 = await _cameraDM_Service.GetByIdAsync(3);
            textBox1.Text = _cameraNo3.InstallTime.ToString();
        }
    }

3.5 demonstration effect

Click the button to get the number of cameras from the camera service.
Implementation of dependency injection instance based on WinForm on net core 3.1

Click OK to get the installation time of camera 3 from the camera service.

Implementation of dependency injection instance based on WinForm on net core 3.1

3.6 how to call the child form in the parent form by using dependency injection.

3.6 is added at 12 / 23 / 13:33, in order to answer the questions asked by netizens on the first floor.
The questions are as follows:
Suppose that button1 “click in MainForm needs to open other windows, how to implement it? (can only serviceprovider be passed to MainForm?)

The serviceprovider is designed to be global static, which can be designed as a singleton mode or directly placed in the main attribute, so that any global subform can access the di instance. Of course, other forms also need to be injected into the container.

3.6.1 injection subform

The injection life cycle is of form1 type instantaneous.
services.AddTransient(typeof(Form1));

private static void ConfigureServices(ServiceCollection services)
        {
            //App
            services.ApplicationServiceIoC();
            //Infra

            //Repo
            services.InfrastructureORM();

            //Presentation other forms can also be injected here
            services.AddSingleton(typeof(MainForm));
            services.AddTransient(typeof(Form1));
            
        }

Because form1 is a subform of MainForm, and MainForm is set to singleton mode, opening form1 in MainForm belongs to the same request. You cannot use addsingleton and addscope modes. If the above two modes are used, the following exceptions will be reported:
For example, set form1 life cycle as singleton mode

services.AddSingleton(typeof(MainForm));
            services.AddSingleton(typeof(Form1));

The first call is normal,

Implementation of dependency injection instance based on WinForm on net core 3.1

When form1 is closed and the button 1 of MainForm is clicked for the second time, the following exception is reported:

Implementation of dependency injection instance based on WinForm on net core 3.1

Because it is a singleton mode, we closed form1 and MainForm is still there. Click button 1 again, we will not find the form1 instance whose life cycle is singleton mode, and the above exception will be reported.
Modified into

services.AddTransient(typeof(Form1));

The problem is solved perfectly. No matter how many times form1 is closed, form1 can be opened through the button1 call of MainForm.

3.6.2 set global serviceprovider Container Service Manager

Modify serviceprovider as the public property (global) of program static class to container servicer to obtain Di instance for component in subform or other WinForm
public static IServiceProvider serviceProvider { get; set; }
The code of program.cs is as follows.

static class Program
    {
        public static IServiceProvider serviceProvider { get; set; }
        /// 
        ///  The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {
            
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Create service container object
            var services = new ServiceCollection();

            //Add service registration
            ConfigureServices(services);
            //Build serviceprovider object
            serviceProvider = services.BuildServiceProvider();

        //Request instance service of MainForm type from service manager
        Application.Run(serviceProvider.GetService());    
        }
        private static void ConfigureServices(ServiceCollection services)
        {
            //App
            services.ApplicationServiceIoC();
            //Infra

            //Repo
            services.InfrastructureORM();

            //Presentation other forms can also be injected here
            services.AddSingleton(typeof(MainForm));
            services.AddTransient(typeof(Form1));

        }
    }

Calling Form1 in 3.6.3 MainForm

Go to the program service manager property to get the form1 instance of the required type with the life cycle set. Show form1.

public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();          
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            var child = (Form)Program.serviceProvider.GetService((typeof(Form1)));
            child.Show();
        }
    }

3.6.4 calling Di instance with constructor method

Form1 calls camera service
Here is the same as 3.5

public partial class Form1 : Form
    {

        ICameraDM_Service _cameraDM_Service;
        public Form1(ICameraDM_Service cameraDM_Service)
        {
            _cameraDM_Service = cameraDM_Service;
            InitializeComponent();

        }
        private async  void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show(_cameraDM_Service.GetAllCameraInfo().ToList().Count().ToString());
        }
    }

3.6.5 effect

Implementation of dependency injection instance based on WinForm on net core 3.1

4. last

Originally I wanted to write a short article, but I didn’t know it was a little long. If you have any doubts after reading this article, please put them forward, and I will patiently answer them; if there is any improper or incorrect knowledge or different opinions, please also point out that I am eager for progress at the same time. Finally, I wish you all a happy winter solstice.