这是任何技术栈的经典问题。为了回答这个问题,我要记住几点:
- 不要重复自己(使用 WebForms 可能会更困难)
- 做一件事,把它做好
我发现客户端功能分为几类:
- 表单验证,通常是应在后端代码中管理的业务规则的扩展
- 可用性增强,例如下拉菜单、将焦点从文本字段移开时自动大写文本等。
- 用户交互管理,这可能是由不容易在后端完成的业务规则驱动的。
(注意:下面的代码可能有一些错误,但它应该给你主要的想法)
使用 ASP.NET WebForms 进行表单验证
这是对我来说最痛苦的地方。我目前正在尝试将FluentValidation 与WebForms 一起使用,实际上进展顺利。关于验证,我最好的建议是:不要使用 <asp:Foo /> 验证器!这是人们抱怨 WebForms 是一个复制粘贴框架的原因。不一定要那样。在快速代码示例之前,也不要使用 Data[Set|Table|Row]s! 你会得到所有数据,但没有任何行为。使用 Entity Framework 或 NHibernate 之类的 ORM,并让您的所有 ASP 页面处理实体类,因为这样您就可以使用 FluentValidation 之类的东西:
App_Code/Models/Entities/Post.cs
namespace Project.Models.Entities
{
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ModifiedAt { get; set; }
}
}
App_Code/Models/Validators/PostValidator.cs
using FluentValidation;
using Project.Models.Entities;
namespace Project.Models.Validators
{
public class PostValidator : AbstractValidator<Post>
{
public PostValidator()
{
RuleFor(p => p.Title)
.NotEmpty()
.Length(1, 200);
RuleFor(p => p.Body)
.NotEmpty();
}
}
}
一旦你有了基本的实体和验证器,就在你的代码中使用它们:
UserControls/PostControl.ascx.cs
namespace Project.UserControls
{
public class PostControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
PostValidator validator = new PostValidator();
Post entity = new Post()
{
// Map form fields to entity properties
Id = Convert.ToInt32(PostId.Value),
Title = PostTitle.Text.Trim(),
Body = PostBody.Text.Trim()
};
ValidationResult results = validator.Validate(entity);
if (results.IsValid)
{
// Save to the database and continue to the next page
}
else
{
BulletedList summary = (BulletedList)FindControl("ErrorSummary");
// Display errors to the user
foreach (var failure in results.Errors)
{
Label errorMessage = FindControl(failure.PropertyName + "Error") as Label;
if (errorMessage == null)
{
summary.Items.Add(new ListItem(failure.ErrorMessage));
}
else
{
errorMessage.Text = failure.ErrorMessage;
}
}
}
}
else
{
// Display form
}
}
...
}
}
UserControls/PostControl.ascx
<asp:BulletedList ID="ErrorSummary" runat="server" CssClass="Error-Summary" />
<p>
<asp:Label ID="PostTitleLabel" AssociatedControlID="PostTitle" runat="server">* Title:</asp:Label>
<asp:TextBox ID="PostTitle" runat="server" />
<asp:Label ID="PostTitleError" runat="server" CssClass="Error" />
</p>
<p>
<asp:Label ID="PostBodyLabel" AssociatedControlID="PostBody" runat="server">* Body:</asp:Label>
<asp:TextBox ID="PostBody" runat="server" TextMode="MultiLine" />
<asp:Label ID="PostBodyError" runat="server" CssClass="Error" />
</p>
<asp:HiddenField ID="PostId" runat="server" />
以编程方式添加客户端验证
现在我们在 C# 方面有了坚实的基础,您可以为每个表单字段添加 HTML 属性并使用jQuery Validate 来触发一些前端验证。您可以通过 FluentValidation 规则以编程方式循环:
PostValidator validator = new PostValidator();
foreach (var rule in validator.AsEnumerable())
{
propertyRule = rule as FluentValidation.Internal.PropertyRule;
if (propertyRule == null)
continue;
WebControl control = (WebControl)FindControl("Post" + propertyRule.PropertyName);
foreach (var x in rule.Validators)
{
if (x is FluentValidation.Validators.NotEmptyValidator)
{
control.Attributes["required"] = "required";
}
else if (x is FluentValidation.Validators.MaximumLengthValidator)
{
var a = (FluentValidation.Validators.MaximumLengthValidator)x;
control.Attributes["size"] = a.Max.ToString();
control.Attributes["minlength"] = a.Min.ToString();
control.Attributes["maxlength"] = a.Max.ToString();
}
...
}
}
复杂的多字段验证
任何需要来自多个字段的数据的验证不应在客户端上处理。在 C# 中执行此操作。尝试在 ASP 页面上将 HTML 和 JavaScript 拼凑在一起变得很麻烦,而且不足以证明增加的开销和维护问题是合理的。
可用性增强
这些 JavaScript sn-ps 为用户提供帮助,对实现业务规则几乎没有任何作用。在我处理的应用程序中,每当用户将焦点从文本框移开时,每个单词都应该大写,这样“foo bar”就变成了“Foo Bar”。 JavaScript 和事件委托的救援:
Scripts/foo.js(在每个页面上导入)
$(document).on("focusout", "input[type=text][data-capitalize-disabled^=true]", function(event) {
event.target.value = event.target.value.replace(/(^|\s+)[a-z]/g, function(match, $1) {
return $1.toUpperCase();
});
});
要禁用此行为:
代码背后:
PostTitle.Attributes["data-capitalize-disabled"] = "true";
平均售价:
<asp:TextBox ... data-capitalize-disabled="true" />
如果您可以在 ASP 文件中进行管理,那么您现在已经完全解耦了前端和后端代码!
用户交互管理
这是前端开发的 800 磅大猩猩。我喜欢在这里使用“小部件模式”,您可以在其中编写一个 JavaScript 类来包含行为,并使用 HTML 属性和类名作为 JavaScript 的钩子来完成它的工作。
脚本/FooWidget.js
function FooWidget(element) {
this.$element = $(element);
this.fillOptions = this.fillOptions.bind(this);
this.$element.on("click", "[data-action=fillOptions]", this.fillOptions);
}
FooWidget.prototype = {
constructor: FooWidget,
fillOptions: function(event) {
// make ajax request:
var select = this.$element.find("select:first")[0],
option = null;
option = document.createElement("option");
option.value = "...";
option.text = "...";
select.appendChild(option);
...
},
focus: function() {
this.$element.find(":input:first").focus();
}
};
在您的 ASP 文件中:
<asp:Panel ID="FooPanel" runat="server">
<button type="button" data-action="fillOptions">Fill Options</button>
<asp:DropDownList ID="OptionsDropdown" runat="server" />
</asp:Panel>
<script type="text/javascript">
var foo = new FooWidget("<%# FooPanel.ClientId %>");
</script>
同样,这里的目的是将 JavaScript 和 HTML 绑定在一起,而不是将 任何 JavaScript 放在 C# 中。