我怎样才能在 ASP.NET 中获得更多的控制权?

我正在尝试建立一个非常,非常简单的“微型网络应用程序”,我猜想如果我完成了它,一些 Stack Overflow 将会感兴趣。我把它托管在我的 C # in Depth 站点上,也就是普通的 ASP.NET 3.5(即不是 MVC)。

The flow is very simple:

  • 如果用户输入的 URL 没有指定所有的参数(或者任何参数都是无效的) ,我只想显示用户输入控件。(只有两个。)
  • 如果一个用户输入的应用程序的 URL,其中 是的有所有必需的参数,我想显示结果 还有的输入控件(这样他们可以改变参数)

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

  • 我希望提交使用 GET 而不是 POST,这样用户可以很容易地将页面加入书签。
  • I 不要 want the URL to end up looking silly after submission, with extraneous bits and pieces on it. Just the main URL and the real parameters please.
  • 理想情况下,我想避免使用 JavaScript,因为在这个应用程序中没有很好的理由使用 JavaScript。
  • 我希望能够访问控件在呈现时间和设置值等。特别是,如果 ASP.NET 不能自动为我设置(在其他限制范围内) ,我希望能够将控件的默认值设置为传入的参数值。
  • 我很乐意自己完成所有的参数验证,并且我不需要太多的服务器端事件。在页面加载时设置所有内容,而不是将事件附加到按钮上,这真的很简单。

大多数这样做是可以的,但是我还没有找到任何方法去除视图状态并保留其余有用的功能。通过使用来自 这篇博文的帖子,我已经设法避免为 viewstate 获得任何实际的 价值-但它仍然作为 URL 上的一个参数结束,这看起来非常难看。

如果我使用纯 HTML 表单而不是 ASP.NET 表单(例如取出 runat="server") ,那么我不会得到任何神奇的视图状态——但是我不能以编程方式访问控件。

我通过忽略大部分 ASP.NET 并使用 LINQtoXML 构建一个 XML 文档和实现 IHttpHandler来完成所有这些工作。不过感觉有点低级。

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

Maybe ASP.NET just doesn't scale 放下 to this sort of app? There's a very likely alternative though: I'm just being stupid, and there's a perfectly simple way of doing it that I just haven't found.

Any thoughts, anyone? (Cue comments of how the mighty are fallen, etc. That's fine - I hope I've never claimed to be an ASP.NET expert, as the truth is quite the opposite...)

9837 次浏览

您有没有考虑过不删除 POST,而是在发布表单时重定向到一个合适的 GET URL。也就是说,接受 GET 和 POST,但是在 POST 上构造一个 GET 请求并重定向到它。如果您希望使其与页面无关,则可以在页面上或通过 HttpModule 进行处理。我觉得这样事情就简单多了。

EDIT: I assume that you have EnableViewState="false" set on the page.

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

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

在 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 属性。

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

效益:

  1. 它真的很快,即使在重新编译之后,也几乎没有延迟(页面类必须很大)
  2. 控件是非常细粒度的(非常适合 SEO,而且可以精心制作 DOM 来与 JS 很好地配合)
  3. 表示与逻辑是分开的
  4. jQuery has total control of the html

倒霉蛋:

  1. 简单的东西需要更长的时间 单个文本框需要代码 在很多地方,但它确实规模 非常好
  2. 它总是诱人的,只是做它的页面查看,直到我看到一个 然后我迅速回到 现实。

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

This solution will give you programmatic access to the controls in their entirety including all attributes on the controls. Also, only the text box values will appear in the URL upon submission so your GET request URL will be more "meaningful"

<%@ 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>

Then in your code-behind you can do everything you need on 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];
}
}

If you don't want a form that has runat="server", then you should use HTML controls. It's easier to work with for your purposes. Just use regular HTML tags and put runat="server" and give them an ID. Then you can access them programmatically 还有 code without a ViewState.

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

我认为 ASP: 重复控制已经过时了。

The ASP.NET template engine is nice but you can just as easily accomplish repeating with a for loop...

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

NET Forms 还可以,Visual Studio 提供了相当不错的支持,但是 runat = “ server”这个东西就不对了。视图状态到。

我建议你看看是什么让 ASP.NET MVC 如此出色,它在不抛弃 ASP.NET Forms 方法的情况下将谁从 ASP.NET Forms 方法中移开。

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

好了,乔恩,首先是观察状态问题:

我还没有检查自2.0以来是否有任何类型的内部代码更改,但下面是几年前我处理清除 viewstate 的方法。实际上,隐藏字段在 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...
}

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

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

因此,您的自定义 Form: 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;
}

In this case I seal the methods 'cause you can't seal the Page (even if it isn't abstract Scott Guthrie will wrap it into yet another one :P) but you can seal your Form.