2

我目前有一个 WinForms 应用程序,我正在将其转换为 ASP,并且很快意识到缓存数据的重要性。

在我的 WinForms 应用程序中,我创建了表单级变量Dictionary(of String, List(of String)),我Form_Load()使用从 SQL Server 数据库中提取的数据将 2 个链接的组合框关联在一起(在我的整个程序中用于各种其他事情)。WinForms 应用程序的美妙之处在于,这个变量对我来说在整个程序中都是可用的,因为它是一个表单级变量,它会随着表单一起消亡。

现在,我试图在我的 ASP 项目中获得相同的功能,因此我将其设置为属性并将其与缓存相关联,如下所示:

Private ReadOnly Property MyDict As Dictionary(Of String, List(Of String))
    Get
        Dim dict As Dictionary(Of String, List(Of String))
        dict = Cache("MyDict")

        If dict Is Nothing Then

            ... QUERY DB AND POPULATE DICTIONARY ...

            Cache("MyDict") = dict
        End If

        Return dict
    End Get
End Property

我的第一个(也是最重要的)问题是我是否完全正确地执行了此操作,或者我只是对缓存不够了解 - 确实,这是我第一次尝试 ASP,而且事实证明它相当令人生畏。

我的下一个问题是,如果我做对了,我如何/在哪里声明缓存的生命周期?我注意到当我重新运行我的程序时,缓存的数据仍然可以从以前的运行中获得……我希望它在页面的整个生命周期内都可用,但一旦它关闭(如果可能的话。

第三,非常感谢任何好的建议/链接/技巧!

谢谢!!!

PS - 我知道我的代码是 VB,但是 C# 的答案也很好,因为这更多的是 .Net 概念级别而不是特定于语言的。谢谢。

4

2 回答 2

1

缓存方法有点问题,并且对于您要实现的目标可能过于复杂。您对设置缓存生命周期的疑问是为什么这是一种非最佳方法的一些症状。使用更传统的方法来设置依赖下拉菜单可以为您节省很多麻烦。

来自 WinForms 开发的 ASP.NET 中最难适应的事情之一正是您正在努力解决的问题 - 服务器端数据在帖子之间“丢失”的事实。

在 WinForms 应用程序中,您可以将 DataTable 声明为全局变量,例如单击按钮或更改下拉列表的值这样简单的操作不会影响 DataTable 仍然存在这一事实,人口稠密。它在那里是因为表格“活着”直到表格被处置。使用 ASP.NET,服务器端对象仅在页面处理期间的回发期间存在,并且需要在回发时重新创建。

抽象这一点背后的大部分魔法都是通过ViewState完成的,它在回发之间保存页面上对象的状态。例如,如果您要在代码中查询数据库,将结果保存到 DataTable,然后将该 DataTable 绑定到 DataGrid,ViewState 将保存 DataGrid 的内容。在下一次回发时,DataGrid 的内容可用,但绑定到 DataGrid 的初始 DataTable 不再存在。

试图使用缓存来模拟对象的持久生命周期以使 ASP.NET 应用程序像 WinForms 应用程序一样工作是对该技术的滥用。它本质上不是坏事也不是不可能这样做,只是使用了错误的方法。它基本上使用了错误的工具来完成这项工作。

与其尝试这样做,不如搜索有关如何执行特定任务的示例来为您提供更好的服务。在您的情况下,您需要搜索“Cascading Drop-Down List ASP.NET”或类似的内容。有几个选项可用,但这个和任何一个一样好:http ://www.aspsnippets.com/Articles/Creating-Cascading-DropDownLists-in-ASP.Net.aspx

此外,如果您还不熟悉它,那么在编写 .NET 应用程序时了解ASP.NET 页面生命周期非常重要。在 ASP.NET 开发中,没有任何一个组件能够让新的 ASP.NET 开发人员失去对页面生命周期的理解。了解这一点对您的帮助比我们可以向您提出的任何其他事情都多,并且它与您提出的问题直接相关。

于 2013-02-26T15:38:49.850 回答
1

首先,在 Windows 窗体中,您有 1 个进程,其中包含 1 个主窗体实例(我不会讨论辅助窗体,或者在创建多个主窗体实例的情况下完全可以接受的情况)。

在这里,在 ASP.NET 中,您有 1 个进程,其中存在许多“主”页面实例。有人可能会说,每个访问您的 Web 应用程序的不同用户都有一个。这是部分正确的:“主”页面实例的瞬时数量可能大于活动用户的瞬时数量(我们谈论的是不是 MVC 的 ASP.NET)。

您仍然可以在全局范围内访问内容,就像您以前在 WinForms 中所做的那样。唯一的区别是:

  1. 在 WinForms 中,因为通常主窗体是独一无二的,您可以将其用作内容的全局容器。在 ASP.NET 的情况下,您不能这样做,因为主页面不只有一个全局实例(例如,即使在同一个会话的情况下,来自同一个浏览器,刷新页面很可能会创建一个新的 Page 实例。为了测试它:实现该页面的其他隐式公共无参数构造函数并使用断点来检查它)

  2. 使处理来自许多浏览器的请求的所有不同线程都访问一些唯一的共享内存通常是危险的(特别是如果您不知道自己做得很好)。

我个人并不完全同意下面的想法,但通常情况下,初学者应该简单地用相当冗余的SELECT命令轰炸数据库。

我会稍微简化您的问题,以便能够提供一个不会轰炸数据库的简单解决方案:假设您不需要缓存。您只需要从数据库中读取大量内容并同意在重新启动 Web 应用程序之前您将永远不会再次读取它的事实。阅读后,该信息应该在 Web 应用程序的各个角落都可用。

如果是这种情况,那么将有一个简单的解决方案:

  1. 使用众所周知的“全局应用程序类”(Global.asax)及其“Application_Start”方法,以便在您的应用程序启动时收到通知(只需将其添加到您的项目中,就像您添加任何源文件一样,您可以在“添加”中找到它新项目对话框)

  2. 使用全局 HttpApplicationState 类,它像字典一样工作,并允许在 ASP.NET 应用程序中共享全局信息

像这样:

public class Global : System.Web.HttpApplication {

    protected void Application_Start(object sender, EventArgs e) {
        // .. read the database here

        HttpContext.Current.Application["SOME_KEY"] = "ANY OBJECT";
        // .. etc
    }

这样,您就可以从 ASP.NET 应用程序的任何位置读取您在全局 HttpApplicationState 实例中编写的内容,如下所示:

public partial class WebForm2 : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {

        object obj = this.Context.Application["SOME_KEY"];
        // ...etc...
    }
}

关于重新运行您的应用程序:大多数时候,Web 服务器(尤其是 IIS,还有 ASP.NET 开发服务器)不会停止。每当您想要“重新运行”您的应用程序(如果它已停止)时,它就会启动。但是,当您停止调试时(单击 Visual Studio 中的停止按钮),这就是您所做的一切。您从 Web 服务器的进程中分离出来,让它安静地运行。

当您“重新运行”应用程序时,如果 Web 服务器已经在运行(有时 ASP.NET 开发服务器崩溃,IIS 也崩溃但不那么频繁),您只是“重新连接 IDE 的调试器”到 Web 服务器,你会发现一切都是一样的。

更重要的是:如果您重建(强制重建,以防不需要重建)Web 服务器不会停止,它会丢弃您的应用程序(在隔离的 AppDomain 中运行)并重新加载新程序集并启动它再次。如果您记录 Global.asax "Application_Started" 方法,您可以看到所有这些内容。

编辑

这是一种安全的缓存方式(虽然经过优化以允许许多读者同时访问一些全局数据,但仍然会减慢速度——拥有缓存是一种奢侈,我能说什么:))。

首先写一个这样的类:

public sealed class SafeCache<T> {

    private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    private readonly Func<T> expensiveReader;

    private readonly TimeSpan lease;
    private DateTime lastRead;
    private T data;

    public SafeCache(TimeSpan lease, Func<T> expensiveReader) {
        this.lease = lease;
        this.expensiveReader = expensiveReader;
        this.data = expensiveReader();
        this.lastRead = DateTime.UtcNow;
    }

    public T Data {
        get {
            this.rwLock.EnterReadLock();
            try {

                if (DateTime.UtcNow - this.lastRead < this.lease)
                    return this.data;

            } finally {
                this.rwLock.ExitReadLock();
            }

            this.rwLock.EnterUpgradeableReadLock();
            try {

                if (DateTime.UtcNow - this.lastRead < this.lease)
                    return this.data;
                else {
                    this.rwLock.EnterWriteLock();
                    try {

                        this.data = expensiveReader();
                        this.lastRead = DateTime.UtcNow;

                        return this.data;

                    } finally {
                        this.rwLock.ExitWriteLock();
                    }
                }

            } finally {
                this.rwLock.ExitUpgradeableReadLock();
            }
        }
    }

}

然后使用 Global.asax 创建它的一个实例并将其放在 HttpApplicationState 全局实例中,在某个任意键处:

public class Global : System.Web.HttpApplication {

    protected void Application_Start(object sender, EventArgs e) {
        HttpContext.Current.Application["SOME_KEY"] = new SafeCache<SomeRecord[]> (
          lease: TimeSpan.FromMinutes(10),
          expensiveReader: () => {
             // .. read the database here
             // and return a SomeRecord[]
             // (this code will be executed for the first time by the ctor of SafeCache
             // and later on, with every invocation of the .Data property getter that discovers 
             // that 10 minutes have passed since the last refresh)
          }
        );
        // .. etc
    }

您还可以创建一个小助手,例如:

public static class Helper {
    public static SomeRecord[] SomeRecords {
        get { 
           var currentContext = HttpContext.Current;
           if (null == currentContext) // return null or throw some clear Exception

           var cache = currentContext.Application["SOME_KEY"] as SafeCache<SomeRecord[]>;
           return cache.Data;
        }
    }
}

当然,在需要的地方相应地使用它:

public partial class WebForm2 : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {

        SomeRecord[] records = Helper.SomeRecords;
        // ...etc...
    }
}

编辑结束

于 2013-02-26T15:46:28.473 回答