MVC批量更新,可验证并解决集合元素不连续控制器接收不完全的问题
2020-12-13 02:17
                         标签:style   blog   class   code   java   tar    在"MVC批量添加,增加一条记录的同时添加N条集合属性所对应的个体"中,有2个问题待解决:     1、由jquery动态生成了表单元素,但不能实施验证。       
 
 批量添加或更新,控制器能接收的方式,大致有2种: 或   □ Model 一个用户,可以有任意多喜欢看的电影。 
 显示更新界面,接收更新数据,并相应视图添加按钮,通过部分视图添加新行。   □ Home/EditUser.cshtml      
   □ Layout.cshtml中把jquery 
ui和验证相关js引进来     @Scripts.Render("~/bundles/jquery")  
   □ 写一个帮助方法,目的是生成如下格式:   帮助方法除了要生成目标格式,还要考虑:         由于BeginCollectionItem()方法返回的类型是实现了IDisposable接口的CollectionItemNamePrefixScope类,所以,我们在部分视图MovieEntryEditor.cshtml中可以使用using语句。 
 但,如果把_Layout.cshtml中有关客户端异步验证的js引用去掉,即把@Scripts.Render("~/bundles/jqueryval")注释掉,再次运行,居然服务端不再报错: 
      
 为什么?这是由User的ModelState状态不一致引起的。先来看下ModelState类: public class ModelState   可见,ModelState不仅保存这有关Model的一切错误信息,还保存着有ValueProvider提供的表单数据。 在提交表单之前,界面的input大概是这样的:   当提交失败,回到原先视图界面,这时候,所有的集合元素都需要通过MovieEntryEditor.cshtml来渲染,界面的input大概变成这样:   提交前后,ModelState的状态是不一致的,导致服务端验证失败。 为了保证有关User的ModelState的状态一致,大体上应该这样做:       
 □ 
对帮助类CollectionEditingHtmlExtensions进行改良 把原先生成字符串的代码: string itemIndex = Guid.NewGuid().ToString();   改成: string collectionIndexFieldName = String.Format("{0}.Index", 
collectionName);//FavouriteMovies.Index     其中,GetCollectionItemIndex()需要根据某种条件排判断: 1、如果渲染新的表单元素,就产生新的GUID字符串      
 改良后的完整代码如下:   在GetCollectionItemIndex()方法中,首先把隐藏域的name属性值,比如这里的FavouriteMovies.Index作为key,把队列Queue 
 如果previousIndicesValues包含集合元素,就说明MovieEntryEditor.cshtml视图需要还原原先的表单元素,每次从队列Queue 
 如果previousIndicesValues中没有集合元素,就说明MovieEntryEditor.cshtml视图需要渲染新的表单元素,就生成一个新的GUID字符串。 
 把刚才注释掉的客户端异步验证js引用,再注释回来。 
 验证不通过:       
 验证通过:       
 □ 最后 通过本篇的方法:   即使集合元素不是连续的,控制器也能接收到所有的集合元素,实现批量添加或更新。 通过部分视图渲染每组集合元素,保证了客户端和服务端的验证功能。 □ 参考资料 MVC批量更新,可验证并解决集合元素不连续控制器接收不完全的问题,搜素材,soscw.com MVC批量更新,可验证并解决集合元素不连续控制器接收不完全的问题 标签:style   blog   class   code   java   tar    原文地址:http://www.cnblogs.com/darrenji/p/3716436.html
2、一旦集合元素不连续,控制器就无法接收到完整的数据。
Category.Name 
    
Category.Products[0].Name     
Category.Products[3].Name   
Category.Products[6].Name      public class User
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        public IList
    }
 
    public class Movie
    {
        [Required]
        public string Title { get; set; }
        public int Rating { get; set; }
    } 
    
□ 
HomeController
    public class HomeController : Controller
    {
        private static User _currentUser;
 
        private static User CurrentUser
        {
            get
            {
                if (_currentUser == null)
                {
                    _currentUser = GetFakeUser();
                }
                return _currentUser;
            }
            set { _currentUser = value; }
        }
 
        private static User GetFakeUser()
        {
            return new User()
            {
                Id = 1,
                Name = "darren",
                FavouriteMovies = new List
                {
                    new Movie(){Title = "movie1"},
                    new Movie(){Title = "movie2"},
                    new Movie(){Title = "movie3"}
                }
            };
        }
 
        public ActionResult EditUser()
        {
            return View(CurrentUser);
        }
 
        [HttpPost]
        public ActionResult EditUser(User user)
        {
            if (!ModelState.IsValid)
            {
                return View(user);
            }
            CurrentUser = user;
            return View(CurrentUser);
        }
 
        //响应视图添加按钮,通过部分视图生成一行
        public ActionResult MovieEntryRow()
        {
            return PartialView("MovieEntryEditor");
        }     
 
其中,每组集合元素,即用户喜欢的电影通过部分视图MovieEntryEditor.cshtml渲染出来。@using VariableCollection.Models
@model VariableCollection.Models.User
 
@{
    ViewBag.Title = "EditUser";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    
    
    
    
    
        "submit" value="提交"/>
    
}
 
@section scripts
{
    
        $(function() {
            $(‘#movieEditor‘).sortable();
 
            $("#addAnother").click(function () {
                $.get(‘/Home/MovieEntryRow‘, function (template) {
                    $("#movieEditor").append(template);
                });
            });
        });
    
}
 
    @Scripts.Render("~/bundles/jqueryui")  
    @Scripts.Render("~/bundles/jqueryval")
...
● 
为了保证隐藏域value值的唯一性,每次渲染部分视图MovieEntryEditor.cshtml,让这里的value有一个唯一的GUID字符串。      
● 
还需要替换ViewData.TemplateInfo的HtmlFieldPrefix属性值为FavouriteMovies[6d85a95b-1dee-4175-bfae-73fad6a3763b]。using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
 
namespace VariableCollection.Extension
{
    public static class CollectionEditingHtmlExtensions
    {
        /// 
        /// 目标是生成如下格式
        ///
        ///
        ///
        ///
        /// 
        /// 
        /// 
        /// 集合属性的名称
        /// 
        public static IDisposable BeginCollectionItem
        {
            string itemIndex = Guid.NewGuid().ToString();
 
            //比如,FavouriteMovies[6d85a95b-1dee-4175-bfae-73fad6a3763b]
            string collectionItemName = string.Format("{0}[{1}]", collectionName, itemIndex);
 
            TagBuilder indexField = new TagBuilder("input");
            indexField.MergeAttributes(new Dictionarystring, string>() {
                { "name", String.Format("{0}.Index", collectionName) }, //name="FavouriteMovies.Index"
                { "value", itemIndex },//value="6d85a95b-1dee-4175-bfae-73fad6a3763b"
                { "type", "hidden" },
                { "autocomplete", "off" }
            });
            html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
 
            return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
        }
 
         private class CollectionItemNamePrefixScope : IDisposable
         {
             private readonly TemplateInfo _templateInfo;
             private readonly string _previousPrefix;
 
             public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
             {
                 this._templateInfo = templateInfo;
                 _previousPrefix = templateInfo.HtmlFieldPrefix;
                 templateInfo.HtmlFieldPrefix = collectionItemName;
             }
 
             public void Dispose()
             {
                 _templateInfo.HtmlFieldPrefix = _previousPrefix;
             }
         }
    }
}
 
@using VariableCollection.Extension
@model VariableCollection.Models.Movie
 
    @using (Html.BeginCollectionItem("FavouriteMovies"))
    {
        
"@Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt=""/>
 
        @Html.LabelFor(model => model.Title)
        @Html.EditorFor(model => model.Title)
        @Html.ValidationMessageFor(model => model.Title)
 
        @Html.LabelFor(model => model.Rating)
        @Html.EditorFor(model => model.Rating)
        @Html.ValidationMessageFor(model => model.Rating)
 
        "#" onclick=" $(this).parent().remove(); ">删除行
    }
运行,如果不符合验证要求,会报错,似乎看上去不错:      

{  
    public 
ModelErrorCollection Errors{get;}  
    public 
ValueProviderResult Value{get;set;}  
}
● 
对于通过部分视图MovieEntryEditor.cshtml渲染出来的新的表单元素,我们希望BeginCollectionItem()方法为我们生成新的GUID字符串。 
     
● 
对于验证不通过,重新由部分视图MovieEntryEditor.cshtml渲染的表单元素,我们希望还是用原先的GUID字符串,以保证ModelState状态一致。
string itemIndex = 
GetCollectionItemIndex(collectionIndexFieldName);
2、如果不是渲染新的表单元素,就使用原先的GUID字符串using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
 
namespace VariableCollection.Extension
{
    public static class CollectionEditingHtmlExtensions
    {
        public static IDisposable BeginCollectionItem
        {
            string collectionIndexFieldName = String.Format("{0}.Index", collectionName);//FavouriteMovies.Index
            string itemIndex = GetCollectionItemIndex(collectionIndexFieldName);
 
            //比如,FavouriteMovies[6d85a95b-1dee-4175-bfae-73fad6a3763b]
            string collectionItemName = string.Format("{0}[{1}]", collectionName, itemIndex);
 
            TagBuilder indexField = new TagBuilder("input");
            indexField.MergeAttributes(new Dictionarystring, string>() {
                { "name", String.Format("{0}.Index", collectionName) }, //name="FavouriteMovies.Index"
                { "value", itemIndex },//value="6d85a95b-1dee-4175-bfae-73fad6a3763b"
                { "type", "hidden" },
                { "autocomplete", "off" }
            });
            html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
 
            return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
        }
 
         private class CollectionItemNamePrefixScope : IDisposable
         {
             private readonly TemplateInfo _templateInfo;
             private readonly string _previousPrefix;
 
             public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
             {
                 this._templateInfo = templateInfo;
                 _previousPrefix = templateInfo.HtmlFieldPrefix;
                 templateInfo.HtmlFieldPrefix = collectionItemName;
             }
 
             public void Dispose()
             {
                 _templateInfo.HtmlFieldPrefix = _previousPrefix;
             }
         }
 
        /// 
        /// 以FavouriteMovies.Index为键,把Guid字符串存放在上下文中
        /// 如果是添加进入部分视图,就直接生成一个Guid字符串
        /// 如果是更新,为了保持和ModelState的一致,就遍历原先的Guid
        /// 
        /// FavouriteMovies.Index
        /// 
        private static string GetCollectionItemIndex(string collectionIndexFieldName)
        {
            Queuestring> previousIndices = (Queuestring>)HttpContext.Current.Items[collectionIndexFieldName];
            if (previousIndices == null)
            {
                HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queuestring>();
                string previousIndicesValues = HttpContext.Current.Request[collectionIndexFieldName];//HttpContext.Current.Request[FavouriteMovies.Index]
                if (!string.IsNullOrWhiteSpace(previousIndicesValues))
                {
                    foreach (string index in previousIndicesValues.Split(‘,‘))
                    {
                        previousIndices.Enqueue(index);
                    }
                }
            }
            return previousIndices.Count > 0 ? previousIndices.Dequeue() : Guid.NewGuid().ToString();
        }
    }
}
 


......
※  Editing Variable Length Reorderable Collections in ASP.NET MVC – 
Part 1: ASP.NET MVC Views
文章标题:MVC批量更新,可验证并解决集合元素不连续控制器接收不完全的问题
文章链接:http://soscw.com/essay/25268.html