Implementation of MVP based on ApplicationController and IoC in a WinForms application

Good afternoon!

In this article, I will talk about how I implemented the MVP pattern in my Windows Forms application and describe practical situations and features of using IoC and ApplicationController. Switching from codebehind to MVP allowed me:

  • improve readability due to better code separation (SRP) - separate BL from View;
  • to develop a methodology for further expanding the functionality of the application;
  • get rid of singleton, which I used to work with application settings.

About app


An app to help manage VC groups. Allows you to fill a group with pending posts. The main functionality at the moment is loading pending posts into the VK group with pictures or videos, hashtags, polls, geo-location and the ability to configure the publication time and the number of posts by day. At the moment, the application has one form.

With the growth of its functionality, a lot of codebehind accumulated, which began to confuse the fact that the form became very loaded and contained everything in itself. When planning the further development of the project, I could not imagine what could be continued further in the same spirit, and now the moment came when the main task was not refining the functional, but refactoring. And I began to look for a solution that would help optimize and split the code and generally improve the application architecture so that it would be more pleasant to work with it.

Source

Refactoring Solution


The solution was to implement the MVP pattern. As a basis, I took the article MVP Implementation Features for Windows Forms .

The article parses an extended example of a simple application with 3 forms: 2 main and 1st modal. The article discusses a very advanced approach:

  • in addition to ApplicationController and IoC, Adapter is also used there, which allows using different IoCs;
  • 3 types of forms: with parameters, without parameters and modal;
  • DIP principle is widely applied.

In my project, I use only one form without arguments, abandoned the adapter (following the YAGNI principle), since IoC Lightinject is enough for me and to a lesser extent use DIP to simplify the project.

MVP implementation


MVP (Model-View-Presenter) is a design pattern designed for the convenience of separating business logic from the way it is displayed. You can read more about the theory in the article above. I will describe the components in my implementation:

  • Model is a data structure transferred between View and Presenter and contains data both for display and for execution of logic. In my case, the model is Settings. When the project starts, Settings are loaded into MainFormView, and when loading starts, MainFormView checks and passes Settigns to Presenter, so that Presenter runs the logic on its side.
  • View is a form in which data is displayed to the user. In my case, this is data from the Settings model, and View also provides events so that Presenter associates View with BL.

MainFormView implements a common IView interface common to all View

    public interface IView
    {
        void Show();

        void Close();
    }

as well as a private IMainFormView interface specific to this View only. At the beginning, I thought about abandoning it, but if you associate Presenter directly with the form, then when working with such a View, the whole set of methods specific to Form will be available, which is not convenient.

    public interface IMainFormView: IView
    {
        void LoadSettings(Settings settings);

        void UpdateSettings(Settings settings);

        void ShowMessage(string message);

        void LoadGroups(List<Group> groups);

        void EnableVKUploadGroupBox();

        bool Check();

        event Action Login;

        new event Action Close;

        event Action VKUpload;
    }

Another MVP innovation is that the Show method has been replaced in the form and ApplicationContext is passed to the form through the constructor, so that when switching from form to form and closing, the main form is reassigned.

        protected ApplicationContext _context;

        public MainForm(ApplicationContext context)
        {
            _context = context;
            InitializeComponent();

            dateTimePickerBeginDate.Format = DateTimePickerFormat.Custom;
            dateTimePickerBeginDate.CustomFormat = "MM/dd/yyyy hh:mm:ss";

            buttonAuth.Click += (sender, args) => Invoke(Login);
            this.FormClosing += (sender, args) => Invoke(Close);
            buttonLoad.Click += (sender, args) => Invoke(VKUpload);
        }

        public new void Show()
        {
            _context.MainForm = this;
            Application.Run(_context);
        }

- Presenter is a class that encapsulates View, Services, and business logic (BL), with the help of which it organizes the interaction between View and Services. BL is mainly implemented in View event handlers. Unlike the previously used CodeBehind, in MVP event handlers executing BL are displayed in Presenter, and for simplicity, events in View are displayed in the form of Action without arguments. Handlers receive all the necessary data for execution through the model obtained from the form through the public method.

Presenter contains the Run method, which is called by the ApplicationController and which launches the form:

    public interface IPresenter
    {
        void Run();
    }

ApplicationController - a single point of control and execution of the entire application. Encapsulates all the logic in itself: IoC, Presenters, View, Services.

Management is via the Run method, which calls the corresponding Presenter. All Presenters are connected to each other through the ApplicationController, which the Presenter receives in the constructor. Thus, the Presenter can call another Presenter by calling the Run method, which internally calls the IoC Container to get the desired Presenter and start it.

    public class ApplicationController
    {
        ServiceContainer _container;

        public ApplicationController(ServiceContainer serviceContainer)
        {
            _container = serviceContainer;
            _container.RegisterInstance<ApplicationController>(this);
        }

        public void Run<TPresenter>() where TPresenter:class, IPresenter
        {
            var presenter = _container.GetInstance<TPresenter>();
            presenter.Run();
        }
    }

IoC container is an aggregator of all the "dependencies" used in the application logic. It contains:

  • View constructors
  • Constructors Presenters
  • service instances
  • application context
  • ApplicationController

All dependencies are added to the container during startup, this can be seen in the Program.cs file:

         static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            ulong appid = ulong.Parse(ConfigurationManager.AppSettings["AppIdForTest"]);
            VKGroupHelperWorker vk = new VKGroupHelperWorker(appid);


            ServiceContainer container = new ServiceContainer();
            container.RegisterInstance<VKGroupHelperWorker>(vk);
            container.RegisterInstance<Settings>(Globals.Settings);
            container.RegisterInstance<ApplicationContext>(Context);
            container.Register<IMainFormView,MainForm>();
            container.Register<MainFormPresenter>();

            ApplicationController controller = new ApplicationController(container);
            controller.Run<MainFormPresenter>();
        }

For IoC, I used the Lightinject component, which must be installed through NPM before use.

Thus, the container can contain both object constructors and the objects themselves, as is done with Settings and VKGroupHelperWorker (VK API client), forming the set of all application resources used. A useful feature of the container is that all these embedded resources, classes can be obtained through the constructor arguments. For example,
ApplicationController, IMainFormView, VKGroupHelperWorker - previously implemented dependencies, which can be either object constructors or instances. If an instance has been implemented, then all generated objects will work with the same instance, which allows you to get rid of the singleton pattern if it was used.

public MainFormPresenter(ApplicationController applicationController, IMainFormView mainForm, Settings settings, VKGroupHelperWorker vk)
        {
            _view = mainForm;
            _settings = settings;
            _vk = vk;

            _view.Login += () => Login();
            _view.Close += () => Close();
            _view.VKUpload += () => VKUpload();
        }

Implementation of MVP allowed me to:

  • partially get rid of Singleton, which I used to work with application settings;
  • Separate BL from View, thereby improving code separation (SRP)
  • to develop an approach to further expand the application without cluttering the View.

More information about what has been done can be found in the project repository .

All Articles