Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serving static files without going through OWIN

Short: For each and every request a new OWIN context is created, I would like to be able to prevent this for certain resource types or paths (images, css, js).

Full: In our application start-up we register a dbcontext creation delegate so that the dbcontext will be created just once per request.

public virtual void Configuration(IAppBuilder app)
{
    app.CreatePerOwinContext(Factory.DbContextCreateDelegate);
}

If a client makes a request for the style sheet, an OWIN context will be created an thus a new dbcontext will also be created. I would like to be able to either not create the OwinContext at all, or at least to be able to prevent some of it's "on create" callbacks to be executed for certain request types/paths.

Alternatively, as I can see why the approach of (partially) "disabling" OWIN would lead to problems, I would like to hear what the best practice would be? How can I serve a static file without creating a database context for each request? (small remark here is that our static files are embedded resources served using a virtual path provider... The problem also occurs for "normal" static files.)

Background: Yesterday I started noticing that every now and then some parts of our application did not load. Sometimes it was single image, sometimes the entire CSS file. After some investigation I saw that some requests were throwing http 500 errors, the exception thrown was often things like a SQL connection time out (but also other exceptions).

While we are of course trying to fix those exceptions. I do believe it to be complete nonsense for our application to set up a database connection when a client makes a request for a single image... That's about 10 database connections for a single page request???

Seems to me like such an obvious problem but I have been Googling all of yesterday and found nothing close to a solution or work around. What am I missing stack?

EDIT: I just tried an approach where I did not actually create the dbcontext but a stub instead. In hindsight this obviously is not the solution to this problem as the OwinContext tries to continue it's process and will critically fail when it tries to get the user from the database using that stub dbcontext. The dbcontext is not the problem, I need to completely bypass Owin... I think...

like image 905
Martijn Kooij Avatar asked Dec 19 '22 19:12

Martijn Kooij


1 Answers

Microsoft.Owin.StaticFiles to the rescue!

I would still like to know why this is not enabled by default in the MVC OWIN template application. But for most cases it is just a matter of a single line of code to enable static files in OWIN.

BASIC SCENARIO

Setting up Owin to treat a folder and its contents as static files:

public virtual void Configuration(IAppBuilder app)
{
    app.UseStaticFiles("/PathToYourStaticFilesFolder");
}

SCENARIO WITH EMBEDDED RESOURCES

Unfortunately for me we are serving most of our static content as embedded resources using an implementation of a VirtualPathProvider. Luckily this is also relatively easy to implement, though it does require writing a wrapper around your VirtualPathProvider to implement the required IFileSystem and IFileInfo interfaces that OWIN requires.

Relevant parts of code from my final solution, not posting the entire VirtualPathProvider as there are plenty of examples for that online.

The wrapper around the VirtualPathProvider:

/// <summary>
/// Represents a virtual file system.
/// A wrapper around <see cref="MyCustomVirtualPathProvider"/> implementing
/// IFileSystem for use in Owin StaticFiles.
/// </summary>
public class VirtualFileSystem : IFileSystem
{
    /// <summary>
    /// Locate the path in the virtual path provider
    /// </summary>
    /// <param name="subpath">The path that identifies the file</param>
    /// <param name="fileInfo">The discovered file if any</param>
    /// <returns>
    /// True if a file was located at the given path
    /// </returns>
    public bool TryGetFileInfo(string subpath, out IFileInfo fileInfo)
    {
        MyCustomVirtualPathProvider virtualPathProvider = 
            (MyCustomVirtualPathProvider) HostingEnvironment.VirtualPathProvider;

        if (!virtualPathProvider.FileExists(subpath))
        {
            fileInfo = null;
            return false;
        }

        try
        {
            EmbeddedResourceVirtualFile virtualFile = 
                (EmbeddedResourceVirtualFile) virtualPathProvider.GetFile(subpath);

            fileInfo = new EmbeddedResourceFileInfo(virtualFile);

            return true;
        }
        catch (InvalidCastException)
        {
            fileInfo = null;
            return false;
        }
    }

    /// <summary>
    /// Not used in our implementation
    /// </summary>
    /// <param name="subpath"></param>
    /// <param name="contents"></param>
    /// <returns></returns>
    public bool TryGetDirectoryContents(string subpath, out IEnumerable<IFileInfo> contents)
    {
        throw new NotImplementedException();
    }
}

The wrapper around the embedded resource:

/// <summary>
/// Represents the file info of an embedded resource
/// </summary>
public class EmbeddedResourceFileInfo : IFileInfo
{
    /// <summary>
    /// Return file contents as readonly stream. Caller should dispose stream when complete.
    /// </summary>
    /// <returns>
    /// The file stream
    /// </returns>
    public Stream CreateReadStream()
    {
        return virtualFile.Open();
    }

    /// <summary>
    /// The length of the file in bytes, or -1 for a directory info
    /// </summary>
    public long Length => virtualFile.Length;

    /// <summary>
    /// The name of the file
    /// </summary>
    public string Name => virtualFile.Name;

    /// <summary>
    /// When the file was last modified
    /// </summary>
    public DateTime LastModified => virtualFile.LastModified;

    /// <summary>
    /// Returns null as these are virtual files
    /// </summary>
    public string PhysicalPath => null;

    /// <summary>
    /// True for the case TryGetDirectoryContents has enumerated a sub-directory
    /// </summary>
    public bool IsDirectory => virtualFile.IsDirectory;

    private readonly EmbeddedResourceVirtualFile virtualFile;

    /// <summary>
    /// Construct using a <see cref="EmbeddedResourceVirtualFile"/>
    /// </summary>
    /// <param name="virtualFile"></param>
    public EmbeddedResourceFileInfo(EmbeddedResourceVirtualFile virtualFile)
    {
        this.virtualFile = virtualFile;
    }
}

And lastly, setting up Owin to use our virtual file system:

public virtual void Configuration(IAppBuilder app)
{
    var staticFilesOptions = new StaticFileOptions
    {
        FileSystem = new VirtualFileSystem()
    };
    app.UseStaticFiles(staticFilesOptions);
}
like image 126
Martijn Kooij Avatar answered Dec 31 '22 12:12

Martijn Kooij