Главная > ASP.NET > Partial Views и Areas в ASP.NET MVC.

Partial Views и Areas в ASP.NET MVC.

Всех с праздником :).

В ASP.NET MVC 2 появилось такое новое понятие как области (Areas), а поскольку релиз второй версии MVC вышел уже почти год назад, то понятие это уже далеко не новое, и проблема, с которой я недавно столкнулся, должна была к настоящему времени ну хоть кого-нибудь затронуть, однако, на просторах сети ее описания не нашлось. Смысл Areas в том, чтобы разделить большой проект на несколько маленьких, не создавая отдельных проектов в солюшене. Т.е., например, есть веб-приложение, состоящее из некоторой общей публичной части, какой-то административной части, блога и форума, каждая из частей выносится в отдельную независимую область (area), в результате все лежит в одном проекте, но в то же время разграничено по смысловой нагрузке. Это очень удобно (проверено лично). Появляется возможность иметь одноименные контроллеры, вьюхи и тд.

Это было как введение, на самом деле пост не об Areas. Речь пойдет о Partial Views (частичные представления), которые находятся в некоторой области (Area). Partial Views — это аналог User Controls в классическом ASP.NET, которые могут использоваться для отрисовки каких-то кусков страницы, например блока новостей.
В первом релизе MVC делалось это так:

// Экшн для отрисовки меню. Вьха "Menu" представляет собой PartialView.
public ActionResult Menu(string id, string parentId, string previousNodeId)
        {
            ...
            return View("Menu");
        }

В MVC2 и MVC3 уже необходимо писать так:

// Именно "PartialView" a не "View"! Если написать "View", то вьюха "Menu" будет рассматриваться как полноценная View со всеми вытекающими отсюда плюшками вроде применения к ней мастер-страниц и прочего.
public ActionResult Move(string id, string parentId, string previousNodeId)
        {
            ...
            return PartialView("Menu");
        }

Едем дальше. Все это прекрасно работает до тех пор, пока вы не начнете работать с этими частичными вьюхами в Areas. Причем проблема весьма «кудрявая». Пусть имеется область «Administrator». Area «Administrator» должна быть зарегистрирована в файле «AdministratorAreaRegistration.cs»:

namespace Web.Areas.Administrator
{
    public class AdministratorAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "Administrator";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "Administrator_default",
                "Administrator/{controller}/{action}/{id}",
                new
                    {
                        controller = "Account",
                        action = "Index",
                        id = UrlParameter.Optional
                    },
                new[] {"Web.Areas.Administrator.Controllers"}
                );
        }                
    }
}

Все области, в свою очередь, должны быть зарегистрированы в Global.asax:

 protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            ...
        }

В области «Administrator» существует контроллер «NavigationController» с экшеном «SomeAction». Что-то вроде этого:

namespace Web.Areas.Administrator.Controllers
{    
    public class NavigationController : Controller
    {        
        public ActionResult SomeAction()
        {
            ...
            return PartialView("Menu");
        }        
    }
}

Menu — это PartialView. Логика такая: результатом вызова экшена «SomeAction» контроллера «Navigation» должно стать частичное представление «Menu». Мы можем проверить результат экшена «SomeAction» просто набрав в адресной строке браузера необходимый нам урл, в нашем случае http://someurl.ru/Administrator/Navigation/SomeAction. Если вы все сделали правильно, то увидите на экране ту самую вьюху «Menu».

Теперь собственно сама проблема. Имеется обычная (не PartialView) вьюха с большим количеством клиентского кода, в которой обращение к нашему методу «SomeAction» осуществляется путем аяксного вызова при помощи jQuery. Я просто хочу полученный в ответе HTML залить в необходимый мне контейнер. Делаю так:

$.ajax({
                url: "/Navigation/SomeAction",
                type: "POST",
                data: ({
                    ...
                }),
                success: function (data) {
                    $("#menuColumn").html(data);
                },
                error: function (request, status, error) {                                        $("#menuColumn").html(request.responseText);
                }                
            });

и получаю ошибку, что вьюха «Menu» нигде не найдена! Текст ошибки да и запуск приложения под дебагом дает понять, что вызов экшена «SomeAction» контроллера «Navigation» происходит нормально, все действия в этом экшене выполняются, просто не находится вьюха. Но ведь немного раньше, вызывая экшен через адресную строку браузера, мы получали представление «Menu» без каких-либо проблем, в чем же дело ? Меняю адрес вызова аяксного кола:

$.ajax({
                url: "/Administrator/Navigation/SomeAction",
                type: "POST",
                data: ({
                    ...
                }),
                success: function (data) {
                    $("#menuColumn").html(data);
                },
                error: function (request, status, error) {                                        $("#menuColumn").html(request.responseText);
                }                
            });

При подобном вызове вьюха «Menu» успешно возвращается. Различия двух вызовов только в адресе запроса, т.е. во втором случае я дополнительно добавляю имя области «Administrator». Экшен «SomeAction» отрабатывает как при первом вызове, так и при втором. Похоже на то, что здесь косячит механизм роутинга, который в первом случае в какой-то момент то ли забывает, то ли, наоборот, придумывает, что вызов идет из Area, а не из основной части веб-приложения.

С одной стороны все это очень сильно похоже на багу, поскольку вызов экшена с одинаковыми параметрами (хотя в нашем случае вообще никаких параметров в экшен не передается) должен вернуть одинаковый результат, однако, это не так. Все дело в том, что контекст запроса обоих вызовов неодинаков. Вы можете в этом убедиться, отдебажив Request:

  • Аяксный вызов по адресу "/Navigation/Delete":

    • Request.Url: "http://someurl.ru/Navigation/SomeAction"
    • Request.AppRelativeCurrentExecutionFilePath: "~/Navigation/Delete"
  • Аяксный вызов по адресу "/Administrator/Navigation/Delete":

    • Request.Url: "http://someurl.ru/Administrator/Navigation/SomeAction"
    • Request.AppRelativeCurrentExecutionFilePath: "~/Administrator/Navigation/Delete"

Так что правильно составляйте запросы :). Жду комментариев.

Небольшой апдейт!

Как мне подсказали на одном форуме, чтобы избегать подобных проблем, никогда не пишите в скриптах в ASP.NET MVC адреса руками, используйте метод хелпера Url.Action(…).

Categories: ASP.NET Tags: , , ,
  1. Пока что нет комментариев.
  1. Пока что нет уведомлений.