Scoping resources per HTTP request

Posted on

Problem

I am using this code in an MVC application to manage objects that should only have one instance per request.

/// <summary>
/// Manage all the resources that we want to scope to a single HTTP request.
/// </summary>
/// <remarks>
/// This class should not be directly referenced in many places.
/// In general, infrastructure code is OK whereas application code is not.
/// </remarks>
sealed class RequestScoped : IDisposable
{
    private static readonly object key = typeof(RequestScoped);

    /// <summary>
    /// Gets the RequestScoped instance for the given HttpContext.
    /// Creates and returns a new instance if it doesn't exist yet.
    /// This method is not thread-safe.
    /// The assumption is that there will only be one thread per request.
    /// </summary>
    public static RequestScoped GetOrCreate(System.Web.HttpContext context)
    {
        if (!context.Items.Contains(key))
        {
            context.Items[key] = new RequestScoped();
            context.AddOnRequestCompleted(Cleanup);
        }
        return (RequestScoped)context.Items[key];
    }

    private static void Cleanup(System.Web.HttpContext context)
    {
        if (context.Items.Contains(key))
        {
            var scope = (RequestScoped)context.Items[key];
            scope.Dispose();
        }
    }

    private readonly Lazy<IDbConnection> connection;
    private readonly Lazy<SessionRepository> sessionRepo;

    private RequestScoped()
    {
        connection = new Lazy<IDbConnection>(() => Utilities.MakeOpenConnection());
        sessionRepo = new Lazy<SessionRepository>(() => new SessionRepository(connection));
    }

    public IDbConnection OpenConnection { get { return connection.Value; } }
    public SessionRepository SessionRepo { get { return sessionRepo.Value; } }

    public void Dispose()
    {
        if (connection.IsValueCreated)
        {
            try
            {
                if (connection.Value.State == ConnectionState.Open)
                {
                    connection.Value.Close();
                }
            }
            catch (Exception)
            {
                // ignore
            }
            connection.Value.Dispose();
        }
    }
}

Example usage is like this:

protected IDbConnection OpenConnection
{
    get { return RequestScoped.GetOrCreate(HttpContext.Current).OpenConnection; }
}

(I am aware of DI containers but I have chosen not to use one in this application.)

Solution

Why is it specific to the two properties you have currently? I think it would make sense to make it more generic, but I can’t think of a good design to do that.


sealed class RequestScoped : IDisposable

Is it okay for users to call Dispose() by themselves? If not, don’t make this type IDisposable and don’t make Dispose() public.


private static readonly object key = typeof(RequestScoped);

Since other pieces of code have access to typeof(RequestScoped), they could potentially read or overwrite the RequestScoped instance that’s stored in the context by accident. To be on the safe side, I would use new object() here.


/// This method is not thread-safe.

I’m not sure you need to call this out here. Especially since this method is thread-safe, as long as different threads use different contexts.


public static RequestScoped GetOrCreate(System.Web.HttpContext context)

The fact that this method either gets existing object or creates a new one sounds like an implementation detail to me. I would probably call it just Get().

Why are you repeating the namespace for HttpContext instead of using using?


public IDbConnection OpenConnection { get { return connection.Value; } }

Is “open” here supposed to be a verb or an adjective? If verb, then that’s not appropriate for a property. If adjective, then it’s I think it’s not necessary.


public SessionRepository SessionRepo { get { return sessionRepo.Value; } }

I don’t see any reason to shorten “repository” to “repo” here, SessionRepository is not that long.


public void Dispose()
{
    if (connection.IsValueCreated)
    {
        try
        {
            if (connection.Value.State == ConnectionState.Open)
            {
                connection.Value.Close();
            }
        }
        catch (Exception)
        {
            // ignore
        }
        connection.Value.Dispose();
    }
}

Why are you not disposing SessionRepository? Is that not necessary?

Also, why are you trying to call Close() and then also call Dispose()? Isn’t just Dispose() enough?

Leave a Reply

Your email address will not be published. Required fields are marked *