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

时间:2014-09-12 21:09:36

标签: c# javascript asp.net webforms code-behind

我在质疑使用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);}";

3 个答案:

答案 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)

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

  1. 不要重复自己(使用WebForms可能会更困难)
  2. 做一件事,做得好
  3. 我发现客户端功能分为几类:

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

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

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

    这对我来说是最痛苦的地方。我目前正在尝试将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));
}