代码之家  ›  专栏  ›  技术社区  ›  Jon Skeet

如何在ASP.NET中获得更多控制?

  •  123
  • Jon Skeet  · 技术社区  · 16 年前

    我正在尝试构建一个非常非常简单的“微webapp”,如果我能做到这一点的话,我怀疑这会引起一些堆栈溢出的兴趣。我将它托管在我的C深度站点上,它是普通的ASP.NET 3.5(即,不是MVC)。

    流程非常简单:

    • 如果用户输入的应用程序的URL没有指定所有参数(或者其中任何参数无效),我只想显示用户输入控件。(只有两个。)
    • 如果用户输入的应用程序的URL 有所有必需的参数,我想显示结果 输入控件(以便更改参数)

    以下是我自己强加的要求(设计和实现的混合):

    • 我希望提交的内容使用get而不是post,主要是为了方便用户将页面添加到书签中。
    • 不要 希望URL在提交之后看起来很傻,上面有一些无关的片段。请只显示主URL和实际参数。
    • 理想情况下,我希望完全避免需要JavaScript。在这个应用程序中没有很好的理由。
    • 我希望能够在呈现时间和设置值等期间访问控件。特别是,如果ASP.NET不能为我自动执行此操作(在其他限制范围内),我希望能够将控件的默认值设置为传入的参数值。
    • 我很乐意自己进行所有的参数验证,并且我不需要太多的服务器端事件。在页面加载时设置所有内容,而不是将事件附加到按钮等,这非常简单。

    大部分都没问题,但我没有找到 完全地 移除视图状态并保留其余有用功能。使用来自的日志 this blog post 我设法避免得到任何实际的 价值 对于viewstate——但是它仍然是URL上的一个参数,看起来很难看。

    如果我将它设置为纯HTML表单而不是ASP.NET表单(即 runat="server" )然后我没有任何神奇的viewstate——但是我不能通过编程访问控件。

    能够 通过忽略大部分ASP.NET并使用LINQ to XML构建XML文档,并实现 IHttpHandler . 不过这感觉有点低。

    我意识到我的问题可以通过放松约束(例如使用post,而不关心多余的参数)或使用ASP.NET MVC来解决,但是我的要求真的不合理吗?

    也许ASP.NET不能扩展 向下 到这种应用程序?不过,还有一个很可能的选择:我只是做傻事,还有一个非常简单的方法,我还没有找到。

    有什么想法吗?(提示评论强大的力量是如何倒下的,等等。那很好-我希望我从未声称自己是ASP.NET专家,因为事实恰恰相反…)

    7 回复  |  直到 9 年前
        1
  •  75
  •   Dan Herbert    13 年前

    此解决方案将为您提供对控件整体的编程访问,包括控件上的所有属性。此外,在提交时,只有文本框值将显示在URL中,因此GET请求URL将更“有意义”

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="JonSkeetForm.aspx.cs" Inherits="JonSkeetForm" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Jon Skeet's Form Page</title>
    </head>
    <body>
        <form action="JonSkeetForm.aspx" method="get">
        <div>
            <input type="text" ID="text1" runat="server" />
            <input type="text" ID="text2" runat="server" />
            <button type="submit">Submit</button>
            <asp:Repeater ID="Repeater1" runat="server">
                <ItemTemplate>
                    <div>Some text</div>
                </ItemTemplate>
            </asp:Repeater>
        </div>
        </form>
    </body>
    </html>
    

    然后,在代码背后,您可以在页面加载时执行所需的所有操作。

    public partial class JonSkeetForm : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            text1.Value = Request.QueryString[text1.ClientID];
            text2.Value = Request.QueryString[text2.ClientID];
        }
    }
    

    如果你不想要有 runat="server" ,然后应该使用HTML控件。为了你的目的而工作更容易。使用普通的HTML标签 RunAT=服务器 给他们一个ID,然后你就可以用程序访问他们了。 没有代码的代码 ViewState .

    唯一的缺点是,您将无法访问许多“有用”的ASP.NET服务器控件,如 GridView 我包括了一个 Repeater 在我的示例中,因为我假设您希望字段与结果位于同一页,并且(据我所知)a 中继器 是将在没有 RunAT=服务器 表单标记中的属性。

        2
  •  12
  •   Dylan Beattie    16 年前

    在表单标记中不使用runat=“server”,这无疑是(imho)正确的选择。这只意味着您需要直接从request.querystring中提取值,不过,在本例中:

    在.aspx页本身中:

    <%@ Page Language="C#" AutoEventWireup="true" 
         CodeFile="FormPage.aspx.cs" Inherits="FormPage" %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <title>ASP.NET with GET requests and no viewstate</title>
    </head>
    <body>
        <asp:Panel ID="ResultsPanel" runat="server">
          <h1>Results:</h1>
          <asp:Literal ID="ResultLiteral" runat="server" />
          <hr />
        </asp:Panel>
        <h1>Parameters</h1>
        <form action="FormPage.aspx" method="get">
        <label for="parameter1TextBox">
          Parameter 1:</label>
        <input type="text" name="param1" id="param1TextBox" value='<asp:Literal id="Param1ValueLiteral" runat="server" />'/>
        <label for="parameter1TextBox">
          Parameter 2:</label>
        <input type="text" name="param2" id="param2TextBox"  value='<asp:Literal id="Param2ValueLiteral" runat="server" />'/>
        <input type="submit" name="verb" value="Submit" />
        </form>
    </body>
    </html>
    

    在后面的代码中:

    using System;
    
    public partial class FormPage : System.Web.UI.Page {
    
            private string param1;
            private string param2;
    
            protected void Page_Load(object sender, EventArgs e) {
    
                param1 = Request.QueryString["param1"];
                param2 = Request.QueryString["param2"];
    
                string result = GetResult(param1, param2);
                ResultsPanel.Visible = (!String.IsNullOrEmpty(result));
    
                Param1ValueLiteral.Text = Server.HtmlEncode(param1);
                Param2ValueLiteral.Text = Server.HtmlEncode(param2);
                ResultLiteral.Text = Server.HtmlEncode(result);
            }
    
            // Do something with parameters and return some result.
            private string GetResult(string param1, string param2) {
                if (String.IsNullOrEmpty(param1) && String.IsNullOrEmpty(param2)) return(String.Empty);
                return (String.Format("You supplied {0} and {1}", param1, param2));
            }
        }
    

    这里的诀窍是,我们在文本输入的值“”属性内使用ASP.NET文本,因此文本框本身不必runat=“server”。然后,结果被包装在一个asp:panel中,并在页面加载时设置visible属性,具体取决于是否要显示任何结果。

        3
  •  2
  •   user134706    16 年前

    好的,乔恩,首先是视图状态问题:

    我从2.0开始就没有检查过是否有任何内部代码更改,但下面是几年前我处理摆脱视图状态的方法。实际上,这个隐藏字段是在HTMLForm中硬编码的,所以您应该派生出新字段并单步执行它的呈现,自己进行调用。请注意,如果您坚持使用普通的旧输入控件(我猜您希望这样做,因为它也有助于不需要客户端上的JS),那么您也可以将eventtarget和eventtarget保留在外:

    protected override void RenderChildren(System.Web.UI.HtmlTextWriter writer)
    {
        System.Web.UI.Page page = this.Page;
        if (page != null)
        {
            onFormRender.Invoke(page, null);
            writer.Write("<div><input type=\"hidden\" name=\"__eventtarget\" id=\"__eventtarget\" value=\"\" /><input type=\"hidden\" name=\"__eventargument\" id=\"__eventargument\" value=\"\" /></div>");
        }
    
        ICollection controls = (this.Controls as ICollection);
        renderChildrenInternal.Invoke(this, new object[] {writer, controls});
    
        if (page != null)
            onFormPostRender.Invoke(page, null);
    }
    

    所以您得到了这3个静态的MethodInfo,并调用它们,跳过这个viewstate部分;)

    static MethodInfo onFormRender;
    static MethodInfo renderChildrenInternal;
    static MethodInfo onFormPostRender;
    

    这是表单的类型构造函数:

    static Form()
    {
        Type aspNetPageType = typeof(System.Web.UI.Page);
    
        onFormRender = aspNetPageType.GetMethod("OnFormRender", BindingFlags.Instance | BindingFlags.NonPublic);
        renderChildrenInternal = typeof(System.Web.UI.Control).GetMethod("RenderChildrenInternal", BindingFlags.Instance | BindingFlags.NonPublic);
        onFormPostRender = aspNetPageType.GetMethod("OnFormPostRender", BindingFlags.Instance | BindingFlags.NonPublic);
    }
    

    如果我把你的问题弄对了,你也不想把post作为表单的动作,下面是你应该怎么做的:

    protected override void RenderAttributes(System.Web.UI.HtmlTextWriter writer)
    {
        writer.WriteAttribute("method", "get");
        base.Attributes.Remove("method");
    
        // the rest of it...
    }
    

    我想差不多就是这样。告诉我进展如何。

    编辑:我忘记了页面视图状态方法:

    所以您的定制表单:htmlform得到了它全新的摘要(或者不是)page:system.web.ui.page:p

    protected override sealed object SaveViewState()
    {
        return null;
    }
    
    protected override sealed void SavePageStateToPersistenceMedium(object state)
    {
    }
    
    protected override sealed void LoadViewState(object savedState)
    {
    }
    
    protected override sealed object LoadPageStateFromPersistenceMedium()
    {
        return null;
    }
    

    在本例中,我密封方法,因为您不能密封页面(即使它不是抽象的),Scott Guthrie将把它包装成另一个:p),但您可以密封您的表单。

        4
  •  1
  •   tvanfosson    16 年前

    您是否考虑过在表单发布时不删除发布,而是重定向到合适的get url。也就是说,接受get和post,但是post构造一个get请求并重定向到它。如果你想让它独立于页面,可以在页面上或者通过httpmodule来处理。我认为这会使事情变得更容易。

    编辑: 我假设您在页面上设置了enableViewState=“false”。

        5
  •  1
  •   Mehrdad Afshari    16 年前

    我将创建一个HTTP模块来处理路由(类似于MVC,但并不复杂,只是几个 if 陈述)并交给 aspx ashx 页。 阿斯克斯 首选,因为修改页面模板更容易。我不会用 WebControls 阿斯克斯 然而。只是 Response.Write .

    顺便说一下,为了简化操作,您可以在模块中进行参数验证(因为它可能与路由共享代码),并将其保存到 HttpContext.Items 然后在页面中呈现它们。这将非常像MVC没有所有的铃声和口哨。这是我在ASP.NET MVC之前做的很多事情。

        6
  •  1
  •   missaghi    16 年前

    我真的很高兴完全放弃了page类,只使用基于url的大开关案例处理每个请求。evey“page”变成了一个HTML模板和一个C对象。模板类使用一个regex,该regex具有与键集合进行比较的匹配委托。

    效益:

    1. 它非常快,即使在重新编译之后,也几乎没有延迟(page类必须很大)
    2. 控制是非常精细的(对于SEO非常好,并且制作DOM以便与JS一起使用)
    3. 陈述与逻辑是分开的
    4. jquery对HTML具有完全控制权

    流浪者:

    1. 简单的东西需要更长的时间 一个文本框需要代码 在一些地方,但它确实有规模 真的很好
    2. 在我看到 视图状态(urgh),然后我回到 现实。

    乔恩,周六早上我们在做什么:)?

        7
  •  1
  •   jotik    9 年前

    我认为asp:repeater控件已经过时了。

    ASP.NET模板引擎很不错,但您也可以用for循环轻松完成重复…

    <form action="JonSkeetForm.aspx" method="get">
    <div>
        <input type="text" ID="text1" runat="server" />
        <input type="text" ID="text2" runat="server" />
        <button type="submit">Submit</button>
        <% foreach( var item in dataSource ) { %>
            <div>Some text</div>   
        <% } %>
    </div>
    </form>
    

    ASP.NET窗体有点不错,有来自Visual Studio的不错的支持,但这个runat=“server”的东西是错误的。视图状态为。

    我建议您看看是什么让ASP.NET MVC如此出色,是谁让它远离了ASP.NET窗体方法而不将其全部丢弃。

    您甚至可以编写自己的构建提供者资料来编译自定义视图,如nhaml。我认为您应该在这里寻找更多的控件,并且仅仅依靠ASP.NET运行时来包装HTTP和作为CLR宿主环境。如果您运行集成模式,那么您也可以操作HTTP请求/响应。