简体   繁体   English

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

[英]How to avoid writing messy JavaScript in an ASP.NET code-behind?

I'm questionning about what is the best practice to use Javascript with ASP.NET. 我在质疑使用Javascript和ASP.NET的最佳做法是什么。

I don't know if it's the best practice but I add the javascript client side event inside the codebehind. 我不知道这是最好的做法,但我在codebehind中添加了javascript客户端事件。 It is working correctly but is this the best practice? 它工作正常,但这是最好的做法吗?

For example, I got a radio button control and I add the Javascript client side event in the Page_Init. 例如,我有一个单选按钮控件,我在Page_Init中添加了Javascript客户端事件。 The page init can be recalled multiple time so the Javascript will be rendered each time that the Page_It is called. 可以多次调用页面init,因此每次调用Page_It时都会呈现Javascript。

Also, it is hard to debug a long Javascript string. 此外,很难调试长Javascript字符串。 How it can be more clean ... is there a way? 如何更干净......有办法吗?

Let see an example of a variable that contains 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);}";

The very first step is to separate out the JavaScript from the code-behind and interpolation of values. 第一步是从值代码隐藏和内插分离出的JavaScript。 Instead of dynamically building JavaScript the approach is then to have a JavaScript function that is given arguments . 而不是动态构建JavaScript,那么方法就是拥有一个给定参数的JavaScript函数。

After the first phase we end up with something like (forgive the partial translation, it was hurting my head) the following. 在第一阶段之后,我们最终得到了类似的东西(原谅部分翻译,这让我感到很伤心)。 Note the use of a closure-builder pattern; 注意使用闭包构建器模式; in real code I would further have this as a separate module. 在实际代码中,我将进一步将其作为一个单独的模块。

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.
    }
}

It should be clear that the JavaScript code is separate from any string interpolation . 应该清楚JavaScript代码与任何字符串插值是分开的 It is a normal function, that when called with certain arguments (defined by an API), has a certain behavior. 这是一个正常的函数,当使用某些参数(由API定义)调用时,具有某种行为。 While there are different approaches to "load/inject" this JavaScript (which does matter when UpdatePanels and nested/complex hierarchies come into play), let's pretend that it is currently placed inside a <script> in the markup of the page. 虽然有不同的方法来“加载/注入”这个JavaScript(当UpdatePanels和嵌套/复杂的层次结构发挥作用时这很重要),让我们假装它当前放在页面标记中的<script>内。

Now, let's wire up the validator to the control - this is entirely fictitious, but it shows the usage of data-binding and actually creating the JavaScript "invocation" in the code-behind, we'll see why in a second. 现在,让我们将验证器连接到控件 - 这完全是虚构的,但它显示了数据绑定的用法,并在代码隐藏中实际创建了JavaScript“调用”,我们将在一秒钟内看到原因。 (Using data-binding correctly is actually important as it delays calling the CreateValidator function until the ClientIDs of the controls have been assigned.) (正确使用数据绑定实际上很重要,因为它会延迟调用CreateValidator函数,直到分配了控件的ClientID为止。)

<!-- Use of the DataBind Container/Eval may be useful, but ignoring that.. --!>
<control:BlahBlah Id="ImaControl"
                  OnClientValidate="<%# CreateValidator(ImaControl) %>"/>

And then back to the code-behind: 然后回到代码隐藏:

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);
}

And that's the gist of it, bugs included. 这就是它的要点,包括错误。 However, there are number of variations of this approach (including the ASP.NET AJAX ScriptControl magic) and subtle factors to consider; 但是,这种方法有很多种变化(包括ASP.NET AJAX ScriptControl魔术)和需要考虑的微妙因素; the big point to remember and to strive for is: 要记住和争取的重点是:

Separate the JavaScript code and use an API to communicate values . 分离JavaScript代码并使用API​​来传达值

This is a classic question for any technology stack. 这是任何技术堆栈的经典问题。 To answer this question, I keep a couple things in mind: 要回答这个问题,我记住几件事:

  1. Don't Repeat Yourself (which can be more difficult with WebForms) 不要重复自己(使用WebForms可能会更困难)
  2. Do one thing, and do it well 做一件事,做得好

I've found client side functionality falls into a couple of categories: 我发现客户端功能分为几类:

  • Form validations, which are often extensions of Business Rules that should be managed in back end code 表单验证,通常是应在后端代码中管理的业务规则的扩展
  • Usability enhancements, such as drop down menus, automatically capitalizing text when moving focus away from a text field, etc. 可用性增强功能,例如下拉菜单,在将焦点从文本字段移开时自动大写文本等。
  • User interaction management, which is likely driven by business rules that are not easily done on the back end. 用户交互管理,可能是由后端不易完成的业务规则驱动的。

( Note: The code below probably has a few bugs in it, but it should give you the main idea) 注意:下面的代码可能有一些错误,但它应该给你主要的想法)

Form Validations With ASP.NET WebForms 使用ASP.NET WebForms进行表单验证

This has been the area causing the most pain for me. 这对我来说是最痛苦的地方。 I'm currently experimenting using FluentValidation with WebForms, and it's actually going pretty well. 我目前正在尝试使用FluentValidation和WebForms,它实际上进展顺利。 My best piece of advice regarding validations: Don't use the <asp:Foo /> validators! 关于验证我最好的建议: 不要使用<asp:Foo />验证器! This is the reason that people complain about WebForms being a copy-and-paste framework. 这是人们抱怨的WebForms是一个复制和粘贴框架原因。 It doesn't have to be that way. 它不一定是这样。 Before a quick code example, don't use Data[Set|Table|Row]s either! 在快速代码示例之前, 不要使用Data [Set | Table | Row]! You get all of the data, but none of the behavior. 您获得所有数据,但没有任何行为。 Use an ORM like Entity Framework or NHibernate, and have all of your ASP pages deal with entity classes, because then you can use something like FluentValidation: 使用像Entity Framework或NHibernate这样的ORM,并让所有ASP页面都处理实体类,因为这样你可以使用像FluentValidation这样的东西:

App_Code/Models/Entities/Post.cs 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/Models/Validators/PostValidator.cs 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();
        }
    }
}

Once you have your basic entities and validators, use them in your code behind: 获得基本实体​​和验证器后,在后面的代码中使用它们:

UserControls/PostControl.ascx.cs 用户控件/ 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 用户控件/ 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" />

Programmatically Adding Client Side Validations 以编程方式添加客户端验证

Now that we have a solid foundation in C#, you can add HTML attributes to each of the form fields and use jQuery Validate to trigger some of the front end validations. 现在我们在C#中有了坚实的基础,您可以为每个表单字段添加HTML属性,并使用jQuery Validate来触发一些前端验证。 You can programatically loop through the FluentValidation rules: 您可以以编程方式循环遍历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();
        }

        ...
    }
}

Complex, Multi Field Validations 复杂的多场验证

Any validation that requires data from more than one field should not be handled on the client. 任何需要来自多个字段的数据的验证都不应在客户端上处理。 Do this in C#. 在C#中执行此操作。 Trying to cobble this together in HTML and JavaScript on an ASP page becomes cumbersome and is not enough of a benefit to justify the added overhead and maintenance issues. 尝试在ASP页面上用HTML和JavaScript拼凑这些内容变得很麻烦,并且不足以证明增加的开销和维护问题。

Usability Enhancements 可用性增强

These JavaScript snippets assist users, and do little to implement business rules. 这些JavaScript代码段可以帮助用户,并且几乎无法实现业务规则。 On an application I work on, whenever the user moves focus away from a text box, each word should be capitalized so "foo bar" becomes "Foo Bar". 在我工作的应用程序上,每当用户将焦点从文本框移开时,每个单词都应该大写,因此“foo bar”变为“Foo Bar”。 JavaScript and event delegation to the rescue: JavaScript和事件委托给救援:

Scripts/foo.js (imported on each page) 脚本/ 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();
    });
});

To disable this behavior: 要禁用此行为:

Code Behind: 代码背后:

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

ASP: ASP:

<asp:TextBox ... data-capitalize-disabled="true" />

If you can manage this in the ASP file, now you've completely decoupled the front end and back end code! 如果你可以在ASP文件中管理它,现在你已经完全解耦了前端和后端代码!

User Interaction Management 用户交互管理

This is the 800 Pound Gorilla of front end development. 这是前端发展的800磅大猩猩。 I like to use a "widget pattern" here, where you write a JavaScript class to encompass the behavior and use HTML attributes and class names as hooks for JavaScript to do its thing. 我喜欢在这里使用“小部件模式”,在这里你编写一个JavaScript类来包含行为,并使用HTML属性和类名作为JavaScript的钩子来做它的事情。

Scripts/FooWidget.js 脚本/ 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();
    }
};

And in your ASP file: 在你的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>

Again, the object here is to keep JavaScript and HTML tied together, and not put any JavaScript in C#. 同样,这里的目标是将JavaScript和HTML绑定在一起,而不是将任何 JavaScript放在C#中。

I found a nice solution for the client side events with javascript. 我用javascript为客户端事件找到了一个很好的解决方案。

So, basically I add the ClientSideEvent inside the .ascx file. 所以,基本上我在.ascx文件中添加了ClientSideEvent。 For example, I add the SelectedIndexChanged event. 例如,我添加了SelectedIndexChanged事件。 When the index of the radio button change, it call a javascript function that is inside a .js file. 当单选按钮的索引发生变化时,它会调用.js文件中的javascript函数。

Let see: 让我们看看:

Client Side Event in the .ascx .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>

After that I add the javascript inside a file called : ClientEvents.js 之后,我将javascript添加到名为ClientEvents.js的文件中

Add javascript code 添加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');

} }

Finally, in the codebehind I add this code in the Page_Load. 最后,在代码隐藏中,我在Page_Load中添加了这段代码。 So, it register the script and link the user control with the javascript file. 因此,它注册脚本并将用户控件与javascript文件链接。

Link the javascript file with the user control 将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));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM