从数据库或者其他位置加载ASP.NET MVC Views 视图 数据库中加载 cshtml


从数据库或者其他位置加载ASP.NET MVC Views 视图 数据库中加载 cshtml

对于绝大多数 ASP.NET Core MVC 应用程序,从文件系统定位和加载视图的传统方法可以很好地完成工作。但您也可以从包括数据库在内的其他来源加载视图。这在您希望为用户提供制作或修改 Razor 文件的选项但不想让他们访问文件系统的情况下很有用。本文着眼于创建组件以从其他来源获取视图所需的工作,使用数据库作为工作示例。

Razor 视图引擎使用称为 FileProviders 的组件来获取视图的内容。视图引擎将迭代其搜索视图的位置集合 (ViewLocationFormats),然后将这些位置依次呈现给每个已注册的 FileProvider,直到返回视图内容。在启动时,向视图引擎注册了 PhysicalFileProvider,该引擎旨在从每个 MVC 项目模板中的常用 Views 文件夹开始在各个位置查找物理 .cshtml 文件。  EmbeddedFileProvider 可用于从嵌入式资源中获取视图内容。如果您想将视图存储在另一个位置,例如数据库,您可以创建自己的  FileProvider并将其注册到视图引擎。

FileProviders 必须实现  IFileProvider 接口。  IFileProvider 接口指定以下成员:

C# 全选
IDirectoryContents GetDirectoryContents(string subpath);
IFileInfo GetFileInfo(string subpath);
IChangeToken Watch(string filter);

其中最重要的是  GetFileInfo 方法,它返回一个对象,该对象实现了表示文件实现的  IFileInfo 接口。  Watch 方法返回  IChangeToken 接口的实现。 当视图引擎第一次找到一个视图时,它必须编译它。 它缓存已编译的视图,以便不必为后续请求再次编译它。 视图引擎需要某种方式来通知原始视图已发生更改,以便可以使用最新版本刷新缓存。  IChangeToken 实例提供该通知。 因此,为了从数据库中获取视图,我们需要  IFileProvider 的实现、 IFileInfo 的实现和  IChangeToken 的实现。

数据库模式

下面说明了存储视图所需的数据库表的最小模式以及用于创建表的 DDL

从数据库或者其他位置加载ASP.NET MVC Views 视图 数据库中加载 cshtml

SQL 全选
CREATE TABLE [dbo].[Views](
    [Location] [nvarchar](150) NOT NULL,
    [Content] [nvarchar](max) NOT NULL,
    [LastModified] [datetime] NOT NULL,
    [LastRequested] [datetime]
) 

Location 字段包含视图的唯一标识符。 视图引擎使用子路径查找视图,因此使用它们来识别单个视图是有意义的。 因此,主页的 Location 值将是视图引擎期望为 Home 控制器的 Index 方法找到视图的路径之一,例如 /views/home/index.cshtml。 Content 字段包含视图文件中的 Razor 和 HTML。 LastModified 字段在创建视图时默认为 GetUtcDate,并在修改视图内容时更新。 每当视图引擎成功检索内容时,LastRequested 字段都会更新为当前的 UTC 日期和时间。 这两个字段用于计算自上次检索、编译和缓存文件以来是否发生任何修改。 您可以将 LastModified 的默认值设置为 GetDate(),然后在作为 CRUD 过程的一部分编辑文件时重置该值。

IFileProvider

C# 全选
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;

namespace RazorEngineViewOptionsFileProviders
{
    public class DatabaseFileProvider : IFileProvider
    {
        private string _connection;
        public DatabaseFileProvider(string connection)
        {
            _connection = connection;
        }
        public IDirectoryContents GetDirectoryContents(string subpath)
        {
            throw new NotImplementedException();
        }

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

        public IChangeToken Watch(string filter)
        {
            return new DatabaseChangeToken(_connection, filter);
        }
    }
}

我已将我的实现命名为 DatabaseFileProvider。 它有一个构造函数,该构造函数采用一个字符串,该字符串表示数据库的连接字符串。 我没有提供 GetDirectoryContents 方法的实现,因为这个用例不需要。 如果找到与指定路径匹配的结果,GetFileInfo 方法将返回我的自定义 IFileInfo,或者返回 NotFoundFileInfo 对象,它告诉视图引擎尝试另一个提供程序或另一个视图位置。 Watch 方法返回我的自定义 IChangeToken 对象。 

IFileInfo

IFileInfo 接口具有以下成员:

C# 全选
public interface IFileInfo
{
    //
    // Summary:
    //     True if resource exists in the underlying storage system.
    bool Exists { get; }
    //
    // Summary:
    //     True for the case TryGetDirectoryContents has enumerated a sub-directory
    bool IsDirectory { get; }
    //
    // Summary:
    //     When the file was last modified
    DateTimeOffset LastModified { get; }
    //
    // Summary:
    //     The length of the file in bytes, or -1 for a directory or non-existing files.
    long Length { get; }
    //
    // Summary:
    //     The name of the file or directory, not including any path.
    string Name { get; }
    //
    // Summary:
    //     The path to the file, including the file name. Return null if the file is not
    //     directly accessible.
    string PhysicalPath { get; }

    //
    // Summary:
    //     Return file contents as readonly stream. Caller should dispose stream when complete.
    //
    // Returns:
    //     The file stream
    Stream CreateReadStream();
}

我已将源代码中的注释留在其中,因为它们很好地解释了每个成员的目的。 重要的是 Name、Exists、Length 属性和 CreateReadStream 方法。 这是 DatabaseFileInfo 类,它是 IFileInfo 的自定义实现,用于从数据库中获取视图内容:

C# 全选
using Microsoft.Extensions.FileProviders;
using System;
using System.Data.SqlClient;
using System.IO;
using System.Text;

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

        public DatabaseFileInfo(string connection, string viewPath)
        {
            _viewPath = viewPath;
            GetView(connection, 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 connection, string viewPath)
        {
            var query = @"SELECT Content, LastModified FROM Views WHERE Location = @Path;
                    UPDATE Views SET LastRequested = GetUtcDate() WHERE Location = @Path";
            try
            {
                using (var conn = new SqlConnection(connection))
                using (var cmd = new SqlCommand(query, conn))
                {
                    cmd.Parameters.AddWithValue("@Path", viewPath);
                    conn.Open();
                    using (var reader = cmd.ExecuteReader())
                    {
                        _exists = reader.HasRows;
                        if (_exists)
                        {
                            reader.Read();
                            _viewContent = Encoding.UTF8.GetBytes(reader["Content"].ToString());
                            _lastModified = Convert.ToDateTime(reader["LastModified"]);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                // if something went wrong, Exists will be false
            }
        }
    }
}

真正的工作是在构造函数中调用的 GetView 方法中完成的。 它检查数据库中是否存在与视图引擎提供的文件路径匹配的条目。 如果找到匹配项,则将 Exists 设置为 true,并通过 CreateReadStream 方法将内容作为 Stream 提供。 对于这个示例,我选择使用普通的 ADO.NET,但也可以使用其他数据访问技术。

IChangeToken

链中的最后一个组件是 IChangeToken 的实现。 这负责通知视图引擎一个视图已被修改,并且缓存的版本应该被更新的版本替换。

C# 全选
using Microsoft.Extensions.Primitives;
using System;
using System.Data.SqlClient;

namespace RazorEngineViewOptionsFileProviders
{
    public class DatabaseChangeToken : IChangeToken
    {
        private string _connection;
        private string _viewPath;

        public DatabaseChangeToken(string connection, string viewPath)
        {
            _connection = connection;
            _viewPath = viewPath;
        }

        public bool ActiveChangeCallbacks => false;

        public bool HasChanged
        {
            get
            {

                var query = "SELECT LastRequested, LastModified FROM Views WHERE Location = @Path;";
                try
                {
                    using (var conn = new SqlConnection(_connection))
                    using (var cmd = new SqlCommand(query, conn))
                    {
                        cmd.Parameters.AddWithValue("@Path", _viewPath);
                        conn.Open();
                        using (var reader = cmd.ExecuteReader())
                        {
                            if (reader.HasRows)
                            {
                                reader.Read();
                                if (reader["LastRequested"] == DBNull.Value)
                                {
                                    return false;
                                }
                                else
                                {
                                    return Convert.ToDateTime(reader["LastModified"]) > Convert.ToDateTime(reader["LastRequested"]);
                                }
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }

                }
                catch (Exception)
                {
                    return false;
                }
            }
        }

        public IDisposable RegisterChangeCallback(Action<object> callback, object state) => EmptyDisposable.Instance;
    }

    internal class EmptyDisposable : IDisposable
    {
        public static EmptyDisposable Instance { get; } = new EmptyDisposable();
        private EmptyDisposable() { }
        public void Dispose() { }
    }
}

接口的关键成员是 HasChanged 属性。 this 的值是通过比较匹配文件条目的最后请求时间和最后修改时间来确定的。 如果文件自上次请求以来已被修改,则该属性设置为 true,这将导致视图引擎检索修改后的版本。

现在唯一要做的就是向视图引擎注册 DatabaseFileProvider 以便它知道使用它。 这是在 Startup.cs 的 ConfigureServices 方法中完成的:

C# 全选
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.Configure<RazorViewEngineOptions>(opts => 
        opts.FileProviders.Add(
            new DatabaseFileProvider(Configuration.GetConnectionString("DefaultConnection"))
        )
    );
}

有几点需要注意。 PhysicalFileProvider 将首先被调用,因为它首先被注册。 如果您在要检查的位置之一中有一个 .cshtml 文件,它将被返回,并且不会为该请求调用 DatabaseFileProvider(或任何后续提供程序)。 在其当前形式中,将为视图引擎检查的每个位置调用 IChangeToken。 出于这个原因,缓存数据库条目存在的路径可能是明智的,如果请求的路径不在缓存中,则防止执行数据库请求。

总结

Razor 视图引擎被设计为完全可扩展的,使您能够插入自己的 FileProvider,以便您可以从可以为其编写提供程序的任何源定位和加载视图。 本文展示了如何使用数据库作为源来做到这一点。 示例站点可从 GitHub 获得。 

参考

 

 

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
管理员
上一篇:当CDN服务不可用时,前端有什么解决办法
下一篇:化繁为简,用几个例子介绍JavaScript异步处理async awite
评论列表

发表评论

评论内容
昵称:
验证码:
验证码
关联文章

数据库或者其他位置ASP.NET MVC Views 视图 数据库中加 cshtml
asp.net core mvc修改cshtml试图热动态更新
ASP.NET MVC快速入门(一)
.NET MVC后台JS代码块
ASP.NET MVC 后台控制器向View前台传递数据的几种方式
asp.net - 在 ASP.NET Core MVC 中嵌套 TagHelper
ASP.NET MVCASP.NET Core MVC中获取当前URL/Controller/Action
ASP.NET Core MVC 在过滤器ActionFilter中保存页面的生成的html静态页面文件
ASP.NET+MVC入门踩坑笔记 (一) 创建项目 项目配置运行 以及简单的Api搭建
网站迁移纪实:Web Form 到 Asp.Net Core (Abp vNext 自定义开发)
C# asp.net mvc 创建虚拟目录
ASP.NET Core官网教程,资料查找
C# ASP.NET Core开发学生信息管理系统(一)
IIS初始化(预),解决第一次访问慢,程序池被回收问题
【推荐】Razor文件编译 ASP.NET Core
服务安装失败:未能文件或程序集
.NET IIS第一次访问慢,程序池被回收问题,IIS初始化(启用预)
BPM数据库存放位置
ASP.NET Core开发者学习路线图
ASP.NET Core MVC中的路由约束

热门标签
.NET Core .NET Reactor ag-grid AI发布 api安全 ASP.NET Core C#DLL加密 C#播放声音 C#代码混淆 C#代码加密 ChromeDriver Codex DateTime DBeaver devexpress devTool DLL混淆 edge.js EF EFCore Electron element-ui el-form el-table excel FastReport FileStream FolderBrowerDialog FolderSelectDialog form提交 git gridcontrol gridview input javascript json字符串 JS转换对象JSON jwt JWT授权 linq log Math MCP mitmproxy MVC MySQL Navicat netstat nginx node_modules NSwag Nuget Nuget镜像 number PowerShell pyinstaller python pythoncom python爬虫 python抓包 pywin32 redis Requests-html RestSharp Selenium sql SQL Server Swagger to-cms Visual Studio VSCode vue VueRouter vue路由 VUE页面通讯 Webpack Windows Windows服务 winform wmi xlrd yaml YESCMS YESWEB开发框架 白象 表单提交 播放声音 打开URL 代码混淆 弹窗提醒 端口占用 对象转换 分布式 公共字典 机器码 进程排查 静态资源 开发指南 路由参数 密钥 配置教程 配置文件 权限 人工智能 任务 任务调度 日期间隔 日志 日志记录 省市区 授权验证 数据库 四舍五入 文案 文件读取 文件夹选择 文件目录选择 问题排查 行政区域数据 页面通讯 中间件 CSharp 事务锁 工单系统 并发控制 重复提交 CMS Markdig Markdown markdown-it marked 技术选型 VS Code 开发工具 源代码管理 版本控制 Docker PostgreSQL 时区 部署排查 CMS架构 EF Core 主题系统 二次开发 插件系统 容器 运维命令 镜像清理 Linux NAS 远程挂载 飞牛 fnOS S/4HANA SAP GUI SAP HANA SAP R/3 SAP入门 SAP版本 ERP SAP SAP MM 库存管理 物料管理 采购管理 入门教程 SAP S/4HANA SPRO 企业结构 采购组织 MM01 物料主数据 物料类型 BP分组 业务伙伴 供应商主数据 ME41 RFQ 库存物料 采购流程 ME51 消耗性物料 科目分配 采购申请 AC03 ML81N 外部服务 服务主数据 Business Partner SAP培训 ME51N MM模块 Lean Services MM-SRV 外部服务采购 PIR 供应来源 采购主数据 采购信息记录 ME31K 框架协议 计划协议 采购合同 ME01 供应来源确定 货源清单 MEQ1 供应源确定 配额安排 配额评分 MD04 MD21 MRP 计划文件 需求计划 批量程序 MD01N MD02 MRP Live MD05 MM 物料计划 优化采购 供应源 采购订单 ME2A 供应商确认 采购监控 Flexible Workflow 凭证释放 采购审批 释放策略 实地盘点 物料凭证 货物移动 MIGO 收货 移动类型 已撤回 供应商退货 货物发出 STO 库存转储 转移过账 生产订单 预留 GR/IR MIRO 供应商发票 物流发票校验 OMR2 税码 FI PP SD 实操教程 MRBR OMR6 发票差异 交货成本 后续借记 MI01 实物盘点 盘点差异 公司代码 工厂 组织结构 OMS2 主数据定制 自动科目确定 BP角色 CVI 伙伴确定 编号范围 凭证类型 字段选择 FBN1 OMBT OMC2 会计凭证 OMJJ BOM 委外加工 项目类别L MRKO 供应商寄售 特殊库存K MRKON PIPE Pipeline 特殊库存P ERS MRIS 发票计划 周期性结算 里程碑付款 变更追踪 版本管理 采购凭证 SFTP WebDAV 网盘 飞牛fnOS AMPL HERS MPN 中文教程 库存确定 可用性检查 缺件检查 Output Management 消息确定 输出确定 分割评估 库存计价 评估类别 评估类型 PB00 RM0000 条件技术 采购定价 MM-FI集成 OBYC 库存估价 文本类型 文本采用 EFB EVO MSV SU3 用户参数 发票校验 合同参照 履约保留款 特别总账 预付款 Fiori Launchpad SAP Fiori 应用导航 用户体验 LSMW LTMC Migration Cockpit 数据迁移 BRFplus OPD Output Control My Inbox 审批流程 灵活工作流 SAP PP 外部加工 SAP QM 检验批 质量信息记录 采购收货 SAP PM 维护BOM 维护订单 SAP SD SAP Service 端到端流程 MM模块培训 FI-MM集成 供应商管理 审批配置 FICO入门 SAP FICO 财务配置 供应商税务 预扣税 House Bank 银行对账 客户清账 应收账款 FI控制 验证与替代 印度 GST 税务配置 F110 FBZP EWM入门 SAP EWM 仓库管理 OX14 成本核算 物料评估 后勤配置 物料组 价值更新 数量更新 PP-PI 流程制造 生产计划 容差配置 SAP事务码 SAP基础 TCODE Basis 事务代码 MMNR 编号区间 采购实操 组织架构 OMSF SAP实操 FI配置
联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码