![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Часто при работе с ASP.NET появляется необходимость повторного использования кода, написанного ранее. Повторно использовать можно отдельные классы, компоненты и т.д. Но вот повторное использование самих ASP.NET-страниц и User Control’ов всегда вызывало проблемы из-за необходимости постоянного копирования ASPX- или ASCX-файлов помимо сборки.
В данной статье показан способ, используя который, можно собрать полностью весь сайт в одну сборку и после этого с легкостью повторно использовать в любом web-приложении. Более того, можно весь сайт поместить в GAC, при этом в виртуальном каталоге останутся только ресурсы и файлы конфигурации.
Чтобы все намеченные планы реализовались, необходимо скачать надстройку Visual Studio 2005 Web Deployment Projects с официального сайта Microsoft и установить ее.
Далее необходимо создать тестовый веб-сайт. Для этого в главном меню Visual Studio 2005 выберите пункт меню «File/New/Web Site». После этого в появившемся окне выберите месторасположение сайта и его название (Например: OneAssembleSite).
Чтобы проект был более или менее похож на боевой, необходимо добавить в него несколько ASPX-страниц, несколько ASCX control-ов, а также несколько папок, классов и рисунков. Понадобится также файл web.config, в котором будут храниться настройки созданного сайта. Пример получившейся структуры сайта:
Пример файла web.config:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" /> <authentication mode="Windows" /> </system.web> </configuration> |
Чтобы различать созданные страницы и пользовательские control-ы, разместим на них различные кнопки или другие control-ы.
Результатом всех этих действий будет веб-сайт с определенной структурой, и теперь можно приступать к его компиляции.
Чтобы скомпилировать Web-сайт в одну, сборку необходимо в окне Solution Exploler выбрать сайт, затем в меню Build выбрать пункт Add Web Deployment Project.
ПРИМЕЧАНИЕ Пункт Add Web Deployment Project появится, только если вы установили надстройку Visual Studio 2005 Web Deployment Projects |
После выбора на экране появится окно Add Web Deployment Project:
В этом окне предлагается выбрать название нового проекта и папку для его размещения.
Когда проект добавлен в Solution, необходимо открыть его свойства и сделать изменения в некоторых разделах проекта.
Изменения в разделе Compilation:
Необходимо убрать галку «Allow this precompiled site to be updatable», это делается для того, чтобы при компиляции сайта вся разметка из ASPX- и ASCX-файлов помещалась в сборку.
Изменения в разделе Output Assemblies:
Это самая главная закладка, которая позволяет настроить функции объединения сборок.
Merge all outputs to a single assembly – позволяет все создаваемые компилятором сборки объединить в один файл.
Merge each individual folder outputs to its own assembly – позволяет для каждой папки сайта создавать собственную сборку.
Merge all pages and control outputs to a single assembly – позволяет скомпилировать в одну сборку все Web-страницы и пользовательские control-ы.
Create a separate assembly for each page and control output – позволяет создать отдельную сборку для каждой страницы и пользовательского control-а.
После компиляции в каталоге, который был выбран при создании Web Deployment Project, будут находиться следующие файлы:
ПРИМЕЧАНИЕ Если открыть созданный файл и посмотреть что в нем находится, то вместо обычного ASP.NET-кода там будет написано: «This is a marker file generated by the precompilation tool, and should not be deleted!» |
После компиляции вся информация о том, какой класс используется для построения Web-страницы, содержится в файлах с расширением compiled.
Вот пример информации, которая содержится в таком файле:
<?xml version="1.0" encoding="utf-8"?> <preserve resultType="3" virtualPath="/OneAssembleSite/TestFolder/Page1.aspx" hash="8a8da6c5a" filehash="75e8ad651e730cb7" flags="110000" assembly="OneAssembleSite" type="ASP.testfolder_page1_aspx"> <filedeps> <filedep name="/OneAssembleSite/TestFolder/Page1.aspx" /> <filedep name="/OneAssembleSite/TestFolder/WebUserControl.ascx" /> <filedep name="/OneAssembleSite/TestFolder/WebUserControl.ascx.cs" /> </filedeps> </preserve> |
Web Deployment Project позволяет также создать подписанные сборки, которые можно будет помещать в GAC. В этом случае Web-сайт будет состоять из файлов настройки, маркеров ASPX, некомпилируемых ресурсов и файлов с расширением compiled.
Но существует возможность избавиться от compiled файлов и от маркеров ASPX и даже от не компилируемых ресурсов.
Чтобы избавиться от compiled-файлов и маркеров ASPX, можно применить подход, который используется для добавления HttpHandles к Web-сайту. Поскольку класс Page реализует интерфейс IHttpHandler, можно в файл web.config добавить запись, которая будет подключать соответствующий класс Web-страницы.
Чтобы добавить в скомпилированный проекта страницу Page1 из папки TestFolder как обработчик запросов, раздел <httpHandlers> файла web.config должен содержать следующую информацию:
<httpHandlers> <add path="TestFolder/Page1.aspx" verb="*" type="ASP.testfolder_page1_aspx,OneAssembleSite" /> </httpHandlers> |
Добавление такой информации в файл web.config – довольно трудная операция, особенно когда в проекте множество ASPX файлов. Есть возможность автоматизировать добавление этой информации.
Чтобы автоматизировать добавление информации в раздел HttpHandler файла web.config, достаточно написать утилиту, которая будет читать информацию из compiled-файлов, и на ее основании создавать соответствующие разделы в файле web.config. Исходный текст утилиты CompileFileParser:
using System; using System.IO; using System.Xml; using System.Collections.Generic; using System.Text; namespace CompileFileParser { class Program { static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("При запуске утилиты укажите каталог, " + "где находится скомпилированный проект"); return; } WorkFolder folder = new WorkFolder(args[0]); if (!folder.CheckFiles()) return; List<FileInfo> files = folder.GetCompiledAspxFiles(); HandlersStore handlers = new HandlersStore(); foreach (FileInfo file in files) handlers.AddHandler(new ComplieXMLParser(file.FullName)); WebConfigParser x = new WebConfigParser(folder.GetWebConfig().FullName); x.AddDataToHttpHandler(handlers.GetXML()); x.Save(); folder.RemoveAllUnusedFiles(); } } /// <summary> /// Класс для получения XML для раздела httpHandlers /// </summary> class HandlersStore { List<ComplieXMLParser> array = new List<ComplieXMLParser>(); public void AddHandler(ComplieXMLParser doc) { array.Add(doc); } public string GetXML() { StringBuilder result = new StringBuilder(); foreach (ComplieXMLParser doc in array) { result.AppendFormat( @"<add path=""{0}"" verb=""*"" type=""{1},{2}""/>", doc.VirtualPathLite, doc.Type, doc.Assembly); } return result.ToString(); } } /// <summary> /// Класс для работы с web.config /// </summary> class WebConfigParser { XmlDocument doc; string _fileName; public WebConfigParser(string fileName) { doc = new XmlDocument(); doc.Load(fileName); _fileName = fileName; } public void AddDataToHttpHandler(string str) { XmlNode httpHandlers = doc.SelectSingleNode("configuration/system.web/httpHandlers"); if (httpHandlers != null) httpHandlers.CreateNavigator().AppendChild(str); else doc.SelectSingleNode("configuration/system.web") .CreateNavigator().AppendChild( "<httpHandlers>" + str + "</httpHandlers>"); } public void Save() { doc.Save(_fileName); } } /// <summary> /// Класс для работы с compiled файлами /// </summary> class ComplieXMLParser { XmlDocument doc; public ComplieXMLParser(string fileName) { doc = new XmlDocument(); doc.Load(fileName); } public string VirtualPath { get { return doc.GetElementsByTagName("preserve")[0] .Attributes["virtualPath"].Value; } } /// <summary> /// VirtualPath без корневого каталога /// </summary> public string VirtualPathLite { get { string folder = doc.GetElementsByTagName("preserve")[0] .Attributes["virtualPath"].Value; return folder.Substring(folder.IndexOf("/", 1) + 1); } } public string Assembly { get { return doc.GetElementsByTagName("preserve")[0] .Attributes["assembly"].Value; } } public string Type { get { return doc.GetElementsByTagName("preserve")[0] .Attributes["type"].Value; } } } /// <summary> /// Класс для работы с каталогом, указанным в параметрах утилиты /// </summary> class WorkFolder { DirectoryInfo dir; public WorkFolder(string folder) { dir = new DirectoryInfo(folder); } public FileInfo GetWebConfig() { return dir.GetFiles("web.config")[0]; } public List<FileInfo> GetCompiledAspxFiles() { List<FileInfo> result = new List<FileInfo>(); FileInfo[] files = dir.GetDirectories("Bin")[0].GetFiles("*.compiled"); foreach (FileInfo file in files) { ComplieXMLParser doc = new ComplieXMLParser(file.FullName); if (doc.VirtualPath.Substring(doc.VirtualPath.Length - 5).ToLower() == ".aspx") result.Add(file); } return result; } public bool CheckFiles() { if (!dir.Exists) { Console.WriteLine("Не найден каталог с веб-сайтом"); return false; } if (dir.GetFiles("web.config").Length == 0) { Console.WriteLine("Не найден файл web.config"); return false; } if (dir.GetDirectories("Bin").Length == 0) { Console.WriteLine("Не найден каталог Bin"); return false; } return true; } public void RemoveAllUnusedFiles() { Console.WriteLine("Remove Compiled Files"); foreach ( FileInfo file in dir.GetDirectories("Bin")[0] .GetFiles("*.compiled")) { Console.WriteLine(file.Name + " - Remove"); file.Delete(); } Console.WriteLine("Remove ASPX Files"); foreach (FileInfo file in dir.GetFiles( "*.aspx", SearchOption.AllDirectories)) { Console.WriteLine(file.Name + " - Remove"); file.Delete(); } } } } |
Для запуска утилиты укажите в командной строке путь к каталогу, где лежит скомпилированный Web-сайт.
ПРИМЕЧАНИЕ Если настройки Web Deployment Project были такими же, как указанные в статье, то утилиту можно запускать примерно следующим образом: CompileFileParser.exe "C:\OneAssembleSite\OneAssembleSite_deploy\Debug" |
После завершения работы утилиты файл web.config будет содержать следующую информацию:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" /> <authentication mode="Windows" /> <httpHandlers> <add path = "Default.aspx" verb = "*" type = "ASP.default_aspx,OneAssembleSite" /> <add path = "Default2.aspx" verb = "*" type = "ASP.default2_aspx,OneAssembleSite" /> <add path = "Default3.aspx" verb = "*" type="ASP.default3_aspx,OneAssembleSite" /> <add path = "TestFolder/Page1.aspx" verb = "*" type = "ASP.testfolder_page1_aspx,OneAssembleSite" /> <add path = "TestFolder/Page2.aspx" verb="*" type="ASP.testfolder_page2_aspx,OneAssembleSite" /> </httpHandlers> </system.web> </configuration> |
Структура Web-сайта будет примерно следующей:
После выполнения всех этих действий необходимо создать виртуальный каталог в IIS и разместить в нем полученный Web-сайт.
Из-за использования технологии HttpHandlers доступ к файлам, которые находятся в корневой папке, можно получить из любой, даже несуществующей папки.
Например, если виртуальный каталог называется OneAssembleSite, и в его корневом каталоге был файл Default.aspx, то теперь к нему можно обратиться не только по URL http://localhost/OneAssembleSite/default.aspx, но и по адресу http://localhost/OneAssembleSite/TestFolder/default.aspx, и даже http://localhost/OneAssembleSite/blablabla/default.aspx.
Так же из-за отсутствия каких бы то ни было файлов в папке Web-сайта нет возможности использовать функцию IIS – документ по умолчанию. Так что если попробовать попасть в корневой каталог Web-сайта, вы получите ошибку 403.
После создания сайта, состоящего из единственной сборки, созданную сборку можно повторно использовать в других сайта.
Создадим новый веб-сайт. Назовём его ReusabilitySite и добавим к нему сборку, которая получилась в результате компиляции первого веб-сайта.
ПРИМЕЧАНИЕ Добавить сборку можно с помощью пункта Add Reference меню WebSite. |
Добавим к проекту файл web.config следующего содержания:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" /> <authentication mode="Windows" /> </system.web> </configuration> |
После всех манипуляций структура сайта должна получиться примерно следующей:
Чтобы добавить к новому веб-сайту страницы из сборки, в файле web.config необходимо указать соответствующие обработчики в разделе httpHandlers. Добавим к проекту страницу Page1.aspx из первого проекта.
Для этого необходимо изменить файл web.config, добавив в него раздел httpHandlers и соответствующий обработчик запросов. В случае проектов, описанных в данной статье, файл web.config должен содержать следующую информацию:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" /> <authentication mode="Windows" /> <httpHandlers> <add path = "Page1.aspx" verb = "*" type = "ASP.testfolder_page1_aspx,OneAssembleSite" /> </httpHandlers> </system.web> </configuration> |
Попробуем выполнить проект и перейти на страницу Page1.aspx. В браузере отобразится страница, созданная в первом проекте.
Добавление пользовательских control-ов - не столь тривиальная задача. Для тестирования можно использовать страницу Default.aspx, которая была автоматически добавлена при создании ReusabilitySite.
Первым делом необходимо зарегистрировать пользовательский control на странице. Для этого используется директива @Register, в которой необходимо указать сборку, пространство имен и тег, используемые для определения control-а на странице.
Для сайтов, созданных в данной статье, эта директива будет выглядеть следующим образом:
<%@ Register Assembly="OneAssembleSite" Namespace="ASP" TagPrefix="uc" %> |
Assembly – название сборки, которая содержит control-ы.
Namespace – пространство имен, в котором определены классы control-ов.
СОВЕТ Пространство имен ASP – это пространство имен, в которое по умолчанию помещаются все Web-страницы и пользовательские control-ы при компиляции веб-сайт. |
TagPrefix – префикс, который будет использоваться для определения control-ов.
После добавления директивы можно поместить на Web-страницу любой пользовательский control, имевшийся в первом веб-сайте, так как они все попали в пространство имен ASP.
Пример определения control-ов на странице:
<%@ Page Language = "C#" AutoEventWireup = "true" CodeFile = "Default.aspx.cs" Inherits = "_Default" %> <%@ Register Assembly="OneAssembleSite" Namespace="ASP" TagPrefix="uc" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <uc:webusercontrol_ascx ID="Webusercontrol_ascx1" runat="server" /> <uc:testfolder_webusercontrol2_ascx ID="Testfolder_webusercontrol2_ascx1" runat="server" /> </div> </form> </body> </html> |
Теперь можно запустить проект на выполнение и убедиться, что добавленный пользовательский control отображается на странице так же, как и обычные пользовательские control-ы.
Чтобы ощутить все преимущества повторного использования кода, можно поместить созданную при компиляции первого сайта сборку в GAC. Тогда ее смогут использовать как первый Web-сайт, так и ReusabilitySite.
Перед размещением сборки в GAC ей необходимо назначить строгое имя (strong name). Для этого необходимо воспользоваться утилитой sn.exe, которая поставляется вместе с Visual Studio 2005 SDK.
СОВЕТ По умолчанию эта утилита находится в каталоге C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin |
Данная утилита позволит создать ключи для подписывания сборки. Чтобы создать ключи, необходимо запустить утилиту и передать ей в командной строке параметр -k и название файла:
sn.exe -k c:\OneAssemble.snk |
После выполнения утилиты в корневом каталоге диска C: появится файл OneAssemble.snk, который содержит ключи для подписывания сборки.
Теперь в свойствах Web Deployment Project первого веб-сайта, необходимо выбрать раздел Signing, включить опцию Enable Strong naming и указать путь к файлу, созданному утилитой sn.exe:
После этого необходимо скомпилировать проект.
Теперь есть возможность положить подписанную сборку в GAC. Файл сборки находится в подкаталоге Bin каталога, в который компилировался Web-сайт (например, C:\OneAssembleSite\OneAssembleSite_deploy\Debug\bin\OneAssembleSite.dll).
Чтобы разместить сборку в GAC, воспользуемся утилитой gacutil.exe, которая также поставляется с VS2005. В командной строке этой утилиты нужно указать опцию /i и путь к сборке:
gacutil.exe /i "C:\OneAssembleSite\OneAssembleSite_deploy\Debug\bin\OneAssembleSite.dll"
|
После успешного выполнения утилиты можно, используя Explorer, открыть каталог assembly в каталоге Windows и найти созданную сборку:
Зайдя в свойства сборки, можно определить ее strong name:
Как можно убедиться, строгое имя сборки - «OneAssembleSite, Version=0.0.0.0, Culture=neutral, PublicKey=a9803a0858b9e7a2».
Для начала можно заставить первый Web-сайт использовать сборку OneAssembleSite. Для этого скомпилируем проект повторно. Воспользуемся утилитой CompileFileParser, чтобы избавиться от compiled-файлов и чтобы заполнить файл web.config информацией об используемых Web-страницах.
Далее необходимо внести изменения в файл web.config, чтобы прописать сборку из GAC.
Если до этого страницы подключались, используя синтаксис:
<add path="Default.aspx" verb="*" type="ASP.default_aspx,OneAssembleSite" /> |
то теперь необходимо вместо обычного имени сборки использовать строгое имя:
<add path="Default.aspx" verb="*" type="ASP.default_aspx,OneAssembleSite, Version=0.0.0.0, Culture=neutral, PublicKeyToken=a9803a0858b9e7a2" /> |
Файл web.config будет содержать следующую информацию:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" /> <authentication mode="Windows" /> <httpHandlers> <add path = "Default.aspx" verb = "*" type = "ASP.default_aspx,OneAssembleSite, Version = 0.0.0.0, Culture=neutral, PublicKeyToken = a9803a0858b9e7a2" /> <add path = "Default2.aspx" verb = "*" type = "ASP.default2_aspx,OneAssembleSite, Version = 0.0.0.0, Culture = neutral, PublicKeyToken = a9803a0858b9e7a2" /> <add path = "Default3.aspx" verb = "*" type = "ASP.default3_aspx,OneAssembleSite, Version = 0.0.0.0, Culture = neutral, PublicKeyToken = a9803a0858b9e7a2" /> <add path = "TestFolder/Page1.aspx" verb = "*" type = "ASP.testfolder_page1_aspx,OneAssembleSite, Version = 0.0.0.0, Culture = neutral, PublicKeyToken = a9803a0858b9e7a2" /> <add path = "TestFolder/Page2.aspx" verb = "*" type = "ASP.testfolder_page2_aspx,OneAssembleSite, Version = 0.0.0.0, Culture = neutral, PublicKeyToken = a9803a0858b9e7a2" /> </httpHandlers> </system.web> </configuration> |
Так как используется сборка из GAC, можно удалить копию сборки из каталога Bin, проверить настройки IIS и попробовать обратиться к какой-нибудь странице.
В проекте ReusabilitySite необходимо также поменять название сборки и удалить файл сборки из каталога Bin.
В данной статье были рассмотрены возможности повторного использования кода Web-страниц и пользовательских control-ов. Были рассмотрены вопросы размещения сборок в GAC, а также вопрос создания Web-сайтов, состоящих только из одной сборки и некомпилируемых ресурсов. Также хотелось бы отметить, что благодаря появлению в FW2.0 метода ClientScriptManager.GetWebResourceUrl есть возможность разместить внутри сборки все некомпилируемые ресурсы, и использовать их как обычные файлы, но это уже тема другой статьи.
Спасибо Красильникову Михаилу за помощь в написании статьи.
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |