关于 ASP.NET 内存缓存你需要知道的 10 点

缓存机制的主要目的是提高应用程序的性能。作为 ASP.NET 开发人员,你可能会意识到 ASP.NET Web 窗体以及 ASP.NET MVC 可以使用 Cache 对象缓存应用程序的数据。这通常被称为服务器端数据缓存,并且常作为框架的内置功能。虽然 ASP.NET Core 中并没有这样的 Cache 对象,但是你可以很容易地实现内存缓存。本文将向你说明如何实现。

在进一步阅读之前,你先创建一个基于 Web 应用程序项目模板的新的 ASP.NET Core 应用程序。

然后按照下面提到的步骤逐一构建和测试由内存缓存提供的各种功能。

1. 内存缓存需要在启动类 Startup 中启用一下

不同于 ASP.NET Web 窗体和 ASP.NET MVC,ASP.NET Core 没有内置的 Cache 对象,可以拿来在控制器里面直接使用。 这里,内存缓存时通过依赖注入来启用的,因此第一步就是在 Startup 类中注册内存缓存的服务。如此,就得打开 Startup 类然后定位到 ConfigureServices() 方法,像下面这样修改 ConfigureServices() 方法:

publicvoidConfigureServices(IServiceCollection services){    services.AddMvc();        services.AddMemoryCache();}

为了向你的应用程序加入内存缓存能力,你需要在服务集合上调用 AddMemoryCache() 方法。采用这种办法就可以让一个内存缓存(它是一个 IMemoryCache 对象)的默认实现可以被注入到控制器中去。

2. 内存缓存使用依赖注入来注入缓存对象

然后打开 HomeController 并对其进行修改,如下所示:

publicclassHomeController:Controller{privateIMemoryCache cache;publicHomeController(IMemoryCache cache){this.cache = cache;    }    ....}

如你所见,上述代码声明了一个 ImemoryCache 的私有变量。该变量会被构造器中被赋值。构造器会通过 DI(依赖注入)接收到缓存参数,然后被存储在本地变量总,提供后续使用。

3. 你可以使用 Set() 方法来在缓存中存东西

等你有了这个 IMemoryCache 对象,就可以读取或者向它写入数据了。向缓存写入数据项是相当直接的。

publicIActionResultIndex(){  cache.Set("timestamp", DateTime.Now.ToString());returnView();}

上述代码在 Index() 这个 action 中设置了一个缓存项。这是通过使用 IMemoryCache 的 Set() 来完成的。Set() 方法的第一个参数是键名,用来标识该数据项。第二个参数是键的取值。在此例中,我们存储一个字符串的键和一个字符串的值,而你也可以存储其它类型 (原生以及自定义的类型) 的键值对。

4. 你可以使用 Get 方法来从缓存中获取到一个数据项

等你向缓存中添加好了数据,也许会想要在应用程序的其它地方去获取到该数据,可以用 Get() 来做到。如下代码会告诉你如何来做这件事情。

public IActionResultShow(){stringtimestamp= cache.Get("timestamp");  return View("Show",timestamp);

}

上述代码从 HomeController 的另外一个action(Show)那里获取到了一个缓存的数据项。Get() 方法会指定数据项的类型以及它的键名。如果该数据项存在的话,就会被返回并且被赋值给 timestamp 这个字符串变量。然后这个 timestamp 的值就会被传递给 Show 视图。

Show 视图只是简单地输出了 timestamp 的值,如下所示:

TimeStamp : @Model

@Html.ActionLink("Go back","Index","Home")

为了对目前为止你所写的代码进行一下测试,请运行应用程序。首先将浏览器导航至 /Home/Index ,这样 timestamp 键就会被赋值。然后导航至 /Home/Show 并查看 timestamp 值是否会输出。下图所示是 Show() 这个 action 运行起来的一个例子。

5. 你可以使用 TryGet() 来检查缓存中是否存在特定的键值

如果你观察前面的示例,会发现每次你导航至 /Home/Index 的时候, 都会有一个新的 timestamp 被赋值给了缓存项。这是因为我们并没有对此进行检查,规定只有在数据项不存在的时候才赋值。许多时候你都会想要这样做的。这里有两种办法可以在 Index() 这个 action 里面来做这样的检查。我们把两种办法都在下面列了出来。

//first wayif(string.IsNullOrEmpty(cache.Get("timestamp"))){  cache.Set("timestamp", DateTime.Now.ToString());}//second wayif(!cache.TryGetValue("timestamp",outstringtimestamp)){    cache.Set("timestamp", DateTime.Now.ToString());}

第一种办法使用了你早先用过的同一个 Get() 方法,这一次它被拿来跟 if 块一起用。如果 Get() 不能在缓存中找到指定的数据项,IsNullOrEmpty() 就会返回 true。而只有这时候 Set() 才会被调用,一次来添加数据项。

第二种办法更加优雅一点。它使用 TryGet() 方法来获取一个数据项。TryGet() 方法会返回一个布尔值来指明数据项有没有被找到。实际的数据项可以使用一个输出参数拉取出来。如果 TryGet() 返回false,Set() 就会被用来添加数据。

6. 如果不存在的话,可以使用 GetOrCreate() 来添加一项

有时你需要从缓存中检索现有项。如果该项目不存在,则希望添加该项。这两个任务 - 如果它存在获取值,否则创建之 - 可以使用 GetOrCreate() 方法来实现。修改后的 Show() 方法展示了如何实现的。

publicIActionResultShow(){stringtimestamp = cache.GetOrCreate  ("timestamp", entry => {returnDateTime.Now.ToString(); });returnView("Show",timestamp);}

Show() 动作现在使用 GetOrCreate() 方法。 GetOrCreate() 方法将检查时间戳的键值是否存在。如果是,现有值将被赋值给局部变量。否则,将根据第二个参数中指定的逻辑创建一个新条目并将其添加到缓存中。

为了测试此代码,请直接运行 /Home/Show,不需要跳转到 /Home/Index。你仍然会看到输出的时间戳值,因为在该值不存在的情况下,GetOrCreate() 现在是添加了它。

7. 你可以在一个缓存的数据项上面设置绝对和滚动的过期时间

在前述示例中,一个缓存项只要被添加到缓存就会一直存储,除非它被明确地使用 Remove() 从缓存中移除。你也可以在一个缓存项上面设置一个绝对和滚动的过期时间。一个绝对的过期设置意味着该缓存项会在严格指定的日期和时间点被移除,而滚动过期设置则意味着它在给定的一段时间量处于空闲状态(也就是没人去访问)之后被移除。

为了能在一个缓存项上面设置这两种过期策略,你要用到 MemoryCacheEntryOptions 对象。如下代码向你展示了如何去使用。

MemoryCacheEntryOptions options =newMemoryCacheEntryOptions();options.AbsoluteExpiration = DateTime.Now.AddMinutes(1);options.SlidingExpiration = TimeSpan.FromMinutes(1);cache.Set("timestamp", DateTime.Now.ToString(), options);

上述代码来自于修改过的 Index() action,它创建了一个 MemoryCacheEntryOptions 的对象,然后将它的 AbsoluteExpiration 属性设置为从此刻到一分钟之后的一个 DateTime 值,它还将 SlidingExpiration 属性设置为一分钟。这些值都指定了该缓存项会在一分钟之后从缓存移除,不管其是否会被访问。此外,如果该缓存项如初持续空闲了有一分钟,它也会被从缓存中移除。

等你将 AbsoluteExpiration 和 SlidingExpiration 的值设置后, Set() 方法就可以被用来将一个数据项添加到缓存。这一次 MemoryCacheEntryOptions 对象会被作为第三个参数传递给 Set() 方法。

8. 当缓存项会被移除时,你可以连接回调

有时你会想要在缓存项从缓存中被移除时收到通知。可能会有多种原因需要从缓存中移除数据项。例如,因为明确地执行了 Remove() 方法而移除了一个缓存项, 也有可能是因为它的 AbsoluteExpiration 和 SlidingExpiration 值已经到期而被移除,诸如此类的原因。

为了能知道项目是何时从缓存移除的,你需要编写一个缓存函数。如下代码向你展示了如何去做这件事情:

MemoryCacheEntryOptions options =newMemoryCacheEntryOptions();options.AbsoluteExpiration = DateTime.Now.AddMinutes(1);options.SlidingExpiration = TimeSpan.FromMinutes(1);options.RegisterPostEvictionCallback(MyCallback,this);cache.Set("timestamp", DateTime.Now.ToString(), options);

上述代码同之前使用 MemoryCacheEntryOptions 来配置 AbsoluteExpiration 和 SlidingExpiration 的代码相当类似。更加重要的是它也调用了 RegisterPostEvictionCallback() 方法来绑定刚刚讨论过的回调函数。在这里回调函数被命名为 MyCallback。第二个参数是一个你会想要传递给回调函数的状态对象。这里我们传入了 HomeController 的实例 (用 this 将当前的 HomeController 对象“点”出来) 作为状态对象。

前面提到的MyCallback函数,其代码如下所示:

privatestaticvoidMyCallback(objectkey,objectvalue,EvictionReason reason,objectstate){varmessage = $"Cache entry was removed : {reason}";    ((HomeController)state).cache.Set("callbackMessage", message);}

请仔细观察这段代码。 MyCallback() 是 HomeController 类里面的一个私有静态函数,它有四个参数。前面两个参数表示刚刚删除的缓存项的键和值,第三个参数表示的是该数据项被删除的原因。EvictionReason 是一个枚举类型,它维护者各种可能的删除原因,如过期,删除以及替换。

在回调函数的内部,我们会基于删除的原因构造一个字符串消息。我们想要将此消息设置成另外一个缓存项。这样做的话就需要访问 HomeController 的缓存对象,此时状态参数就可以排上用场了。使用状态对象,你可以对 HomeController 的缓存对象进行控制,并使用 Set() 增加一个 callbackMessage 缓存项。

你可以通过 Show() 这个 action 来访问到 callbackMessage,如下所示:

public IActionResultShow(){stringtimestamp= cache.Get("timestamp");  ViewData["callbackMessage"] =    cache.Get("callbackMessage");  return View("Show",timestamp);

}

最后就可以在 Show 视图中显示出来了:

TimeStamp : @Model

@ViewData["callbackMessage"]

@Html.ActionLink("Go back","Index","Home")

欢迎关注我的公众号(同步更新文章)DoNet技术分享平台

阅读原文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容