我在质疑使用Javascript和ASP.NET的最佳做法是什么。
我不知道它是否是最佳做法,但我在代码隐藏中添加了javascript客户端事件。它工作正常,但这是最好的做法吗?
例如,我有一个单选按钮控件,我在Page_Init中添加了Javascript客户端事件。可以多次调用页面init,因此每次调用Page_It时都会呈现Javascript。
此外,很难调试长Javascript字符串。如何更干净......有办法吗?
让我们看一个包含Javascript的变量示例:
scripts.Text += "<script type='text/javascript'>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);}";
答案 0 :(得分:4)
第一个步骤是将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和嵌套/复杂的层次结构发挥作用时这很重要),让我们假装它当前放在页面标记中的<script>
内。
现在,让我们将验证器连接到控件 - 这完全是虚构的,但是它显示了数据绑定的用法并且在代码隐藏中实际创建了JavaScript“调用”,我们将在一秒钟内看到原因。 (正确使用数据绑定实际上很重要,因为延迟调用CreateValidator函数,直到分配了控件的ClientID为止。)
<!-- Use of the DataBind Container/Eval may be useful, but ignoring that.. --!>
<control:BlahBlah Id="ImaControl"
OnClientValidate="<%# CreateValidator(ImaControl) %>"/>
然后回到代码隐藏:
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 :(得分:1)
这是任何技术堆栈的经典问题。要回答这个问题,我会记住几件事:
我发现客户端功能分为几类:
(注意:下面的代码可能有一些错误,但它应该给你一个主要的想法)
这对我来说是最痛苦的地方。我目前正在尝试将FluentValidation与WebForms一起使用,而且它实际上进展顺利。关于验证我最好的建议是:不要使用<asp:Foo />
验证器!这是人们抱怨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<Post>
{
public PostValidator()
{
RuleFor(p => p.Title)
.NotEmpty()
.Length(1, 200);
RuleFor(p => p.Body)
.NotEmpty();
}
}
}
获得基本实体和验证器后,请在后面的代码中使用它们:
<强>用户控件/ 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 强>
<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代码段可以帮助用户,并且几乎无法实现业务规则。在我工作的应用程序上,每当用户将焦点从文本框移开时,每个单词都应该大写,以便#34; foo bar&#34;成为&#34; Foo Bar&#34;。 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:
<asp:TextBox ... data-capitalize-disabled="true" />
如果您可以在ASP文件中进行管理,那么现在您已经完全解除了前端和后端代码的连接!
这是前端发展的800磅大猩猩。我喜欢使用&#34;小部件模式&#34;在这里,你编写一个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#中。
答案 2 :(得分:1)
我用javascript为客户端事件找到了一个很好的解决方案。
所以,基本上我在.ascx文件中添加了ClientSideEvent。例如,我添加了SelectedIndexChanged事件。当单选按钮的索引发生变化时,它会调用.js文件中的javascript函数。
让我们看看:
.ascx中的客户端事件
<dx:ASPxRadioButtonList runat="server" ID="rblistComment">
<Items>
<dx:ListEditItem Text="Nouvelle information" Value="0" />
<dx:ListEditItem Text="Correction de valeurs" Value="1" />
<dx:ListEditItem Text="Autre" Value="2" />
</Items>
<ClientSideEvents SelectedIndexChanged="rblistComment_SelectIndexChanged" />
</dx:ASPxRadioButtonList>
之后,我将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));
}