Главная > ASP.NET, Web > ASP.NET MVC. Перехватываем нулевую модель (null model handling).

ASP.NET MVC. Перехватываем нулевую модель (null model handling).

Много раз сталкивался с тем, что контроллеры в ASP.NET MVC со временем начинают обрастать бизнес-логикой или, что еще хуже, «сервисной» логикой (создание/удаление файлов, редактирование каких-нибудь конфигов и тд.). Слова «контроллеры», «контроллер» и «вьюха» сейчас будут повторяться слишком часто, но ничего не поделать :). Так вот,  такие контроллеры сложно тестировать и поддерживать. В идеале, контроллер должен содержать только логику по формированию представления (View), а на деле — контроллер превращается в своеобразный комбайнер (собиратель), который по частям заполняет модель отовсюду, откуда только можно, выглядеть это может примерно так:

    public class AbstractController : Controller
    {
        public ActionResult Index()
        {
            var model = new SomeModel();
            model.Property1 = SomeService1.GetProperty1();
            model.Property2 = AnotherService.GetProperty2();
            SomeStrangeHelper.DoSomethingWithModel(model);
            ...

            return View(model);
        }
    }

Из-за такой вот фигни, точек выхода из экшена может быть ну очень много, я имею в виду нечто подобное:

    public ActionResult Index()
    {
        var model = new SomeModel();
        model.Property1 = SomeService1.GetProperty1();
        if(model.Property1 == null)
        {
            return RedirectToAction("FarAway", "AnyController");
        }

        model.Property2 = AnotherService.GetProperty2();
        if(model.Property2 == QweHelper.GetValue(DateTime.Now))
        {
            throw new Exception("something happened");
        }

        SomeStrangeHelper.UpdateModel(model);
        ...

        return View(model);
    }

, даже несмотря на то, что все эти точки могут складываться в глубокую «матрешку» из всяких условий — это еще цветочки, ягодки начинаются на вьюхе — различные NullReference exceptions из-за того, что одно из свойств мы забыли (или забили) заполнить, или отложенное свойство начало разворачивать свой Linq`овский запрос и грохнуло вьюху, вообщем вариантов много. Решение одно — контроллер должен содержать только логику по передаче модели во вьюху. Из-за этого должен появиться некоторый сервис, на который будет распространяться достаточно жесткое требование — такой сервис возвращает валидную c точки зрения вьюхи модель, либо null.

Но что значит, если модель равна null ? Спорить на эту тему можно долго, я буду рассматривать вариант, когда это означает, что запрашиваемый ресурс не найден, в контексте веба это равносильно ошибке 404 — NotFound. Т.е. нулевая модель означает, что мы должны вернуть HttpNotFound(). Сделать просто можно так:

    public ActionResult Index()
    {
        var model = Services.GetModel();
        return model == null ? (ActionResult)HttpNotFound() : View();
    }

, а можно сделать ActionFilterAttribute. Такой атрибут, должен проверять результат экшена, но до того момента, как он будет полностью выполнен, в этом нам поможет метод OnResultExecuting, в отличие от OnResultExecuted, он не кинет исключение при нулевой модели, потому что еще не завершился полностью. Ниже приведен код этого атрибута, все просто:

    public class HandleNullModelAttribute : ActionFilterAttribute
    {
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            var result = filterContext.Result as ViewResult;
            if (result != null)
            {
                if (result.Model == null)
                {
                    filterContext.Cancel = true; // Отменяем текущий результат.
                    filterContext.Result = new HttpNotFoundResult(); // Устанавливаем новый результат.
                    filterContext.Result.ExecuteResult(filterContext.Controller.ControllerContext); // Выполняем.
                }
            }
        }
    }

Пример использования:

    public class SitemapController : Controller
    {
        [HandleNullModel]
        public ContentResult Index()
        {
            var model = SitemapGenerator.GenerateSitemap();
            return Content(model, "text/xml");
        }
    }

Что можно сделать еще — расширить атрибут для возможности валидации ViewData и ViewBag, а это уже немного сложнее. По определенным причинам я не буду приводить пример такого расширения.

  1. Пока что нет комментариев.
  1. Пока что нет уведомлений.