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

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

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

И мы можем пойти еще дальше и создать алиасы для методов действий, которые также были переделаны и больше не присутствует в контроллере. Чтобы сделать это, мы просто создаем статический URL и добавляем значения controller и action в качестве значений по умолчанию, как показано в листинге 13-14.

Листинг 13-14: Создание алиасов для контроллера и действия

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

routes.MapRoute("ShopSchema2", "Shop/OldAction", new { controller = "Home", action = "Index" }); routes.MapRoute("ShopSchema", "Shop/{action}", new { controller = "Home" }); routes.MapRoute("", "X{controller}/{action}");

routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" }); routes.MapRoute("", "Public/{controller}/{action}", new { controller = "Home", action = "Index" });

}

}

}

Обратите внимание еще раз, что мы разместили наш новый роут так, чтобы он определялся в первую очередь. Это потому что он является более конкретным, чем роуты, которые следуют за ним. Если бы запрос для Shop/OldAction был обработан, например, следующим роутом, мы получили бы другой результат, а не тот, что мы хотим. Запрос бы обработался с ошибкой 404—Not Found, а не переведен, чтобы сохранить контакт с нашими клиентами.

Юнит тест: тестирование статических сегментов

Еще раз, мы можем использовать вспомогательные методы для роутов, URL паттерны которых содержат статические сегменты. Вот что мы добавили в тестовый метод TestIncomingRoutes, чтобы протестировать роут, добавленный в листинге 13-14:

...

[TestMethod]

public void TestIncomingRoutes() { TestRouteMatch("~/", "Home", "Index"); TestRouteMatch("~/Customer", "Customer", "Index"); TestRouteMatch("~/Shop/Index", "Home", "Index");

TestRouteMatch("~/Customer/List", "Customer", "List"); TestRouteFail("~/Customer/List/All");

}

...

321

Определение пользовательских переменных для сегментов

Сегментные переменные controller и action имеют особое значение для MVC и, очевидно, что они соответствуют контроллеру и методу действия, которые будут использоваться для обработки запроса. Мы не ограничены этими встроенными сегментными переменными: мы также можем определить наши собственные переменные, как показано в листинге 13-15. (Мы удалили все существующие роуты из предыдущего раздела, чтобы начать все сначала).

Листинг 13-15: Определение дополнительных переменных в URL паттерне

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new

{

controller = "Home", action = "Index",

id = "DefaultId"

});

}

}

}

Роут URL паттерна определяет стандартные переменные controller и action, а также пользовательскую переменную id. Этот роут будет соответствовать любому URL с количеством сегментов от нуля до трех. Содержание третьего сегмента будет присвоено переменной id, а если третьего сегмента нет, будет использоваться значение по умолчанию.

Внимание

Некоторые имена зарезервированы и не доступны для имен пользовательских переменных сегмента. Это controller, action и area. Смысл первых двух очевиден, и мы объясним роль областей в следующей главе.

Мы можем получить доступ к любой сегментной переменной в методе действий с помощью свойства RouteData.Values. Чтобы продемонстрировать это, мы добавили метод действия CustomVariable в класс HomeController, как показано в листинге 13-16.

Листинг 13-16: Доступ к пользовательской переменной сегмента в методе действия

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers

322

{

public class HomeController : Controller

{

public ActionResult Index()

{

ViewBag.Controller = "Home"; ViewBag.Action = "Index"; return View("ActionName");

}

public ActionResult CustomVariable()

{

ViewBag.Controller = "Home"; ViewBag.Action = "CustomVariable";

ViewBag.CustomVariable = RouteData.Values["id"]; return View();

}

}

}

Этот метод получает значение пользовательской переменной в роутовом URL паттерне и передает его представлению при помощи ViewBag. Щелкните правой кнопкой мыши по новому методу действия в редакторе кода и выберите Add View. Назовите представление CustomVariable и нажмите кнопку Add. Visual Studio создаст новый файл CustomVariable.cshtml в папке /Views/Home.

Измените представление так, чтобы оно соответствовало содержанию, показанному в листинге 1317.

Листинг 13-17: Отображение значения пользовательской сегментной переменной

@{

Layout = null;

}

<!DOCTYPE html> <html>

<head>

<meta name="viewport" content="width=device-width" /> <title>Custom Variable</title>

</head>

<body>

<div>The controller is: @ViewBag.Controller</div> <div>The action is: @ViewBag.Action</div>

<div>The custom variable is: @ViewBag.CustomVariable</div> </body>

</html>

Чтобы увидеть результат использования пользовательской переменной сегмента, запустите приложение и перейдите по URL /Home/CustomVariable/Hello. Вызывается метод действия CustomVariable в контроллере Home, и значение пользовательской переменной сегмента извлекается из ViewBag и передается представлению. Вы можете увидеть результат на рисунке 13-7.

Рисунок 13-7: Отображение значения пользовательской сегментной перменной

323

Мы предоставили значение по умолчанию для переменной сегмента id, что означает, что вы увидите результаты, показанные на рисунке 13-8, если вы перейдете на /Home/CustomVariable.

Рисунок 13-8: Значение по умолчанию для пользовательской сегментной переменной

Юнит тест: тестирование пользовательских сегментных переменных

Мы включили поддержку для тестирования пользовательских переменных сегмента во вспомогательные методы тестирования. Метод TestRouteMatch имеет дополнительный параметр, который принимает анонимный тип, содержащий имена свойств, которые мы хотим протестировать, и ожидаемые значения. Вот изменения, которые мы сделали для тестового метода TestIncomingRoutes, чтобы проверить роут, определенный в листинге 13-15:

...

[TestMethod]

public void TestIncomingRoutes() {

TestRouteMatch("~/", "Home", "Index", new { id = "DefaultId" }); TestRouteMatch("~/Customer", "Customer", "index", new { id = "DefaultId" }); TestRouteMatch("~/Customer/List", "Customer", "List",

new { id = "DefaultId" });

TestRouteMatch("~/Customer/List/All", "Customer", "List", new { id = "All" }); TestRouteFail("~/Customer/List/All/Delete");

}

...

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

Использование свойства RouteData.Values является только одним способом доступа к пользовательским переменным роута. Другой способ гораздо более элегантный. Если мы определим параметры нашего метода действия именами, которых соответствуют переменным URL паттерна, MVC передаст значения, полученные из URL, в качестве параметров методу действия. Например, пользовательская переменная, которая была определена в роуте в листинге 13-15, называется id. Мы можем изменить метод действия CustomVariable, так чтобы он имел соответствующий параметр, как показано в листинге 13-18.

Листинг 13-8: Изменение метода действия так, чтобы сегментная переменная соответствовала его параметру

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

324

namespace UrlsAndRoutes.Controllers

{

public class HomeController : Controller

{

public ActionResult Index()

{

ViewBag.Controller = "Home"; ViewBag.Action = "Index"; return View("ActionName");

}

public ActionResult CustomVariable(string id)

{

ViewBag.Controller = "Home"; ViewBag.Action = "CustomVariable"; ViewBag.CustomVariable = id; return View();

}

}

}

Когда система маршрутизации находит соответствие URL с определенным нами в листинге 13-15 роутом, значение третьего сегмента в URL присваивается пользовательской переменной id. MVC сравнивает список сегментных переменных со списком параметров метода действия, и если имена совпадают, передает значения из URL в метод.

Мы определили параметр id как string, но MVC попытается преобразовать URL значение в тот тип параметра, который мы определим. Если мы объявили параметр id как int или DateTime, то мы получим из URL значение, разобранное как экземпляр этого типа. Это элегантная и полезная возможность, которая устраняет необходимость самим проводить преобразование типов.

Примечание

MVC использует систему модели связывания данных для преобразования значений, содержащихся в URL, в .NET типы и может обрабатывать гораздо более сложные ситуации, чем показанная в этом примере. Мы расскажем о модели связывания данных в главе 22.

Определение дополнительных URL сегментов

Дополнительный сегмент URL это тот, который пользователь не должен указывать, но для которого не задано значение по умолчанию. В листинге 13-19 показан пример, и мы указываем, что переменная сегмента не является обязательной, установив значение по умолчанию

UrlParameter.Optional.

Листинг 13-19: Указание дополнительного URL сегмента

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

325

routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new

{

controller = "Home", action = "Index",

id = UrlParameter.Optional

});

}

}

}

Этот роут будет соответствовать URL, независимо от того, был ли указан id сегмент. В таблице 13-3 показано, как это работает с различными URL.

Таблица 13-3: Соответствие URL с дополнительной сегментной переменной

Число

Пример

Соответствие

 

 

 

 

 

сегментов

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

mydomain.com

controller

=

,

=

Index

 

 

 

Home action

 

 

 

 

 

 

 

 

 

 

1

mydomain.com/Customer

controller

=

,

 

 

=

Index

 

 

 

Customer action

 

 

 

 

 

 

 

 

 

 

2

mydomain.com/Customer/List

controller

=

,

 

 

=

List

 

 

 

Customer action

 

 

 

 

 

3

mydomain.com/Customer/List/All

controller =

Customer, action = List,

id = All

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Нет соответствия: слишком много

4 mydomain.com/Customer/List/All/Delete

сегментов

Как вы можете видеть из таблицы, переменная id добавляется в набор переменных только при наличии соответствующего сегмента во входящем URL. Эта функция полезна, если вам нужно знать, задал ли пользователь значение для сегмента переменной. Если значение не было предоставлено для дополнительной сегментной переменной, значение соответствующего параметра будет null. В листинге 13-20 мы обновили наш контроллер, чтобы он реагировал, если для сегментной переменной id не было предоставлено значение.

Листинг 13-20: Проверка на то, было ли предоставлено значение для дополнительной сегментной переменной

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers

{

public class HomeController : Controller

{

public ActionResult Index()

{

ViewBag.Controller = "Home"; ViewBag.Action = "Index"; return View("ActionName");

}

public ActionResult CustomVariable(string id)

{

ViewBag.Controller = "Home"; ViewBag.Action = "CustomVariable";

ViewBag.CustomVariable = id == null ? "<no value>" : id; return View();

}

326

}

}

Вы можете увидеть результат, если запустите приложение и перейдете по /Home/CustomVariable (здесь не определено значение сегментной переменной id), на рисунке 13-9.

Рисунок 13-9: Определение того, что URL не содержит значение для дополнительной сегментной переменной

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

Некоторые разработчики, которые очень сосредоточены на разделении понятий в MVC паттерне, не любят устанавливать значения по умолчанию для переменных сегмента в роутах для приложения. Если это касается и вас, вы можете использовать дополнительные параметры C# наряду с дополнительной переменной сегмента в роуте, чтобы определить значения по умолчанию для параметров метода действия. В качестве примера в листинге 13-21 показано, как мы изменили метод действия CustomVariable, чтобы определить значение по умолчанию для параметра id, который будет использоваться, если URL не содержит значения.

Листинг 13-21: Определение значения по умолчанию для параметра метода действия

...

public ActionResult CustomVariable(string id = "DefaultId") { ViewBag.Controller = "Home";

ViewBag.Action = "CustomVariable";

ViewBag.CustomVariable = id; return View();

}

...

Для параметра id всегда будет значение (либо из URL, либо по умолчанию), поэтому мы удалили код, который имеет дело со значением null. Этот метод действия совместно с роутом, который мы определили в листинге 13-19, функционально эквивалентен роуту, который мы определили в листинге 13-15:

...

routes.MapRoute("MyRoute", "{controller}/{action}/{id}",

new { controller = "Home", action = "Index", id = "DefaultId" });

...

Разница заключается в том, что значение по умолчанию для сегментной переменной id обозначается в коде контроллера, а не в определении роута.

Юнит тестирование: дополнительные URL сегменты

327

Единственный вопрос, который необходимо учитывать при тестировании дополнительных URL сегментов, заключается в том, что сегментная переменная не будет добавлена в коллекцию RouteData.Values, если значение не было найдено в URL. Это означает, что вам не следует включать переменную в анонимный тип, если вы тестируете URL, который содержит дополнительный сегмент. Вот наши изменения тестового метода TestIncomingRoutes для роута, определенного в листинге 13-19.

...

[TestMethod]

public void TestIncomingRoutes() { TestRouteMatch("~/", "Home", "Index"); TestRouteMatch("~/Customer", "Customer", "index");

TestRouteMatch("~/Customer/List", "Customer", "List"); TestRouteMatch("~/Customer/List/All", "Customer", "List", new { id = "All" }); TestRouteFail("~/Customer/List/All/Delete");

}

...

Определение роутов с разным числом сегментов

Другой способ изменить стандартный консерватизм URL паттернов – это принять число переменных URL сегментов. Это позволяет принимать URL произвольной длины в одном роуте. Вы определяете поддержку переменных сегмента, обозначив одну из сегментных переменных как catchall, что делается при помощи добавления звездочки (*), как показано в листинге 13-22.

Листинг 13-22: Назначение catchall переменной

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", new

{

controller = "Home", action = "Index",

id = UrlParameter.Optional });

}

}

}

Мы расширили роут из предыдущего примера, чтобы добавить переменную сегмента catchall, которую мы образно назвали catchall. Этот роут теперь будет соответствовать любому URL, независимо от количества сегментов, которые он содержит, или значения любого из этих сегментов. Первые три сегмента используются для установки значений для переменных controller, action и id соответственно. Если URL содержит дополнительные сегменты, все они назначаются переменной catchall, как показано в таблице 13-4.

328

Таблица 13-4: Соответствие URL с сегментной переменной catchall

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Число

Пример

Соответствие

 

 

 

 

 

 

 

сегментов

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

/

controller

=

,

=

Index

 

 

 

 

 

Home action

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1

/Customer

controller

=

,

 

 

=

Index

 

 

 

 

 

Customer action

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

/Customer/List

controller

=

,

 

 

=

List

 

 

 

 

 

Customer action

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

/Customer/List/All

controller

=

,

 

 

=

,

=

All

 

 

 

Customer action

 

List id

 

 

 

 

 

4

/Customer/List/All/Delete

controller =

Customer, action = List, id = All,

catchall = Delete

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

/Customer/List/All/Delete/Perm

controller = Customer, action = List, id = All,

 

 

catchall = Delete/Perm

 

 

 

 

 

 

Здесь нет верхнего ограничения по числу сегментов, поэтому URL паттерн данного роута будет совпадать с любым подходящим URL. Обратите внимание, что сегменты, находящиеся в catchall, представлены в форме segment/segment/segment. Мы должны обрабатывать строку, чтобы получить отдельные сегменты.

Юнит тест: тестирование сегментных переменных catchall

Мы можем обрабатывать переменную catchall как пользовательскую переменную. Разница лишь в том, что мы должны ожидать несколько сегментов, которые связываются в одно значение, например, segment/segment/segment. Обратите внимание, что мы не получим начальный или конечный знаки /. Вот изменения метода TestIncomingRoutes, которые демонстрируют тестирование сегмента catchall. Тут используется роут, определенный в листинге 13-22, и URL, показанные в таблице 13-4:

...

[TestMethod]

public void TestIncomingRoutes() { TestRouteMatch("~/", "Home", "Index"); TestRouteMatch("~/Customer", "Customer", "Index");

TestRouteMatch("~/Customer/List", "Customer", "List"); TestRouteMatch("~/Customer/List/All", "Customer", "List", new { id = "All" }); TestRouteMatch("~/Customer/List/All/Delete", "Customer", "List",

new { id = "All", catchall = "Delete" }); TestRouteMatch("~/Customer/List/All/Delete/Perm", "Customer", "List",

new { id = "All", catchall = "Delete/Perm" });

}

...

Определение приоритета контроллера по пространству имен

Если входящий URL соответствует роуту, MVC фреймворк берет значение переменной controller и ищет соответствующее имя. Например, если значение переменной controller равно Home, то MVC ищет контроллер HomeController. Это неполное имя класса, то есть MVC не знает, что делать, если есть два или более класса HomeController в разных пространствах имен.

Чтобы понять эту проблему, создайте новую папку в корневом каталоге проекта, назовите ее AdditionalControllers и добавьте новый контроллер Home. Установите содержание, чтобы оно соответствовало листингу 13-23.

Листинг 13-23: Добавление второго контроллера Home в проект

using System;

using System.Collections.Generic;

329

using System.Linq; using System.Web; using System.Web.Mvc;

namespace UrlsAndRoutes.AdditionalControllers

{

public class HomeController : Controller

{

public ActionResult Index()

{

ViewBag.Controller = "Additional Controllers - Home"; ViewBag.Action = "Index";

return View("ActionName");

}

}

}

Если вы запустите приложение, вы увидите ошибку, показанную на рисунке 13-10.

Рисунок 13-10: Возникает ошибка, если есть два контроллера с одним и тем же именем

MVC фреймворк искал класс HomeController и нашел два: один в нашем исходном проекте и один в нашем проекте AdditionalControllers. Если вы прочтете текст ошибки, показанной на рисунке 1310, вы увидите, что MVC фреймворк услужливо сообщает нам, какие классы он нашел.

Эта проблема возникает чаще, чем вы можете себе представить, особенно если вы работаете над большим MVC проектом, который использует библиотеки контроллеров других команд

330

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