在此示例ASP.Net MVC 4程序中,我让用户填写了有关赛马的详细信息。比赛的名称以及所涉及的赛马名单。每匹马都有一个名字和年龄。

该表单使用ajax和javascript允许用户即时添加和删除马输入字段,然后在按下“提交”按钮时立即全部提交。

为了使这个过程对我来说很容易,我使用了html helper制作的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());
}

尽管我不了解所有详细信息(请阅读博客文章),但我确实知道它会将索引值更改为guid而不是顺序整数。这使我可以删除列表中间的项目,而无需重新计算索引。

这是我的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>

View /共享/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>

View /主页/AjaxMakeHorseEntry.cshtml
@model IEnumerable<CollectionAjaxPosting.Models.Horse>

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

数据流与此代码一起工作。一个人可以在页面上创建和删除任意数量的马条目,并且在提交表单时,所有输入的值都将提供给action方法。

但是,如果用户未在马条目中输入[Required]信息,则ModelState.IsValid将为false,再次显示该表单,但是不会为Horse属性显示验证消息。验证错误确实显示在ValidationSummary列表中。

例如,如果Race Name与一个Horse's Name留为空白,则将显示前者的验证消息。后者将具有类别为“field-validation-valid”的验证<span>

c# - 每次使用新的Guid索引时,如何获取验证消息以呈现在集合属性上?-LMLPHP

我非常确定,这是因为每次创建页面时EditorForMany方法都会为每个属性创建新的Guid,因此验证消息无法与正确的字段匹配。

我该怎么做才能解决此问题?我是否需要放弃引导索引的创建,或者是否可以对EditorForMany方法进行更改以允许正确传递验证消息?

最佳答案



是的;这就是这里正在发生的事情。

要解决此问题,我们需要修改EditorForMany(),以便它重新使用GUID代替一项,而不是生成一个新项。反过来,这意味着我们需要跟踪将哪些GUID分配给了哪个项目,以便可以重复使用它。

前者可以通过对EditorForMany()进行内部修改来实现。后者要求我们:

  • 向我们的模型添加一个属性,可以在其中存储分配的GUID
  • EditorForMany()添加参数以告诉帮助程序哪个属性包含要重用的GUID(如果有)。

  • 这使得EditorForMany帮助程序看起来像这样;
    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());
        }
    }
    

    然后,我们还需要更改模型,以添加用于存储GUID的属性。
    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; }
    }
    

    ...最后,将我们对EditorForMany()的用法更改为使用新的签名;

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

    ...,这将使验证消息出现。


    顺便说一句,我建议不要将htmlFieldName参数用于EditorForMany,而应将 Controller 操作更改为;
    [HttpGet]
    public ActionResult AjaxMakeHorseEntry()
    {
        var model = new Race();
    
        model.HorsesInRace.Add(new Horse());
        return PartialView(model);
    }
    

    ...那么您的AjaxMakeHorseEntry.cshtml View 将是公正的;
    @model Models.Race
    
    @Html.EditorForMany(x => x.HorsesInRace, x => x.Index)
    

    否则,当嵌套使用name时,生成的EditorForMany()属性会中断。

    由于这个原因,我将更新博客文章以使用以上版本的EditorForMany(),而不是接受htmlFieldName参数。

    关于c# - 每次使用新的Guid索引时,如何获取验证消息以呈现在集合属性上?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34949320/

    10-12 01:24