如何避免在ASP.NET代码隐藏中编写凌乱的JavaScript?

我在质疑使用Javascript和ASP.NET的最佳做法是什么。

我不知道这是最好的做法,但我在代码隐藏中添加了javascript客户端事件。 它工作正常,但这是最好的做法吗?

例如,我有一个单选按钮控件,我在Page_Init中添加了Javascript客户端事件。 可以多次调用页面init,因此每次调用Page_It时都会呈现Javascript。

此外,很难调试长Javascript字符串。 如何更干净……有办法吗?

让我们看一个包含Javascript的变量的示例:

scripts.Text += "function ValidateDdl" + metachamp.ID + "(sender, args) { if(" + txtReason.ClientID + ".GetText() != '' ||" + dynamicControl.ClientID + ".style.display == 'none' || HiddenFieldSaveError.Contains('" + metachamp.ID + "') ){" + dynamicControl.ClientID + ".className='';HiddenFieldError.Remove(" + metachamp.ID + ");" + errorImage.ClientID + ".SetClientVisible(false);args.IsValid = true;}else{var comboVal = document.getElementById('" + Image9.ClientID + "'.substring(0,'" + Image9.ClientID + "'.length - 6) + 'ddl').value ;if (comboVal != '0' ) {args.IsValid = true;HiddenFieldError.Remove(" + metachamp.ID + ");" + validImage.ClientID + ".SetClientVisible(false);HiddenField.Remove('Bypass-' + '" + metachamp.ID.ToString() + "');HiddenFieldSaveError.Remove(" + metachamp.ID + ");" + dynamicControl.ClientID + ".className='';" + errorImage.ClientID + ".SetClientVisible(false);}"; 

第一步是将JavaScript与代码隐藏和值插值分开。 而不是动态构建JavaScript,然后方法是拥有一个给定参数的JavaScript函数。

在第一阶段之后,我们最终会得到类似的东西(原谅部分翻译,这会伤害我的头脑)。 注意使用闭包构建器模式; 在实际代码中,我将进一步将其作为一个单独的模块。

 function makeValidator(champId, opts) { return function (sender, args) { // Now this is when it gets harry.. // // Use $get (and $find) inside ASP.NET, especially when // dealing with ASP.NET AJAX integration to find a control by ID. // // HOWEVER, the code uses what appears to be some DevExpress // controls and thus must be accessed.. differently, mainly either by // 1. `window[clientId]` or // 2. `ASPxClientControl.GetControlCollection().GetByName(id);` // This is just one of those icky things to deal with; I've shown usage // of the former and it may need to be applied to the other controls as well. // var reasonControl = window[opts.reasonId]; // DX control var dynamicControl = $get(opts.dynamicControlId); // normal ASP.NET/DOM var errorImage = window[opts.errorImageId]; // DX control if(reasonControl.GetText() != '' || dynamicControl.style.display == "none") { dynamicControl.className=''; errorImage.SetClientVisible(false); args.IsValid = true; } // etc. } } 

应该清楚JavaScript代码与任何字符串插值是分开的 。 这是一个普通函数,当使用某些参数(由API定义)调用时,具有某种行为。 虽然有不同的方法来“加载/注入”这个JavaScript(当UpdatePanels和嵌套/复杂的层次结构发挥作用时这很重要),让我们假装它当前放在页面标记中的内。

现在,让我们将validation器连接到控件 - 这完全是虚构的,但它显示了数据绑定的用法,并且实际上在代码隐藏中创建了JavaScript“调用”,我们将在一秒钟内看到原因。 (正确使用数据绑定实际上很重要,因为它会延迟调用CreateValidator函数,直到分配了控件的ClientID为止。)

   

然后回到代码隐藏:

 protected string CreateValidator(Control c) { var champId = c.ClientID; // example, not necessarily true // Then setup other values to supply to the function. While JSON is not // *exactly* like a JS object literal it is close enough so we Just Don't Care. // I prefer Json.NET from Newtonsoft, but the standard support is just fine. // (The champId could also be serialized here, but I chose to show passing // two arguments, one NOT escaped; we assume champId doesn't contain \s or 's.) var opts = new JavaScriptSerializer().Serialize(new { reasonId = reasonControl.ClientID, dynamicControlId = dynamicControl.ClientID, errorImageId = Error9.ClientId }); // The use of parenthesis and actual JavaScript returned depends on if the // client-side validation property takes JavaScript to execute (common) or if // it takes a function to execute later, as found in DevExpress/some libraries. // (Remember from above that makeValidator returns a new function.) // For DX/DevExpress: return string.Format("makeValidator('{0}', {1})", champId, opts); // Normal ASP.NET might look like this: return string.Format("return makeValidator('{0}', {1}).apply(this, arguments)", champId, opts); } 

这就是它的要点,包括错误。 但是,这种方法有很多种变化(包括ASP.NET AJAX ScriptControl魔术)和需要考虑的微妙因素; 要记住和争取的重点是:

分离JavaScript代码并使用API​​来传递值

这是任何技术堆栈的经典问题。 要回答这个问题,我记住几件事:

  1. 不要重复自己(使用WebForms可能会更困难)
  2. 做一件事,做得好

我发现客户端function分为几类:

  • 表单validation,通常是应在后端代码中管理的业务规则的扩展
  • 可用性增强function,例如下拉菜单,在将焦点从文本字段移开时自动将文本大写等。
  • 用户交互管理,可能是由在后端不容易完成的业务规则驱动的。

注意:下面的代码可能有一些错误,但它应该给你一个主要的想法)

使用ASP.NET WebForms进行表单validation

这对我来说是最痛苦的地方。 我目前正在尝试使用FluentValidation和WebForms,它实际上进展顺利。 关于validation我最好的建议: 不要使用validation器! 这就是人们抱怨WebForms是一个复制粘贴框架的原因。 它不一定是这样。 在快速代码示例之前, 不要使用Data [Set | Table | Row]! 您获得了所有数据,但没有一个行为。 使用像Entity Framework或NHibernate这样的ORM,并让所有的ASP页面都处理实体类,因为那时你可以使用像FluentValidation这样的东西:

App_Code文件/模型/实体/ 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文件/模型/校验/ PostValidator.cs

 using FluentValidation; using Project.Models.Entities; namespace Project.Models.Validators { public class PostValidator : AbstractValidator { public PostValidator() { RuleFor(p => p.Title) .NotEmpty() .Length(1, 200); RuleFor(p => p.Body) .NotEmpty(); } } } 

获得基本实体​​和validation器后,在后面的代码中使用它们:

用户控件/ 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 } } ... } } 

用户控件/ PostControl.ascx

  

* Title:

* Body:

以编程方式添加客户端validation

现在我们在C#中有了坚实的基础,您可以为每个表单字段添加HTML属性,并使用jQuery Validate来触发一些前端validation。 您可以以编程方式循环遍历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(); } ... } } 

复杂的多场validation

任何需要来自多个字段的数据的validation都不应在客户端上处理。 在C#中执行此操作。 试图在ASP页面上用HTML和JavaScript拼凑这些内容变得很麻烦,并且不足以certificate增加的开销和维护问题。

可用性增强

这些JavaScript代码段可以帮助用户,并且几乎无法实现业务规则。 在我工作的应用程序上,每当用户将焦点从文本框移开时,每个单词都应该大写,因此“foo bar”变为“Foo Bar”。 JavaScript和事件委托给救援人员:

脚本/ foo.js (在每页上导入)

 $(document).on("focusout", "input[type=text][data-capitalize-disabled^=true]", function(event) { event.target.value = event.target.value.replace(/(^|\s+)[az]/g, function(match, $1) { return $1.toUpperCase(); }); }); 

要禁用此行为:

代码背后:

 PostTitle.Attributes["data-capitalize-disabled"] = "true"; 

ASP:

  

如果你可以在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文件中:

      

同样,这里的目标是将JavaScript和HTML捆绑在一起,而不是将任何 JavaScript放在C#中。

我用javascript为客户端事件找到了一个很好的解决方案。

所以,基本上我在.ascx文件中添加了ClientSideEvent。 例如,我添加了SelectedIndexChanged事件。 当单选按钮的索引发生变化时,它会调用.js文件中的javascript函数。

让我们看看:

.ascx中的客户端事件

         

之后,我将javascript添加到名为ClientEvents.js的文件中

添加javascript代码

 function rblistComment_SelectIndexChanged(s,e) { var btnOk = eval($("[id$=btnOK]").attr("id")); var txtCommentPopup = eval($("[id$=txtCommentPopup]").attr("id")); btnOk.SetEnabled(s.GetValue() != null); txtCommentPopup.SetVisible(s.GetValue() == '2'); 

}

最后,在代码隐藏中,我在Page_Load中添加了这段代码。 因此,它注册脚本并将用户控件与javascript文件链接。

将javascript文件与用户控件链接

 const string csname = "ClientEvents"; const string csurl = "~/js/EtudeCliniqueScript/ClientEvents.js"; Type cstype = this.GetType(); ClientScriptManager cs = Page.ClientScript; if (!cs.IsClientScriptIncludeRegistered(cstype, csname)) { cs.RegisterClientScriptInclude(cstype, csname, ResolveClientUrl(csurl)); }