11/24/2006 8:54:00 AM

Ковыряясь в ASP.NET обнаружил вот какую штуку: оказывается, нельзя поместить MasterPage в каталог темы (App_Themes\MyTheme, например).
Хотя, казалось бы, вполне разумное решение для реализации скинов - положить все мастер-шаблоны, стили и .skin-файлы в одной папке в одном месте. Тогда и темы для приложения будет делать и деплоить легко - скопировал папочку и готово.
Ан нет. ASP.NET за этим делом следит и не дает руки распускать. А хранить шаблоны в одном месте, а темы - в другом как-то не удобно... Пришлось ковыряться глубже.

Наковырял следующее: в ASP.NET есть специальный провайдер для вычисления виртуальных путей.
Так, получается, можно сделать такой вот "финт ушами": складывать все, что касается тем, в одну папку, но не в App_Themes, а в другую, скажем, в папку Skins. А все запросы к папке App_Themes просто перенаправлять в Skins с помощью провайдера.

Забегая вперед скажу, что совсем избавиться от каталога App_Themes не удастся, придется создать его и какую-нибудь пустую папку-тему-заглушку в нем (я назвал ее Virtual). Класть туда ничего не нужно, но нужно прописать эту тему как тему по умолчанию в конфигурационном файле:

<pages validateRequest="false" theme="Virtual">

Так, делаю провайдер, наследуюсь от VirtualPathProvider. В этом провайдере переопределяю методы GetDIrectory и GetFile. Соответственно, делаю своих потомков VirtualDirectory и VirtualFile. Идея в том, чтобы VirtualDirectory представлялся системе как каталог "App_Themes/Virtual", но возвращал список файлов из другого, нужного нам каталога.

 

public class ThemedVirtualPathProvider : VirtualPathProvider
{
    private const string _stubTheme = "/App_Themes/Virtual/";
    private const string _newThemesFolder = "/Skins/";
 
    public override VirtualDirectory GetDirectory(string virtualDir)
    {
        if (IsThemeFolder(virtualDir))
            return new ThemedVirtualDirectory(virtualDir);
        return base.GetDirectory(virtualDir);
    }
 
    public override VirtualFile GetFile(string virtualPath)
    {
        if (IsThemeFolder(virtualPath))
            return new ThemedVirtualFile(virtualPath);
        return base.GetFile(virtualPath);
    }
 
    internal static bool IsThemeFolder(string virtualPath)
    {
        return virtualPath.Contains(_stubTheme);
    }
 
    internal static string ReplaceThemePath(string virtualPath)
    {
        virtualPath = virtualPath.Replace(
            _stubTheme,
            String.Concat(_newThemesFolder, SiteSettings.Instance.Theme, Path.AltDirectorySeparatorChar)
        );
        return virtualPath;
    }
}

Реализация классов ThemedVirtualFile и ThemedVirtualDirectory достаточно тривиальна.
Вот пример ThemedVirtualDirectory:

 

 

private class ThemedVirtualDirectory : VirtualDirectory
{
    public ThemedVirtualDirectory(string virtualDir)
        : base(virtualDir)
    {
        GetData();
    }
 
    protected void GetData()
    {
        string virtualDirectoryName = ThemedVirtualPathProvider.ReplaceThemePath(this.VirtualPath);
        string directoryName = HostingEnvironment.MapPath(virtualDirectoryName);
        if (Directory.Exists(directoryName))
        {
            string tempVirtualEntry;
            string[] fileSystemEntries = Directory.GetDirectories(directoryName);
            ThemedVirtualFile vFile;
            ThemedVirtualDirectory vDirectory;
 
            foreach (string dir in fileSystemEntries)
            {
                tempVirtualEntry = VirtualPathUtility.Combine(this.VirtualPath, Path.GetFileName(dir));
                vDirectory = new ThemedVirtualDirectory(tempVirtualEntry);
                _children.Add(vDirectory);
                _directories.Add(vDirectory);
            }
 
            fileSystemEntries = Directory.GetFiles(directoryName);
            foreach (string file in fileSystemEntries)
            {
                tempVirtualEntry = VirtualPathUtility.Combine(virtualDirectoryName, Path.GetFileName(file));
                vFile = new ThemedVirtualFile(tempVirtualEntry);
                _children.Add(vFile);
                _files.Add(vFile);
            }
        }
    }
 
    private List<VirtualFileBase> _children = new List<VirtualFileBase>();
    public override IEnumerable Children
    {
        get { return _children; }
    }
 
    private List<VirtualFileBase> _directories = new List<VirtualFileBase>();
    public override IEnumerable Directories
    {
        get { return _directories; }
    }
 
    private List<VirtualFileBase> _files = new List<VirtualFileBase>();
    public override IEnumerable Files
    {
        get { return _files; }
    }
}

 

Здесь одна только особенность: при создании каталога ему в конструктор передается "изначальный" виртуальный путь, то есть, тот, который с "App_Themes". Потому, что ASP.NET за этим следит и если попытаться вернуть другое имя, то грязно выругается. А вот "внутри" себя он работает с нужным нам каталогом. Его подкаталоги и файлы и возвращает. А вот при создании файла ему в качестве виртуального пути передается его "реальный" путь. То есть, путь после замены "App_Themes\Virtual" на нужный нам каталог с темой. Для того, чтобы ASP.NET, вставляя, скажем, стиль в качестве ресурса страницы, ссылался все же на существующий файл.

Реализацию наследника виртуального файла я уже не привожу, должно быть все понятно. Единственное, что там нужно сделать - так это определить метод Open, который вернет поток с контентом файла..

Да, теперь как это вызывать.
Для начала провайдер нужно зарегистрировать, написав следующий вызов:

HostingEnvironment.RegisterVirtualPathProvider(new ThemedVirtualPathProvider());

Сделать это можно либо в публичном статическом методе AppInitialize() любого из классов в App_Code, либо в Global.asax в методе Application_Start, как удобнее. И в дальнейшем все вызовы будут проходить через этот провайдер.

А в базовом классе страницы в методе OnPreInit я написал следующее:

string themedMasterFile = HttpPath.Concat("~/Skins", SiteSettings.Instance.Theme, "Layout.master");
this.MasterPageFile = themedMasterFile;

То есть, тут я указываю шаблон для страницы в соответствии с темой (свойство SiteSettings.Instance.Theme возвращает имя выбранной темы), а сама тема будет подключена уже с помощью провайдера...

P.S. Поискав подобное решение в интернете я нашел лишь убогую реализацию в каком-то mojoFramework. Убогую потому, что они не предполагают, что контент в разных темах может быть разным. Потому, видимо, и не работают со списком файлов вообще. Таким образом их фреймворк позволяет в теме иметь только те, скажем, .skin-файлы, которые заданы в теме-пустышке. Только они будут "перенаправлены", об остальных система даже не узнает. Моя реализация лишена подобного недостатка ;)

Так что, надеюсь, это поможет.

Tags:

Comments are closed

Powered by BlogEngine.NET 2.5.0.6

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Twitter

Widget Twitter not found.

Root element is missing.X


Recent posts

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012

Sign in