Главная страница   /   17.2. Создание списка параметров строки запроса (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

17.2. Создание списка параметров строки запроса

При разработке MVC представлений вам часто придется подготавливать списки параметров строки запроса. Эти списки параметров используются для создания URL для использования в HTML-элементах таких, как гиперссылки или теги <form>. Способ создания таких URL, используемый по умолчанию, благоприятствует неявной форме дублирования, которая может тормозить или предотвращать будущие изменения. В этом разделе вы научитесь создавать новые URL, укомплектованные параметрами строки запроса, и таким образом, вы сможете вносить безопасные изменения в списки параметров метода действия.

Действие контроллера для данного примера довольно простое, всего лишь с одним параметром, как это показано ниже:

Листинг 17-6: Действие Edit редактирования профиля
public ViewResult Edit(string username)
{
	var profile = _profileRepository.Find(username);
	return View(new EditProfileInput(profile));
}

Листинг 17-6 демонстрирует метод действия, который принимает в качестве параметра имя пользователя и отправляет модель представления в представление, используемое по умолчанию. В ASP.NET MVC существует 2 варианта построения списков параметров: мы можем создать RouteValueDictionary или анонимный тип, оба из которых продемонстрированы ниже:

Листинг 17-7: Текущие варианты создания URL, основанных на роутах
@Html.ActionLink("Edit", "Edit",
	new RouteValueDictionary(new Dictionary<string, object>
	{
		{"username", Model.Username }
	}
	))
@Html.ActionLink("Edit", "Edit", new { username = Model.Username })

Первый вариант, с использованием RouteValueDictionary, довольно неприглядный. RouteValueDictionary принимает множество параметров, перед тем, как вы вдруг обнаружите, что пытаетесь задать параметр username. Второй вариант – короче, но менее интуитивен. Копия этого перегруженного ActionLink принимает параметр с названием routeValues, но только типа object.

Разработчики должны сами определить, когда эти перегруженные методы, принимающие параметры типа object, должны включаться в работу при отсутствии подходящего синтаксиса инициализатора словаря в C#. Изнутри метод ActionLink использует рефлексию для поиска свойств и значений, определенных в анонимном типе. Затем метод ActionLink конструирует словарь из определенных свойств и их значений. Названия свойств становятся ключами значений роута, а значения свойств – значениями роута.

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

У нас есть два варианта обращения к такому дублированию. Первый вариант – создать строго типизированные модели для каждого метода действия, которые принимают параметры. Второй – инкапсулировать построение списков параметров в объекте строителе. Мы могли бы затем использовать этот строитель параметров для построения списков параметров в наших представлениях и действиях контроллеров. Обычно размещение структуры вокруг параметров строки запроса является предпочтительным, поскольку это поможет предотвратить появление типовых ошибок.

Для начала нам необходимо создать наш объект-строитель параметров.

Листинг 17-8: Объект ParamBuilder
public class ParamBuilder : ExplicitFacadeDictionary<string, object>
{
	private readonly IDictionary<string, object> _params
		= new Dictionary<string, object>();

	protected override IDictionary<string, object> Wrapped
	{
		get { return _params; }
	}

	public static implicit operator RouteValueDictionary(ParamBuilder paramBuilder)
	{
		return new RouteValueDictionary(paramBuilder);
	}

	public ParamBuilder Username(string value)
	{
		_params.Add("username", value);
		return this;
	}
}

Наш класс ParamBuilder унаследован от особого класса словаря, ExplicitFacadeDictionary. Этот класс является реализацией параметризованного IDictionary, в котором каждый метод реализуется открыто для того, чтобы убедиться в том, что пользователи ParamBuilder не будут засыпаны массой методов словаря. Абстрактному классу ExplicitFacadeDictionary необходимы исполнители, чтобы предоставить в свойстве Wrapped упакованный объект словарь.

Далее мы определяем неявный оператор конвертации из ParamBuilder в RouteValueDictionary, обеспечивая возможность передавать объект ParamBuilder напрямую в методы, подразумевающие использование RouteValueDictionary.

Наконец, мы определяем метод Username, предназначенный для инкапсуляции параметра действия username. Поскольку нам может потребоваться использование более одного параметра действия, метод Username возвращает экземпляр ParamBuilder, с тем, чтобы разработчик мог связывать вместе разнообразные параметры.

Для того чтобы использовать класс ParamBuilder, нам для начала необходим экземпляр ParamBuilder. Вместо создания экземпляра нового строителя в наших представлениях, мы можем определить новую базовую страницу представления для поддержки нашего нового вспомогательного объекта.

Листинг 17-9: Класс базовой страницы представления
public abstract class ViewPageBase<TModel> : WebViewPage<TModel>
{
	public ParamBuilder Param { get { return new ParamBuilder(); } }
}

Для того чтобы использовать этот класс базовой страницы представления, мы выполняем наследование от параметризованного ViewPageBase<T> вместо параметризованного WebViewPage<T>. В целом создание класса базовой страницы представления является хорошей идеей, поскольку это позволяет нам конструировать сквозные вспомогательные методы представлений аналогично созданию сквозных супертипов слоя (layer supertype) контроллеров. Мы можем определить класс базовой страницы Razor, которую наследует наше представление, с помощью следующей Razor директивы:

@inherits ViewPageBase<Profile>

В противном случае мы можем определить глобальный класс базовой страницы Razor в элементе <pages> в секции файла конфигурации <system.web.webPages.razor>:

<pages pageBaseType="System.Web.Mvc.WebViewPage">

Теперь, когда наше представление унаследовано от параметризованного ViewPageBase<T>, мы можем использовать свойство Param для построения списка параметров:

Листинг 17-10: Использование ParamBuilder в нашем представлении
@Html.ActionLink("Edit", "Edit", Param.Username(Model.Username)) |
@Html.ActionLink("Back to List", "Index")

В ссылке на действие Edit мы используем свойство Param для задания элемента Username. Поскольку теперь мы управляем нашими параметрами посредством объекта ParamBuilder, определенного в нашем коде, мы можем построить перегрузки для того, чтобы параметризованные методы могли принимать параметры различных типов. Все преобразования объектов моделей в значения параметров можно инкапсулировать в нашем ParamBuilder, очищая при этом наши представления.

Движок представления, используемый в ASP.NET MVC по умолчанию, – это движок Razor, но он не является единственным доступным движком представления. В следующем разделе мы рассмотрим популярный движок представления Spark.