我的ASP.NET MVC 3网站上都有这样的控制器代码:
[HttpPost]
public ActionResult Save(PostViewModel viewModel)
{
// VM -> Domain Mapping. Definetely belongs here. Happy with this.
var post = Mapper.Map<PostViewModel, Post>(viewModel);
// Saving. Again, fine. Controllers job to update model.
_postRepository.Save(post);
// No. Noooo..caching, thread spawning, something about a user?? Why....
Task.Factory.StartNew(() => {
_cache.RefreshSomeCache(post);
_cache2.RefreshSomeOtherCache(post2);
_userRepository.GiveUserPoints(post.User);
_someotherRepo.AuditThisHappened();
});
// This should be the 3rd line in this method.
return RedirectToAction("Index");
}
基本上,我指的是线程块中的代码。所有事情都需要发生,但用户不需要等待它们(对于后台线程来说是好的情况,对吧?)。
为了清楚起见,我在整个站点使用缓存(常规ASP.NET数据缓存),其中大部分都有“无过期”缓存策略,因此我在需要时手动逐出(如上所述)。
用户部分基本上是让用户代表做某事(比如Stack)。
让我们回顾一下:我们有缓存,用户信誉处理,审计,一体化。真的不属于一个地方吗。因此,当前代码的问题,以及试图找出如何将其移走的问题。
我想重构这个的原因有以下几个原因:
我解决了1)将线程产生代码包装到一个接口中,然后嘲笑/伪造它。
但是我想做某种模式,我的代码看起来像这样:
[HttpPost]
public ActionResult Save(PostViewModel viewModel)
{
// Map.
var post = Mapper.Map<PostViewModel, Post>(viewModel);
// Save.
_postRepository.Save(post);
// Tell someone about this.
_eventManager.RaiseEvent(post);
// Redirect.
return RedirectToAction("Index");
}
基本上,将责任放在“别的东西”上作出反应,而不是控制者。
我听说过有关任务,命令,事件等的内容,但尚未看到在ASP.NET MVC空间中实现的内容。
首先想到的是要创建某种“事件管理器”。但后来我想,这是怎么回事?在域名?那么它如何处理与缓存的交互,这是一个基础设施问题。然后是线程,这也是一个基础设施问题。如果我想要做的是同步,而不是异步?是什么决定的?
我不想只是把所有这些逻辑都堆积在其他地方。理想情况下,它应该重新考虑到可管理和有意义的组件,而不是转移责任,如果这是有道理的。
有什么建议吗?
答案 0 :(得分:2)
首先想到的是要创建某种“事件管理器”。但后来我想,这是怎么回事?在域名?
这是我解决问题的方式。我将事件管理器视为基础架构。但实际事件属于域名。
那么它如何处理与缓存的交互,这是一个基础设施问题。然后是线程,这也是一个基础设施问题。如果我想要做的是同步,而不是异步?是什么决定的?
异步很好,但是使事务处理变得复杂。如果您使用IoC容器,则您已经具有明确定义的范围和可在事件传播期间使用的事务。
如果用户知道它的事件处理需要时间,那么由用户来安排/解决它的任务。建议的解决方案:
使用您的IoC容器发布事件。我会让存储库发布事件(PostUpdated
或EntityUpdated
,取决于您要对事件做什么)而不是控制器(以减少代码重复)。
我为autofac做了一个IoC实现,它允许你:
DomainEventDispatcher.Current.Dispatch(new EntityUpdated(post));
订阅:
public class CacheService : IAutoSubscriberOf<EntityUpdated>
{
public void Handle(EntityUpdated domainEvent) {};
}
https://github.com/sogeti-se/Sogeti.Pattern/wiki/Domain-events
典型用法
ServiceResolver.Assign(new yourResolver(yourContainer))
答案 1 :(得分:2)
也许post对象应该更新自己,并传递一个IRepository(它本身被传递给控制器。)(这是基本依赖注入/ IOC,并保持控制器更加苗条)
//in controller:
var post = Mapper.Map<PostViewModel, Post>(viewModel);
post.Update(_postRepository);
//inside Post.cs:
public Update(IRepository rep){
//update db with the repo
//give points
}
答案 2 :(得分:2)
您可以对此特定问题使用aspect oriented programming。 .NET世界中常用的产品是PostSharp。
我们的想法是在方法上方添加一个属性。该属性将告诉应该执行哪些特定操作(在您的情况下缓存刷新,指向增加等)以及何时应该发生(例如在退出方法时)。
您还可以将这些属性与不同的属性分开,这样您就可以进行不同类型的组合。
答案 3 :(得分:1)
作为PortableArea功能的一部分,MvcContrib中有一个消息总线实现。它的主要目的是允许独立实现的功能触发和监听事件,这听起来非常像你想要的。
我不确定它是否是最好的选择,因为MvcContrib的状态有点无证且粗略。有些部分是积极维护的,而有些则是过时的。
另一个需要考虑的选择是ZeroMQ,但对您的需求可能有点过分。
答案 4 :(得分:1)
如果您正在处理缓存清除或审计(可能更好地异步服务),您可能需要考虑实施服务总线系统,例如NServiceBus。
使用消息总线,事件消息可以异步发布到任意数量的事件处理程序(例如缓存处理程序),因此您的应用程序可以触发消息,然后继续快速提供同步页面。
答案 5 :(得分:0)
MVC是一个UI图层模式。我通常在操作体中放置一些基于http上下文的逻辑,并将业务逻辑放入较低层,例如处理域模型的服务。在你的情况下,我想添加一个IPostService并将你的保存函数和缓存函数放入其中。这是类似的东西:
[HttpPost]
public ActionResult Save(PostViewModel viewModel)
{
// Map.
var post = Mapper.Map<PostViewModel, Post>(viewModel);
// Save.
_postService.Save(post);
// Redirect.
return RedirectToAction("Index");
}
因为缓存与业务逻辑无关,所以不需要出现在服务接口中。这是一个实施细节。
此外,我认为使用AOP触发事件并使用IoC容器注入缓存逻辑(以及事务逻辑)是件好事。类似的东西:
//codes in your PostService which implements IPostService
[CacheEvent("POST")]
[Transaction]
public void Save(Post post) //care about domain model instead of view model
{
// Save.
_postReposity.Save(post);
}
答案 6 :(得分:0)
接受@ jgauffin的回答,但我想我会添加一个单独的答案,因为我使用的是StructureMap,它需要一个额外的步骤。
我遵循@jgauffin指定的内容,但它没有触发我的事件处理程序。
所以我把它添加到我的StructureMap配置中:
x.Scan(y =>
{
y.AssembliesFromApplicationBaseDirectory();
y.AddAllTypesOf(typeof (IAutoSubscriberOf<>));
y.WithDefaultConventions();
});
然后它有效。
我猜这个替代方法是手动指定每个注册的监听器,这有点疯狂。
使用StructureMap的人是否可以告诉我这是否是正确的方法?