Winforms - MVP模式:使用静态ApplicationController协调应用程序? [英] Winforms - MVP Pattern: Using static ApplicationController to coordinate application?

查看:259
本文介绍了Winforms - MVP模式:使用静态ApplicationController协调应用程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景



我正在构建一个两层C#.net应用程序:


  1. 方法1:使用MVP(Model-View-Presenter)设计模式Winforms客户端应用程序。

  2. 第2层:WebAPI RESTful服务的实体框架和SQL Server。

目前,我对Winforms客户端应用程序的整体架构有疑问。我是一个新的编程(大约一年),但我已经取得了良好的进步与这个应用程序。我想退后一步,重新评估我现在的方法,以检查一般是否正确的方向。



应用程序域 / p>

Winforms应用程序是一个相当简单的安全人员跟踪应用程序。主要视图(Form)是应用程序的重点,并且具有将内容分组到功能区域的不同部分(例如用于跟踪人员进度的部分,用于跟踪谁分配在哪里的部分等)。应用程序侧面的菜单启动辅助视图(例如,历史记录,统计信息,联系人等)。这个想法是,安全办公室可以使用该应用程序来组织日常操作,然后在数据库中保留所有内容的详细历史记录。



技术细节



如上所述,Winforms客户端是使用MVP模式(被动视图)构建的,重点是尽可能多地使用依赖注入(通过SimpleInjector IoC容器)。每个视图(形式)与单个演示者配对。视图实现接口,允许演示者控制视图(无论具体实现)。这个观点引发了主持人订阅的事件。目前,演示者不能直接与另一位演示者沟通。



应用程序控制器用于协调应用程序。这是我的应用程序架构,我是最摇摇欲坠的(因此是标题)。应用程序控制器目前用于:


  1. 打开新视图(表单)并管理打开的表单。

  2. 通过事件聚合器促进应用程序组件之间的通信。一个主持人发布一个事件,任何数量的演示者都可以订阅该事件。

  3. 主机会话信息(即安全上下文/登录,配置数据等)

应用程序启动时,IoC容器已注册到应用程序控制器中。这允许应用程序控制器从容器中创建演示者,然后使所有后续的依赖关系(视图,服务等)由容器自动处理。



问题



为了使应用程序控制器可供所有演示者使用,我已经将控制器创建为静态类。 / p>

  public static class ApplicationController 
{
private static Session _session;
private static INavigationWorkflow _workflow;
private static EventAggregator _aggregator;

#region注册

public static void RegisterSession(Session session){}

public static void RegisterWorkflow(INavigationWorkflow workflow){}

public static void RegisterAggregator(EventAggregator aggregator){}

#endregion

#region属性

public static会话会话
{
get {return _session;
}

#endregion

#region导航

public static void NavigateToView(Constants.View view){}

#endregion

#region事件

public static Subscription< TMessageType>订阅< TMessageType>(Action< TMessageType> action){}

public static void Publish< TMessageType>(TMessageType message){}

public static void取消订阅< TMessageType>订阅< TMessageType>订阅){}

#endregion
}



<这是一个可以接受的做法,这样做一个静态类?我的意思是,它肯定有效。它只是感觉...关闭?还有什么其他的洞可以根据我的描述在建筑中看到?



-



** 编辑 **



此修改是针对Ric .Net的答案发布的。



我已经阅读了你的所有建议。由于我致力于尽可能地利用依赖注入,所以我可以提供所有的建议。那是从一开始的我的计划,但是当我遇到的东西我不明白如何通过注入完成,我转向全局静态控制器类来解决我的问题(一个神级,它确实变了,Yikes!) 。其中一些问题依然存在:



事件聚合器



是什么应该被认为是可选的,我想。在概述我的问题之前,我将提供一些有关我的应用程序的内容。使用网络术语,我的主要形式通常如布局视图,托管导航控件和左侧菜单中的通知部分,部分视图托管在中心。回到winforms术语,部分视图只是定制的UserControls,我对待像视图,并且每个都与自己的演示者配对。我的主要表格中有6个部分视图,它们作为应用程序的肉和土豆。



例如,一个局部视图列出了可用的保安人员,另一个列出了潜在的巡逻区域。在典型的用例中,用户将可用的安全警卫从可用的列表拖到潜在的巡逻区之一,有效地分配给该区域。然后巡视区域视图将更新,以显示分配的保安人员,并将卫兵从可用的列表视图中删除。使用拖放事件,我可以处理这种交互。



当我需要处理各种部分视图之间的其他类型的交互性时,我的问题来了。例如,双击分配给某个位置的警卫(如在一个部分视图中看到的)可以突出显示另一部分视图上的守卫姓名,显示所有人员进度表,或者在另一部分视图中显示员工详细信息/历史记录。我可以看到部分视图对其他局部视图中发生的事件感兴趣的图形/矩阵变得相当复杂,我不知道如何通过注入来处理它。有6个部分视图,我不想将其他5个部分视图/演示者注入每个视图。我正计划通过事件聚合器完成这个。另一个例子我可以想到,需要根据在主窗体上的一个部分视图上发生的事件单独的视图(自己的形式)更新数据。



会话&表单开启者



我真的很喜欢你的想法。我将要采取这些想法并与他们一起运行,并看看我在哪里结束!



安全性



您有什么想法根据他们拥有的帐户类型控制用户访问某些功能?我在线阅读的建议表示,可以通过根据帐户类型修改视图来实现安全性。思想是,如果用户无法与UI元素交互来启动某个任务,则演示者将永远不会被要求执行该任务。如果您将WindowsUserContext注入每个演示者并进行其他检查,特别是对于http服务绑定请求,我很好奇。



我没有做太多的开发服务但是对于http服务绑定请求,我想象您需要发送安全信息以及每个请求,以便服务可以对请求进行身份验证。我的计划是将WindowsUserContext直接注入到最终提出服务请求的winforms服务代理(即安全验证不会来自演示者)。在这种情况下,服务代理可以在发送请求之前进行最后一分钟的安全检查。

解决方案

静态类是在某些情况下当然可以使用,但是这种做法有很多缺点。




  • 倾向于成长为像上帝类的东西。你已经看到这个发生了。所以这个类违反了 SRP

  • 静态类不能因此需要使用服务定位器反模式来获取它是依赖关系。如果您认为此课程是组合根,但是,这往往是错误的方式。



在提供的代码中,我看到这个类的三个职责。


  1. EventAggregator

  2. 你所说的会话信息

  3. 打开其他视图的服务

有关这三个部分的反馈: / p>

EventAggregator



虽然这是一个广泛使用的模式,有时它可以是非常强大的我本人不喜欢这种模式。我看到这种模式是提供可选运行时数据的东西,在大多数情况下,这个运行时数据根本不是可选的。换句话说,只能使用这种模式来进行真正的可选数据。对于不是真正可选的一切,使用硬依赖,使用构造函数注入。



在这种情况下需要信息的那些取决于 IEventListener< TMessage> 。发布该事件的,取决于 IEventPublisher< TMessage>

  public interface IEventListener< TMessage> 
{
事件操作< TMessage>收到消息;
}

public interface IEventPublisher< TMessage>
{
void Publish(TMessage message);
}

public class EventPublisher< TMessage> :IEventPublisher< TMessage>
{
private readonly EventOrchestrator< TMessage>协调器;

public EventPublisher(EventOrchestrator< TMessage> orchestrator)
{
this.orchestrator = orchestrator;
}

public void Publish(TMessage message)=> this.orchestrator.Publish(消息);
}

public class EventListener< TMessage> :IEventListener< TMessage>
{
private readonly EventOrchestrator< TMessage>协调器;

public EventListener(EventOrchestrator< TMessage> orchestrator)
{
this.orchestrator = orchestrator;
}

public event Action< TMessage> MessageReceived
{
add {orchestrator.MessageReceived + = value; }
remove {orchestrator.MessageReceived - = value; }
}
}

public class EventOrchestrator< TMessage>
{
public void Publish(TMessage message)=> this.MessageReceived(消息);
public event Action< TMessage> MessageReceived =(e)=> {};
}

为了保证事件存储在一个位置,我们提取存储(事件)到自己的类中, EventOrchestrator



注册如下:

  container.RegisterSingleton(typeof(IEventListener&),typeof(EventListener< >)); 
container.RegisterSingleton(typeof(IEventPublisher<)),typeof(EventPublisher<>));
container.RegisterSingleton(typeof(EventOrchestrator)),typeof(EventOrchestrator));

使用简单:

  public class SomeView 
{
private readonly IEventPublisher< GuardChanged> eventPublisher;

public SomeView(IEventPublisher< GuardChanged> eventPublisher)
{
this.eventPublisher = eventPublisher;
}

public void GuardSelectionClick(Guard guard)
{
this.eventPublisher.Publish(new GuardChanged(guard));
}
//其他代码
}

public class SomeOtherView
{
public SomeOtherView(IEventListener< GuardChanged> eventListener)
{
eventListener.MessageReceived + = this.GuardChanged;
}

private void GuardChanged(GuardChanged changedGuard)
{
this.CurrentGuard = changedGuard.SelectedGuard;
}
//其他代码..
}

如果另一个视图将收到很多事件,您可以随时将该View的所有IEventListener包装在特定的 EventHandlerForViewX 类中,该类可以获得所有重要的 IEventListener<> / code>注入。



会话



在这个问题中,您定义了几个环境上下文变量为会话信息。通过静态类暴露这种信息可以促进与这个静态类的紧密耦合,从而使应用单元测试部分变得更加困难。 IMO由 Session 提供的所有信息是静态的(在应用程序的整个生命周期中它不会改变),这些数据可以很容易地注入到那些实际上需要这个数据。因此,应该完全从静态类中删除 Session 。一些例子如何以SOLID的方式解决这个问题:



配置值



组合根负责从配置源读取所有信息(例如您的app.config文件)。该信息可以存储在为其使用而制作的POCO类中。

  public interface IMailSettings 
{
string MailAddress {get; }
string DefaultMailSubject {get; }
}

public interface IFtpInformation
{
int FtpPort {get; }
}

public interface IFlowerServiceInformation
{
string FlowerShopAddress {get;
}

public class ConfigValues:
IMailSettings,IFtpInformation,IFlowerServiceInformation
{
public string MailAddress {get;组; }
public string DefaultMailSubject {get;组; }

public int FtpPort {get;组; }

public string FlowerShopAddress {get;组;
}
//注册为
public static void RegisterConfig(此容器容器)
{
var config = new ConfigValues
{
MailAddress = ConfigurationManager.AppSettings [MailAddress],
DefaultMailSubject = ConfigurationManager.AppSettings [DefaultMailSubject],
FtpPort = Convert.ToInt32(ConfigurationManager.AppSettings [FtpPort]),
FlowerShopAddress = ConfigurationManager.AppSettings [FlowerShopAddress],
};

var registration = Lifestyle.Singleton.CreateRegistration< ConfigValues>(()=>
config,container);
container.AddRegistration(typeof(IMailSettings),注册);
container.AddRegistration(typeof(IFtpInformation),注册);
container.AddRegistration(typeof(IFlowerServiceInformation),注册);
}

您需要某些特定信息,例如要发送电子邮件的信息,您可以将 IMailSettings 放在需要信息的类型的构造函数中。



这也将使您可以使用不同的配置值测试组件,如果所有配置信息必须来自静态 ApplicationController



对于安全信息,例如可以使用登录用户相同的模式。定义一个 IUserContext 抽象,创建一个WindowsUserContext实现,并使用组合根中的登录用户填写。因为该组件现在取决于 IUserContext ,而不是从静态类获取用户的运行时间,同样的组件也可以在MVC应用程序中使用,您可以在其中替换 WindowsUserContext HttpUserContext 实现。



打开其他表单



这实际上是很难的部分。我通常还会使用一些大型静态类与各种方法来打开其他形式。我不会将 IFormOpener 这个答案给我的其他表单,因为他们只需要知道,该怎么办,而不是为他们的任务形式。所以我的静态类暴露了这种方法:

  public SomeReturnValue OpenCustomerForEdit(客户客户)
{
var form = MyStaticClass.FormOpener.GetForm< EditCustomerForm>();
form.SetCustomer(customer);
var result = MyStaticClass.FormOpener.ShowModalForm(form);
return(SomeReturnValue)result;
}

然而....


$ b $我对这种方法并不满意,因为随着时间的流逝,这个班级的成长和发展。使用WPF我使用另一种机制,我认为也可以与WinForms一起使用。此方法基于中描述的基于邮件的架构,以及这个令人敬畏的博客。虽然起初这些信息看起来并不完全相关,但它是基于消息的概念,让这些模式摇滚!



我所有的WPF窗口都实现了一个开放的通用接口,例如IEditView。如果一些视图需要编辑一个客户,那么只需要注入这个IEditView。使用装饰器实际上以与前面提到的 FormOpener 相同的方式显示视图。在这种情况下,我使用一个特定的简单注入器功能,称为装饰工厂装饰器,您可以在需要时使用它们来创建表单,就像 FormOpener 一样,直接使用容器来创建表单它需要。



所以我没有真正测试这个,所以WinForms可能会有一些陷阱,但是这个代码似乎在第一次和单次运行时起作用。

  public class EditViewShowerDecorator< TEntity> :IEditView< TEntity> 
{
private readonly Func< IEditView< TEntity>> viewCreator;

public EditViewShowerDecorator(Func< IEditView< TEntity>> viewCreator)
{
this.viewCreator = viewCreator;
}

public void EditEntity(TEntity entity)
{
//从容器获取视图
var view = this.viewCreator.Invoke();
// initview with information
view.EditEntity(entity);
使用(var form =(Form)view)
{
//显示视图
form.ShowDialog();
}
}
}

表单和装饰应该是注册为:

  container.Register(typeof(IEditView&)),new [] {Assembly.GetExecutingAssembly()}) ; 
container.RegisterDecorator(typeof(IEditView<)),typeof(EditViewShowerDecorator),
Lifestyle.Singleton);

安全



IUserContext 必须具有所有安全性的基础。



对于用户界面,我通常会隐藏某个userrole无法访问的所有控件/按钮。最好的地方是在 Load 事件中执行此操作。



因为我使用了所描述的命令/处理程序模式 here 对于我所使用的表单/视图外的所有操作一个装饰器来检查用户是否有权执行这个特定的命令(或查询)。



我建议你几次阅读这篇文章,直到你真的得到它的悬念。一旦你熟悉这种模式,你将不会做任何其他事情!



如果您对这些模式有任何疑问,以及如何应用(权限)装饰器,请添加评论!


Background

I'm building a two-tiered C# .net application:

  1. Tier 1: Winforms client application using the MVP (Model-View-Presenter) design pattern.
  2. Tier 2: WebAPI RESTful service sitting on top of Entity Framework and SQL Server.

Currently, I have questions relating to the overall architecture of the Winforms client application. I'm new to programming (about a year) but I've made good progress with this application. I want to step back and re-evaluate my current approach to check that I'm generally heading in the right direction.

Application Domain

The Winforms application is a fairly simple security personnel tracking application. The main view (Form) is the focus of the application, and has different sections which group content into functional areas (e.g. a section for tracking personnel schedules, a section for tracking who is assigned where, etc.). A menu on the side of the application launches secondary views (e.g. history, statistics, contacts, etc.). The idea is that the app could be used by a security office to organize daily operations and then keep a detailed history of everything in a database for reporting on in the future.

Technical Details

As mentioned, the Winforms client is built using the MVP pattern (passive view), focusing on using dependency injection as much as possible (via SimpleInjector IoC container). Each view (form) is paired up with a single presenter. The views implement interfaces, allowing the presenter to control the view (regardless of the concrete implementation). The view raises events for the presenter to subscribe to. Currently, presenters are not allowed to directly communicate to another presenter.

An application controller is used to coordinate the application. This is the area of my application architecture where I'm the most shakey (hence the post title). The application controller is currently used to:

  1. Open new views (forms) and manage open forms.
  2. Facilitate communication between application components via an event aggregator. One presenter publishes an event and any number of presenter can subscribe to that event.
  3. Host session information (i.e. security context/logon, config data, etc.)

The IoC container is registered into the application controller at application start-up. This allows the application controller, for example, to create a presenter from the container, and then have all subsequent dependencies (view, services, etc.) to be automatically handled by the container.

Question

In order to make the Application Controller accessible to all presenters, I have created the controller as a static class.

public static class ApplicationController
{
    private static Session _session;
    private static INavigationWorkflow _workflow;
    private static EventAggregator _aggregator;

    #region Registrations

    public static void RegisterSession(Session session) {}

    public static void RegisterWorkflow(INavigationWorkflow workflow) {}

    public static void RegisterAggregator(EventAggregator aggregator) {}

    #endregion

    #region Properties

    public static Session Session
    {
        get { return _session; }
    }

    #endregion

    #region Navigation

    public static void NavigateToView(Constants.View view) {}

    #endregion

    #region Events

    public static Subscription<TMessageType> Subscribe<TMessageType>(Action<TMessageType> action) {}

    public static void Publish<TMessageType>(TMessageType message) {}

    public static void Unsubscribe<TMessageType>(Subscription<TMessageType> subscription) {}

    #endregion
}

Is this considered an acceptable practice to make a static class like this? I mean, it certainly works. It just feels... off? Are there any other holes that you can see in my architecture based on what I have described?

-

** EDIT **

This edit is made in response to Ric .Net’s answer posted below.

I have read through all of your suggestions. As I am committed to utilizing dependency injection to the fullest extent I can, I’m onboard with all of your suggestions. That was my plan from the beginning, but when I ran into things I didn’t understand how to accomplish via injection, I turned to the global static controller class to solve my problems (A god class it is becoming, indeed. Yikes!). Some of those questions still exist:

Event Aggregator

The defining line here is what should be considered optional, I think. I’ll provide a bit more context about my app before outlining my problem. Using web terminology, my main form generally acts like a layout view, hosting navigation controls and a notification section in the left menu, and partial views being hosted in the center. Coming back to winforms terminology, the partial views are just custom made UserControls that I treat like views, and each of them are paired up with their own presenter. I have 6 of these partial views hosted on my main form, and they serve as the meat and potatoes of the application.

As an example, one partial view lists available security guards and another lists potential patrol areas. In a typical use case, a user would drag an available security guard from the available list to one of the potential patrol areas, effectively becoming assigned to that area. The patrol area view would then update to show the assigned security guard and the guard would be removed from the available list view. Utilizing drag-and-drop events, I can handle this interaction.

My questions come when I need to handle other types of interactivity between the various partial views. For example, double clicking on guard that is assigned to a location (as seen in one partial view) could highlight that guard’s name on another partial view showing all personnel schedules, or bring up employee details/history on another partial view. I could see the graph/matrix of what partial views are interested in events occurring in other partial views as becoming quite complex, and I’m not sure how to handle that via injection. With 6 partial views, I wouldn’t want to inject the other 5 partial views/presenters into each one. I was planning on accomplishing this via the event aggregator. Another example I could think of is needing to update data on a separate view (its own form) based off an event that occurs on one of the partial views on the main form.

Session & Form Opener

I really like your thoughts here. I’m going to take these ideas and run with them, and see where I end up!

Security

What are your thoughts on controlling user access to certain functionality based on what type of account they have? The recommendations I’ve been reading online say that security could be implemented by modifying the views based on their account type. The thought being, if a user can’t interact with a UI element to kick off a certain task, then the presenter will never be asked to perform that task. I’m curious if you inject the WindowsUserContext into each presenter and do additional checks, especially for http service bound requests?

I haven’t done too much development on the service side of things yet, but for http service bound requests, I imagine you need to send security information along with each request so that the service can authenticate the request. My plan was to inject the WindowsUserContext directly into the winforms service agents that end up making the service requests (i.e. the security validation would not be coming from the presenter). In that case, the service agents could potentially do a last minute security check before sending off a request.

解决方案

A static class is of course handy in some cases but there are a lot of downsides to this approach.

  • The tend to grow into something like a God class. You already see this happening. So this class violates SRP
  • A static class cannot have dependencies and therefore it needs to use the Service Locator anti pattern to get it's dependencies. This is not a problem perse if you consider this class to be part of the composition root, but nevertheless, this often heads the wrong way.

In the supplied code I see three responsibilities of this class.

  1. EventAggregator
  2. What you call Session information
  3. A service to open other views

Some feedback on this three parts:

EventAggregator

Although this is a widely used pattern and sometimes it can be very powerful I myself am not fond of this pattern. I see this pattern as something that provides optional runtime data where in most cases this runtime data is not optional at all. In other words, only use this pattern for truly optional data. For everything that is not really optional, use hard dependencies, using constructor injection.

The ones that need the information in that case depend upon IEventListener<TMessage>. The one that publish the event, depend upon IEventPublisher<TMessage>.

public interface IEventListener<TMessage> 
{
    event Action<TMessage> MessageReceived;
}

public interface IEventPublisher<TMessage> 
{
    void Publish(TMessage message);
}

public class EventPublisher<TMessage> : IEventPublisher<TMessage> 
{
    private readonly EventOrchestrator<TMessage> orchestrator;

    public EventPublisher(EventOrchestrator<TMessage> orchestrator) 
    {
        this.orchestrator = orchestrator;
    }

    public void Publish(TMessage message) => this.orchestrator.Publish(message);
}

public class EventListener<TMessage> : IEventListener<TMessage> 
{
    private readonly EventOrchestrator<TMessage> orchestrator;

    public EventListener(EventOrchestrator<TMessage> orchestrator) 
    {
        this.orchestrator = orchestrator;
    }

    public event Action<TMessage> MessageReceived 
    {
        add { orchestrator.MessageReceived += value; }
        remove { orchestrator.MessageReceived -= value; }
    }
}

public class EventOrchestrator<TMessage> 
{
    public void Publish(TMessage message) => this.MessageReceived(message);
    public event Action<TMessage> MessageReceived = (e) => { };
}

To be able to guarantee events are stored in one single location, we extract that storage (the event) into its own class, the EventOrchestrator.

The registration is as follows:

container.RegisterSingleton(typeof(IEventListener<>), typeof(EventListener<>));
container.RegisterSingleton(typeof(IEventPublisher<>), typeof(EventPublisher<>));
container.RegisterSingleton(typeof(EventOrchestrator<>), typeof(EventOrchestrator<>));

Usage is trivial:

public class SomeView
{
    private readonly IEventPublisher<GuardChanged> eventPublisher;

    public SomeView(IEventPublisher<GuardChanged> eventPublisher)
    {
        this.eventPublisher = eventPublisher;
    }

    public void GuardSelectionClick(Guard guard)
    {
        this.eventPublisher.Publish(new GuardChanged(guard));
    }
    // other code..
}

public class SomeOtherView
{
    public SomeOtherView(IEventListener<GuardChanged> eventListener)
    {
        eventListener.MessageReceived += this.GuardChanged;
    }

    private void GuardChanged(GuardChanged changedGuard)
    {
        this.CurrentGuard = changedGuard.SelectedGuard;
    }
    // other code..
}

If another view will receive a lot of events you could always wrap all IEventListeners of that View in a specific EventHandlerForViewX class which get all important IEventListener<> injected.

Session

In the question you define several ambient context variables as Session information. Exposing this kind of information through a static class promotes tight coupling to this static class and thus makes it more difficult to unit test parts of your application. IMO all information provided by Session is static (in the sense that it doesn't change throughout the lifetime of the application) data that could just as easily be injected into those parts that actually need this data. So Session should completely be removed from the static class. Some examples how to solve this in a SOLID manner:

Configuration values

The composition root is in charge of reading all information from the configuration source (e.g. your app.config file). This information can there be stored in a POCO class crafted for its usage.

public interface IMailSettings
{
    string MailAddress { get; }
    string DefaultMailSubject { get; }
}

public interface IFtpInformation
{
    int FtpPort { get; }
}

public interface IFlowerServiceInformation
{
    string FlowerShopAddress { get; }
}

public class ConfigValues :
    IMailSettings, IFtpInformation, IFlowerServiceInformation
{
    public string MailAddress { get; set; }
    public string DefaultMailSubject { get; set; }

    public int FtpPort { get; set; }

    public string FlowerShopAddress { get; set; }
}
// Register as
public static void RegisterConfig(this Container container)
{
    var config = new ConfigValues
    {
        MailAddress = ConfigurationManager.AppSettings["MailAddress"],
        DefaultMailSubject = ConfigurationManager.AppSettings["DefaultMailSubject"],
        FtpPort = Convert.ToInt32(ConfigurationManager.AppSettings["FtpPort"]),
        FlowerShopAddress = ConfigurationManager.AppSettings["FlowerShopAddress"],
    };

    var registration = Lifestyle.Singleton.CreateRegistration<ConfigValues>(() => 
                                config, container);
    container.AddRegistration(typeof(IMailSettings),registration);
    container.AddRegistration(typeof(IFtpInformation),registration);
    container.AddRegistration(typeof(IFlowerServiceInformation),registration);
}

And where you need some specific information, e.g. information to send an email you can just put IMailSettings in the constructor of the type needing the information.

This will also give you the possibility to test a component using different config values, which would be harder to do if all config information had to come from the static ApplicationController.

For security information, e.g. the logged on User the same pattern can be used. Define an IUserContext abstraction, create a WindowsUserContext implementation and fill this with the logged on user in the composition root. Because the component now depends on IUserContext instead of getting the user at runtime from the static class, the same component could also be used in an MVC application, where you would replace the WindowsUserContext with an HttpUserContext implementation.

Opening other forms

This is actually the hard part. I normally also use some big static class with all kinds of methods to open other forms. I don't expose the IFormOpener from this answer to my other forms, because they only need to know, what to do, not which form does that task for them. So my static class exposes this kinds of methods:

public SomeReturnValue OpenCustomerForEdit(Customer customer)
{ 
     var form = MyStaticClass.FormOpener.GetForm<EditCustomerForm>();
     form.SetCustomer(customer);
     var result = MyStaticClass.FormOpener.ShowModalForm(form);
     return (SomeReturnValue) result;
}

However....

I'm not at all happy with this approach, because over time this class grows and grows. With WPF I use another mechanism, which I think could also be used with WinForms. This approach is based on a message based architecture described in this and this awesome blogposts. Although at first the information looks as it is not at all related, it is the message based concept that let these patterns rock!

All my WPF windows implement an open generic interface, e.g. IEditView. And if some view needs to edit a customer, it just get's this IEditView injected. A decorator is used to actually show the view in pretty much the same way as the forementioned FormOpener does it. In this case I make use of a specific Simple Injector feature, called decorate factory decorator, which you can use to create forms whenever it is needed, just as the FormOpener used the container directly to create forms whenever it needs to.

So I did not really test this, so there could be some pitfalls with WinForms, but this code seems to work on a first and single run..

public class EditViewShowerDecorator<TEntity> : IEditView<TEntity>
{
    private readonly Func<IEditView<TEntity>> viewCreator;

    public EditViewShowerDecorator(Func<IEditView<TEntity>> viewCreator)
    {
        this.viewCreator = viewCreator;
    }

    public void EditEntity(TEntity entity)
    {
        // get view from container
        var view = this.viewCreator.Invoke();
        // initview with information
        view.EditEntity(entity);
        using (var form = (Form)view)
        {
            // show the view
            form.ShowDialog();
        }
    }
}

The forms and decorator should be registered as:

container.Register(typeof(IEditView<>), new[] { Assembly.GetExecutingAssembly() });
container.RegisterDecorator(typeof(IEditView<>), typeof(EditViewShowerDecorator<>), 
                             Lifestyle.Singleton);

Security

The IUserContext must the base for all security.

For the userinterface I normally hide all controls/buttons that a certain userrole doesn't have access to. The best place is to perform this in the Load event.

Because I use the command/handler pattern as described here for my all actions external of my forms/views I use a decorator to check if a user has permission to perform this certain command (or query).

I would advise you to read this post a few times until you really get the hang of it. Once you get familiar with this pattern you won't do anything else!

If you have any questions about these patterns and how to apply a (permission)decorator, add a comment!

这篇关于Winforms - MVP模式:使用静态ApplicationController协调应用程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆