ОК, давайте попробуем поставить этот контроллер на диету .
Мы начнем с того, что исправим ваш репозиторий и оставим его возвращать ноль вместо некоторого поста по умолчанию с 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));
, а затем фильтр настраиваемых действий, который позаботится о коротком замыкании действия в случае, если сообщение является нулевым или почтовый слаг отличается от того, который был передан в качестве параметра:
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
, а затем извлекли из него модель, передали ее AutoMapper с данными 2 типами и заменили модель моделью представления.