65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ASP.NET 中添加 ProcessContext

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (12投票s)

2005年4月11日

8分钟阅读

viewsIcon

75118

downloadIcon

545

一种简单可靠的方法,可在 ASP.NET 的多阶段过程中构建上下文。

引言

ASP.NET 是一个构建 Web 应用程序的绝佳平台。它提供的最主要服务之一是在内存中存储/缓存收集到的或有用的数据。

ASP.NET 支持多种方式来限定缓存数据的范围:在应用程序级别使用 CacheApplication 对象,在用户级别使用 Session,对于用户与特定页面的交互过程使用 View State,最后在请求过程中在 Context 中存储。

概念上这看起来有点像这样

背景

所有这些都很有用。然而,似乎有一个显眼的疏漏。如何存储信息以完成用户多阶段过程所需的请求链?

有很多多阶段过程的例子,例如

  • 一个呼叫中心应用程序,其中一个帮助台员工(Dave B.)在用户打电话来时输入用户问题。整个问题捕获过程分三个步骤:在第一个页面上 Dave 捕获用户的详细信息,在第二个页面上他捕获 Bug 的详细信息,在最后一个页面上他捕获用户提出的任何杂项评论。
  • 一个健康与安全应用程序,其中一个健康官员(Sally)捕获有关工作场所危害的信息。在第一个屏幕上 Sally 捕获危害的详细信息,在第二个屏幕上她捕获关于如何减轻危害的想法和思考,最后在第三个屏幕上她捕获她的行动计划。

你可能认为 Session 是存储每一步捕获信息的绝佳选择。然而,Session 的作用域是用户而不是特定的用户进程。这有什么问题?嗯,如果你有一个像 Dave 这样的高级用户,他自认为是 PC 专家,你会发现有些用户喜欢同时在多个窗口或选项卡中处理问题。如果你使用 Session 来捕获这些信息,你可能会轻易丢失数据,因为窗口之间会互相破坏数据。

那么 ViewState 呢?嗯,ViewState 有一些显著的限制。首先,默认情况下,你的数据必须被序列化,以便与每次响应一起从服务器发送,并与每次请求一起从浏览器返回。并非所有对象都可以被序列化,因此这限制了你可以放入 ViewState 的内容。此外,由于 ViewState 会随着每个请求和响应一起发送,如果它变得太大,感知到的性能将受到影响。如果 Dave 和 Sally 的应用程序每页加载需要 5 秒钟,他们会非常恼火!当然,如果你能挂接到 ASP.NET ViewState 的默认行为并修改它,这些限制是可以克服的;然而,这并不适合新手。

ViewState 一个更无法避免的限制是它绑定到单个页面或控件。多阶段过程通常涉及多个页面,不幸的是,信息不能轻易地从一个页面的 ViewState 传输到另一个页面的 ViewState。

所以,似乎我们缺少一个合适的地方来存储多阶段过程中的信息。我认为解决方案是创建一个称为“ProcessContext”的东西。ProcessContext 是一个框架,你可以使用它来轻松创建让 Dave 和 Sally 这类用户满意的应用程序。

ProcessContext 的关键要求是

  1. 放入 ProcessContext 的数据存储在服务器上。
  2. ProcessContext 可以在页面之间传输。
  3. ProcessContexts 是相互隔离的。例如,如果同一个用户启动了两个进程,一个进程中的数据不会破坏另一个。

概念上,如果我们有一个 ProcessContext,事情看起来是这样的

实现 ProcessContext

创建 ProcessContext 的第一步是创建一个唯一标识它的方法。我们称之为 ProcessID。一旦我们有了 ProcessID,我们就可以根据该 ProcessID 存储特定 ProcessContext 的信息。ProcessContexts 本身存储在哪里并不重要,重要的是我们将来能找回正确的那个。

所以,让我们定义一个名为 IProcessParticipant 的接口,然后可以由页面、控件或你将来想到的任何东西来实现。

IProcessParticipant 接口看起来像这样

public interface IProcessParticipant
{
    string AttachProcessToUrl(string url);
    string ProcessID {get;set;}
    Hashtable ProcessContext{get;}
}

要实际实现 ProcessContext,我需要继承 System.Web.UI.Page 并在一个名为 ProcessParticipantPage. 的类中实现该接口。要做到这一点,我们首先重写 OnInit,检查是否通过 QueryString 传入了 ProcessID

protected override void OnInit(EventArgs e)
{
    ProcessID = Request.QueryString["ProcessID"];
    base.OnInit (e);
}

这使得 ProcessContext 可以通过 QueryString 轻松地在页面之间传输。事实上,AttachProcessToUrl 用于获取一个 URL 并用当前的 ProcessID 来修饰它的 QueryString

public string AttachProcessToUrl(string url)
{
    if (url.IndexOf("?") == -1) 
        url = url + "?";
    else
        url = url + "&";
    return url + string.Format("ProcessID={0}",ProcessID);
}

ProcessID 的 getter 和 setter 使用页面的 ViewState 中的 ProcessID 键。如果 ViewState 中没有找到 ProcessID,则会生成一个新的。这确保了 ProcessID 在需要时始终可用。

public string ProcessID
{
    get
    {
                
        if (ViewState["ProcessID"] == null)
            ViewState["ProcessID"] = Guid.NewGuid().ToString();
        return ViewState["ProcessID"] as string;
    }
    set
    {
        if (value == null) return;
        else ViewState["ProcessID"] = value;
    }
}

请注意,此实现使用 ViewState 来存储 ProcessID,因为它是最简单的可用选项。ProcessID 本身就是一个 GUID,它当然不需要大量序列化到客户端和服务器之间,因此几乎没有开销。另一种选择是在 Web 表单中包含一个隐藏字段,即以类似 ViewState 的方式。隐藏字段将保证在所有情况下都能工作,而不仅仅是 ViewState 启用时。但为了简单起见,我们使用 ViewState,所以我们需要确保 ViewState 始终对我们的页面可用。

public override bool EnableViewState
{
    get
    {
        return true;
    }
    set
    {
        if (value == false) throw new Exception("ViewState is Required");
    }
}

现在为了清晰起见,我们将与 ProcessContext 相关的信息存储在 Session 对象的 ProcessID 键下,但如果你愿意,你也可以轻松地将其更改为使用 Cache 对象甚至数据库。为了确保灵活性,我们使用 Hashtable 来实际存储与特定 ProcessContext 相关的信息。

public Hashtable ProcessContext
{
    get
    {
        Hashtable results = Session[ProcessID] as Hashtable;
        if (results == null)
        {
            results = new Hashtable();
            Session[ProcessID] = results;
        }
        return results;
    }
}

现在我们有了一个完整的、可工作的 ASP.NET 页面类,它可以作为所有参与进程并且需要与其他进程页面共享信息的页面的基类。

使用 ProcessContext

要在你的网页代码中启用 ProcessContext,你只需要简单地更改代码中的这行

public class YourPage: System.Web.UI.Page

改为这样。

public class YourPage: ProcessParticipantPage:

一旦 ProcessContext 可用,你就可以像使用任何 hashtable 一样使用它,例如

ProcessContext["YourKey"] = data;
string stored = ProcessContext["YourKey"] as string;
ProcessContext.Clear();

正如你所见,这非常简单,希望你也觉得它很有用。

为了向你展示它有多么简单,我创建了一个简单的 ASP.NET 应用程序供 Sally 捕获她的健康与安全危害,抱歉 Dave,你得自己找个程序员了!

Sally 的应用程序有三个页面

  • HazardDetails.aspx
  • Analysis.aspx
  • ActionPlan.aspx

    HazardDetails.aspxbtnNext 事件处理程序中,我们从表单中获取内容,并将其放入 ProcessContext 的相应键下。然后,我们将 ProcessContext 附加到进程中的下一个页面(Analysis.aspx)并重定向到该 URL。

    private void btnNext_Click(object sender, System.EventArgs e)
    {
        string title = this.txtTitle.Text;
        string severity = this.txtSeverity.Text;
        string description = this.txtDescription.Text;
        ProcessContext["title"] = title;
        ProcessContext["severity"] = severity;
        ProcessContext["description"] = description;
        Response.Redirect(this.AttachProcessToUrl("Analysis.aspx"));
    }

    在 Analysis 页面的 btnNext 事件处理程序中,我们也做类似的事情,捕获 Sally 的想法和思考。最后,在最后一个页面 ActionPlan.aspx 中,当他们点击 btnSubmit 时,我们捕获该页面新的计划和时间表,并抓取已放入 ProcessContext 的内容,一次性将完整的危害和分析数据提交给数据库。

    private void btnSubmit_Click(object sender, System.EventArgs e)
    {
        string title = ProcessContext["title"] as string;
        string severity = ProcessContext["severity"] as string;
        string description = ProcessContext["description"] as string;
        string ideas = ProcessContext["ideas"] as string;
        string analysis = ProcessContext["analysis"] as string;
        string plan = this.txtPlan.Text;
        DateTime duedate = DateTime.Parse(this.txtDue.Text);
        InsertInDB(title,severity,description,ideas,analysis,plan,due);
    }

    结论

    这个应用程序很简单,但它展示了 ProcessContext 的强大功能。如果你尝试在两个窗口中打开 HazardDetails.aspx,你将看到在一个窗口中输入的内容如何与另一个窗口隔离。所以,如果 Sally 成为一个高级用户,这个应用程序将会准备好!

    在这篇文章中,我演示了一种在 ASP.NET 应用程序中添加 ProcessContext 的简单方法,这在许多情况下都很有用,尤其是在向导式的过程中,同一个用户可以在多个窗口中同时运行。我希望你觉得这个例子很有帮助。

    记住,一切都关乎上下文!

    更新

    ASP.NET 2.0 将提供一个名为 Cross Page Posting 的功能,该功能允许你将 Web 表单发布到序列中的下一个页面,然后下一个页面可以访问 PreviousPage,从中你可以访问前一个表单的信息。这有助于处理向导,但仍然不如功能齐全的 ProcessContext 强大。

    更新 #2

    根据 Thomas Eyde 和 Hzi 的建议,我创建了一个 ProcessContext独立版本,它公开了一个 static 属性 ProcessContext.Current,你可以从中访问 ProcessContext 数据。

    它的工作方式是首先在 QueryString 中查找 ProcessID,然后在 Form 中查找一个名为 __PROCESSID 的隐藏字段,最后,如果没有找到,它会创建一个新的 ProcessID,并注册隐藏字段以在 Postback 之间存储 ProcessID,前提是你还没有让 QueryString 包含 ProcessID

    你仍然可以像以前一样使用 ProcessContext.Current.AttachProcessToUrl,还有一个方便的 ProcessContext.Current.Redirect 方法,它执行 AttachProcessToUrl 然后进行 Response.Redirect

    此外,如果你担心存储在 Session 中的数据量,可以使用 ProcessContext.Current.Discard 来释放内存。

    现在有了这个版本,你只需要这样做

    ProcessContext.Current["MyData"] = value; //Store info
    
    string myData = ProcessContext.Current["MyData"] as string; //Access
    
    ProcessContext.Current.Redirect("Page2.aspx");//Redirect the ProcessContext

    也许是时候写一篇新文章了?

    © . All rights reserved.