无需通过 OWIN 即可提供静态文件

Serving static files without going through OWIN

短: 对于每个请求都会创建一个新的 OWIN 上下文,我希望能够针对某些资源类型或路径(图像、css、js)阻止这种情况。

已满: 在我们的应用程序启动中,我们注册了一个 dbcontext 创建委托,以便每个请求只创建一次 dbcontext。

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

如果客户端请求样式 sheet,将创建一个 OWIN 上下文,因此也将创建一个新的 dbcontext。我希望能够根本不创建 OwinContext,或者至少能够防止针对某些请求 types/paths.

执行其中的某些 "on create" 回调

或者,我明白为什么(部分)"disabling" OWIN 的方法会导致问题,我想听听最佳做法是什么?如何在不为每个请求创建数据库上下文的情况下提供静态文件? (这里需要注意的是,我们的静态文件是使用虚拟路径提供程序提供服务的嵌入式资源..."normal" 静态文件也会出现此问题。

背景: 昨天我开始注意到我们应用程序的某些部分时不时地没有加载。有时它是单个图像,有时是整个 CSS 文件。经过一些调查后,我看到一些请求抛出 http 500 错误,抛出的异常通常是 SQL 连接超时(但也有其他异常)。

虽然我们当然会尝试修复这些异常。我确实认为当客户端请求单个图像时我们的应用程序建立数据库连接完全是胡说八道......单个页面请求大约有 10 个数据库连接???

在我看来,这是一个如此明显的问题,但我昨天一直在谷歌搜索,但没有找到接近解决方案或变通的方法。我缺少什么堆栈?

编辑: 我只是尝试了一种方法,我实际上并没有创建 dbcontext,而是创建了一个存根。事后看来,这显然不是解决这个问题的方法,因为 OwinContext 试图继续它的过程,并且当它试图使用该存根 dbcontext 从数据库中获取用户时会严重失败。 dbcontext 不是问题,我需要完全绕过 Owin...我认为...

Microsoft.Owin.StaticFiles 救援!

我仍然想知道为什么在 MVC OWIN 模板应用程序中默认情况下未启用此功能。但在大多数情况下,只需一行代码即可在 OWIN 中启用静态文件。

基本场景

设置 Owin 以将文件夹及其内容视为静态文件:

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

具有嵌入式资源的场景

对我来说不幸的是,我们使用 VirtualPathProvider 的实现将大部分静态内容作为嵌入式资源提供。幸运的是,这也相对容易实现,尽管它确实需要围绕 VirtualPathProvider 编写一个包装器来实现 OWIN 所需的 IFileSystem 和 IFileInfo 接口。

我最终解决方案中的相关代码部分,没有发布整个 VirtualPathProvider,因为网上有很多示例。

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();
    }
}

嵌入资源的包装器:

/// <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;
    }
}

最后,设置 Owin 以使用我们的虚拟文件系统:

public virtual void Configuration(IAppBuilder app)
{
    var staticFilesOptions = new StaticFileOptions
    {
        FileSystem = new VirtualFileSystem()
    };
    app.UseStaticFiles(staticFilesOptions);
}