16 сентября 2008

Использование провайдеров компиляции в Asp.net

.NET

В качестве вступления



В 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-файла вида:

  1. <classes>
  2. <class name=«С1»>
  3. <property type=«int» name=«x1» value=«17» />
  4. <property type=«string» name=«x2» value=«Hello, World!» />
  5. </class>
  6. <class name=«С2»>
  7. <property type=«int» name=«x1» value="-5" />
  8. <property type=«string» name=«x2» value=«Это строка!» />
  9. </class>
  10. </classes>
* This source code was highlighted with Source Code Highlighter.


в статический класс, содержащий константы.

2. Создадим новый проект. В качестве типа проекта выберем «Class Library», назовём его «MyLib».

3. Все провайдеры компиляции являются производными от класса «System.Web.Compilation.BuildProvider», поэтом добавим ссылку на сборку «System.Web» и используемые пространства имён.

Конструктор класса ничего не выполняет, а вот метод «GenerateCode» нам потребуется переопределить. В итоге должно получиться следующее:

  1. using System;
  2. using System.Web.Compilation;
  3. using System.Xml;
  4. using System.IO;
  5. using System.Web.Hosting;
  6. namespace MyLib
  7. {
  8. public class MyBP: BuildProvider
  9. {
  10. public MyBP() { }
  11. public override void GenerateCode(AssemblyBuilder assemblyBuilder)
  12. {
  13. //В переменную writer будем записывать генерируемый код
  14. using (TextWriter writer = assemblyBuilder.CreateCodeFile(this))
  15. {
  16. //Создаём xml-документ используя путь к анализируемому файлу
  17. XmlDocument xml = new XmlDocument();
  18. xml.Load(VirtualPathProvider.OpenFile(base.VirtualPath));
  19. //Получаем все классы
  20. XmlNodeList classes = xml.GetElementsByTagName(«class»);
  21. foreach (XmlNode _class in classes)
  22. {
  23. //Получаем название класса
  24. string cName = _class.Attributes[«name»].Value;
  25. if (cName == null || cName.Length == 0) continue;
  26. //Получаем поля класса
  27. string cProperties = "";
  28. //Получаем все поля
  29. XmlNodeList properties = _class.ChildNodes;
  30. foreach (XmlNode property in properties)
  31. {
  32. if (property.Name != «property») continue;
  33. //Получаем тип поля
  34. string pType = property.Attributes[«type»].Value;
  35. if (pType == null || pType.Length == 0) continue;
  36. //Получаем название поля
  37. string pName = property.Attributes[«name»].Value;
  38. if (pName == null || pName.Length == 0) continue;
  39. //Получаем значение поля
  40. string pValue = property.Attributes[«value»].Value;
  41. if (pValue == null || pValue.Length == 0) continue;
  42. //Если поле имеет строковой тип, берём его в кавычки
  43. if (pType == «string») pValue = string.Format("\"{0}\"", pValue);
  44. //Добавляем поле
  45. cProperties += string.Format(" public const {0} {1} = {2}; ", pType, pName, pValue);
  46. }
  47. //Добавляем класс
  48. writer.Write(" public static partial class " + cName + " { " + cProperties + " } ");
  49. }
  50. }
  51. }
  52. }
  53. }
* This source code was highlighted with Source Code Highlighter.


Компилируем и получаем сборку.

4. Создаём новый сайт (Назовём его «MySite»). Первое, что нужно сделать, создать папки bin и App_Code. В папку bin скопируем только что созданную библиотеку. Теперь нужно подключить провайдер компиляции. Для этого идём в файл Web.config и в разделе «compilation» добавляем блок:

  1. <buildProviders>
  2. <add type=«MyLib.MyBP, MyLib» extension=".cc" />
  3. </buildProviders>
* This source code was highlighted with Source Code Highlighter.


Это определение говорит, что для файлов с расширением «cc» будет использован провайдер компиляции MyLib.MyBP, который будет взят из сборки MyLib".

5. Создаём файл «my.cc» в папке «App_Code» с xml-кодом, вроде того, что приведён выше. Через некоторое время (обычно секунд десять, когда файл обработается) в любом C#-коде можно будет использовать классы «C1» и «C2» и их константные поля. (Результаты можно увидеть на скриншотах)

6. Скриншоты:

Документ, обрабатываемый провайдером компиляции:


Появились классы «C1» и «C2»:


И их поля:


Ну и в качестве концовки



Безусловно данный пример не может претендовать на полноценный провайдер компиляции, а показывает только общую идею, в коде я опустил многие проверки, обработку исключений пожертвовал оптимальностью ради сокращения кода.

PS: Я не нашёл способа не помещать провайдер в отдельную сборку, а просто добавить класс в папку App_Code. В данном случае возникает ошибка о невозможности загрузить тип.

Надеюсь, что найдутся люди, которые знают решение данной проблеммы.
Литература: Дино Эспозито «Asp.net 2.0 Углублённое изучение».

Огромное спасибо выражаю пользователю zabr за советы и помощь, а так же всем, кто помог опубликовать этот топик.
Теги:asp.netпровайдер компиляцииcsharp.net
Хабы: .NET
+42
1,6k 23
Комментарии 39
Реклама
Лучшие публикации за сутки

Рекомендуем