自动处理空模型以清理控制器和视图

时间:2012-01-22 19:57:22

标签: asp.net-mvc model null

我真的希望通过减少我正在进行的空检查来提高代码的当前可读性。我觉得虽然检查某些条件是件好事,但在各个地方重复这些检查并不是一个好主意。例如,这是我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并检查那里的异常,记录它们然后重定向到自定义错误视图吗?这听起来像是一种合理的做法吗?

谢谢。

1 个答案:

答案 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,并将模型替换为查看模型。

相关问题