Как изменить значение атрибута имени элемента ввода в модели вида бритвы с использованием настраиваемого атрибута в модели?
У меня есть следующее:
@model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
@using (Html.BeginForm("Index", "Search", FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
@Html.LabelFor(m => m.SearchPhrase, new { @class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
@Html.TextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
Как вы видите, это создает элемент ввода.
Модель представления, переданная в представление, содержит следующее:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
public string SearchPhrase { get; set; }
}
В настоящий момент элемент ввода содержит атрибут name со значением "SearchPhrase", но я хотел бы, чтобы значение было просто "q" без переименования свойства.
Я бы предпочел расширение, которое позволяет мне вызвать TextBoxFor, но без необходимости предоставления свойства Name, так что пользовательский атрибут каким-то образом автоматически устанавливает значение свойства Name в значение, указанное в пользовательском атрибуте.
Ниже приведен пример того, что я имею в виду:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
[Input(Name = "q")]
public string SearchPhrase { get; set; }
}
В сочетании с:
@model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
@using (Html.BeginForm("Index", "Search", FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
@Html.LabelFor(m => m.SearchPhrase, new { @class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
@Html.TextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
Что бы потом произвело нечто похожее на следующее:
<div class="smart-search">
<form action="/Search/Index" method="get" class="form-horizontal" role="form">
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
<label for="Search" class="control-label">Search</label>
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
<input type="text" name="q" id="Search" value="" class="form-control" />
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
</form>
</div>
Я бы хотел, чтобы этот пользовательский атрибут вступал в силу всякий раз, когда используется SearchBoxViewModel, независимо от того, какой шаблон используется для предотвращения ошибок, с целью быть понятным для программистов, создавая для пользователя удобную строку запроса.
Возможно ли это сделать с помощью настраиваемого атрибута в свойстве SearchPhase аналогично изменению отображаемого имени?
Ответы
Ответ 1
Я написал что-то простое, но может начать писать полное решение.
Сначала я написал простой Attribute
с именем, которое вы указали:
public class InputAttribute : Attribute
{
public string Name { get; set; }
}
Затем я написал html-помощник, который обертывает по умолчанию TextBoxFor
и ищет атрибут Input
, и если он есть, он заменит атрибут имени сгенерированного HtmlString
из TextBoxFor
:
public static MvcHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
var memberExpression = expression.Body as MemberExpression;
var attr = memberExpression.Member.GetCustomAttribute(typeof (InputAttribute)) as InputAttribute;
var result = htmlHelper.TextBoxFor(expression, htmlAttributes);
if (attr != null)
{
var resultStr = result.ToString();
var match = Regex.Match(resultStr, "name=\\\"\\w+\\\"");
return new MvcHtmlString(resultStr.Replace(match.Value, "name=\"" + attr.Name + "\""));
}
return result;
}
Затем используйте этот html-помощник в виде бритвы:
@Html.MyTextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })
Также ваша модель выглядит следующим образом:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
[Input(Name = "q")]
public string SearchPhrase { get; set; }
}
Это способ завершить решение:
- Вы должны выполнить все перегрузки
TextBoxFor
- Если вы попытаетесь отправить данные формы в действие с параметром типа
SearchBoxViewModel
, вы получите 404, потому что ModelBinder
не может привязать параметры запроса к этому ViewModel
. Поэтому вам нужно написать ModelBinder
, чтобы решить эту проблему.
- Вы должны написать
LabelFor
соответственно, чтобы правильно соответствовать атрибуту for
.
РЕДАКТИРОВАТЬ: В случае вашей проблемы вам не нужно иметь дело с case 2, потому что вы отправляете запрос GET
, и вы получите параметры формы в строке запроса. Поэтому вы можете написать свою сигнатуру действия, например:
public ActionResult Search(string q)
{
// use q to search
}
Проблема возникает, когда у вас есть не примитивный тип в ваших действительных параметрах. В этом случае ModelBinder
пытается сопоставить элементы строки запроса (или запросить полезную нагрузку) со свойствами типа параметра действия. Например:
public ActionResult Search(SearchBoxViewModel vm)
{
// ...
}
В этом случае строка запроса (или запрашиваемая полезная нагрузка) имеет ваш поисковый запрос с параметром с именем q
(поскольку имя ввода q
и html-форма отправляет запрос в виде значений ключа, состоящих из имени ввода и входное значение). Поэтому MVC не может привязать q
к SearchPhrase
в LoginViewModel
, и вы получите 404.
Ответ 2
Я знаю, что это не то, о чем вы явно просите, но я чувствую, что наличие другого имени ViewModel из фактического имени формы подрывает одно из основных соглашений в MVC и может вводить в заблуждение.
В качестве альтернативы вы можете просто добавить новое свойство VM, которое отражает SearchPhrase и имеет собственное имя:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
public string SearchPhrase { get; set; }
[Display(Name = "Search")]
public string q
{
get { return SearchPhrase; }
set { SearchPhrase = value; }
}
}
Измените свой вид, чтобы использовать их:
@model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
@using (Html.BeginForm("Index", "Search", FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
@Html.LabelFor(m => m.q, new { @class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
@Html.TextBoxFor(m => m.q, new { @class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
Это позволит вам сохранить код в конце, ссылаясь на SearchPhrase
вместо q
, чтобы упростить работу с программистами. Надеюсь, этот взгляд не распространяется повсюду, и у вас есть только один редактор или частичный.