【问题标题】:ASP.NET MVC 2 validation on dynamic page动态页面上的 ASP.NET MVC 2 验证
【发布时间】:2013-01-02 14:50:31
【问题描述】:

我的 asp.net mvc 2 应用程序上有多个页面的注册向导。在第一页上,我应该有参与该过程的人员的基本数据表格。我应该有 3 个标记为名字、姓氏和地址的文本框,以及 1 个带有文本“添加另一个人”的复选框。当用户点击单选按钮时,新的文本框会出现新的单选按钮,所以我们可以在同一个表单中添加多个人。理论上,我们应该能够插入尽可能多的人。所有字段都是必填的,所以在页面顶部的验证摘要中,我应该有类似“请输入第二个人的名字”之类的内容。我有 DTO 课程:

public class Person
{
     public string FullName { get; set; }
     public string LastName { get; set; }
     public string Address{ get; set; }
}

我想我的这个页面的模型应该是List<Person>,我会用 javascript/jQuery 为新人附加 html。请在这里帮助我,我应该如何验证这个动态页面?我可以使用 Save 和 Back 按钮浏览此向导,而且,我们应该能够取消单击页面上的任何单选按钮,并且该特定人应该消失并且验证器不应再捕获它。我的整个向导都在使用服务器端验证(DataAnnotations),我不想使用客户端验证。提前致谢。

更新:

我需要更多帮助。我想用新属性扩展 Person 类:

public int Percent { get; set; } 

如果IEnumerable<Person> 中每个人的所有百分比之和等于100,我希望在提交时进行服务器验证。我可以为此创建自定义属性吗?如何创建?我的模型是通用列表,我不能在上面应用[CustomAttribute],对吧?
另外,我应该在页面顶部有验证摘要,而不是在每个输入之后。我放了:<%:Html.ValidationSummary(false, "Please correct the following and resubmit the page:")%> 有没有办法为每个人设置不同的验证消息?谢谢

【问题讨论】:

    标签: c# jquery .net asp.net-mvc-2 asp.net-mvc-validation


    【解决方案1】:

    在开始执行此任务之前,我强烈建议您阅读 Steven Sanderson 的 Editing a variable length list, ASP.NET MVC 2-style

    准备好了吗?

    好的,现在我们可以进入实现了。

    第一件事是为任务定义我们的视图模型。已经有了,只要在上面定义相应的验证规则即可:

    public class Person
    {
        [Required]
        public string FullName { get; set; }
    
        [Required]
        public string LastName { get; set; }
    
        [Required]
        public string Address{ get; set; }
    }
    

    我想我的这个页面的模型应该是列表

    是的,绝对的。

    让我们继续创建我们的PersonsController

    public class PersonsController : Controller
    {
        public ActionResult Index()
        {
            var model = new[] 
            {
                new Person()
            };
            return View(model);
        }
    
        [HttpPost]
        public ActionResult Index(IEnumerable<Person> persons)
        {
            if (!ModelState.IsValid)
            {
                return View(persons);
            }
    
            // To do: do whatever you want with the data
            // In this example I am simply dumping it to the output
            // but normally here you would update your database or whatever
            // and redirect to the next step of the wizard
            return Content(string.Join(Environment.NewLine, persons.Select(p => string.Format("name: {0} address: {1}", p.FullName, p.Address))));
        }
    
        public ActionResult BlankEditorRow()
        {
            return PartialView("_PersonEditorRow", new Person());
        }
    }
    

    现在让我们定义视图 (~/Views/Persons/Index.cshtml):

    @model IEnumerable<Person>
    
    @using (Html.BeginForm())
    {
        <div id="editorRows">
            @foreach (var item in Model)
            {
                Html.RenderPartial("_PersonEditorRow", item);
            }
        </div>    
    
        @Html.ActionLink(
            "Add another person", 
            "BlankEditorRow", 
            null, 
            new { id = "addItem" }
        )
    
        <p>
            <button type="submit">Next step</button>
        </p>
    }
    
    <script type="text/javascript">
        $('#addItem').click(function () {
            $.ajax({
                url: this.href,
                cache: false,
                success: function (html) { $('#editorRows').append(html); }
            });
            return false;
        });
    
        $(document).delegate('a.deleteRow', 'click', function () {
            $(this).parents('div.editorRow:first').remove();
            return false;
        });
    </script>
    

    以及对应的局部视图(~/Views/Persons/_PersonEditorRow.cshtml):

    @model Person
    
    <div class="editorRow">
        @using(Html.BeginCollectionItem("persons")) 
        {
            <div>
                @Html.LabelFor(x => x.FullName)
                @Html.EditorFor(x => x.FullName)
                @Html.ValidationMessageFor(x => x.FullName)
            </div>
            <div>
                @Html.LabelFor(x => x.LastName)
                @Html.EditorFor(x => x.LastName)
                @Html.ValidationMessageFor(x => x.LastName)
            </div>
            <div>
                @Html.LabelFor(x => x.Address)
                @Html.EditorFor(x => x.Address)
                @Html.ValidationMessageFor(x => x.Address)
            </div>
    
            <a href="#" class="deleteRow">delete</a>
        }
    </div>
    

    备注:此处使用的 Html.BeginCollectionItem 助手取自 Steven Sanderson 的博客文章,我之前在我的答案中链接到了该文章,您已经阅读并熟悉了该文章。以下是完整的源代码:

    public static class HtmlPrefixScopeExtensions
    {
        private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
    
        public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
        {
            var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
            string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
    
            // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
            html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
    
            return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
        }
    
        public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
        {
            return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
        }
    
        private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
        {
            // We need to use the same sequence of IDs following a server-side validation failure,  
            // otherwise the framework won't render the validation error messages next to each item.
            string key = idsToReuseKey + collectionName;
            var queue = (Queue<string>)httpContext.Items[key];
            if (queue == null)
            {
                httpContext.Items[key] = queue = new Queue<string>();
                var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
                if (!string.IsNullOrEmpty(previouslyUsedIds))
                    foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                        queue.Enqueue(previouslyUsedId);
            }
            return queue;
        }
    
        private class HtmlFieldPrefixScope : IDisposable
        {
            private readonly TemplateInfo templateInfo;
            private readonly string previousHtmlFieldPrefix;
    
            public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
            {
                this.templateInfo = templateInfo;
    
                previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
                templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
            }
    
            public void Dispose()
            {
                templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
            }
        }
    }
    

    更新:

    我的错,我刚刚注意到您的问题标有asp.net-mvc-2。所以我想我的 Razor 观点不适用于你的情况。尽管如此,其他一切都应该是一样的。您需要做的就是更新视图,以便它们使用 WebForms 视图引擎:

    这是~/Views/Persons/Index.aspx

    <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Person>>" %>
    <% using (Html.BeginForm()) { %>
        <div id="editorRows">
            <% foreach (var item in Model) { %>
                <% Html.RenderPartial("_PersonEditorRow", item); %>
            <% } %>
        </div>    
    
        <%= Html.ActionLink(
            "Add another person", 
            "BlankEditorRow", 
            null, 
            new { id = "addItem" }
        ) %>
    
        <p>
            <button type="submit">Next step</button>
        </p>
    <% } %>
    
    <script type="text/javascript">
        $('#addItem').click(function () {
            $.ajax({
                url: this.href,
                cache: false,
                success: function (html) { $('#editorRows').append(html); }
            });
            return false;
        });
    
        $(document).delegate('a.deleteRow', 'click', function () {
            $(this).parents('div.editorRow:first').remove();
            return false;
        });
    </script>
    

    最后是 (~/Views/Persons/_PersonEditorRow.ascx) 部分:

    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Person>" %>
    <div class="editorRow">
        <% using(Html.BeginCollectionItem("persons")) { %>
            <div>
                <%= Html.LabelFor(x => x.FullName) %>
                <%= Html.EditorFor(x => x.FullName) %>
                <%= Html.ValidationMessageFor(x => x.FullName) %>
            </div>
            <div>
                <%= Html.LabelFor(x => x.LastName) %>
                <%= Html.EditorFor(x => x.LastName) %>
                <%= Html.ValidationMessageFor(x => x.LastName) %>
            </div>
            <div>
                <%= Html.LabelFor(x => x.Address) %>
                <%= Html.EditorFor(x => x.Address) %>
                <%= Html.ValidationMessageFor(x => x.Address) %>
            </div>
    
            <a href="#" class="deleteRow">delete</a>
        <% } %>
    </div>
    

    【讨论】:

    • 谢谢达林,这很好用。我需要更多帮助。我想用新属性扩展 Person 类:public int Percent { get; set; } 并且如果 IEnumerable 中每个人的所有百分比的总和等于 100,我希望在提交时进行服务器验证。我可以创建自定义这个属性和如何?我的模型是通用列表,我不能在上面应用 [CustomAttribute] 对吧?
    • 另外,我应该在页面顶部有验证摘要,而不是在每个输入之后。我放了:&lt;%:Html.ValidationSummary(false, "Please correct the following and resubmit the page:")%&gt; 有没有办法为每个人设置不同的验证消息?谢谢。
    猜你喜欢
    • 1970-01-01
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多