Сообщений 1    Оценка 170 [+1/-0]         Оценить  
Система Orphus

Одна сборка – один Web-сайт

или повторное использование всего Web-сайта в .NET Framework 2.0

Автор: Егоров Никита (C...R...a...S...H)
LUXOFT Member of the IBS group

Источник: RSDN Magazine #1-2007
Опубликовано: 01.02.2007
Исправлено: 06.06.2007
Версия текста: 1.0
Актуальность
Подготовка
Компиляция Web-сайта в одну сборку
Избавляемся от маркеров и compiled файлов
Утилита автоматизации добавления HttpHandlers
Узкие места используемого метода
Повторное использование Web-страниц и пользовательских контролов
Повторное использование Web-страниц
Повторное использование пользовательских control-ов
Сохранения сборки в GAC
Использование сборки из GAC
Заключение
Ссылки по теме
Благодарности

Актуальность

Часто при работе с 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-сайта в одну сборку

Чтобы скомпилировать 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 файлов

Чтобы избавиться от 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 файлов. Есть возможность автоматизировать добавление этой информации.

Утилита автоматизации добавления HttpHandlers

Чтобы автоматизировать добавление информации в раздел 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.

Повторное использование Web-страниц и пользовательских контролов

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

Создадим новый веб-сайт. Назовём его 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-страниц

Для этого необходимо изменить файл 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-ов

Добавление пользовательских 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

Чтобы ощутить все преимущества повторного использования кода, можно поместить созданную при компиляции первого сайта сборку в 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».

Использование сборки из GAC

Для начала можно заставить первый 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 есть возможность разместить внутри сборки все некомпилируемые ресурсы, и использовать их как обычные файлы, но это уже тема другой статьи.

Ссылки по теме

Благодарности

Спасибо Красильникову Михаилу за помощь в написании статьи.


Эта статья опубликована в журнале RSDN Magazine #1-2007. Информацию о журнале можно найти здесь
    Сообщений 1    Оценка 170 [+1/-0]         Оценить