我怎样才能得到验证消息上呈现集合属性使用新的GUID索引中的每个时间? [英] How can I get validation messages to render on collection properties when using new guid indexes each time?

查看:144
本文介绍了我怎样才能得到验证消息上呈现集合属性使用新的GUID索引中的每个时间?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这个例子中ASP.Net MVC 4项目我有一个用户填写有关赛马的细节。比赛有一个名字一个以及所涉及的马的列表。每匹马都有一个名字和年龄。

该表单使用Ajax和JavaScript允许人在飞行,然后提交一次全部当提交按钮添加和删除马输入字段为pressed。

为了使这个过程容易让我来说,我使用的是<一个href=\"http://www.mattlunn.me.uk/blog/2014/08/how-to-dynamically-via-ajax-add-new-items-to-a-bound-list-model-in-asp-mvc-net/\">html助手通过马特·伦恩的。

 公共静态MvcHtmlString EditorForMany&LT;的TModel,TValue&GT;(此的HtmlHelper&LT;的TModel&GT; HTML,防爆pression&LT; Func键&LT;的TModel,IEnumerable的&LT; TValue&GT;&GT;&GT;前pression,串htmlFieldName = NULL)其中的TModel:类
{
    VAR项目=前pression.Compile()(html.ViewData.Model);
    VAR SB =新的StringBuilder();    如果(String.IsNullOrEmpty(htmlFieldName))
    {
        VAR preFIX = html.ViewContext.ViewData.TemplateInfo.HtmlField preFIX;        htmlFieldName =(prefix.Length大于0(preFIX +)的String.Empty。)+防爆pressionHelper.GetEx pressionText(如pression);
    }    的foreach(在项目VAR项)
    {
        VAR哑=新{ITEM =项目};
        VAR GUID = Guid.NewGuid()的ToString()。        VAR memberExp =前pression.MakeMemberAccess(前pression.Constant(虚拟),dummy.GetType()的getProperty(项目)。);
        VAR singleItemExp =前pression.Lambda&LT;&Func键LT;的TModel,TValue&GT;&GT;(memberExp,前pression.Parameters);        sb.Append(的String.Format(@&LT;输入类型=隐藏NAME ={0}的.index值={1}/&gt;中,htmlFieldName,GUID)) ;
        sb.Append(html.EditorFor(singleItemExp,空,的String.Format({0} [{1}],htmlFieldName,GUID)));
    }    返回新MvcHtmlString(sb.ToString());
}

虽然我不知道所有的细节(请阅读博客文章),我不知道它改变了索引值成的GUID,而不是连续的整数。这让我删除列表中的中间项,而无需重新计算索引。

下面是我的code我MCVE

其余

HomeController.cs

 公共类HomeController的:控制器
{
    [HTTPGET]
    公众的ActionResult指数()
    {
        VAR模型=新的种族();        //已填写之一启动
        model.HorsesInRace.Add(新马(){名称=史酷比,年龄= 10});        返回查看(模型);
    }    [HttpPost]
    公众的ActionResult指数(赛postedModel)
    {
        如果(ModelState.IsValid)
            //模型是有效的,重定向到另一页
            返回RedirectToAction(ViewHorseListing);
        其他
            //模型是无效的,以验证错误再次显示页面
            返回查看(postedModel);
    }    [HTTPGET]
    公众的ActionResult AjaxMakeHorseEntry()
    {
        //新的空白马Ajax调用
        VAR模型=新的List&LT;马&GT;(){新马()};
        返回PartialView(模型);
    }
}

Models.cs

 公共类种族
{
    公开赛(){HorsesInRace =新的List&LT;马&GT;(); }    [显示(名称=种族名称),必需]
    公共字符串RaceName {搞定;组; }    [显示(NAME =马在比赛中)]
    公开名单&LT;马&GT; HorsesInRace {搞定;组; }
}公共类马
{
    [显示(NAME =马的名称),必需]
    公共字符串名称{;组; }    [显示(NAME =马的时代),要求]
    公众诠释年龄{搞定;组; }
}

Index.cshtml

  @model CollectionAjaxPosting.Models.Race
&LT; H1&GT;比赛详情及LT; / H1&GT;
@using(Html.BeginForm())
{
    @ Html.ValidationSummary()
    &LT;小时/&GT;
    &LT; D​​IV&GT;
        @ Html.DisplayNameFor(X =&GT; x.RaceName)
        @ Html.EditorFor(X =&GT; x.RaceName)
        @ Html.ValidationMessageFor(X =&GT; x.RaceName)
    &LT; / DIV&GT;
    &LT;小时/&GT;
    &LT; D​​IV ID =马上市&GT; @ Html.EditorForMany(X =&GT; x.HorsesInRace)LT; / DIV&GT;
    &LT;按钮ID =BTN-加上马型=按钮&gt;添加新豪&LT; /按钮&GT;
    &LT;输入类型=提交值=输入马/&GT;
}&LT;脚本类型=文/ JavaScript的&GT;
    $(文件)。就绪(函数(){        //添加按钮逻辑
        $('#BTN-添加马')。点击(函数(){
            $阿贾克斯({
                网址:'@ Url.Action(AjaxMakeHorseEntry)',
                缓存:假的,
                方法:GET,
                成功:功能(HTML){
                    $('#马上市)追加(HTML);
                }
            })
        });        //删除马按钮
        $('#马上市)。在('点击','button.delete马',函数(){
            VAR horseEntryToRemove = $(本).closest('div.horse');
            。horseEntryToRemove preV(输入[类型=隐藏]')删除();
            horseEntryToRemove.remove();
        });    });
&LT; / SCRIPT&GT;

查看/共享/ EditorTemplates / Horse.cshtml

  @model CollectionAjaxPosting.Models.Horse&LT; D​​IV CLASS =马&GT;
    &LT; D​​IV&GT;
        @ Html.DisplayNameFor(X =&GT; x.Name)
        @ Html.EditorFor(X =&GT; x.Name)
        @ Html.ValidationMessageFor(X =&GT; x.Name)
    &LT; / DIV&GT;
    &LT; D​​IV&GT;
        @ Html.DisplayNameFor(X =&GT; x.Age)
        @ Html.EditorFor(X =&GT; x.Age)
        @ Html.ValidationMessageFor(X =&GT; x.Age)
    &LT; / DIV&GT;
    &LT;按钮式=按钮级=删除马&GT;删除马&LT; /按钮&GT;
    &LT;小时/&GT;
&LT; / DIV&GT;

查看/主页/ AjaxMakeHorseEntry.cshtml

  @model IEnumerable的&LT; CollectionAjaxPosting.Models.Horse&GT;@ Html.EditorForMany(X =&X的催化剂,HorsesInRace)

数据流可以与此code。一个人能够在页面上创建和删除条目马,因为他们想要为多,而表单提交时所有输入的值都送给了操作方法。

但是,如果用户没有在马条目 [必需] 信息输入, ModelState.IsValid 将假再次展示形式,但否验证消息将显示作为马的属性。验证错误,请在的ValidationSummary 列表中显示出来,但。

例如,如果种族名称留空,与沿一个马的名称,验证消息会示出了前者。后期将有一个确认&LT;跨度方式&gt; 与类现场验证,有效

我很肯定,这是造成的,因为在 EditorForMany 方法为每个属性创建新的GUID每次创建页面时,这样的验证消息不能匹配正确的字段。

我能做些什么来解决这个问题?我是否需要放弃GUID索引创建或可以的改变将在 EditorForMany 方法制成的,以允许正确传递沿验证消息?


解决方案

  

我很肯定,这是造成的,因为在 EditorForMany 方法为每个属性创建新的GUID每次创建页面时,这样的验证消息不能匹配正确的字段。


是的;这正是这里发生了什么。

要解决这个问题,我们需要修改 EditorForMany(),使其重新使用GUID一个项目,而不是生成一个新的。反过来,这意味着我们需要跟踪什么GUID已分配给什么产品,因此,它可被重新使用。

前者可以通过内部修改来完成,以 EditorForMany()。后者要求我们:


  1. 属性添加到我们的模型中,所分配的GUID可以存储

  2. 添加参数 EditorForMany()来知道哪些属性包含GUID重新使用(如果有的话)的帮手。

这离开 EditorForMany 助手看起来像这样;

 公共静态类HtmlHelperExtensions
{
    公共静态MvcHtmlString EditorForMany&LT;的TModel,TValue&GT;(此的HtmlHelper&LT;的TModel&GT; HTML,防爆pression&LT; Func键&LT;的TModel,IEnumerable的&LT; TValue&GT;&GT;&GT; propertyEx pression,防爆pression&LT; Func键&LT; TValue ,串&GT;&GT; indexResolverEx pression = NULL,字符串htmlFieldName = NULL)其中的TModel:类
    {
        htmlFieldName = htmlFieldName?防爆pressionHelper.GetEx pressionText(propertyEx pression);        VAR项目= propertyEx pression.Compile()(html.ViewData.Model);
        VAR htmlBuilder =新的StringBuilder();
        VAR htmlFieldNameWith preFIX = html.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName);
        FUNC&LT; TValue,串&GT; indexResolver = NULL;        如果(indexResolverEx pression == NULL)
        {
            indexResolver = X =&GT;空值;
        }
        其他
        {
            indexResolver = indexResolverEx pression.Compile();
        }        的foreach(在项目VAR项)
        {
            VAR哑=新{ITEM =项目};
            VAR GUID = indexResolver(项目);
            VAR memberExp =前pression.MakeMemberAccess(前pression.Constant(虚拟),dummy.GetType()的getProperty(项目)。);
            VAR singleItemExp =前pression.Lambda&LT;&Func键LT;的TModel,TValue&GT;&GT;(memberExp,propertyEx pression.Parameters);            如果(String.IsNullOrEmpty(GUID))
            {
                。GUID = Guid.NewGuid()的ToString();
            }
            其他
            {
                GUID = html.AttributeEn code(GUID);
            }            htmlBuilder.Append(的String.Format(@&LT;输入类型=隐藏NAME ={0}的.index值={1}/&gt;中,htmlFieldNameWith preFIX ,GUID));            如果(indexResolverEx pression!= NULL)
            {
                htmlBuilder.Append(的String.Format(@&LT;输入类型=隐藏NAME ={0} [{1}] {2},值={1}/&GT; htmlFieldNameWith preFIX,GUID,防爆pressionHelper.GetEx pressionText(indexResolverEx pression)));
            }            htmlBuilder.Append(html.EditorFor(singleItemExp,空,的String.Format({0} [{1}],htmlFieldName,GUID)));
        }        返回新MvcHtmlString(htmlBuilder.ToString());
    }
}

我们还再需要改变我们的模型,添加其中GUID存储的属性;

 公共类种族
{
    公开赛(){HorsesInRace =新的List&LT;马&GT;(); }    [显示(名称=种族名称),必需]
    公共字符串RaceName {搞定;组; }    [显示(NAME =马在比赛中)]
    公开名单&LT;马&GT; HorsesInRace {搞定;组; }
}公共类马
{
    [显示(NAME =马的名称),必需]
    公共字符串名称{;组; }    [显示(NAME =马的时代),要求]
    公众诠释年龄{搞定;组; }    //注意,除了指数在这里。
    公共字符串指数{搞定;组; }
}

......,最后,改变我们的使用 EditorForMany()来使用新的签名;

Index.cshtml 的;

 &LT; D​​IV ID =马上市&GT; @ Html.EditorForMany(X =&GT; x.HorsesInRace,X =&GT; x.Index)LT; / DIV&GT;

AjaxMakeHorseEntry.cshtml 的;

  @ Html.EditorForMany(X =&GT; X,X =&GT; x.Index,HorsesInRace)

...那么它应该验证消息出现。



顺便说一句,我建议不要使用 htmlFieldName 参数 EditorForMany ,而是改变你的控制器动作;

  [HTTPGET]
公众的ActionResult AjaxMakeHorseEntry()
{
    VAR模型=新的种族();    model.HorsesInRace.Add(新马());
    返回PartialView(模型);
}

...那么你的 AjaxMakeHorseEntry.cshtml 的看法是公平的。

  @model Models.Race@ Html.EditorForMany(X =&GT; x.HorsesInRace,X =&GT; x.Index)

否则,嵌套 EditorForMany的使用()。名称属性突破>

我会更新博客文章使用 EditorForMany(),验收少 htmlFieldName以上版本参数,因为这个原因。

In this example ASP.Net MVC 4 program I have a user fill in details about a horse race. The race has a name a well as a list of horses involved. Each horse has a name and an age.

The form uses ajax and javascript to allow the person to add and delete horse input fields on the fly, which is then submitted all at once when the submit button is pressed.

To make this process easy for me, I'm using an html helper made by Matt Lunn.

public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string htmlFieldName = null) where TModel : class
{
    var items = expression.Compile()(html.ViewData.Model);
    var sb = new StringBuilder();

    if (String.IsNullOrEmpty(htmlFieldName))
    {
        var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;

        htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
    }

    foreach (var item in items)
    {
        var dummy = new { Item = item };
        var guid = Guid.NewGuid().ToString();

        var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
        var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);

        sb.Append(String.Format(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldName, guid));
        sb.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
    }

    return new MvcHtmlString(sb.ToString());
}

While I don't understand all the details (please read the blog post), I do know that it changes the index values into guids rather than sequential integers. This allows me to delete items in the middle of the list without needing to recalculate indexes.

Here is the rest of my code for my MCVE

HomeController.cs

public class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        var model = new Race();

        //start with one already filled in
        model.HorsesInRace.Add(new Horse() { Name = "Scooby", Age = 10 });

        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Race postedModel)
    {
        if (ModelState.IsValid)
            //model is valid, redirect to another page
            return RedirectToAction("ViewHorseListing");
        else
            //model is not valid, show the page again with validation errors
            return View(postedModel);
    }

    [HttpGet]
    public ActionResult AjaxMakeHorseEntry()
    {
        //new blank horse for ajax call
        var model = new List<Horse>() { new Horse() };
        return PartialView(model);
    }
}

Models.cs

public class Race
{
    public Race() { HorsesInRace = new List<Horse>(); }

    [Display(Name = "Race Name"), Required]
    public string RaceName { get; set; }

    [Display(Name = "Horses In Race")]
    public List<Horse> HorsesInRace { get; set; }
}

public class Horse
{
    [Display(Name = "Horse's Name"), Required]
    public string Name { get; set; }

    [Display(Name = "Horse's Age"), Required]
    public int Age { get; set; }
}

Index.cshtml

@model CollectionAjaxPosting.Models.Race
<h1>Race Details</h1>
@using (Html.BeginForm())
{
    @Html.ValidationSummary()
    <hr />
    <div>
        @Html.DisplayNameFor(x => x.RaceName)
        @Html.EditorFor(x => x.RaceName)
        @Html.ValidationMessageFor(x => x.RaceName)
    </div>
    <hr />
    <div id="horse-listing">@Html.EditorForMany(x => x.HorsesInRace)</div>
    <button id="btn-add-horse" type="button">Add New Horse</button>
    <input type="submit" value="Enter Horses" />
}

<script type="text/javascript">
    $(document).ready(function () {

        //add button logic
        $('#btn-add-horse').click(function () {
            $.ajax({
                url: '@Url.Action("AjaxMakeHorseEntry")',
                cache: false,
                method: 'GET',
                success: function (html) {
                    $('#horse-listing').append(html);
                }
            })
        });

        //delete-horse buttons
        $('#horse-listing').on('click', 'button.delete-horse', function () {
            var horseEntryToRemove = $(this).closest('div.horse');
            horseEntryToRemove.prev('input[type=hidden]').remove();
            horseEntryToRemove.remove();
        });

    });
</script>

Views/Shared/EditorTemplates/Horse.cshtml

@model CollectionAjaxPosting.Models.Horse

<div class="horse">
    <div>
        @Html.DisplayNameFor(x => x.Name)
        @Html.EditorFor(x => x.Name)
        @Html.ValidationMessageFor(x => x.Name)
    </div>
    <div>
        @Html.DisplayNameFor(x => x.Age)
        @Html.EditorFor(x => x.Age)
        @Html.ValidationMessageFor(x => x.Age)
    </div>
    <button type="button" class="delete-horse">Remove Horse</button>
    <hr />
</div>

Views/Home/AjaxMakeHorseEntry.cshtml

@model IEnumerable<CollectionAjaxPosting.Models.Horse>

@Html.EditorForMany(x => x, "HorsesInRace")

The data flow works with this code. A person is able to create and delete horse entries as much as they want on the page, and when the form is submitted all entered values are given to the action method.

However, if the user does not enter in the [Required] information on a horse entry, ModelState.IsValid will be false showing the form again, but no validation messages will be shown for the Horse properties. The validation error do show up in the ValidationSummary list though.

For example, if Race Name is left blank, along with one Horse's Name, a validation message will be shown for the former. The latter will have a validation <span> with the class "field-validation-valid".

I'm very sure this is caused because the EditorForMany method creates new guids for each property each time the page is created, so validation messages can't be matched to the correct field.

What can I do to fix this? Do I need to abandon guid index creation or can an alteration be made to the EditorForMany method to allow validation messages to be passed along correctly?

解决方案

I'm very sure this is caused because the EditorForMany method creates new guids for each property each time the page is created, so validation messages can't be matched to the correct field.

Yep; that's exactly what is happening here.

To fix this, we need to amend EditorForMany() so that it re-uses the GUID for an item, rather than generating a new one. In turn, this means we need to track what GUID has been assigned to what item, so that it can be re-used.

The former can be accomplished by internal modifications to EditorForMany(). The latter requires us to:

  1. Add a property to our models in which the assigned GUID can be stored
  2. Add a parameter to EditorForMany() to tell the helper which property contains the GUID to re-use (if any).

This leaves the EditorForMany helper looking like this;

public static class HtmlHelperExtensions
{
    public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> propertyExpression, Expression<Func<TValue, string>> indexResolverExpression = null, string htmlFieldName = null) where TModel : class
    {
        htmlFieldName = htmlFieldName ?? ExpressionHelper.GetExpressionText(propertyExpression);

        var items = propertyExpression.Compile()(html.ViewData.Model);
        var htmlBuilder = new StringBuilder();
        var htmlFieldNameWithPrefix = html.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName);
        Func<TValue, string> indexResolver = null;

        if (indexResolverExpression == null)
        {
            indexResolver = x => null;
        }
        else
        {
            indexResolver = indexResolverExpression.Compile();
        }

        foreach (var item in items)
        {
            var dummy = new { Item = item };
            var guid = indexResolver(item);
            var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
            var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, propertyExpression.Parameters);

            if (String.IsNullOrEmpty(guid))
            {
                guid = Guid.NewGuid().ToString();
            }
            else
            {
                guid = html.AttributeEncode(guid);
            }

            htmlBuilder.Append(String.Format(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldNameWithPrefix, guid));

            if (indexResolverExpression != null)
            {
                htmlBuilder.Append(String.Format(@"<input type=""hidden"" name=""{0}[{1}].{2}"" value=""{1}"" />", htmlFieldNameWithPrefix, guid, ExpressionHelper.GetExpressionText(indexResolverExpression)));
            }

            htmlBuilder.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
        }

        return new MvcHtmlString(htmlBuilder.ToString());
    }
}

We also then need to change our models, to add a property in which the GUID is stored;

public class Race
{
    public Race() { HorsesInRace = new List<Horse>(); }

    [Display(Name = "Race Name"), Required]
    public string RaceName { get; set; }

    [Display(Name = "Horses In Race")]
    public List<Horse> HorsesInRace { get; set; }
}

public class Horse
{
    [Display(Name = "Horse's Name"), Required]
    public string Name { get; set; }

    [Display(Name = "Horse's Age"), Required]
    public int Age { get; set; }

    // Note the addition of Index here.
    public string Index { get; set; }
}    

... and lastly, change our usage of EditorForMany() to use the new signature;

Index.cshtml;

<div id="horse-listing">@Html.EditorForMany(x => x.HorsesInRace, x => x.Index)</div>

AjaxMakeHorseEntry.cshtml;

@Html.EditorForMany(x => x, x => x.Index, "HorsesInRace")

... which should then make the validation messages appear.


As an aside, I recommend not using the htmlFieldName parameter for EditorForMany, and instead changing your controller action to;

[HttpGet]
public ActionResult AjaxMakeHorseEntry()
{
    var model = new Race();

    model.HorsesInRace.Add(new Horse());
    return PartialView(model);
}

... then your AjaxMakeHorseEntry.cshtml view to be just;

@model Models.Race

@Html.EditorForMany(x => x.HorsesInRace, x => x.Index)

Otherwise, the generated name attributes break when nesting usage of EditorForMany().

I'm going to update the blog post to use the above version of EditorForMany(), less the acceptance of the htmlFieldName parameter, for this reason.

这篇关于我怎样才能得到验证消息上呈现集合属性使用新的GUID索引中的每个时间?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆