我怎样才能更好地控制ASP.NET?
我正在尝试构build一个非常非常简单的“微型Web应用程序”,如果我完成了这个任务,那么我怀疑这个微型Web应用程序会对几个Stack Overflow's感兴趣。 我在我的C#深度网站,这是香草ASP.NET 3.5(即不是MVC)托pipe它。
stream程非常简单:
- 如果用户input的应用程序的URL没有指定所有参数(或者它们中的任何一个是无效的),我只想显示用户input控件。 (只有两个)
- 如果用户input具有所有必需参数的URL,我想显示结果和input控件(以便他们可以更改参数)
以下是我自己设定的要求(devise和实施的混合):
- 我希望提交使用GET而不是POST,所以用户可以很容易地为页面添加书签。
- 我不希望这个URL在提交之后变得愚蠢,并且无关紧要。 只是主要的url和真实的参数请。
- 理想情况下,我想避免需要JavaScript。 这个应用程序没有很好的理由。
- 我希望能够访问控制在渲染时间和设置值等。特别是,我想能够设置控件的默认值传递给参数值,如果ASP.NET无法自动执行此操作对我来说(在其他限制内)。
- 我很高兴自己做所有的参数validation,我不需要太多的服务器端事件。 设置页面加载的所有内容非常简单,而不是将事件附加到button等。
大多数这是好的,但我还没有find任何方式完全删除视图状态,并保持其他有用的function。 使用这篇博文中的文章,我已经设法避免获得视图状态的任何实际值 – 但它仍然作为URL的参数,看起来真的很丑。
如果我使它成为一个纯粹的HTML表单,而不是一个ASP.NET窗体(即拿出runat="server"
),那么我没有得到任何魔术viewstate – 但是我不能以编程方式访问控制。
我可以通过忽略大部分的ASP.NET,并用LINQ to XML构buildXML文档,并实现IHttpHandler
来做到这一切。 这感觉虽然有点低。
我意识到我的问题可以通过放宽我的约束(例如使用POST和不关心剩余参数)或使用ASP.NET MVC来解决,但是我的要求真的不合理吗?
也许ASP.NET只是不缩小到这种types的应用程序? 这里有一个非常可能的select:我只是愚蠢的,有一个非常简单的做法,我只是没有find。
任何想法,任何人? (提示如何强大的堕落等等,这很好 – 我希望我从来没有声称是一个ASP.NET专家,因为事实正好相反…)
此解决scheme将使您可以通过程序访问整个控件,包括控件上的所有属性。 另外,只有文本框的值会在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服务器控件,比如GridView
。 我在我的例子中包括一个Repeater
,因为我假设你想在结果的同一页上有字段,并且(据我所知) Repeater
是唯一的没有runat="server"
属性的DataBound控件在Form标签中。
你肯定(恕我直言)在正确的轨道上不使用runat =“服务器”在您的FORM标记。 这只是意味着您需要直接从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)); } }
这里的技巧是我们在文本input的value =“”属性中使用了ASP.NET Literals,所以文本框本身不需要runat =“server”。 然后将结果包装在ASP:Panel中,并在页面加载时设置Visible属性,具体取决于您是否要显示任何结果。
好的乔恩,视图状态问题第一:
我没有检查自2.0以来是否有任何内部代码更改,但这是几年前我如何处理视图状态。 事实上,隐藏的领域是硬编码HtmlForm里面,所以你应该派生你的新的一步,并进入其自己的渲染进行调用。 请注意,如果你坚持使用普通的input控件(我猜你会想要,因为它也可以帮助客户端不需要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); }
所以你得到这三个静态MethodInfo的,并调用它们跳过视图状态的一部分;)
static MethodInfo onFormRender; static MethodInfo renderChildrenInternal; static MethodInfo onFormPostRender;
这里是你的表单的types构造函数:
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方法:
因此,您的自定义窗体: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),但是你可以封印你的表格。
你有没有想过不消除POST,而是当表单被POST时redirect到合适的GET url。 也就是说,同时接受GET和POST,但在POST上构buildGET请求并redirect到它。 这可以在页面上处理,也可以通过HttpModule处理,如果你想使页面无关。 我认为这会让事情变得更容易。
编辑:我假设你有EnableViewState =“false”在页面上设置。
我将创build一个处理路由的HTTP模块(类似于MVC,但不复杂,只是几个if
语句),并将其传递给aspx
或ashx
页面。 aspx
是首选,因为它更容易修改页面模板。 但是,我不会在aspx
使用WebControls
。 只是Response.Write
。
顺便说一句,为了简化事情,你可以在模块中进行参数validation(因为它可能与路由共享代码)并将其保存到HttpContext.Items
,然后在页面中呈现它们。 这将非常像MVC没有所有的钟声和口哨声。 这是我在ASP.NET MVC日子之前做了很多事情。
我真的很高兴完全放弃页面类,只是处理每个请求与基于url的大开关情况。 Evey“页面”成为一个html模板和ac#对象。 模板类使用一个正则expression式与一个匹配委托相比较一个密钥集合。
好处:
- 即使经过重新编译,速度也非常快,几乎没有滞后(页面类必须很大)
- 控制是真的粒度(伟大的search引擎优化,并制定的DOM与JS打好)
- 演示文稿是与逻辑分开的
- jQuery有完全控制的HTML
缺点,第:
- 简单的东西需要更长的时间,一个文本框需要在几个地方的代码,但它确实扩大了
- 直到我看到一个视图状态(urgh),然后才能回到现实,总是很有诱惑力。
乔恩,星期六早上我们在做什么:)?
我认为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窗体是好的,有从Visual Studio的体面的支持,但这runat =“服务器”的东西,这是错的。 ViewState。
我build议你看一下ASP.NET MVC如此之大的原因,以及如何从ASP.NET Forms的方法中脱离出来,而不是把它扔掉。
你甚至可以编写你自己的构build提供者的东西来编译像NHaml的自定义视图。 我认为你应该在这里寻找更多的控制权,并简单地依靠ASP.NET运行时来封装HTTP和作为CLR托pipe环境。 如果你运行集成模式,那么你也可以操纵HTTP请求/响应。