话不多说,直接贴代码

一下代码根据自己需求进行修改,总的来说就是重写视图路径

net mvc:
    /// <summary>
    /// 主题路径提供者视图引擎
    /// </summary>
    public abstract class ThemeVirtualPathProviderViewEngine : VirtualPathProviderViewEngine
    {
        //工作上下文
        private static IThemeContext themeContext;

        //模板视图位置格式
        private readonly string[] _templateslocationformats = new string[] {
            "~/{0}/{1}/Views/{2}.cshtml",
            "~/{0}/{1}/Views/Shared/{2}.cshtml",
            "~/Views/Shared/{2}.cshtml"
        };

        //模板视图位置格式
        private readonly string[] _arealocationformats = new string[] {
            //themes
            "~/Areas/{2}/Themes/{1}/Views/{0}.cshtml",
            "~/Areas/{2}/Themes/{1}/Views/Shared/{0}.cshtml", 
            
            //default
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
        };

        protected ThemeVirtualPathProviderViewEngine()
        {
            //将视图文件扩展名限制为cshtml
            FileExtensions = new[] { "cshtml" };
        }

        #region 重写方法
        /// <summary>
        /// 使用指定的控制器上下文和母版视图名称来查找指定的视图
        /// </summary>
        /// <param name="controllerContext">控制器上下文</param>
        /// <param name="viewName">视图的名称</param>
        /// <param name="masterName">母版视图的名称</param>
        /// <param name="useCache">若为 true,则使用缓存的视图</param>
        /// <returns>页视图</returns>
        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            //构建一个视图引擎结果
            ViewEngineResult result = FindThemeView(controllerContext, viewName, masterName, useCache);
            return result;
        }

        /// <summary>
        /// 寻找分部视图的方法
        /// </summary>
        /// <param name="controllerContext">控制器上下文</param>
        /// <param name="partialViewName">分部视图的名称</param>
        /// <param name="useCache">若为 true,则使用缓存的分部视图</param>
        /// <returns>分部视图</returns>
        public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            //构建一个分部视图引擎结果
            ViewEngineResult result = FindThemePartialView(controllerContext, partialViewName, useCache);
            return result;
        }
        #endregion

        protected virtual string GetCurrentTheme(bool mobile)
        {
            themeContext = ThemeContext.GetInstance();

            if (mobile)
                return themeContext.WorkingMobileTheme;
            else
                return themeContext.WorkingDesktopTheme;
        }

        #region 工具方法
        /// <summary>
        /// 获取文件的路径
        /// </summary>
        private string GetPath(ControllerContext controllerContext, string name, string controllerName, string theme, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
        {
            searchedLocations = null;

            //视图位置列表
            string[] locations = null;

            //获取区域
            string area = GetRouteDataTokenValue("area", controllerContext.RouteData.DataTokens).ToLower();
            if (area.Length == 0)//前台视图位置的处理
            {
                locations = _templateslocationformats;
            }
            else
            {
                locations = _arealocationformats;
            }

            //是否为特殊路径的标识
            bool flag2 = IsSpecificName(name);

            //从缓存中获取视图位置
            string cacheKey = CreateCacheKey(cacheKeyPrefix, name, flag2 ? string.Empty : controllerName, area, theme);//视图位置的缓存键
            if (useCache)
            {
                //从缓存中得到视图位置
                var cachedPath = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
                if (cachedPath != null)
                {
                    return cachedPath;
                }
            }

            //如果视图位置不在缓存中,则构建视图位置并存储到缓存中
            if (!flag2)//不是特殊路径时的操作
            {
                return GetPathFromGeneralName(controllerContext, locations, name, theme, controllerName, area, cacheKey, ref searchedLocations);
            }
            else//特殊路径时的操作
            {
                return GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations);
            }
        }

        /// <summary>
        /// 特殊名称时构建视图路径
        /// </summary>
        private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
        {
            //将路径添加到视图位置缓存
            ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, name);
            //扩展名不为“.cshtml”或者文件不存在时
            if (!IsSupportedExtension(name) || !FileExists(controllerContext, name))
            {
                searchedLocations = new string[] { name };
                return string.Empty;
            }
            return name;
        }

        /// <summary>
        /// 普通名称时构建视图路径
        /// </summary>
        private string GetPathFromGeneralName(ControllerContext controllerContext, string[] viewLocationFormats, string name, string theme, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations)
        {
            int count = viewLocationFormats.Length;
            searchedLocations = new string[count];

            //循环视图位置
            for (int i = 0; i < count; i++)
            {
                string path = string.Empty;
                if (!string.IsNullOrEmpty(areaName))//后台访问
                {
                    path = string.Format(viewLocationFormats[i], name, controllerName, areaName);
                }
                else
                {
                    path = string.Format(viewLocationFormats[i], DTKeys.DIRECTORY_REWRITE_MVC, theme, name);
                }
                if (FileExists(controllerContext, path))
                {
                    searchedLocations = null;
                    ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, path);
                    return path;
                }
                //将路径添加到搜索位置列表中
                searchedLocations[i] = path;
            }

            return string.Empty;
        }

        /// <summary>
        /// 创建视图位置的缓存键
        /// </summary>
        private string CreateCacheKey(string prefix, string name, string controllerName, string area, string theme)
        {
            return string.Format(":ViewCacheKey:{0}:{1}:{2}:{3}:{4}:{5}", new object[] { base.GetType().AssemblyQualifiedName, prefix, name, controllerName, area, theme });
        }

        /// <summary>
        /// 构建视图引擎结果
        /// </summary>
        private ViewEngineResult FindThemeView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            //视图文件路径搜索列表
            string[] strArray1 = null;
            //布局文件路径搜索列表
            string[] strArray2 = null;

            //获取控制器名称
            string controllerName = controllerContext.RouteData.GetRequiredString("controller");

            //获取视图文件路径
            string viewPath = GetPath(controllerContext, viewName, controllerName, GetCurrentTheme(false), "View", useCache, out strArray1);

            //当视图文件存在时
            if (!string.IsNullOrWhiteSpace(viewPath))
            {
                if (string.IsNullOrWhiteSpace(masterName))
                {
                    return new ViewEngineResult(CreateView(controllerContext, viewPath, string.Empty), this);
                }
                else
                {
                    //获取布局文件的路径
                    string masterPath = GetPath(controllerContext, masterName, controllerName, GetCurrentTheme(false), "Master", useCache, out strArray2);
                    if (!string.IsNullOrWhiteSpace(masterPath))
                    {
                        return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
                    }
                }
            }

            //当视图文件或布局文件不存在时将搜索路径返回
            if (strArray2 == null)
            {
                return new ViewEngineResult(strArray1);
            }
            else
            {
                return new ViewEngineResult(strArray1.Union<string>(strArray2));
            }
        }

        /// <summary>
        /// 构建分部视图引擎结果
        /// </summary>
        private ViewEngineResult FindThemePartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            //分部视图文件路径搜索列表
            string[] strArray;

            //获取控制器名称
            string controllerName = controllerContext.RouteData.GetRequiredString("controller");

            //获取分部视图文件路径
            string partialViewPath = GetPath(controllerContext, partialViewName, controllerName, GetCurrentTheme(false), "Partial", useCache, out strArray);

            //当分部视图文件存在时
            if (!string.IsNullOrWhiteSpace(partialViewPath))
            {
                return new ViewEngineResult(CreatePartialView(controllerContext, partialViewPath), this);
            }
            //分部视图文件不存在时返回搜索路径
            return new ViewEngineResult(strArray);
        }

        /// <summary>
        /// 判读视图名称是否以“~”或“/”开头
        /// </summary>
        private bool IsSpecificName(string name)
        {
            char ch = name[0];
            if (ch != '~')
            {
                return (ch == '/');
            }
            return true;
        }

        /// <summary>
        /// 判读文件扩展名是否为“.cshtml”
        /// </summary>
        private bool IsSupportedExtension(string path)
        {
            return path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase);
        }

        /// <summary>
        /// 获取路由集合中指定键的值
        /// </summary>
        private string GetRouteDataTokenValue(string key, RouteValueDictionary dict)
        {
            object value = dict[key];
            if (value == null)
                return string.Empty;
            return value.ToString();
        }
        #endregion
    }


net core:

    /// <summary>
    /// 指定Microsoft使用的视图位置扩展器的约定。Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine实例来确定视图的搜索路径
    /// </summary>
    public class ThemeableViewLocationExpander : IViewLocationExpander
    {
        private const string THEME_KEY = "dtcms.themename";

        /// <summary>
        /// 工作上下文
        /// </summary>
        private static IThemeContext themeContext;

        protected virtual string GetCurrentTheme(bool mobile)
        {
            themeContext = ThemeContext.GetInstance();

            if (mobile)
            {
                return themeContext.WorkingMobileTheme;
            }
            else
            {
                return themeContext.WorkingDesktopTheme;
            }
        }

        /// <summary>
        /// Invoked by a Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine to determine the
        /// 此实例将使用的值 Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander
        /// 计算出的值用于确定视图位置自上次定位以来是否发生了更改
        /// </summary>
        /// <param name="context"></param>
        public void PopulateValues(ViewLocationExpanderContext context)
        {
            //根本不需要添加可主题化的视图位置,因为管理层无论如何都不应该是可主题化的
            if (context.AreaName != null)
            {
                if (context.AreaName.ToLower()?.Equals("admin") ?? false)
                {
                    return;
                }
            }
            context.Values[THEME_KEY] = GetCurrentTheme(false);
        }

        /// <summary>
        /// 用于确定视图的潜在位置
        /// </summary>
        /// <param name="context">Context</param>
        /// <param name="viewLocations">视图位置</param>
        /// <returns></returns>
        public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
        {
            if (context.Values.TryGetValue(THEME_KEY, out string theme))
            {
                viewLocations = new[] {
                        $"/templates/{theme}/Views/0.cshtml",
                        $"/templates/{theme}/Views/Shared/0.cshtml",
                    }
                    .Concat(viewLocations);
            }
            return viewLocations;
        }
    }

net core需要注意的是,还要要注册服务:

//添加主题模板
services.Configure<RazorViewEngineOptions>(options =>
{
  options.ViewLocationExpanders.Add(new ThemeableViewLocationExpander());
});