如何在ASP.NET中进行更多控制?

时间:2009-01-10 16:44:25

标签: .net asp.net .net-3.5 viewstate

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

流程非常简单:

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

以下是我的自我要求(设计和实施的混合):

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

大多数情况都没问题,但我没有找到任何完全移除视图状态并保留其余有用功能的方法。使用this blog post中的帖子,我设法避免为视图状态获取任何实际的 - 但它仍然最终作为URL上的参数,看起来非常难看。

如果我将它设为纯HTML表单而不是ASP.NET表单(即取出runat="server"),那么我没有得到任何魔术视图状态 - 但是我无法以编程方式访问控件。 / p>

可以通过忽略大部分ASP.NET并使用LINQ to XML构建XML文档并实现IHttpHandler来完成所有这些工作。但感觉有点低。

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

也许ASP.NET不会将 down 扩展到这种应用程序?但是有一种非常可能的替代方案:我只是愚蠢,并且有一种非常简单的方法,我还没有找到它。

任何想法,有人吗? (提示评论强大如何堕落等等。这很好 - 我希望我从未声称自己是ASP.NET专家,因为事实恰恰相反......)

7 个答案:

答案 0 :(得分:75)

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

<%@ 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服务器控件,如GridView。我在我的示例中包含了Repeater,因为我假设您希望将字段与结果放在同一页面上(据我所知)Repeater是唯一将运行的DataBound控件表单标记中没有runat="server"属性。

答案 1 :(得分: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属性,具体取决于您是否要显示任何结果。

答案 2 :(得分:2)

好的Jon,首先是viewstate问题:

我没有检查自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 part out;)

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

我想这就是它。让我知道它是怎么回事。

编辑:我忘记了Page viewstate方法:

所以你的自定义表单: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;
}

在这种情况下,我密封方法,因为你不能封印页面(即使它不是抽象的斯科特格思里将其包装到另一个:P)但你可以封印你的表格。

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

好处:

  1. 它真的很快,即使重新编译后,也几乎没有延迟(页面类必须很大)
  2. 控制非常精细(非常适合搜索引擎优化,并且可以使用DOM来制作DOM)
  3. 演示文稿与逻辑分开
  4. jQuery完全控制了html
  5. 的缺点,第:

    1. 简单的东西需要更长的时间 单个文本框需要代码 在几个地方,但它确实规模 非常好
    2. 总是很想用页面视图来做它,直到我看到一个 viewstate(urgh)然后我回复 现实。
    3. 乔恩,我们星期六早上在SO上做了什么:)?

答案 6 :(得分: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 to。

我建议你看看是什么让ASP.NET MVC如此出色,是什么让它远离ASP.NET Forms方法,而不是把它全部抛弃。

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