Используете фильтр для выполнения другого действия?
-
19-09-2019 - |
Вопрос
Я хочу избежать большого количества if Request.IsAjaxRequest()
в моих контроллерах.Я думал, что если бы я мог сжать эту логику до ActionFilter, было бы легко принять в моем приложении соглашение, обеспечивающее второе действие для любого запроса, который может используйте Ajax, обеспечивая при этом запасной вариант, если JavaScript отключен.
public ActionResult Details(int id)
{
// called normally, show full page
}
public ActionResult Details_Ajax(int id)
{
// called through ajax, return a partial view
}
Сначала я думал, что смогу сделать что-то вроде этого:
public class AjaxRenameAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RouteData.Values["action"] = filterContext.RouteData.Values["action"] + "_Ajax";
}
Но это не сработает, поскольку определяется действие, которое необходимо вызвать, а затем обрабатываются фильтры для него.
На самом деле я не хочу возвращать RedirectResult каждый раз, когда кто-то вызывает действие, кажется немного бессмысленным удваивать количество Http-запросов.
Есть ли другой способ перенаправить запрос к другому действию?Или то, что я делаю, нецелесообразно, и мне следует поискать лучший способ сделать что-то?
Ваше здоровье
Решение
AcceptAjaxAttribute
возможно, это то, что здесь нужно;однако я хотел бы предложить другой подход к этой проблеме.
Не все запросы Ajax одинаковы.Запрос Ajax может пытаться выполнить любую из следующих задач:
- Привязка данных JSON к расширенной сетке (например, jqGrid);
- Анализ/преобразование XML-данных, таких как RSS-канал;
- Загрузка частичного HTML в область страницы;
- Асинхронная загрузка скрипта (
google.load
могу это сделать); - Обработка одностороннего сообщения от клиента;
- И, возможно, еще несколько, о которых я забыл.
Когда вы «выбираете» конкретное «альтернативное действие», основываясь исключительно на IsAjaxRequest
метод, вы привязываете что-то очень общее — асинхронный запрос — к конкретным функциям сервера.В конечном итоге это сделает ваш проект более хрупким, а также затруднит модульное тестирование вашего контроллера (хотя есть способы сделать это, вы можете имитировать контекст).
Хорошо продуманный действие должно быть последовательный, его должно волновать только что запрос был за, а не за как запрос был сделан.Можно указать на другие атрибуты, такие как AuthorizeAttribute
В качестве исключений я бы выделил фильтры, которые в большинстве случаев описывают поведение, которое должно произойти либо «до», либо «после» совершения действия, а не «вместо».
Переходя к сути, цель, заявленная в вопросе, является хорошей;вам следует определенно имеют разные методы для того, что правильно описывается как разные действия:
public ActionResult Details(int id)
{
return View("Details", GetDetails(id));
}
public ActionResult JsonDetails(int id)
{
return Json(GetDetails(id));
}
public ActionResult PartialDetails(int id)
{
return PartialView("DetailTable", GetDetails(id));
}
И так далее.Однако использование селектора действий Ajax для выбора между этими методами соответствует практике «изящная деградация», который по существу был заменен (по крайней мере, IMO) на прогрессивное улучшение.
Вот почему, хотя я и люблю ASP.NET MVC, я в основном избегаю AjaxHelper
, потому что я не считаю, что это хорошо выражает эту концепцию;он пытается скрыть от вас слишком многое.Вместо того, чтобы использовать концепцию «формы Ajax» или «действия Ajax», давайте покончим с различием и будем придерживаться простого HTML, а затем внедрить функциональность Ajax отдельно, как только мы будем уверены, что клиент справится с ней.
Вот пример в jQuery, хотя вы можете сделать это и в MS AJAX:
$(function() {
$("#showdetails").click(function() {
$("#details").load("PartialDetails", { id: <%= Record.ID %> });
return false;
}
});
Это все, что нужно для внедрения Ajax на страницу MVC.Начните с простой старой ссылки HTML и переопределите ее с помощью вызова Ajax. это переходит в другое действие контроллера.
Теперь, если где-то на вашем сайте вы решите использовать вместо этого сетку, но не хотите разбивать страницы с помощью частичного рендеринга, вы можете написать что-то вроде этого (скажем, у вас есть одна страница master-detail с список «заказов» слева и подробная таблица справа):
$(".detaillink").click(function() {
$('#detailGrid').setGridParam({
url: $(this).attr("href").replace(/\/order\/details/i,
"/order/jsondetails")
});
$("#detailGrid").trigger("reloadGrid");
});
Этот подход полностью отделяет поведение клиента от поведения сервера.Сервер фактически говорит клиенту: Если вам нужна версия JSON, просить для версии JSON, и, кстати, вот скрипт для преобразования ваших ссылок, если вы знаете, как его запустить. Никаких селекторов действий и возни с перегрузками методов, никаких особых издевательств, которые нужно делать, чтобы запустить простой тест, никакой путаницы в отношении того, какое действие, что и когда делает.Всего пара строк JavaScript.Действия контроллера короткие и приятные, именно такими, какими и должны быть.
Это не единственный подход.Очевидно, что такие классы, как AcceptAjaxAttribute
существуют, потому что они ожидали, что некоторые разработчики будут использовать метод обнаружения запросов.Но, поэкспериментировав с обоими, я нашел такой способ. много об этом легче рассуждать и, следовательно, легче правильно проектировать/кодировать.
Другие советы
Как насчет AcceptAjaxAttribute в MvcFutures?