Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
29
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

using System.Web.Http; using System.Web.Mvc; using System.Web.Routing;

using ControllerExtensibility.Infrastructure; namespace ControllerExtensibility

{

public class MvcApplication : System.Web.HttpApplication

{

protected void Application_Start()

{

AreaRegistration.RegisterAllAreas();

WebApiConfig.Register(GlobalConfiguration.Configuration);

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

RouteConfig.RegisterRoutes(RouteTable.Routes);

ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new CustomControllerActivator()));

}

}

}

Вы можете увидеть эффект применения пользовательского активатора, если запустите приложение и перейдите по ссылке /Product. Маршрут указывает на контроллер Product, и DefaultControllerFactory попросит активатор создать экземпляр класса ProductFactory - но вместо этого активатор перехватывает запрос и создает экземпляр класса CustomerController. Результат показан на рисунке 17-3.

Рисунок 17-3: Перехват запросов на создание экземпляра с помощью пользовательского активатора контроллера

Переопределяем методы DefaultControllerFactory

Чтобы изменить процедуру создания контроллеров, можно переопределить методы в классе DefaultControllerFactory. В таблице 17-2 описаны три переопределяемых метода, каждый из которых выполняет несколько специфическую задачу.

Таблица 17-2: Переопределяемые методы DefaultContollerFactory

Метод

 

Результат

Описание

 

 

 

 

 

 

 

Реализация метода CreateController из интерфейса

 

 

 

IControllerFactory. По умолчанию этот метод вызывает

CreateController

 

IController

GetControllerType, чтобы определить, для какого типа

 

 

 

нужно создать экземпляр, а затем получает объект

 

 

 

контроллера, передавая результат в метод

 

 

 

GetControllerInstance.

Соотносит запросы с типами контроллеров. Здесь GetControllerType Type применяется большинство критериев, перечисленных ранее в

этой главе.

GetControllerInstanceIControllerСоздает экземпляр указанного типа.

431

Создание пользовательского средства вызова метода действия (action invoker)

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

Подсказка

Если вы создаете контроллер непосредственно из интерфейса IController, то вы

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

Controller.

Механизм вызова действий реализует интерфейс IActionInvoker, который показан в листинге 17-12.

Листинг 17-12: Интерфейс IActionInvoker

namespace System.Web.Mvc

{

public interface IActionInvoker

{

bool InvokeAction(ControllerContext controllerContext, string actionName);

}

}

В интерфейсе есть только один член: InvokeAction. Параметрами являются объект ControllerContext (о котором мы рассказывали в главе 15) и string, который содержит имя вызываемого действия. Он возвращает логическое значение: true означает, что действие был найдено и вызвано, а false - что в контроллере нет соответствующего действия.

Обратите внимание, что в этом описании мы не использовали слово метод. Связь между действиями и методами является чисто опциональной. Хотя описанному выше подходу следует встроенный механизм вызова действий, вы можете обрабатывать действия любым способом, который выберете. В листинге 17-13 показана реализация интерфейса IActionInvoker, которая использует другой подход.

Листинг 17-13: Пользовательский механизм вызова действий

using System.Web.Mvc;

namespace ControllerExtensibility.Infrastructure

{

public class CustomActionInvoker : IActionInvoker

{

public bool InvokeAction(ControllerContext controllerContext, string actionName)

{

if (actionName == "Index")

{

controllerContext.HttpContext.

Response.Write("This is output from the Index action"); return true;

}

else

{

432

return false;

}

}

}

}

Этот механизм вызова действий не затрагивает методы в классе контроллера. Фактически он работает с самими действиями. Если поступает запрос к действию Index, то он запишет сообщение непосредственно в Response. Если поступит запрос к любому другому действию, то он вернет false, после чего пользователю будет показана ошибка 404 — Not found.

Связь механизма вызова действий с контроллером устанавливается с помощью свойства Controller.ActionInvoker. Это означает, что разные контроллеры в одном приложении могут использовать разные механизмы вызова. Чтобы это продемонстрировать, мы добавили в проект новый контроллер под названием ActionInvoker, определение которого вы можете увидеть в листинге 17-14.

Листинг 17-14: Используем пользовательский механизм вызова действий в контроллере

using ControllerExtensibility.Infrastructure; using System.Web.Mvc;

namespace ControllerExtensibility.Controllers

{

public class ActionInvokerController : Controller

{

public ActionInvokerController()

{

this.ActionInvoker = new CustomActionInvoker();

}

}

}

В этом контроллере нет методов действий, и запросы обрабатывает механизм вызова действий. Вы можете увидеть, как он работает, запустив приложение и перейдя по ссылке /ActionInvoker/Index.

Пользовательский механизм вызова будет генерировать ответ, показанный на рисунке 17-4. Если перейдете по ссылке, ведущей к какому-либо другому действию этого контроллера, вы увидите страницу с ошибкой 404.

Рисунок 17-4: Эффект применения пользовательского механизма вызова действий

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

433

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

Встроенный механизм вызова действий, то есть класс 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()

434

{

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

435

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

Чтобы использовать имена действий, которые не соответствуют правилам 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;

436

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

{

437

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,

438

который передает информацию о запросе, и объект 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.

439

Рисунок 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: Используем атрибут селекции для исключения неоднозначности методов действий

440

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]