【问题标题】:IFileProvider does not execute if a cshtml file already exists on disk如果磁盘上已存在 cshtml 文件,则 IFileProvider 不会执行
【发布时间】:2018-11-19 04:16:15
【问题描述】:

我正在写一个IFileProvider,我打算用它从数据库中加载内容。我在下面修改了这段代码,作为概念验证,如果特定请求与“联系人”匹配,我只是对字符串进行硬编码。

在磁盘上,我的 Pages 文件夹中有一个文件 contact.cshtml。如果我使用我的新DatabaseFileProvider,那么contact.cshtml 页面将按预期完美呈现。

当我在下面包含 IFileProvider 时,对 contact.cshtml 页面的请求确实被覆盖了 - 再次,正如预期的那样。

但是,如果尝试拦截对 不存在 的 cshtml 文件的请求,我的代码实际上永远不会被命中。例如,您可以在我的 cmets 中看到,如果我将工作“联系人”替换为“动态”,则无法执行。

我确定这与我将 IFileProvider 与 MVC 的默认 PhysicalFileProvider 交织在一起的方式有关。

这是我的设置:

public class DatabaseFileProvider : IFileProvider
    {
        
        public IFileInfo GetFileInfo(string subpath)
        {
            var result = new DatabaseFileInfo(subpath);
            return result.Exists ? result as IFileInfo : new NotFoundFileInfo(subpath);
        }
    }

public class DatabaseFileInfo : IFileInfo
    {
        private string _viewPath;
        private byte[] _viewContent;
        private DateTimeOffset _lastModified;
        private bool _exists = false;

        public DatabaseFileInfo(string viewPath)
        {
            _viewPath = viewPath;
            GetView(viewPath);
        }
        public bool Exists => _exists;

        public bool IsDirectory => false;

        public DateTimeOffset LastModified => _lastModified;

        public long Length
        {
            get
            {
                using (var stream = new MemoryStream(_viewContent))
                {
                    return stream.Length;
                }
            }
        }

        public string Name => Path.GetFileName(_viewPath);

        public string PhysicalPath => null;

        public Stream CreateReadStream()
        {
            return new MemoryStream(_viewContent);
        }

        private void GetView(string viewPath)
        {
            if (viewPath == null) return;
            if (viewPath.ToLower().IndexOf("_view") != -1) return;

            // PROBLEM: this only works if the file EXISTS on disk ???!!!  Try switching out "contact" (works) with "dynamic" (doesn't work)
            // Not even the breakpoint gets hit?

            if (viewPath.ToLower().ToLower().IndexOf("contact") == -1) return;

            var html = "This is NOT cshtml";

            _viewContent = Encoding.UTF8.GetBytes(html);
            _lastModified = DateTime.Now;
            _exists = true;
        }
    }

...在我的Startup.cs 文件中:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            
            services.Configure<RazorViewEngineOptions>(opts => {
                    opts.FileProviders.Clear();
                    opts.FileProviders.Add(new DatabaseFileProvider());
                    opts.FileProviders.Add(Environment.ContentRootFileProvider);

                    /*
                    opts.FileProviders.Clear();
                    opts.FileProviders.Add(new CompositeFileProvider(
                        Environment.ContentRootFileProvider,
                        new DatabaseFileProvider()
                    )); 
                    */
                }
            );
            
        }

如您所见,我使用CompositeFileProvider 进行了第二个实现,但问题仍然存在。我也改变了供应商的顺序。

我确定我在做一些愚蠢的事情 - 有人能指出我正确的方向吗?

【问题讨论】:

    标签: asp.net-core asp.net-core-mvc


    【解决方案1】:

    仅供参考,我最终找到了答案,即确保您实现了IFileProvider.GetDirectoryContents 方法。我一直忽略这个,因为我认为它只是为了方便目录浏览,但它看起来像.Net Core在启动时预先查询所有提供者内容,并且这个方法用于准确告诉它你有。

    下面附上完整的源代码:

    public class FoundationEmbeddedResourcesFileProvider : IFileProvider
        {
    
            private static readonly Assembly FoundationAssembly = typeof(Foundation.UI.Web.Core.Code.Library).GetTypeInfo().Assembly;
            private static readonly List<string> ResourceNames = FoundationAssembly.GetManifestResourceNames().ToList();
            private bool IsPartOfCompositeProvider { get; set; }
    
    
            public class FoundationEmbeddedResourceDirectory : IDirectoryContents
            {
    
                private IEnumerator<IFileInfo> Files { get; set; }
    
                public FoundationEmbeddedResourceDirectory(Assembly ass, string subPath)
                {
                    var subFolderNames = new List<string>();
                    var subFilePaths = new List<string>();
    
                    foreach (var resource in ResourceNames) {
                        var filePath = ConvertResourceFormatToFilePath(resource); // e.g. /Pages/Foundation/Views/MainMenu.cshtml
    
                        // If this item is not in our directory, we break
                        if (!filePath.ToLower().StartsWith(subPath.ToLower())) continue;
    
    
                        var subFolders = filePath.Substring(subPath.Length).Split("/").Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
    
                        // Remove the 'file' component from the directories list
                        subFolders = subFolders.Take(subFolders.Count - 1).ToList();
    
                        // Direct directory?
                        if (subFolders.Count == 1) {
                            subFolderNames.Add(subFolders[0]);
                            continue;
                        }
    
                        // Direct file?
                        if (subFolders.Count == 0) {
                            subFilePaths.Add(filePath);
                            continue;
                        }
    
                    }
    
                    var files = new List<IFileInfo>();
                    files.AddRange(subFolderNames.Distinct().Select(x => new ResourceStreamResult(ass, x, true)));
                    files.AddRange(subFilePaths.Distinct().Select(x => new ResourceStreamResult(ass, x, false)));
    
                    // Add sub folders
                    // if (subPath == "/Pages") files.Add(new ResourceStreamResult(ass, "Foundation/Views", true));
    
                    this.Exists = files.Any();
                    this.Files = files.GetEnumerator();
                }
    
                #region Implementation of IEnumerable
    
                /// <inheritdoc />
                public IEnumerator<IFileInfo> GetEnumerator()
                {
                    return this.Files;
                }
    
                /// <inheritdoc />
                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }
    
                #endregion
    
                #region Implementation of IDirectoryContents
    
                /// <inheritdoc />
                public bool Exists { get; set; }
    
                #endregion
            }
    
            public class FoundationEmbeddedResourceChangeToken : IChangeToken
            {
                #region Implementation of IChangeToken
    
                /// <inheritdoc />
                public IDisposable RegisterChangeCallback(Action<object> callback, object state)
                {
                    return null;
                }
    
                /// <inheritdoc />
                public bool HasChanged
                {
                    get { return true; }
                }
    
                /// <inheritdoc />
                public bool ActiveChangeCallbacks
                {
                    get { return false; }
                }
    
                #endregion
            }
    
            public class ResourceStreamResult : IFileInfo
            {
                #region Implementation of IFileInfo
    
                // private Assembly Assembly = null;
                private Stream Stream = null;
                private string ResourceName = "";
    
                public ResourceStreamResult(Assembly assembly, string filePath, bool isDirectory)
                {
                    this.IsDirectory = isDirectory;
                    // this.Assembly = assembly;
                    this.PhysicalPath = filePath;
                    this.ResourceName = ConvertFilePathToResourceFormat(filePath);
                    if (!string.IsNullOrWhiteSpace(this.ResourceName)) this.Stream = assembly.GetManifestResourceStream(this.ResourceName);
                }
    
                /// <inheritdoc />
                public Stream CreateReadStream()
                {
                    return this.Stream;
                }
    
                /// <inheritdoc />
                public bool Exists
                {
                    get {
                        if (this.IsDirectory) return IsDirectory;
                        return (this.Stream != null);
                    }
                }
    
                /// <inheritdoc />
                public long Length
                {
                    get
                    {
                        if (this.Stream == null) return 0;
                        return this.Stream.Length;
                    }
                }
    
                /// <inheritdoc />
                public string PhysicalPath { get; set; }
    
                /// <inheritdoc />
                public string Name
                {
                    get {
                        if (this.IsDirectory) return this.PhysicalPath;
                        return System.IO.Path.GetFileName(this.PhysicalPath);
                    }
                }
    
                /// <inheritdoc />
                public DateTimeOffset LastModified
                {
                    get { return DateTime.UtcNow; }
                }
    
                /// <inheritdoc />
                public bool IsDirectory { get;set; }
    
                #endregion
            }
    
            public FoundationEmbeddedResourcesFileProvider(bool isPartOfCompositeProvider)
            {
                this.IsPartOfCompositeProvider = isPartOfCompositeProvider;
            }
    
    
            #region Implementation of IFileProvider
    
            public static string ConvertFilePathToResourceFormat(string filePath)
            {
                var s = filePath.Replace("/", ".");
                if (s.StartsWith(".")) s = s.Substring(1);
    
                // CSHTML files are prefixed with the /Pages root, but content files (.js etc) are not.  This makes sense because there are different providers for static files and content files
                // BUT, we source all our content files through the /Pages sub directory because we like to nest javascript/css alongside their views
                if (!s.StartsWith("Pages.")) s = "Pages." + s;
    
                // Prefix with assembly name
                s = "Foundation.UI.Web.Core." + s;
    
                // Resource names are case sensitive but the incoming request is not necessarily
                s = ResourceNames.FirstOrDefault(x => x.ToLower() == s.ToLower());
    
                return s;
            }
    
            public static string ConvertResourceFormatToFilePath(string resourceName)
            {
                var s = resourceName;
                s = s.Replace("Foundation.UI.Web.Core", "");
    
                s = s.Replace(".", "/");
    
                // Retain file extension
                var lastSlash = s.LastIndexOf("/");
                if (lastSlash != -1) s = s.Substring(0, lastSlash) + "." + s.Substring(lastSlash + 1);
    
                return s;
            }
    
            /// <inheritdoc />
            public IFileInfo GetFileInfo(string subpath)
            {
                var resource = new ResourceStreamResult(FoundationAssembly, subpath, false);
    
                if (!resource.Exists) {
                    // Composite providers expect NULL in order to move on to the next item
                    if (this.IsPartOfCompositeProvider) return null;
                    return new NotFoundFileInfo(subpath);
                }
                return resource;
            }
    
            /// <inheritdoc />
            public IDirectoryContents GetDirectoryContents(string subpath)
            {
                return new FoundationEmbeddedResourceDirectory(FoundationAssembly, subpath);
            }
    
            /// <inheritdoc />
            public IChangeToken Watch(string filter)
            {
                return new FoundationEmbeddedResourceChangeToken();
            }
    
            #endregion
        }
    

    【讨论】:

    • 您能分享一下GetDirectoryContents 的实现吗?
    • 当然没问题我已经修改了已回答的帖子。它不会编译,因为我没有包含依赖项,但希望你能明白。
    猜你喜欢
    • 2015-10-04
    • 1970-01-01
    • 2016-03-06
    • 2016-06-03
    • 1970-01-01
    • 2016-06-26
    • 2015-10-13
    • 1970-01-01
    相关资源
    最近更新 更多