我的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。请在这里帮助我,我该如何验证这个动态页面?我可以使用“保存”和“返回”按钮浏览此向导,此外,我们应该能够取消单击页面上的任何单选按钮,并且该特定人员应该消失,验证者不应再抓住它。我的整个向导都使用服务器端验证(DataAnnotations),我不想使用客户端验证。提前谢谢。
更新:
我还需要一些帮助。我想用新属性扩展Person类:
public int Percent { get; set; }
如果IEnumerable<Person>
中每个人的所有百分比总和等于100,我希望在提交时进行服务器验证。我可以为此创建自定义属性以及如何创建自定义属性?我的模型是通用列表,我不能在其上应用[CustomAttribute]
,对吧?
此外,我应该在页面顶部有验证摘要,而不是在每个输入之后。我放了:<%:Html.ValidationSummary(false, "Please correct the following and resubmit the page:")%>
有没有办法为每个人设置不同的验证消息?感谢
答案 0 :(得分:3)
在开始执行此任务之前,我强烈建议您阅读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; }
}
我想这个页面的模型应该是List
是的,绝对。
让我们继续创建我们的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
助手取自史蒂芬桑德森的博客文章,我已将其链接到我之前的答案中,您已经阅读并熟悉了该文章。这是完整性的源代码:
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>