如何在 WinForms 客户端应用程序中使用命令模式?
How to use command pattern in a WinForms client application?
背景
我正在构建一个两层 C# .net 应用程序:
- 第 1 层:使用 MVP(模型-视图-呈现器)设计模式的 Winforms 客户端应用程序。
- 第 2 层:WebAPI RESTful 服务位于 Entity Framework 和 SQL 服务器之上。
如果您想了解我正在构建的应用程序的更多详细信息,我可能给出了过于详尽的解释 。
当前发展
目前,我正在开发 Winforms 客户端。特别是,我正在尝试在此客户端中充分实施命令模式。我很幸运地通过解释他如何将查询与命令分开来偶然发现 this excellent blog post that outlines a solid command architecture. To complement that post, the author followed up。阅读这些博客后,很明显我的第 2 层(web api 服务)将从实施这两个方面受益匪浅。通用实现具有出色的灵活性、可测试性和可扩展性。
问题
我不太清楚的是我如何在 winforms 客户端(第 1 层)上实现这些模式。查询和命令在这里继续被认为是分开的吗?考虑一个基本操作,例如登录尝试。那是查询还是命令?最终,您需要从 Web 服务返回数据(服务器上的用户信息),所以这会让我认为这是一个查询。另一种情况呢,比如请求创建一个新用户。我知道您会创建一个命令对象来存储用户信息并将其发送到服务。命令应该是即发即弃的,但是您不想从服务那里得到命令成功的某种确认吗?此外,如果命令处理程序 returns 无效,您将如何告诉演示者用户创建请求是否成功?
归根结底,对于任何给定的 UI 任务(例如用户创建请求),您是否最终也拥有基于 query/command 的 winforms 客户端?作为处理请求的 command/query 的网络 api 服务版本?
Do queries and commands continue to be considered separate here?
是的,通常您会触发命令,如果您需要在执行此操作后更新 UI,您将执行查询以获取新信息。举个例子可以清楚地说明这一点。
假设您要为某个区域分配一个特定的守卫。命令(只有一个DTO)需要的唯一信息是守卫的Id
和区域的Id
。关联的 CommandHandler
将执行所有任务来处理这个问题,例如将那个守卫从另一个区域移走,将他记为不可用等等
现在您的 UI 想要显示更改。 UI 可能有某种列表,其中包含所有警卫及其分配的区域。此列表将由一个 GetActiveGuardsAndAreaQuery
填充,它将 return 一个 List<GuardWithAreaInformationDto>
。这个DTO
可以包含所有警卫的各种信息。从命令返回此信息并不是关注点的完全分离,因为原子命令处理可以很好地用于类似但略有不同的 UI,这将需要对 UI 进行略微不同的更新资料。
such as a login attempt. Is that a query or a command?
IMO 登录尝试都不是。这是一个横切关注点,是数据隐藏在安全连接后面的实现细节。然而,应用程序不应该关心这个细节。考虑与另一个客户一起使用该应用程序,您可以在其中托管 WebApi 服务,并且可以在 Active Directory
域中使用 Windows Authentication
。在那种情况下,用户只需登录到他的机器,安全由客户端和服务器在通信时处理OS。
使用您所指的模式,使用 AuthenticateToWebApiServiceCommandHandlerDecorator
可以很好地完成此操作,它通过以模态形式询问用户并从配置文件,或其他任何东西。
检查凭据是否有效可以通过执行您的应用程序始终需要的一种标准 Query
来完成,例如 CheckIfUpdateIsAvailableQuery
。如果查询成功,则登录尝试成功,否则失败。
if a command handler returns void, how would you tell the presenter whether or not the user creation request was successful?
虽然看起来 void
没有 return 任何东西,但事实并非如此。因为如果它没有因某些异常而失败(并明确显示出了什么问题!),它就一定成功了。
在提到的博客 follow up 中,@dotnetjunkie 描述了一种从命令中获取 return 信息的方法,但请注意 [=57] 顶部添加的评论=].
总而言之,从失败的命令中抛出明显的异常。您可以添加一个额外的抽象客户端层来很好地处理这个问题。您可以注入一个 IPromptableCommandHandler
,它在编译时只有一个开放的通用实现,而不是直接将 commandhandler 注入到不同的演示者中:
public interface IPromptableCommandHandler<TCommand>
{
void Handle(TCommand command, Action succesAction);
}
public class PromptableCommandHandler<TCommand> : IPromptableCommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> commandHandler;
public PromptableCommandHandler(ICommandHandler<TCommand> commandHandler)
{
this.commandHandler = commandHandler;
}
public void Handle(TCommand command, Action succesAction)
{
try
{
this.commandHandler.Handle(command);
succesAction.Invoke();
}
catch (Exception)
{
MessageBox.Show("An error occured, please try again.");
// possible other actions like logging
}
}
}
// use as:
public void SetGuardActive(Guid guardId)
{
this.promptableCommandHandler.Handle(new SetGuardActiveCommand(guardId),() =>
this.RefreshGuardsList());
}
At the end of the day, for any given UI task (say the user creation request), does it end up that you end up having a winforms client based query/command, as well as a web api service version of the command/query which handles the request on that end?
没有!
客户端你应该创建一个单一的开放通用CommandHandlerProxy
,其唯一任务是将命令 dto 传递给 WebApi 服务。
对于服务端架构,您应该阅读另一篇后续文章:Writing Highly Maintainable WCF Services,它描述了一个服务器端架构,可以很好地处理这个问题。链接的项目还包含 WebApi 的实现!
背景
我正在构建一个两层 C# .net 应用程序:
- 第 1 层:使用 MVP(模型-视图-呈现器)设计模式的 Winforms 客户端应用程序。
- 第 2 层:WebAPI RESTful 服务位于 Entity Framework 和 SQL 服务器之上。
如果您想了解我正在构建的应用程序的更多详细信息,我可能给出了过于详尽的解释
当前发展
目前,我正在开发 Winforms 客户端。特别是,我正在尝试在此客户端中充分实施命令模式。我很幸运地通过解释他如何将查询与命令分开来偶然发现 this excellent blog post that outlines a solid command architecture. To complement that post, the author followed up。阅读这些博客后,很明显我的第 2 层(web api 服务)将从实施这两个方面受益匪浅。通用实现具有出色的灵活性、可测试性和可扩展性。
问题
我不太清楚的是我如何在 winforms 客户端(第 1 层)上实现这些模式。查询和命令在这里继续被认为是分开的吗?考虑一个基本操作,例如登录尝试。那是查询还是命令?最终,您需要从 Web 服务返回数据(服务器上的用户信息),所以这会让我认为这是一个查询。另一种情况呢,比如请求创建一个新用户。我知道您会创建一个命令对象来存储用户信息并将其发送到服务。命令应该是即发即弃的,但是您不想从服务那里得到命令成功的某种确认吗?此外,如果命令处理程序 returns 无效,您将如何告诉演示者用户创建请求是否成功?
归根结底,对于任何给定的 UI 任务(例如用户创建请求),您是否最终也拥有基于 query/command 的 winforms 客户端?作为处理请求的 command/query 的网络 api 服务版本?
Do queries and commands continue to be considered separate here?
是的,通常您会触发命令,如果您需要在执行此操作后更新 UI,您将执行查询以获取新信息。举个例子可以清楚地说明这一点。
假设您要为某个区域分配一个特定的守卫。命令(只有一个DTO)需要的唯一信息是守卫的Id
和区域的Id
。关联的 CommandHandler
将执行所有任务来处理这个问题,例如将那个守卫从另一个区域移走,将他记为不可用等等
现在您的 UI 想要显示更改。 UI 可能有某种列表,其中包含所有警卫及其分配的区域。此列表将由一个 GetActiveGuardsAndAreaQuery
填充,它将 return 一个 List<GuardWithAreaInformationDto>
。这个DTO
可以包含所有警卫的各种信息。从命令返回此信息并不是关注点的完全分离,因为原子命令处理可以很好地用于类似但略有不同的 UI,这将需要对 UI 进行略微不同的更新资料。
such as a login attempt. Is that a query or a command?
IMO 登录尝试都不是。这是一个横切关注点,是数据隐藏在安全连接后面的实现细节。然而,应用程序不应该关心这个细节。考虑与另一个客户一起使用该应用程序,您可以在其中托管 WebApi 服务,并且可以在 Active Directory
域中使用 Windows Authentication
。在那种情况下,用户只需登录到他的机器,安全由客户端和服务器在通信时处理OS。
使用您所指的模式,使用 AuthenticateToWebApiServiceCommandHandlerDecorator
可以很好地完成此操作,它通过以模态形式询问用户并从配置文件,或其他任何东西。
检查凭据是否有效可以通过执行您的应用程序始终需要的一种标准 Query
来完成,例如 CheckIfUpdateIsAvailableQuery
。如果查询成功,则登录尝试成功,否则失败。
if a command handler returns void, how would you tell the presenter whether or not the user creation request was successful?
虽然看起来 void
没有 return 任何东西,但事实并非如此。因为如果它没有因某些异常而失败(并明确显示出了什么问题!),它就一定成功了。
在提到的博客 follow up 中,@dotnetjunkie 描述了一种从命令中获取 return 信息的方法,但请注意 [=57] 顶部添加的评论=].
总而言之,从失败的命令中抛出明显的异常。您可以添加一个额外的抽象客户端层来很好地处理这个问题。您可以注入一个 IPromptableCommandHandler
,它在编译时只有一个开放的通用实现,而不是直接将 commandhandler 注入到不同的演示者中:
public interface IPromptableCommandHandler<TCommand>
{
void Handle(TCommand command, Action succesAction);
}
public class PromptableCommandHandler<TCommand> : IPromptableCommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> commandHandler;
public PromptableCommandHandler(ICommandHandler<TCommand> commandHandler)
{
this.commandHandler = commandHandler;
}
public void Handle(TCommand command, Action succesAction)
{
try
{
this.commandHandler.Handle(command);
succesAction.Invoke();
}
catch (Exception)
{
MessageBox.Show("An error occured, please try again.");
// possible other actions like logging
}
}
}
// use as:
public void SetGuardActive(Guid guardId)
{
this.promptableCommandHandler.Handle(new SetGuardActiveCommand(guardId),() =>
this.RefreshGuardsList());
}
At the end of the day, for any given UI task (say the user creation request), does it end up that you end up having a winforms client based query/command, as well as a web api service version of the command/query which handles the request on that end?
没有!
客户端你应该创建一个单一的开放通用CommandHandlerProxy
,其唯一任务是将命令 dto 传递给 WebApi 服务。
对于服务端架构,您应该阅读另一篇后续文章:Writing Highly Maintainable WCF Services,它描述了一个服务器端架构,可以很好地处理这个问题。链接的项目还包含 WebApi 的实现!