Как изменить значение атрибута имени элемента ввода в модели вида бритвы с использованием настраиваемого атрибута в модели?

У меня есть следующее:

@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, чтобы упростить работу с программистами. Надеюсь, этот взгляд не распространяется повсюду, и у вас есть только один редактор или частичный.