【问题标题】:How to avoid writing messy JavaScript in an ASP.NET code-behind?如何避免在 ASP.NET 代码隐藏中编写混乱的 JavaScript?
【发布时间】:2014-11-07 03:21:41
【问题描述】:

我想知道在 ASP.NET 中使用 Javascript 的最佳实践是什么。

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

例如,我有一个单选按钮控件,并在 Page_Init 中添加了 Javascript 客户端事件。可以多次调用页面初始化,因此每次调用 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);}";

【问题讨论】:

  • 在我看来,客户端代码不应该与服务器端代码混合。不幸的是,asp.net 网络表单不这么认为。
  • @DavidSherret 这不是关于 WebForms 的“事实”,它只是一种经常使用的方法的混乱。真的和 PHP 和 SQL 注入一样。
  • 无论如何,我确实认为这绝对是 a 问题,所以我删除了“最佳实践”;没有(或不应该)认为应该清理这样的代码,尽管有几种方法可以做到这一点。
  • @user2864740 我更多的是指在服务器端代码中处理客户端控件的想法,但过去我在 MSDN 上看到过将 javascript 与 c# 代码类似的文档(但显然更整洁)。
  • @DavidSherret 我想我是从我的傲慢风格和代码库/经验说的。我永远不会让这样的代码飞起来(主要是因为它伤害我的脑袋想想)并且专门编写了几个控件来对抗它。再说一次,我也是不在标记中使用“声明性”数据源的人,并坚持认为,是的,该死的,WebForms 可以 进行 DI 和路由:

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


【解决方案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>

之后,我在一个名为 ClientEvents.js 的文件中添加了 javascript

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

【讨论】:

    【解决方案2】:

    这是任何技术栈的经典问题。为了回答这个问题,我要记住几点:

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

    我发现客户端功能分为几类:

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

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

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

    这是对我来说最痛苦的地方。我目前正在尝试将FluentValidation 与WebForms 一起使用,实际上进展顺利。关于验证,我最好的建议是:不要使用 &lt;asp:Foo /&gt; 验证器!这是人们抱怨 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# 中。

    【讨论】:

      【解决方案3】:

      第一步是将 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 和嵌套/复杂的层次结构发挥作用时这很重要),让我们假设它当前放置在页面标记中的 &lt;script&gt; 内。

      现在,让我们将验证器连接到控件 - 这完全是虚构的,但它显示了数据绑定的用法并在代码隐藏中实际创建了 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 来传递值

      【讨论】:

      • 这样更好吗?
      • @brz 因为 JavaScript 和标记/HTML 以及 C#/代码隐藏都是单独的关注点,除了在其中传递值的小区域。在我自己的应用程序中,我使用了这种方法的扩展,其中 JavaScript 作为一个很好的打包模块存在于它自己的文件中(根据需要动态加载);这使得它与标记/HTML 更加隔离。关键是使用任何类型的字符串插值(非常小的桥接代码除外)来构建 JavaScript - 否则结果是难以编辑和推理的代码。
      • @brz 将代码完全移动到标记然后绑定数据(例如..; var reasonControl = window['&lt;%# reason.ClientId %&gt;']; ..)是帮助最小化这种耦合的一种方法,但它仍然需要第一个- 步骤隔离,如图所示是一个干净且可维护的解决方案。也就是说,数据绑定仍然应该只用于将值注入到其他有效代码中,并且应该用于生成代码本身。这整个分离类似于 SQL应该 使用占位符 - 它是 valuescode 的分离,它支持简洁的设计。
      • @user2864740 这是一个有趣的解决方案。我正在尝试将其集成到我的代码中。我正在尝试添加一个 OnSelectedIndexChanged="" 但它不会调用代码隐藏函数,它是 CreateValidator。
      • @Dave.Lebr1 DataBind 需要被调用。请参阅stackoverflow.com/questions/1603398/…msdn.microsoft.com/en-us/library/… - 对于没有设置声明性 DataSourceID 的控件(使用 DataBinding 语法),请确保它们在 PreRender 事件中调用了 DataBind()。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-22
      • 2012-01-23
      • 2021-04-13
      • 2019-07-10
      • 1970-01-01
      • 2012-05-02
      相关资源
      最近更新 更多