В качестве вступления
В Asp.net любая интернет-страница представляется в виде двух файлов: *.aspx и *.aspx.cs. В *.aspx-файлах содержится html-подобная разметка самой страницы, а в *.aspx.cs-файлах код на языке C#, который представлен в виде отдельного класса.
В разметку страницы можно добавлять серверные элементы управления, например тегом <asp:Button ID=«MyButton» runat=«server» />.
Причём с каждым таким объявлением будет связана переменная, то есть в нашем случае мы получим доступ к переменной типа Button и именем MyButton, хотя на первый взгляд эта переменная нигде не объявлена. (Хотя в первой версии Asp.net объявление переменных вставлялись в тот же файл.)
На самом деле это не так. Класс, описаный в *.aspx.cs-файле является частичным (он помечен модификатором partial), одна его часть описана в *.aspx.cs-файле, а вторая находится во временном файле, который генерируется на основании просмотра *.aspx-файла. Генерацией этого временного файла как раз и занимается провайдер компиляции.
Пишем провайдер компиляции
1. Определимся, что будет делать провайдер компиляции.
Чтобы особо не мудрить, пусть он выполняет преобразование xml-файла вида:
* This source code was highlighted with Source Code Highlighter.
- <classes>
- <class name=«С1»>
- <property type=«int» name=«x1» value=«17» />
- <property type=«string» name=«x2» value=«Hello, World!» />
- </class>
- <class name=«С2»>
- <property type=«int» name=«x1» value="-5" />
- <property type=«string» name=«x2» value=«Это строка!» />
- </class>
- </classes>
в статический класс, содержащий константы.
2. Создадим новый проект. В качестве типа проекта выберем «Class Library», назовём его «MyLib».
3. Все провайдеры компиляции являются производными от класса «System.Web.Compilation.BuildProvider», поэтом добавим ссылку на сборку «System.Web» и используемые пространства имён.
Конструктор класса ничего не выполняет, а вот метод «GenerateCode» нам потребуется переопределить. В итоге должно получиться следующее:
* This source code was highlighted with Source Code Highlighter.
- using System;
- using System.Web.Compilation;
- using System.Xml;
- using System.IO;
- using System.Web.Hosting;
- namespace MyLib
- {
- public class MyBP: BuildProvider
- {
- public MyBP() { }
- public override void GenerateCode(AssemblyBuilder assemblyBuilder)
- {
- //В переменную writer будем записывать генерируемый код
- using (TextWriter writer = assemblyBuilder.CreateCodeFile(this))
- {
- //Создаём xml-документ используя путь к анализируемому файлу
- XmlDocument xml = new XmlDocument();
- xml.Load(VirtualPathProvider.OpenFile(base.VirtualPath));
- //Получаем все классы
- XmlNodeList classes = xml.GetElementsByTagName(«class»);
- foreach (XmlNode _class in classes)
- {
- //Получаем название класса
- string cName = _class.Attributes[«name»].Value;
- if (cName == null || cName.Length == 0) continue;
- //Получаем поля класса
- string cProperties = "";
- //Получаем все поля
- XmlNodeList properties = _class.ChildNodes;
- foreach (XmlNode property in properties)
- {
- if (property.Name != «property») continue;
- //Получаем тип поля
- string pType = property.Attributes[«type»].Value;
- if (pType == null || pType.Length == 0) continue;
- //Получаем название поля
- string pName = property.Attributes[«name»].Value;
- if (pName == null || pName.Length == 0) continue;
- //Получаем значение поля
- string pValue = property.Attributes[«value»].Value;
- if (pValue == null || pValue.Length == 0) continue;
- //Если поле имеет строковой тип, берём его в кавычки
- if (pType == «string») pValue = string.Format("\"{0}\"", pValue);
- //Добавляем поле
- cProperties += string.Format(" public const {0} {1} = {2}; ", pType, pName, pValue);
- }
- //Добавляем класс
- writer.Write(" public static partial class " + cName + " { " + cProperties + " } ");
- }
- }
- }
- }
- }
Компилируем и получаем сборку.
4. Создаём новый сайт (Назовём его «MySite»). Первое, что нужно сделать, создать папки bin и App_Code. В папку bin скопируем только что созданную библиотеку. Теперь нужно подключить провайдер компиляции. Для этого идём в файл Web.config и в разделе «compilation» добавляем блок:
* This source code was highlighted with Source Code Highlighter.
- <buildProviders>
- <add type=«MyLib.MyBP, MyLib» extension=".cc" />
- </buildProviders>
Это определение говорит, что для файлов с расширением «cc» будет использован провайдер компиляции MyLib.MyBP, который будет взят из сборки MyLib".
5. Создаём файл «my.cc» в папке «App_Code» с xml-кодом, вроде того, что приведён выше. Через некоторое время (обычно секунд десять, когда файл обработается) в любом C#-коде можно будет использовать классы «C1» и «C2» и их константные поля. (Результаты можно увидеть на скриншотах)
6. Скриншоты:
Документ, обрабатываемый провайдером компиляции:
Появились классы «C1» и «C2»:
И их поля:
Ну и в качестве концовки
Безусловно данный пример не может претендовать на полноценный провайдер компиляции, а показывает только общую идею, в коде я опустил многие проверки, обработку исключений пожертвовал оптимальностью ради сокращения кода.
PS: Я не нашёл способа не помещать провайдер в отдельную сборку, а просто добавить класс в папку App_Code. В данном случае возникает ошибка о невозможности загрузить тип.
Надеюсь, что найдутся люди, которые знают решение данной проблеммы.
Литература: Дино Эспозито «Asp.net 2.0 Углублённое изучение».
Огромное спасибо выражаю пользователю zabr за советы и помощь, а так же всем, кто помог опубликовать этот топик.