如何使用模型中的自定义属性更改razor视图模型中的输入元素名称属性值?

时间:2021-11-23 20:32:03

I have the following:

我有以下几点:

@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>

As you can see this is creating an input element.

如您所见,这是在创建一个输入元素。

The view model passed to the view contains the following:

传递给视图的视图模型包含以下内容:

public class SearchBoxViewModel
{
    [Required]
    [Display(Name = "Search")]
    public string SearchPhrase { get; set; }
}

At the moment the input element contains a name attribute with the value of "SearchPhrase" but I would like the value to be just "q" without renaming the property.

目前,input元素包含一个name属性,其值为“SearchPhrase”,但我希望该值仅为“q”,而不重命名属性。

I would prefer an extension which allows me to call TextBoxFor but without the need of having to supply the Name property, so that the custom attribute somehow sets the value of the Name property automatically to the value specified in the custom attribute.

我希望扩展允许我调用TextBoxFor,但不需要提供Name属性,以便自定义属性以某种方式将Name属性的值自动设置为自定义属性中指定的值。

The following is an example of what I mean:

下面是我的一个例子:

public class SearchBoxViewModel
{
    [Required]
    [Display(Name = "Search")]
    [Input(Name = "q")]
    public string SearchPhrase { get; set; }
}

Combined with:

结合:

@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>

Which would then produce something similar to the following:

然后会产生类似于以下内容的东西:

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

I would like this custom attribute to take effect whenever the SearchBoxViewModel is used regardless of what template is used to prevent errors, with the intention of being clear to programmers, while creating a user-friendly query string for the user.

我希望这个自定义属性在使用SearchBoxViewModel时生效,而不管使用什么模板来防止错误,同时为用户创建一个用户友好的查询字符串。

Is it possible to do this using a custom attribute on the SearchPhrase property in a similar fashion to how the display name is changed?

是否可以使用SearchPhrase属性上的自定义属性来实现这一点,方法与如何更改显示名类似?

2 个解决方案

#1


2  

I wrote something simple but can be a start to write the complete solution.

我写了一些简单的东西,但可以作为编写完整解决方案的开始。

First I wrote a simple Attribute with the name you provided:

首先,我用你提供的名字写了一个简单的属性:

public class InputAttribute : Attribute
{
    public string Name { get; set; }
}

Then I wrote a Html helper that wraps default TextBoxFor and searches for Input attribute and if any, It will replace name attribute of generated HtmlString from TextBoxFor:

然后我编写了一个Html助手,它封装了默认的TextBoxFor和搜索输入属性,如果有的话,它将替换TextBoxFor生成的HtmlString的name属性:

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;
}

Then use this html helper in razor views:

然后在剃刀视图中使用这个html助手:

@Html.MyTextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })

Also your model is as follows:

你的模型如下:

public class SearchBoxViewModel
{
    [Required]
    [Display(Name = "Search")]
    [Input(Name = "q")]
    public string SearchPhrase { get; set; }
}

This is a way to complete solution:

这是一个完整的解决方案:

  1. You have to implement all of the overloads of TextBoxFor
  2. 必须实现TextBoxFor的所有重载
  3. If you try to send form data to an action with parameter of type SearchBoxViewModel you will get a 404 because ModelBinder can not bind request parameters to this ViewModel. So you will have to write a ModelBinder to solve this problem.
  4. 如果您尝试向带有SearchBoxViewModel参数的操作发送表单数据,您将得到404,因为ModelBinder不能将请求参数绑定到这个ViewModel。因此,您将不得不编写一个ModelBinder来解决这个问题。
  5. You have to write LabelFor accordingly to match for attribute correctly.
  6. 您必须编写相应的LabelFor才能正确匹配属性。

EDIT: In case of your problem you don't have to deal with case 2 because you send a GET request and you will get the form parameters in query string. So you may write your action signature like:

编辑:如果您有问题,您不必处理案例2,因为您发送了一个GET请求,您将在查询字符串中获得表单参数。因此,您可以将您的动作签名写为:

public ActionResult Search(string q)
{
  // use q to search
}

The problem occurs when you have a non-primitive type in your action parameters. In this case ModelBinder tries to match query string items (Or request payload) with properties of type of action parameter. For example:

当操作参数中有非基元类型时,就会出现问题。在本例中,ModelBinder试图将查询字符串项(或请求有效负载)与动作参数类型的属性匹配。例如:

public ActionResult Search(SearchBoxViewModel vm)
{
  // ...
}

In this case, query string (or request payload) has your search query in a parameter named q (because name of input is q and html form sends request in form of key-values consist of input name and input value). So MVC can not bind q to SearchPhrase in LoginViewModel and you will get a 404.

在这种情况下,查询字符串(或请求负载)将搜索查询放在一个名为q的参数中(因为输入的名称是q,而html表单以键值的形式发送请求,由输入名称和输入值组成)。MVC不能将q绑定到LoginViewModel中的SearchPhrase你会得到404。

#2


3  

I know this isn't what you are explicitly asking for but I feel that having a different ViewModel name from the actual form name undermines one of the core conventions in MVC and may be misleading.

我知道这不是您明确要求的,但是我觉得与实际的窗体名称不同的ViewModel名称会破坏MVC中的一个核心约定,可能会产生误导。

As an alternative, you can just add a new property to the VM that mirrors SearchPhrase and has the proper name:

作为替代方案,您只需向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; }
    }
}

Change your view to use these:

改变你的观点来使用这些:

@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>

This would allow you to keep your code in the back end referencing SearchPhrase instead of q to make it easier on the programmers. Hopefully this view isn't spread out everywhere and you only have a single EditorTemplate or partial.

这将允许您将代码保存在后端引用SearchPhrase,而不是q,以使程序员更容易理解。希望这个视图不会在任何地方展开,您只有一个编辑器模板或部分编辑器。

#1


2  

I wrote something simple but can be a start to write the complete solution.

我写了一些简单的东西,但可以作为编写完整解决方案的开始。

First I wrote a simple Attribute with the name you provided:

首先,我用你提供的名字写了一个简单的属性:

public class InputAttribute : Attribute
{
    public string Name { get; set; }
}

Then I wrote a Html helper that wraps default TextBoxFor and searches for Input attribute and if any, It will replace name attribute of generated HtmlString from TextBoxFor:

然后我编写了一个Html助手,它封装了默认的TextBoxFor和搜索输入属性,如果有的话,它将替换TextBoxFor生成的HtmlString的name属性:

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;
}

Then use this html helper in razor views:

然后在剃刀视图中使用这个html助手:

@Html.MyTextBoxFor(m => m.SearchPhrase, new { @class = "form-control" })

Also your model is as follows:

你的模型如下:

public class SearchBoxViewModel
{
    [Required]
    [Display(Name = "Search")]
    [Input(Name = "q")]
    public string SearchPhrase { get; set; }
}

This is a way to complete solution:

这是一个完整的解决方案:

  1. You have to implement all of the overloads of TextBoxFor
  2. 必须实现TextBoxFor的所有重载
  3. If you try to send form data to an action with parameter of type SearchBoxViewModel you will get a 404 because ModelBinder can not bind request parameters to this ViewModel. So you will have to write a ModelBinder to solve this problem.
  4. 如果您尝试向带有SearchBoxViewModel参数的操作发送表单数据,您将得到404,因为ModelBinder不能将请求参数绑定到这个ViewModel。因此,您将不得不编写一个ModelBinder来解决这个问题。
  5. You have to write LabelFor accordingly to match for attribute correctly.
  6. 您必须编写相应的LabelFor才能正确匹配属性。

EDIT: In case of your problem you don't have to deal with case 2 because you send a GET request and you will get the form parameters in query string. So you may write your action signature like:

编辑:如果您有问题,您不必处理案例2,因为您发送了一个GET请求,您将在查询字符串中获得表单参数。因此,您可以将您的动作签名写为:

public ActionResult Search(string q)
{
  // use q to search
}

The problem occurs when you have a non-primitive type in your action parameters. In this case ModelBinder tries to match query string items (Or request payload) with properties of type of action parameter. For example:

当操作参数中有非基元类型时,就会出现问题。在本例中,ModelBinder试图将查询字符串项(或请求有效负载)与动作参数类型的属性匹配。例如:

public ActionResult Search(SearchBoxViewModel vm)
{
  // ...
}

In this case, query string (or request payload) has your search query in a parameter named q (because name of input is q and html form sends request in form of key-values consist of input name and input value). So MVC can not bind q to SearchPhrase in LoginViewModel and you will get a 404.

在这种情况下,查询字符串(或请求负载)将搜索查询放在一个名为q的参数中(因为输入的名称是q,而html表单以键值的形式发送请求,由输入名称和输入值组成)。MVC不能将q绑定到LoginViewModel中的SearchPhrase你会得到404。

#2


3  

I know this isn't what you are explicitly asking for but I feel that having a different ViewModel name from the actual form name undermines one of the core conventions in MVC and may be misleading.

我知道这不是您明确要求的,但是我觉得与实际的窗体名称不同的ViewModel名称会破坏MVC中的一个核心约定,可能会产生误导。

As an alternative, you can just add a new property to the VM that mirrors SearchPhrase and has the proper name:

作为替代方案,您只需向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; }
    }
}

Change your view to use these:

改变你的观点来使用这些:

@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>

This would allow you to keep your code in the back end referencing SearchPhrase instead of q to make it easier on the programmers. Hopefully this view isn't spread out everywhere and you only have a single EditorTemplate or partial.

这将允许您将代码保存在后端引用SearchPhrase,而不是q,以使程序员更容易理解。希望这个视图不会在任何地方展开,您只有一个编辑器模板或部分编辑器。