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


124

我正在尝试构建一个非常非常简单的“微型Web应用程序”,我怀疑如果我能完成的话,它将引起一些Stack Overflow的关注。我将其托管在我的C#深度站点中,该站点是原始ASP.NET 3.5(即非MVC)。

流程非常简单:

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

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

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

大部分都可以,但是我还没有找到完全删除viewstate并保留其余有用功能的任何方法。使用此博客文章中的文章,我设法避免获得viewstate的任何实际 -但它仍然作为URL上的参数而结束,这看起来确实很丑陋。

如果我将其设置为普通的HTML表单而不是ASP.NET表单(即取出runat="server"),则不会获得任何神奇的viewstate-但是我无法以编程方式访问控件。

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

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

也许ASP.NET不会缩小到此类应用程序?但是,有一个非常可能的选择:我只是愚蠢,而且有一种我还没有发现的非常简单的方法。

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


16
“提示有关强大力量如何倒下的评论”-我们都是无知的,只是不同的事情。我只是最近才开始参加这里,但是我对这个问题的欣赏远胜于所有要点。您显然仍在思考和学习。恭喜您。
duffymo

15
我认为我永远不会注意放弃学习的人:)
乔恩·斯凯特

1
在一般情况下是正确的。在计算机科学中非常正确。
Mehrdad Afshari,2009年

3
并且您的下一本书将是“ ASP.NET的深度”吗?:-P
chakrit

20
是的,应该在2025年推出;)
乔恩·斯基特

Answers:


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

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


1
我的字段太少了,手动进行操作真的很容易:)关键是我不知道可以将runat = server与普通的HTML控件一起使用。我还没有实现结果,但这很容易。就快到了!
乔恩·斯基特

实际上,即使您在页面级别设置EnableViewState =“ False”,<form runat =“ server”>也会添加__VIEWSTATE(和其他一些)隐藏字段。如果您要在页面上松开ViewState,则可以采用这种方法。至于网址的友善性,可以选择urlrewriting。
瑟吉·达米安

1
无需重写。这个答案很好用(尽管它的确意味着有一个ID为“ user”的控件-由于某些原因,我无法单独将文本框控件的名称与其ID分开更改)。
乔恩·斯基特

1
只是为了确认,这确实非常有效。非常感谢!
乔恩·斯基特

14
看起来您应该刚刚用经典的ASP编写了它!
ScottE,2009年

12

通过在FORM标签中不使用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));
        }
    }

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


它工作得很好,但是URL不会像StackOverflow那样友好。
Mehrdad Afshari,2009年

1
我认为这些URL将会非常友好……这似乎是一个非常好的解决方案。
乔恩·斯基特

恩,我较早前读过您的推文,对它进行了研究,但现在我想念您的问题了,我的孩子正在准备浴缸……:-)
splattne

2

好的,乔恩,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部分;)

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

我想这差不多了。让我知道事情的后续。

编辑:我忘记了页面viewstate方法:

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

在这种情况下,我密封了方法,因为您无法密封Page(即使它不是抽象的,Scott Guthrie也会将其包装到另一个:P中),但是您可以密封表格。


谢谢您-尽管听起来工作量很大。Dan的解决方案对我来说效果很好,但是拥有更多选择总是很好。
乔恩·斯基特

1

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

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


好主意。好吧,关于被迫这样做的想法很可怕,但是就它可能起作用而论:)可以尝试...
Jon Skeet

是的,我在所有地方都尝试过EnableViewState = false。它并没有完全禁用它,只是将其削减了。
乔恩·斯基特

乔恩:如果您不使用该死的服务器控件(没有runat =“ server”)并且根本没有<form runat =“ server”>,那么ViewState不会有问题。这就是为什么我说不要使用服务器控件。您可以始终使用Request.Form集合。
Mehrdad Afshari

但是在控件上没有runat = server的情况下,在渲染时再次将值传播到控件是很痛苦的。幸运的是,带有runat = server的HTML控件运行良好。
乔恩·斯基特

1

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

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


1

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

好处:

  1. 它确实非常快,即使重新编译后也几乎没有延迟(页面类必须很大)
  2. 控件确实非常精细(非常适合SEO,并精心设计DOM以使其与JS配合使用)
  3. 演示与逻辑是分开的
  4. jQuery可以完全控制html

笨蛋:

  1. 简单的内容要花更长的时间,因为单个文本框需要在多个位置编写代码,但是确实可以很好地扩展
  2. 在页面视图中这样做总是很诱人的,直到我看到一个viewstate(urgh),然后我才回到现实。

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


1
今天是星期六晚上。这样可以吗?(我很想看看我的发帖时间/天的散布图,顺便说一下...)
乔恩·斯基特

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请求/响应。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.