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

时间:2015-06-10 15:59:08

标签: c# asp.net-mvc razor

我有以下内容:

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

目前,input元素包含一个name属性,其值为&#34; SearchPhrase&#34;但我希望价值只是&#34; q&#34;没有重命名属性。

我更喜欢一个扩展,它允许我调用TextBoxFor,但不需要提供Name属性,因此自定义属性会以某种方式将Name属性的值自动设置为自定义属性中指定的值。 / p>

以下是我的意思的一个例子:

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时生效,无论使用什么模板来防止错误,目的是让程序员清楚,同时为用户创建用户友好的查询字符串。

是否可以使用SearchPhrase属性上的自定义属性以与显示名称更改方式类似的方式执行此操作?

2 个答案:

答案 0 :(得分:3)

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

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

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

然后我编写了一个包含默认TextBoxFor的Html帮助器并搜索Input属性,如果有的话,它将从HtmlString替换生成的TextBoxFor的名称属性:< / p>

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

这是一种完成解决方案的方法:

  1. 您必须实现TextBoxFor
  2. 的所有重载
  3. 如果您尝试将表单数据发送到参数类型为SearchBoxViewModel的操作,则会得到404,因为ModelBinder无法将请求参数绑定到此ViewModel。所以你必须写一个ModelBinder来解决这个问题。
  4. 您必须相应地写LabelFor以正确匹配for属性。
  5. 编辑:如果您遇到问题,则不必处理案例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.

答案 1 :(得分:3)

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

作为替代方案,您只需向反映SearchPhrase并具有正确名称的VM添加新属性:

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,以便让程序员更轻松。希望这种观点不会散布到任何地方,你只有一个EditorTemplate或部分。