关于.net:如何在ASP.NET中进行更多控制?

How can I take more control in ASP.NET?

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

流程非常简单:

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

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

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

大多数情况都可以,但我没有找到任何方法可以完全删除视图状态并保留其余有用的功能。使用这个博客文章中的文章,我设法避免获得任何实际的viewstate值——但是它仍然是URL上的一个参数,看起来很难看。

如果我把它变成一个普通的HTML表单而不是一个ASP.NET表单(即去掉EDOCX1[0]),那么我就不会得到任何神奇的viewstate,但是我不能通过编程的方式访问控件。

我可以通过忽略大部分ASP.NET,用linq-to-xml构建一个XML文档,并实现IHttpHandler,来完成所有这些工作。不过这感觉有点低。

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

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ 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">
    Jon Skeet's Form Page
</head>
<body>
    <form action="JonSkeetForm.aspx" method="get">
   
        <input type="text" ID="text1" runat="server" />
        <input type="text" ID="text2" runat="server" />
        <button type="submit">Submit</button>
       
            <ItemTemplate>
                Some text
            </ItemTemplate>
        </asp:Repeater>
   
    </form>
</body>
</html>

然后,在代码背后,您可以在页面加载时完成所需的一切工作。

1
2
3
4
5
6
7
8
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是唯一一个没有aspx运行的数据绑定控件。8〕表单标记中的属性。


在表单标签中不使用runat="server",这绝对是(imho)正确的做法。这只意味着您需要直接从request.querystring中提取值,不过,在本例中:

在.aspx页本身中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<%@ 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>
  ASP.NET with GET requests and no viewstate
</head>
<body>
   
      Results:
     
      <hr />
    </asp:Panel>
    Parameters
    <form action="FormPage.aspx" method="get">
    <label for="parameter1TextBox">
      Parameter 1:</label>
    <input type="text" name="param1" id="param1TextBox" value=''/>
    <label for="parameter1TextBox">
      Parameter 2:</label>
    <input type="text" name="param2" id="param2TextBox"  value=''/>
    <input type="submit" name="verb" value="Submit" />
    </form>
</body>
</html>

在后面的代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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="attributes"中使用ASP.NET文本,因此文本框本身不必runat="server"。然后,结果被包装在一个asp:panel中,并在页面加载时设置visible属性,具体取决于是否要显示任何结果。


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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("<input type="hidden" name="__eventtarget" id="__eventtarget" value="" /><input type="hidden" name="__eventargument" id="__eventargument" value="" />");
    }

    ICollection controls = (this.Controls as ICollection);
    renderChildrenInternal.Invoke(this, new object[] {writer, controls});

    if (page != null)
        onFormPostRender.Invoke(page, null);
}

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

1
2
3
static MethodInfo onFormRender;
static MethodInfo renderChildrenInternal;
static MethodInfo onFormPostRender;

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

1
2
3
4
5
6
7
8
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作为表单的动作,下面是你该怎么做的:

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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),但您可以密封您的表单。


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

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

1
2
3
4
5
6
7
8
9
10
<form action="JonSkeetForm.aspx" method="get">

    <input type="text" ID="text1" runat="server" />
    <input type="text" ID="text2" runat="server" />
    <button type="submit">Submit</button>
    <% foreach( var item in dataSource ) { %>
        Some text  
    <% } %>

</form>

ASP.NET表单有点不错,Visual Studio提供了不错的支持,但是这个runat="server"的东西,是错误的。视图状态。

我建议你看看是什么让ASP.NET MVC如此伟大,是谁让它远离了ASP.NET窗体方法而不把它全部扔掉。

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


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

效益:

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

  • 简单的东西需要更长的时间一个文本框需要代码在一些地方,但它确实有规模真的很好
  • 在我看到视图状态(urgh),然后我回到现实。
  • 乔恩,周六早上我们在做什么:)?


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

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


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

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