124

我正在尝试构建一个非常非常简单的“微型网络应用程序”,如果我完成它,我怀疑一些 Stack Overflow 的人会对它感兴趣。我将它托管在我的 C# in Depth 站点上,它是 vanilla ASP.NET 3.5(即不是 MVC)。

流程非常简单:

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

这是我自己提出的要求(设计和实现的混合):

  • 我希望提交使用 GET 而不是 POST,主要是为了让用户可以轻松地为页面添加书签。
  • 希望 URL 在提交后看起来很傻,上面有多余的点点滴滴。请仅提供主 URL 和实际参数。
  • 理想情况下,我想完全避免需要 JavaScript。在这个应用程序中没有充分的理由。
  • 我希望能够在渲染期间访问控件并设置值等。特别是,如果 ASP.NET 无法自动执行此操作,我希望能够将控件的默认值设置为传入的参数值对我来说(在其他限制范围内)。
  • 我很高兴自己完成所有参数验证,而且我不需要太多服务器端事件的方式。在页面加载时设置所有内容而不是将事件附加到按钮等非常简单。

大部分都可以,但我还没有找到完全删除视图状态并保留其余有用功能的任何方法。使用这篇博文中的帖子,我设法避免获得视图状态的任何实际- 但它仍然最终作为 URL 上的参数,看起来非常难看。

如果我将其设为纯 HTML 表单而不是 ASP.NET 表单(即取出runat="server"),那么我不会得到任何神奇的视图状态 - 但是我无法以编程方式访问控件。

可以通过忽略大部分 ASP.NET 并使用 LINQ to XML 构建 XML 文档并实现IHttpHandler. 不过感觉水平有点低。

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

也许 ASP.NET 并没有缩小到这种应用程序?不过,有一个很可能的选择:我只是愚蠢,而且有一种非常简单的方法可以做到,我只是还没有找到。

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

4

7 回答 7

76

该解决方案将使您能够以编程方式访问整个控件,包括控件上的所有属性。此外,提交时只有文本框值会出现在 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>

然后在您的代码隐藏中,您可以在 PageLoad 上执行您需要的所有操作

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="server"并给它们一个 ID。然后,您可以以编程方式访问它们编写不带ViewState.

唯一的缺点是您将无法访问许多“有用”的 ASP.NET 服务器控件,例如GridViews。我在示例中包含了 a Repeater,因为我假设您希望将字段与结果放在同一页面上,并且(据我所知) aRepeater是唯一一个runat="server"在 Form 标签中没有属性的情况下运行的 DataBound 控件。

于 2009-01-10T17:18:36.200 回答
12

通过不在您的 FORM 标签中使用 runat="server" ,您绝对(恕我直言)在正确的轨道上。这只是意味着您需要直接从 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));
        }
    }

这里的技巧是我们在文本输入的 value="" 属性中使用 ASP.NET Literals,因此文本框本身不必 runat="server"。然后将结果包装在 ASP:Panel 中,并在页面加载时设置 Visible 属性,具体取决于您是否要显示任何结果。

于 2009-01-10T17:02:23.660 回答
2

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

我没有检查自 2.0 以来是否有任何内部代码更改,但这是我几年前处理摆脱视图状态的方式。实际上,该隐藏字段在 HtmlForm 中是硬编码的,因此您应该派生新的字段并自行进行调用并进入其渲染。请注意,如果您坚持使用普通的旧输入控件,您也可以将 __eventtarget 和 __eventtarget 排除在外(我想您会想要这样做,因为它也有助于在客户端上不需要 JS):

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 获得了全新的抽象(或没有)页面: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),但您可以密封您的表单。

于 2009-07-09T19:05:11.097 回答
1

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

编辑:我假设您在页面上设置了 EnableViewState="false" 。

于 2009-01-10T16:52:24.053 回答
1

我将创建一个处理路由的 HTTP 模块(类似于 MVC,但并不复杂,只是几个if语句)并将其交给aspxashx页面。aspx是首选,因为它更容易修改页面模板。但是我不会使用WebControlsaspx只是Response.Write

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

于 2009-01-10T16:55:07.370 回答
1

我真的很高兴完全放弃页面类,只使用基于 url 的大 switch case 处理每个请求。每一个“页面”变成了一个 html 模板和 ac# 对象。模板类使用带有匹配委托的正则表达式,该委托与密钥集合进行比较。

好处:

  1. 真快,重新编译后几乎没有卡顿(页面类一定要大)
  2. 控制非常精细(非常适合 SEO,以及制作 DOM 以与 JS 很好地配合)
  3. 呈现与逻辑是分开的
  4. jQuery 完全控制了 html

无赖:

  1. 简单的东西需要更长的时间,因为单个文本框需要多个地方的代码,但它确实可以很好地扩展
  2. 总是很想只用页面视图来做,直到我看到一个视图状态(呃)然后我又回到现实。

乔恩,星期六早上我们在 SO 上做什么:)?

于 2009-01-10T17:18:32.847 回答
1

我认为 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 Forms 还可以,Visual Studio 提供了不错的支持,但这个 runat="server" 是错误的。ViewState 到。

我建议你看看是什么让 ASP.NET MVC 如此出色,它从 ASP.NET Forms 方法中移开而不将其全部丢弃。

您甚至可以编写自己的构建提供程序来编译像 NHaml 这样的自定义视图。我认为您应该在这里寻找更多控制权,并简单地依赖 ASP.NET 运行时来包装 HTTP 并作为 CLR 托管环境。如果您运行集成模式,那么您也可以操作 HTTP 请求/响应。

于 2009-02-23T12:39:45.227 回答