Главная страница   /   17.5. Использование встроенного средства вызова метода действия (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

17.5. Использование встроенного средства вызова метода действия

Встроенный механизм вызова действий, то есть класс ControllerActionInvoker, использует очень сложные технологии для сопоставления запросов с действиями. И, в отличие от нашей реализации в предыдущем разделе, механизм вызова действий по умолчанию работает с методами.

Чтобы обрабатываться как действие, метод должен соответствовать следующим критериям:

  • метод должен содержать пометку public;
  • метод не должен содержать пометку static;
  • метод не должен присутствовать в System.Web.Mvc.Controller или в любом из его базовых классов;
  • метод не должен иметь специальное имя.

Первые два критерия достаточно просты. Что касается третьего, который исключает методы, присутствующие в классе Controller и его базовых классах, то он означает, что исключаются методы вроде ToString и GetHashCode, а также методы, которые реализуют интерфейс IController. Это разумно, потому что мы не хотим демонстрировать внутренние элементы контроллеров. Последний критерий означает, что исключаются конструкторы, свойства и средства доступа к событиям – фактически из этого следует, что ни один член класса с флагом IsSpecialName из System.Reflection.MethodBase не будет использоваться для обработки действий.

Примечание

Методы, которые имеют общие параметры (такие как MyMethod<T>()), отвечают всем критериям, но при вызове такого метода для обработки запроса MVC Framework выбросит исключение.

По умолчанию ControllerActionInvoker находит метод с таким же именем, как и у запрошенного действия. Так, например, если в системе маршрутизации свойство action содержит Index, то ControllerActionInvoker будет искать метод под названием Index, который соответствует критериям действия. Если он найдет такой метод, то вызовет его для обработки запроса. Такое поведение будет удовлетворять нашим нуждам в большинстве случаев, но, как и следовало ожидать, MVC Framework предоставляет некоторые возможности для тонкой настройки процесса.

Переопределяем имя действия

Обычно имя метода действия определяет действие, которое он представляет. Метод действия Index обслуживает запросы к действию Index. Вы можете переопределить это поведение с помощью атрибута ActionName, который мы применили к контроллеру Customer в листинге 17-15.

Листинг 17-15: Используем пользовательское имя действия
using ControllerExtensibility.Models;
using System.Web.Mvc;

namespace ControllerExtensibility.Controllers
{
	public class CustomerController : Controller
	{
		public ViewResult Index()
		{
			return View("Result", new Result
			{
				ControllerName = "Customer",
				ActionName = "Index"
			});
		}

		[ActionName("Enumerate")]
		public ViewResult List()
		{
			return View("Result", new Result
			{
				ControllerName = "Customer",
				ActionName = "List"
			});
		}
	}
}

В этом листинге мы применили атрибут к методу List, передав в параметр значение Enumerate. Когда механизм вызова действий получает запрос к действию Enumerate, для его обслуживания он теперь будет использовать метод List.

Вы можете увидеть эффект атрибута ActionName, запустив приложение и перейдя по ссылке /Customer/Enumerate. Как видите, результаты, показанные в браузере на рисунке 17-5, получены от метода List.

Рисунок 17-5: Эффект атрибута ActionName

Применение атрибута переопределяет имя действия. Это означает, что URL, которые ведут непосредственно к методу List, больше работать не будут, как показано на рисунке 17-6.

Рисунок 17-6: Используем имя метода в качестве действия после применения атрибута ActionName

Существуют две основные причины переопределить таким образом имя метода.

  • Чтобы использовать имена действий, которые не соответствуют правилам C# (Например, [ActionName("User-Registration")]).
  • Вам могут понадобиться два различных метода C#, которые принимают один и тот же набор параметров и должны обрабатывать одно и то же имя действия, но в ответ на различные типы запросов HTTP (например, один для [HttpGet], а другой для [HttpPost]). В таком случае вы можете дать методам различные имена C#, чтобы не возникло проблем с компилятором, но затем с помощью [ActionName] соотнести их с одним именем действия.

У этого атрибута есть один странный аспект: Visual Studio будет использовать имя оригинального метода в диалоговом окне Add View. Итак, если вы кликните правой кнопкой мыши по методу List и выберите пункт Add View, вы увидите диалоговое окно, показанное на рисунке 17-7.

Рисунок 17-7: Visual Studio не обнаруживает атрибут ActionName

Это проблема, потому что MVC Framework будет искать стандартные представления по имени действия, которым в нашем примере является List, что было определено в атрибуте. При создании представления по умолчанию для метода действия, который использует атрибут ActionName, вы должны убедиться, что его имя соответствует имени, указанному в атрибуте, а не методу C#.

Используем селектор метода действия

Часто контроллер будет содержать несколько действий с одним и тем же именем. Так может получиться, если у вас есть несколько методов с различными параметрами, или если вы использовали атрибут ActionName, так что несколько методов представляют собой одно и то же действие.

В таких ситуациях MVC Framework требуется помощь при выборе соответствующего действия, которое должно обработать запрос. Механизм такой помощи называется селектором метода действия. Он позволяет определить виды запросов, которые может обрабатывать действие. Вы уже видели пример селектора метода действия, когда мы ограничили действие с помощью атрибута HttpPost в приложении SportsStore. У нас было два метода под названием Checkout в контроллере Cart, и с помощью HttpPost мы указали, который из них должен использоваться только для запросов HTTP POST, как показано в листинге 17-16.

Листинг 17-16: Используем атрибут HttpPost
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers
{
	public class CartController : Controller
	{
// ...other instance members omitted for brevity...
		[HttpPost]
		public ViewResult Checkout(Cart cart, ShippingDetails shippingDetails)
		{
			if (cart.Lines.Count() == 0)
			{
				ModelState.AddModelError("", "Sorry, your cart is empty!");
			}
			if (ModelState.IsValid)
			{
				orderProcessor.ProcessOrder(cart, shippingDetails);
				cart.Clear();
				return View("Completed");
			}
			else
			{
				return View(shippingDetails);
			}
		}

		public ViewResult Checkout()
		{
			return View(new ShippingDetails());
		}
	}
}

Механизм вызова действий использует селекторы методов действий, чтобы избежать неоднозначности при выборе действий. В листинге 17-16 есть два кандидата для действия Checkout. Механизм вызова отдает предпочтение действиям с селекторами. В этом случае он смотрит на селектор HttpPost, чтобы решить, может ли запрос быть обработан данным методом. Если да, то механизм вызова выберет именно этот метод. Если нет, то будет использоваться его «конкурент».

Для различных видов HTTP-запросов существуют встроенные атрибуты, которые работают как селекторы: HttpPost для запросов POST, HttpGet для запросов GET, HttpPut для PUT и так далее. NonAction – это еще один встроенный атрибут, который указывает механизму вызова действий, что этот метод нельзя использовать, хотя он и является действительным методом действия. Мы применили атрибут NonAction в листинге 17-17, где мы также определили новый метод действия в контроллере Customer.

Листинг 17-17: Используем селектор NonAction
using ControllerExtensibility.Models;
using System.Web.Mvc;
namespace ControllerExtensibility.Controllers
{
	public class CustomerController : Controller
	{
		// ...other action methods omitted for brevity...
		[NonAction]
		public ActionResult MyAction()
		{
			return View();
		}
	}
}

Метод MyAction в листинге не будет рассматриваться как метод действия, хотя он и отвечает всем критериям поиска механизма вызова. Таким образом можно гарантировать, что внутренние методы вашего приложения не будут использоваться как действия. Конечно, обычно такие методы просто должны иметь пометку private, которая не позволит вызывать их как действия, однако, если по каким-то причинам необходимо пометить такие методы как public, то придется использовать [NonAction]. Ссылки, которые ведут к методам NonAction, будут генерировать ошибки 404—Not Found, как показано на рисунке 17-8.

Рисунок 17-8: Эффект запроса URL, который ведет к методу NonAction

Создаем пользовательский селектор метода действия

Селекторы методов действий наследуют от класса ActionMethodSelectorAttribute, который показан в листинге 17-18.

Листинг 17-18: Класс ActionMethodSelectorAttribute
using System.Reflection;
namespace System.Web.Mvc
{
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
	public abstract class ActionMethodSelectorAttribute : Attribute
	{
		public abstract bool IsValidForRequest(
			ControllerContext controllerContext,
			MethodInfo methodInfo);
	}
}

Класс ActionMethodSelectorAttribute является абстрактным и определяет один абстрактный метод: IsValidForRequest. Параметрами этого метода являются объект ControllerContext, который передает информацию о запросе, и объект MethodInfo, с помощью которого можно получить информацию о методе, к которому применен селектор. IsValidForRequest возвращает true, если метод может обработать запрос, в противном случае - false. Мы создали простой пользовательский селектор метода действия под названием LocalAttribute в папке Infrastructure нашего проекта, как показано в листинге 17-19.

Листинг 17-19: Пользовательский селектор метода действия
using System.Reflection;
using System.Web.Mvc;
namespace ControllerExtensibility.Infrastructure
{
	public class LocalAttribute : ActionMethodSelectorAttribute
	{
		public override bool IsValidForRequest(
			ControllerContext controllerContext,
			MethodInfo methodInfo)
		{
			return controllerContext.HttpContext.Request.IsLocal;
		}
	}
}

Мы переопределили метод IsValidForRequest так, чтобы он возвращал true, когда запрос исходит их локальной машины. Чтобы продемонстрировать работу пользовательского селектора метода действия, мы создали в проекте контроллер Home, как показано в листинге 17-20.

Листинг 17-20: Контроллер Home
using System.Web.Mvc;
using ControllerExtensibility.Infrastructure;
using ControllerExtensibility.Models;

namespace ControllerExtensibility.Controllers
{
	public class HomeController : Controller
	{
		public ActionResult Index()
		{
			return View("Result", new Result
			{
				ControllerName = "Home",
				ActionName = "Index"
			});
		}

		[ActionName("Index")]
		public ActionResult LocalIndex()
		{
			return View("Result", new Result
			{
				ControllerName = "Home",
				ActionName = "LocalIndex"
			});
		}
	}
}

Мы использовали атрибут ActionName, чтобы создать ситуацию, в которой есть два метода действия Index. На данный момент механизм вызова действий не может выяснить, какой из них следует использовать для запроса ссылки /Home/Index, и при получении такого запроса будет генерировать ошибку, показанную на рисунке 17-9.

Рисунок 17-9: Ошибка для неоднозначных имен методов действий

Чтобы разрешить эту ситуацию, мы можем применить атрибут селекции к одному из неоднозначных методов, как показано в листинге 17-21.

Листинг 17-21: Применяем атрибут селекции
[Local]
[ActionName("Index")]
public ActionResult LocalIndex()
{
	return View("Result", new Result
	{
		ControllerName = "Home",
		ActionName = "LocalIndex"
	});
}

Если вы перезапустите приложение и перейдите по корневой ссылке из браузера, работающего на локальной машине, вы увидите, что MVC Framework принимает во внимание атрибуты селекции, чтобы устранить неоднозначность методов в классе контроллера, как показано на рисунке 17-10.

Рисунок 17-10: Используем атрибут селекции для исключения неоднозначности методов действий

Устранение неоднозначности методов действий

Теперь, когда вы видели изнанку базового класса селектора, вам будет легче понять, как механизм вызова действий выбирает метод действия. Механизм вызова сначала просматривает список методов контроллера, которые отвечают критериям и являются методами действий. Затем он проходит через следующий процесс:

Механизм вызова сортирует методы по имени. В списке остаются только методы с тем же именем, что и запрашиваемое действие, или с соответствующим атрибутом ActionName.

Механизм вызова отбрасывает методы с атрибутом селектора, который возвращает false для текущего запроса.

Если остается только один метод действия с селектором, то используется он. Если остается больше одного метода с селектором, то выбрасывается исключение, потому что механизм вызова не может устранить неоднозначность между доступными методами.

Если методов с селектором не остается, то механизм вызова просматривает методы без селекторов. Если есть только один такой метод, то вызывается именно он. Если таких методов больше, то выбрасывается исключение, потому что механизм вызова не может выбрать один из них.

Обработка неизвестных действий

Если механизм вызова действий не может найти подходящий метод действия, то его метод InvokeAction возвращает false. Когда это происходит, класс Controller вызывает метод HandleUnknownAction. По умолчанию этот метод возвращает ответ 404 — Not Found. Такой ответ используется в большинстве приложений, но если вы хотите отображать что-то другое, то можете переопределить этот метод в каком-либо контроллере. В листинге 17-22 показано переопределение метода HandleUnknownAction в контроллере Home.

Листинг 17-22: Переопределяем метод HandleUnknownAction
using System.Web.Mvc;
using ControllerExtensibility.Infrastructure;
using ControllerExtensibility.Models;
namespace ControllerExtensibility.Controllers
{
	public class HomeController : Controller
	{
		// ...other action methods omitted for brevity...
		protected override void HandleUnknownAction(string actionName)
		{
			Response.Write(string.Format("You requested the {0} action", actionName));
		}
	}
}

Если вы запустите приложение и перейдите по ссылке, ведущей к несуществующему методу действия, то увидите ответ, показанный на рисунке 17-11.

Рисунок 17-11: Ответ для запросов к несуществующим методам действий