我真的希望通过减少我正在进行的空检查来提高代码的当前可读性。我觉得虽然检查某些条件是件好事,但在各个地方重复这些检查并不是一个好主意。例如,这是我PostsController
的一种方法:
public ActionResult ShowPost(int PostID, string slug)
{
PostViewModel viewModel = new PostViewModel();
Post model = postRepository.FindPost(PostID, filterByPublished: true);
if (model.PostID == 0)
return Redirect(Url.Home());
else if (model.Slug != slug)
return RedirectPermanent(Url.ShowPost(model.PostID, model.Slug));
postRepository.PostVisited(model);
Mapper.Map(model, viewModel);
return View(viewModel);
}
首先,检查PostID
是否为0.由于我在存储库中设置该方法的方式,它可以为0:
public Post FindPost(int id, bool filterByPublished = false)
{
var query = db.Posts.Where(post => post.PostID == id);
if (filterByPublished)
query = query.Where(post => post.IsPublished == filterByPublished);
return query.Select(post => post).SingleOrDefault() ?? new Post { PostID = 0 };
}
我不喜欢我在那里推动那个小黑客只是为了迎合处理一个空模型。我还检查了各种强类型视图中的空模型,这些视图需要PostViewModel
。
我的第一个想法是创建一个动作过滤器,覆盖OnResultExecuting
并检查那里的空模型。我实际上非常喜欢这个想法,它确实解决了在控制器和视图中检查空模型的问题。但是,它没有做的是迎合这样的情况:
else if (model.Slug != slug)
这会给我一个空的引用异常,就像将模型传递给postRepository
来更新视图计数一样。我猜这个对Mapper.Map
的调用会做同样的事情。
那么我可以做些什么呢?我可以简单地覆盖OnActionExecuted
并检查那里的异常,记录它们然后重定向到自定义错误视图吗?这听起来像是一种合理的做法吗?
谢谢。
答案 0 :(得分:7)
好的,我们试试putting this controller on a diet。
我们首先修复你的存储库并让它返回null而不是一些ID = 0的默认帖子,这有点像你注意到的那样奇怪:
public Post FindPost(int id, bool filterByPublished = false)
{
var query = db.Posts.Where(post => post.PostID == id);
if (filterByPublished)
query = query.Where(post => post.IsPublished == filterByPublished);
return query.Select(post => post).SingleOrDefault();
}
然后我们可以为Post
模型编写自定义模型绑定器:
public class PostModelBinder : IModelBinder
{
private readonly IPostsRepository _repository;
public PostModelBinder(IPostsRepository repository)
{
_repository = repository;
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var postIdValue = controllerContext.Controller.ValueProvider.GetValue("postid");
int postId;
if (postIdValue == null || !int.TryParse(postIdValue.AttemptedValue, out postId))
{
return null;
}
return _repository.FindPost(postId, true);
}
}
可能与Post
中的Application_Start
类型相关联:
var postsRepository = DependencyResolver.Current.GetService<IPostsRepository>();
ModelBinders.Binders.Add(typeof(Post), new PostModelBinder(postsRepository));
然后是一个自定义动作过滤器,如果帖子为null或post slug不同于作为参数传递的帖子,则会小心操作短路:
public class PostActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var postParameter = filterContext
.ActionDescriptor
.GetParameters()
.Where(p => p.ParameterType == typeof(Post))
.FirstOrDefault();
if (postParameter == null)
{
return;
}
var post = (Post)filterContext.ActionParameters[postParameter.ParameterName];
if (post == null)
{
filterContext.Result = new RedirectResult(Url.Home());
}
var slug = filterContext.Controller.ValueProvider.GetValue("slug");
if (slug != null && post.Slug != slug.AttemptedValue)
{
filterContext.Result = new RedirectResult(
Url.ShowPost(post.PostID, post.Slug),
true
);
}
}
}
如果您使用的是ASP.NET MVC 3,则可以在Global.asax
中将此自定义属性注册为全局属性:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new PostActionFilterAttribute());
...
}
最后你的控制器动作将变为:
public ActionResult ShowPost(Post post)
{
postRepository.PostVisited(post);
var viewModel = Mapper.Map<Post, PostViewModel>(post);
return View(viewModel);
}
甚至更进一步,并引入自定义映射器操作过滤器:
[AutoMap(typeof(Post), typeof(PsotViewModel))]
public ActionResult ShowPost(Post post)
{
postRepository.PostVisited(post);
return View(post);
}
AutoMap
操作过滤器非常容易实现。您将订阅OnActionExecuted
方法,并检查控制器操作返回的结果是否为ViewResultBase
,然后从中提取模型,在给定2种类型的情况下将其传递给AutoMapper,并将模型替换为查看模型。