در این مقاله قصد داریم قابلیت ایجاد قالب را در پروژههای ASP.NET MVC، فراهم کنیم تا ظاهر سایت یا به اصطلاح قالب سایت از طریق فایل کانفیگ تغییر کند. همانطور که میدانید معماری ASP.NET MVC براساس قراردادهای پیش فرض، قابل تعویض و تغییر طراحی شده است. یکی از این قراردادها، نحوهی پیدا کردن یک view برای کنترلر و اکشنهای آن است که به صورت زیر در ViewEngine تعریف شدهاست:
ViewEngine.ViewLocationFormats= "~/Views/{controller}/{action}.cshtml"
"~/Themes/{ThemeName}/Views/{controller}/{action}.cshtml"
public static void Themeable(this VirtualPathProviderViewEngine engine) { var ThemePath = "~/Themes"; var ThemeName = WebConfigurationManager.AppSettings["MvcTheme"]; if (string.IsNullOrEmpty(ThemeName)) return; var themeFolder = HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/", ThemePath, ThemeName)); if (!Directory.Exists(themeFolder)) throw new DirectoryNotFoundException(string.Format("Theme folder not exists: {0}/{1}}", ThemePath, ThemeName)); var newViewLocations = new[] { string.Format("{0}/{1}/Views/{2}/{3}.cshtml", ThemePath, ThemeName, "{1}", "{0}"), string.Format("{0}/{1}/Views/Shared/{2}.cshtml", ThemePath, ThemeName, "{0}"), // vb.net : // string.Format("{0}/{1}/Views/{2}/{3}.vbhtml", ThemePath, ThemeName, "{1}", "{0}"), // string.Format("{0}/{1}/Views/Shared/{2}.vbhtml", ThemePath, ThemeName, "{0}"), }; engine.ViewLocationFormats = newViewLocations; engine.PartialViewLocationFormats = newViewLocations; }
<appSettings> ... <add key="MvcTheme" value="Test1" /> </appSettings>
و در فایل Global.asax، قابلیت فراخوانی قالب را فعال کنید:
ViewEngines.Engines.OfType<RazorViewEngine>().Single().Themeable();
@{ // Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/themes/test1/Views/Shared/_Layout.cshtml"; }
ابتدا مدل زیر را تعریف کنید:
public class ThemeBundle { public BundleType BundleType { get; set; } public string VirtualPath { get; set; } public string[] Urls { get; set; } } public enum BundleType { Style, Script }
توسط کد زیر bundleها را از محل پوشهی قالب، فراخوانی میکنیم:
public static void RegisterThemeBundels(BundleCollection bundles) { var ThemePath = "~/Themes"; var ThemeName = WebConfigurationManager.AppSettings["MvcTheme"]; var ThemeBundleFileName = "ThemeBundle.json"; List<ThemeBundle> list; try { JavaScriptSerializer jss = new JavaScriptSerializer(); var jsonaddress = System.Web.HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/{2}", ThemePath, ThemeName, ThemeBundleFileName)); var json = System.IO.File.ReadAllText(jsonaddress); list = jss.Deserialize<List<ThemeBundle>>(json); } catch (Exception ex) { throw new Exception(string.Format("Cannot read {0}. see more error in inner exception.", ThemeBundleFileName), ex); } foreach (var themeBundle in list) { switch (themeBundle.BundleType) { case BundleType.Script: bundles.Add(new ScriptBundle(themeBundle.VirtualPath).Include( themeBundle.Urls)); break; case BundleType.Style: bundles.Add(new StyleBundle(themeBundle.VirtualPath).Include( themeBundle.Urls)); break; default: throw new ArgumentOutOfRangeException(nameof(themeBundle.BundleType)); } } }
public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { if (MvcTheme.ThemeName != null) { MvcTheme.RegisterThemeBundels(bundles); return; } bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js")); bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( "~/Scripts/jquery.validate*")); ... } }
[ { "BundleType": "Script", "VirtualPath": "~/themes/test1/js/jquery", "Urls": [ "~/themes/test1/js/jquery-1.10.2.js" ] }, { "BundleType": "Script", "VirtualPath": "~/themes/test1/js/jqueryval", "Urls": [ "~/themes/test1/js/jquery.validate.js", "~/themes/test1/js/jquery.validate.unobtrusive.js" ] }, { "BundleType": "Script", "VirtualPath": "~/themes/test1/js/modernizr", "Urls": [ "~/themes/test1/js/modernizr-2.6.2.js" ] }, { "BundleType": "Script", "VirtualPath": "~/themes/test1/js/bootstrap", "Urls": [ "~/themes/test1/js/bootstrap.js", "~/themes/test1/js/respond.js" ] }, { "BundleType": "Style", "VirtualPath": "~/themes/test1/css/css", "Urls": [ "~/themes/test1/css/bootstrap.css", "~/themes/test1/css/site.css" ] } ]
Themes ├───Test1 │ │ThemeBundle.json │ ├───Css │ ├───Fonts │ ├───Images │ ├───Js │ └───Views ├───Test2 │ │ThemeBundle.json │ ├───Css │ ├───Fonts │ ├───Images │ ├───Js │ └───Views
کد کامل و نهایی :
public static class MvcTheme { public static string ThemeName { get; } public static string ThemePath { get; set; } private const string AppSettingName = "MvcTheme"; private const string ThemeBundleFileName = "ThemeBundle.json"; static MvcTheme() { ThemePath = "~/Themes"; ThemeName = WebConfigurationManager.AppSettings[AppSettingName]; } public static void Themeable(this VirtualPathProviderViewEngine engine) { if (string.IsNullOrEmpty(ThemeName)) return; var themeFolder = HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/", ThemePath, ThemeName)); if (!Directory.Exists(themeFolder)) throw new DirectoryNotFoundException(string.Format("Theme folder not exists: {0}/{1}}", ThemePath, ThemeName)); var newViewLocations = new[] { string.Format("{0}/{1}/Views/{2}/{3}.cshtml", ThemePath, ThemeName, "{1}", "{0}"), string.Format("{0}/{1}/Views/Shared/{2}.cshtml", ThemePath, ThemeName, "{0}"), // vb.net : // string.Format("{0}/{1}/Views/{2}/{3}.vbhtml", ThemePath, ThemeName, "{1}", "{0}"), // string.Format("{0}/{1}/Views/Shared/{2}.vbhtml", ThemePath, ThemeName, "{0}"), }; engine.ViewLocationFormats = newViewLocations; engine.PartialViewLocationFormats = newViewLocations; } public static void RegisterThemeBundels(BundleCollection bundles) { if(ThemeName == null) return; var list = ReadThemeBundles(); foreach (var themeBundle in list) { switch (themeBundle.BundleType) { case BundleType.Script: bundles.Add(new ScriptBundle(themeBundle.VirtualPath).Include( themeBundle.Urls)); break; case BundleType.Style: bundles.Add(new StyleBundle(themeBundle.VirtualPath).Include( themeBundle.Urls)); break; default: throw new ArgumentOutOfRangeException(nameof(themeBundle.BundleType)); } } } public static List<ThemeBundle> ReadThemeBundles() { try { JavaScriptSerializer jss = new JavaScriptSerializer(); var jsonaddress = System.Web.HttpContext.Current.Server.MapPath(string.Format("{0}/{1}/{2}", ThemePath, ThemeName, ThemeBundleFileName)); var json = System.IO.File.ReadAllText(jsonaddress); var list = jss.Deserialize<List<ThemeBundle>>(json); return list; } catch (Exception ex) { throw new Exception(string.Format("Cannot read {0}. see more error in inner exception.", ThemeBundleFileName), ex); } } } public class ThemeBundle { public BundleType BundleType { get; set; } public string VirtualPath { get; set; } public string[] Urls { get; set; } } public enum BundleType { Style, Script }
- نکته یک: با حذف مقدار نام قالب در فایل کانفیگ، به راحتی به حالت پیش فرض asp.net mvc بر میگردید.
- نکته دو: نام bundle را حتما هم عمق با آدرس قالب تعریف کنید تا وقتی فایل css به پوشهی images یا فونت مجاور خود اشاره میکند، آدرس دهی معتبر باشد.
- نکته سه: اگر از RazorGenerator استفاده میکنید، در فایل RazorGeneratorMvcStart متد ()Themeable را بر روی engine آن صدا بزنید.