Skip to content

文章

在 ASP.Net Core 中实现请求缓存

2019年7月8日 • 5 分钟阅读

在 ASP.Net Core 中实现请求缓存

在应用程序开发的某个阶段,通常在相当早期,你会意识到应用程序很慢。经过一些研究后,罪魁祸首是不必要地重复检索相同的数据,然后灵光一闪,你想到:“我需要一些缓存。”

缓存是一种宝贵的模式,可以消除对数据库或第三方 API 的冗余调用。Microsoft 提供了 IMemoryCache 用于基于时间的缓存,但有时基于时间的缓存并不是你需要的。在本文中,我们将探讨请求作用域缓存及其如何使我们受益。

什么是请求缓存?请求缓存是一种在 Web 请求的生命周期内缓存数据的机制。在 .NET 中,我们通过 HttpContext.Items 集合在某种程度上拥有这种能力,但是 HttpContext 并不以其可注入性而闻名。

请求作用域缓存有几个好处:首先,它消除了对过时数据的顾虑。在大多数情况下,请求在不到一秒内执行,这通常不足以让数据变得过时。其次,过期不是一个问题,因为数据在请求结束时就消失了。

开箱即用,ASP.Net Core 没有可注入的缓存。如前所述,HttpContext.Items 是一个选项,但它不是一个优雅的解决方案。

幸运的是,ASP.Net Core 为我们提供了工具,通过使用内置的依赖注入 (DI) 框架来创建可注入的请求缓存实现。

内置的 DI 框架为依赖项提供了三种生命周期:SingletonScopedTransientSingleton 用于应用程序的生命周期,Scoped 用于请求的生命周期,Transient 是每个请求的新实例。

我创建了一个以 IMemoryCache 接口为模型的接口,以保持一致性。

接口

public interface IRequestCache
{
    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    void Add<TValue>(string key, TValue value);

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    void Remove(string key);

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add);

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    TValue Retrieve<TValue>(string key);
}

实现

public class RequestCache : IRequestCache
{
    IDictionary<string, object> _cache = new Dictionary<string, object>();

    /// <summary>
    /// Add the value into request cache. If the key already exists, the value is overwritten.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <typeparam name="TValue"></typeparam>
    public void Add<TValue>(string key, TValue value)
    {
        _cache[key] = value;
    }

    /// <summary>
    /// Remove the key from the request cache
    /// </summary>
    /// <param name="key"></param>
    public void Remove(string key)
    {
        if (_cache.ContainsKey(key))
        {
            _cache.Remove(key);
        }
    }

    /// <summary>
    /// Retrieve the value by key, if the key is not in the cache then the add func is called
    /// adding the value to cache and returning the added value.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="add"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        var value = add();

        _cache[key] = value;

        return value;
    }

    /// <summary>
    /// Retrieves the value by key. When the key does not exist the default value for the type is returned.
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="TValue"></typeparam>
    /// <returns></returns>
    public TValue Retrieve<TValue>(string key)
    {
        if (_cache.ContainsKey(key))
        {
            return (TValue)_cache[key];
        }

        return default(TValue);
    }
}

使用 ASP.Net Core 的 DI 框架,我们将其配置为 Scoped

services.AddScoped<IRequestCache, RequestCache>();

用法

public class UserService
{
    private readonly IRequestCache _cache;
    private readonly IUserRepository _userRepository;

    public UserService(IRequestCache cache, IUserRepository userRepository)
    {
        _cache = cache;
        _userRepository = userRepository;
    }

    public User RetrieveUserById(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        return _cache.RetrieveOrAdd(BuildCacheKey, () => { return _userRepository.RetrieveUserBy(userId); });
    }

    public void Delete(int userId)
    {
        var buildCacheKey = UserService.BuildCacheKey(userId);

        _userRepository.Delete(userId);
        _cache.Remove(BuildCacheKey(userId));
    }

    private static string BuildCacheKey(int userId)
    {
        return $"user_{userId}";
    }
}

就这样!请求缓存现在可以在你需要的任何地方注入。

访问 Git 仓库,随时可以尝试这些代码。

作者:Chuck Conway 是一位 AI 工程师,拥有近 30 年的软件工程经验。他构建实用的 AI 系统——内容管道、基础设施代理和解决实际问题的工具——并分享他沿途的学习成果。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTubeSubStack

↑ 返回顶部

你可能也喜欢