开发者

How can i use Lazy<T> in an ASP.NET MVC Controller?

I've got a simple ASP.NET MVC controller. Inside a few action methods, I access a resource which I'll say is expensive.

So I thought, why not make it static. So instead of doing double checked locking I thought I can leverage the use of Lazy<T> in .NET 4.0. Call the expensive service once instead of multiple times.

So, if this is my pseduo code, how can I change it do use Lazy<T>. For this contrite example, I'll use the File System as the expensive resource So with this example, instead of getting all the files from the destination path, every time a request calls that ActionMethod, I was hoping to use Lazy to hold that list of files .. which of c开发者_开发技巧ourse, makes the call the first time only.

Next assumption: don't worry if the content is changed. That's out of scope, here.

public class FooController : Controller
{
    private readonly IFoo _foo;
    public FooController(IFoo foo)
    {
        _foo = foo;
    }

    public ActionResult PewPew()
    {
        // Grab all the files in a folder.
        // nb. _foo.PathToFiles = "/Content/Images/Harro"
        var files = Directory.GetFiles(Server.MapPath(_foo.PathToFiles));

        // Note: No, I wouldn't return all the files but a concerete view model
        //       with only the data from a File object, I require.
        return View(files);
    }
}


In your example, the result of Directory.GetFiles depends on the value of _foo, which is not static. Therefore you cannot use a static instance of Lazy<string[]> as a shared cache between all instances of your controller.

The ConcurrentDictionary<TKey, TValue> sounds like something that is closer to what you want.

// Code not tested, blah blah blah...
public class FooController : Controller
{
    private static readonly ConcurrentDictionary<string, string[]> _cache
        = new ConcurrentDictionary<string, string[]>();

    private readonly IFoo _foo;
    public FooController(IFoo foo)
    {
        _foo = foo;
    }

    public ActionResult PewPew()
    {
        var files = _cache.GetOrAdd(Server.MapPath(_foo.PathToFiles), path => {
            return Directory.GetFiles(path);
        });

        return View(files);
    }
}


I agree with Greg that Lazy<> is inappropriate here.

You could try using asp.net caching to cache the contents of a folder, using _foo.PathToFiles as your key. This has an advantage over Lazy<> that you can control the lifetime of the caching so it will refetch the contents say every day or every week without requiring an application restart.

Also caching is friendly to your server in that it will gracefully degrade if there is not enough memory to support it.


Lazy<T> works best when you're not sure if you're going to need the resource, so it's loaded just-in-time only when it's actually needed. The action is always going to load the resource regardless, but because it's expensive you probably want to cache it somewhere? You could try something like this:

public ActionResult PewPew()
{
    MyModel model;
    const string cacheKey = "resource";
    lock (controllerLock)
    {
        if (HttpRuntime.Cache[cacheKey] == null)
        {
            HttpRuntime.Cache.Insert(cacheKey, LoadExpensiveResource());
        }
        model = (MyModel) HttpRuntime.Cache[cacheKey];
    }

    return View(model);
}


I just had the same problem you described so I created a class CachedLazy<T> -> allows values to be shared between controller instances, but with optional timed expiry and one-time creation unlike ConcurrentDictionary.

/// <summary>
/// Provides a lazily initialised and HttpRuntime.Cache cached value.
/// </summary>
public class CachedLazy<T>
{
    private readonly Func<T> creator;

    /// <summary>
    /// Key value used to store the created value in HttpRuntime.Cache
    /// </summary>
    public string Key { get; private set; }

    /// <summary>
    /// Optional time span for expiration of the created value in HttpRuntime.Cache
    /// </summary>
    public TimeSpan? Expiry { get; private set; }

    /// <summary>
    /// Gets the lazily initialized or cached value of the current Cached instance.
    /// </summary>
    public T Value
    {
        get
        {
            var cache = HttpRuntime.Cache;

            var value = cache[Key];
            if (value == null)
            {
                lock (cache)
                {
                    // After acquiring lock, re-check that the value hasn't been created by another thread
                    value = cache[Key];
                    if (value == null)
                    {
                        value = creator();
                        if (Expiry.HasValue)
                            cache.Insert(Key, value, null, Cache.NoAbsoluteExpiration, Expiry.Value);
                        else
                            cache.Insert(Key, value);
                    }
                }
            }

            return (T)value;
        }
    }

    /// <summary>
    /// Initializes a new instance of the CachedLazy class. If lazy initialization occurs, the given
    /// function is used to get the value, which is then cached in the HttpRuntime.Cache for the 
    /// given time span.
    /// </summary>
    public CachedLazy(string key, Func<T> creator, TimeSpan? expiry = null)
    {
        this.Key = key;
        this.creator = creator;
        this.Expiry = expiry;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜