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(…).



Последние комментарии