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

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

16.7. Использование других возможностей фильтров

В предыдущих примерах мы разобрали все, что необходимо для эффективной работы с фильтрами. Далее мы продемонстрируем вам некоторые дополнительные возможности фильтрации MVC Framework, которые представляют некоторый интерес, хотя и не так широко используются.

Фильтрация без атрибутов

Обычно для использования фильтров мы применяем атрибуты, как это уже было показано в предыдущих разделах. Тем не менее, этому есть альтернатива - класс Controller реализует интерфейсы IAuthorizationFilter, IActionFilter, IResultFilter и IExceptionFilter. Он также предоставляет пустые виртуальные реализации всех методов OnXXX, с которыми вы уже также знакомы, например, OnAuthorization и OnException. В листинге 16-32 мы обновили контроллер Home, чтобы использовать эту функцию и создать класса контроллера, который будет самостоятельно анализировать свою производительность.

Листинг 16-32: Используем методы фильтров контроллера
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Filters.Infrastructure;
using System.Diagnostics;
namespace Filters.Controllers
{
	public class HomeController : Controller
	{
		private Stopwatch timer;
		[Authorize(Users = "adam, steve, jacqui", Roles = "admin")]
		public string Index()
		{
			return "This is the Index action on the Home controller";
		}
		[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError")]
		public string RangeTest(int id)
		{
			if (id > 100)
			{
				return String.Format("The id value is: {0}", id);
			}
			else
			{
				throw new ArgumentOutOfRangeException("id", id, "");
			}
		}
		public string FilterTest()
		{
			return "This is the FilterTest action";
		}
		protected override void OnActionExecuting(ActionExecutingContext filterContext)
		{
			timer = Stopwatch.StartNew();
		}
		protected override void OnResultExecuted(ResultExecutedContext filterContext)
		{
			timer.Stop();
			filterContext.HttpContext.Response.Write(
				string.Format("<div>Total elapsed time: {0}</div>",
					timer.Elapsed.TotalSeconds));
		}
	}
}

Мы удалили фильтры из метода действия FilterTest, потому что они больше не требуется - контроллер Home будет добавлятьть информацию о производительности к ответу для каждого метода действия.

На рисунке 16-11 показан эффект запуска программы и перехода по ссылке /Home/RangeTest/200, которая приведет нас к действию RangeTest, не вызывая исключения, которое мы создали для демонстрации фильтра HandleError.

Рисунок 16-11: Эффект реализации методов фильтра непосредственно в контроллере

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

Подсказка

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

Используем глобальные фильтры

Глобальные фильтры применяются ко всем методам действий во всех контроллерах приложения. Превратить обычный фильтр в глобальный можно с помощью метода RegisterGlobalFilters, определенного в файле App_Start/FilterConfig.cs. В листинге 16-33 показано, как сделать фильтр ProfileAll, который мы создали в листинге 16-30, глобальным.

Листинг 16-33: Создаем глобальный фильтр в файле FilterConfig.cs
using System.Web;
using System.Web.Mvc;
using Filters.Infrastructure;
namespace Filters
{
	public class FilterConfig
	{
		public static void RegisterGlobalFilters(GlobalFilterCollection filters)
		{
			filters.Add(new HandleErrorAttribute());
			filters.Add(new ProfileAllAttribute());
		}
	}
}

Метод RegisterGlobalFilters вызывается из метода Application_Start в файле Global.asax, что гарантирует регистрацию глобальных фильтров при запуске приложения.

Примечание

Первый оператор в методе RegisterGlobalFilters, создаваемом Visual Studio по умолчанию, настраивает стандартные правила обработки исключений MVC. Он будет визуализировать представление /Views/Shared/Error.cshtml при возникновении необработанного исключения. Во время разработки эти правила обработки исключений по умолчанию отключены. В секции "Создание фильтра исключения"далее в этой главе говорится о том, как ее включить в файле Web.config.

Параметром для метода RegisterGlobalFilters является GlobalFilterCollection. Чтобы зарегистрировать глобальный фильтр, используйте метод Add, например:

filters.Add(new ProfileAllAttribute());

Обратите внимание, что к фильтру нужно обращаться, используя полное имя класса (ProfileAllAttribute), а не краткое, которое применяется при обращении к фильтру как к атрибуту (ProfileAll). Фильтр, зарегистрированный таким образом, будет применен к каждому действию метода.

Чтобы продемонстрировать работу глобальных фильтров, мы создали контроллер под названием Customer, как показано в листинге 16-34. Новый контроллер нам нужен потому, что мы хотим использовать код, к которому не применялись фильтры в предыдущих разделах.

Листинг 16-34: Контроллер Customer
using System.Web.Mvc;
namespace Filters.Controllers
{
	public class CustomerController : Controller
	{
		public string Index()
		{
			return "This is the Customer controller";
		}
	}
}

Это очень простой контроллер, в котором есть действие Index, возвращающее строку. На рисунке 16-12 показан эффект применения глобального фильтра, который мы увидели, запустив приложение и перейдя по ссылке /Customer.

Хотя мы не применяли фильтр непосредственно к контроллеру, глобальный фильтр добавляет информацию о производительности, как показано на рисунке.

Рисунок 16-12: Эффект применения глобального фильтра

Задаем порядок выполнения фильтров

Мы уже говорили о том, что фильтры выполняются в определенной последовательности, в зависимости от типа: фильтры авторизации, фильтры действий и затем фильтры результатов. Фильтры исключений выполняются на любом этапе, когда появляется необработанное исключение. Однако внутри типа порядок выполнения отдельных фильтров определяется вами.

В листинге 16-35 показан простой класс фильтра действий под названием SimpleMessageAttribute, который мы добавили в папку Infrastructure и на примере которого продемонстрируем, как задавать порядок выполнения фильтров.

Листинг 16-35: Простой фильтр действий
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Filters.Infrastructure
{
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
	public class SimpleMessageAttribute : FilterAttribute, IActionFilter
	{
		public string Message { get; set; }
		public void OnActionExecuting(ActionExecutingContext filterContext)
		{
			filterContext.HttpContext.Response.Write(
				string.Format("<div>[Before Action: {0}]<div>", Message));
		}
		public void OnActionExecuted(ActionExecutedContext filterContext)
		{
			filterContext.HttpContext.Response.Write(
				string.Format("<div>[After Action: {0}]<div>", Message));
		}
	}
}

При вызове методов OnActionExecuting и OnActionExecuted этот фильтр записывает в ответ сообщение, часть которого задается с помощью свойства Message.

Мы можем применить несколько экземпляров этого фильтра к методу действия, как показано в листинге 16-36 (обратите внимание, что в атрибуте AttributeUsage мы устанавливаем свойству AllowMultiple значение true).

Листинг 16-36: Применяем несколько фильтров к действию
using System.Web.Mvc;
using Filters.Infrastructure;
namespace Filters.Controllers
{
	public class CustomerController : Controller
	{
		[SimpleMessage(Message = "A")]
		[SimpleMessage(Message = "B")]
		public string Index()
		{
			return "This is the Customer controller";
		}
	}
}

Мы создали два фильтра с разными сообщениями: в первом – сообщение А, во втором - сообщение B. Мы могли бы использовать два различных фильтра, но такой подход позволит упростить наш пример. Когда вы запустите приложение и перейдете по ссылке /Customer, то увидите результат, показанный на рисунке 16-13.

Рисунок 16-13: Несколько фильтров в одном методе действия

Когда мы запустим этот пример, MVC Framework выполнит фильтр А перед фильтром B, но могло быть и наоборот – платформа не гарантирует какого-либо определенного порядка выполнения. В большинстве случаев порядок не имеет значения, но если он важен, используйте свойство Order, как показано в листинге 16-37.

Листинг 16-37: Используем свойство Order в фильтре
[SimpleMessage(Message = "A", Order = 2)]
[SimpleMessage(Message = "B", Order = 1)]
public ActionResult Index()
{
	Response.Write("Action method is running");
	return View();
}

Параметр Order принимает значение int, и MVC Framework выполняет фильтры в порядке его возрастания. В листинге мы назначили фильтру B наименьшее значение, поэтому он выполняется первым, как показано на рисунке 16-14.

Рисунок 16-14: Указываем порядок выполнения фильтров

Примечание

Обратите внимание, что методы OnActionExecuting выполняются в порядке, который определили мы, а методы OnActionExecuted - в обратном порядке. MVC Framework строит стек фильтров, когда выполняет их перед методом действия, а затем раскручивает этот стек, или возвращает его в исходное положение. Поведение для раскрутки стека нельзя изменить.

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

Если несколько фильтров одного типа (например, фильтров действий) имеют одинаковое значение Order (скажем, 1), то MVC Framework определяет порядок выполнения на основании того, к чему применен фильтр. Глобальные фильтры выполняются в первую очередь, затем - фильтры, примененные к классу контроллера, затем - к методу действия.

Примечание

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