Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
Использование картинок из .NET-сборок
Автор: Morgan Skinner, Microsoft Developer Services
Перевод: Шатохина Надежда(sna@uneta.org), Ukraine .Net Alliance (http://www.uneta.org)
Июнь 2003
Применяется к:
  • Microsoft® .NET Framework версии 1.0 и 1.1
  • Microsoft® Visual C#®
  • Microsoft® ASP.NET
  • Обзор:
    Большинству, если не всем, Web-сайтам для пользовательских интерфейсов необходимы картинки, а они обычно сохранены на диске. В этой статье показано, как можно использовать картинки из сборки, что может предотвратить быстрое увеличение количества файлов на диске, может упростить установку и настройку Web-сервера и повысить безопасность этих картинок.
    Содерджание:
  • Введение
  • Проблема в общих чертах
  • Использование картинки
  • Использование картинки с ASPX-страницы
  • Использование картинки из специального обработчика
  • Повышение надежности кода
  • Формирование корректного URL
  • Безопасность
  • Заключение
  • Об авторе
  • Введение

    Чаще всего от программистов, писавших элементы управления для Windows, а затем перешедших к Web, можно услышать жалобу: "Почему я не могу сохранять картинки в той же сборке, что и элемент управления? " Ответ прост: вы можете, просто вы должны знать, как это делать. В этой статье показано, как можно использовать картинки из сборки, и приведены два альтернативных метода для поиска картинок. Скачайте файл MFRImages.exe (http://download.microsoft.com/download/b/9/f/b9f63014-005e-438c-bb0c-d993e8f41d5f/MFRImages.exe), чтобы более полно изучить пример кода, который я обсуждаю далее.

    Проблема в общих чертах

    Картинка на Web-сайте всегда используется через URL, например, http://www.morganskinner.com/images/morganskinner.gif (http://www.morganskinner.com/images/morganskinner.gif). Он показывает клиенту, где искать картинку, которая загружается на Web-страничку отдельно от текста. Картинки обычно хранятся на Web-сервере в подкаталоге, например, с именем /images, и страницы просто предоставляют ссылки на эти картинки, чтобы они могли отображаться в браузере клиента.

    Там же находятся картинки, которые надо сохранить на диск перед тем, как они могут быть загружены пользователю, при создании специального элемента управления. Как создатель элемента управления вы бы предпочли предоставить одну сборку, которая содержала бы не только ваш элемент управления, но также и соответствующие картинки. Для элемента управления автономность является преимуществом, потому что в этом случае пользователь принимает меньшее участие в конфигурировании, когда он может забыть скопировать некоторые файлы картинок с рабочего сервера, что приведет к довольно неприятному для него результату.

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

    Основная проблема при использовании картинок из сборки — HTTP требует URL для образа, вы не можете просто вернуть пользователю с HTML последовательность байтов и ожидать, что образ будет отображен правильно. Необходим какой-то способ переадресации запроса на картинку в ресурс, находящийся в сборке, и в этой статье я представлю два метода для этого.

    Однако, перед тем как мы продолжим, необходимо обратить внимание вот на что. Обеспечить полностью автономный элемент управления, который будет корректно формировать картинки без всякого дополнительного конфигурирования Web-сервера, невозможно. Чтобы доставить картинки клиенту, вам понадобится создать на сервере, по крайней мере, еще один файл или внести некоторые изменения в метабазу IIS; однако, сделав эти простые действия один раз, вы сможете запросто использовать картинки из любых сборок.

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

    Поскольку основной проблемой является использование картинки, прямо с этого и начнем. Нам нужен метод, который при получении некоторой информации от вызывающего будет загружать картинку и возвращать ее в потоке ответа. Обратите внимание, что в этом примере используется только один тип картинок — .gif. Чтобы использовать другие типы, из расширения картинки должны быть получены тип содержимого и формат картинки.

    Следующая функция показывает, как картинка может быть загружена из данной сборки и возвращена клиенту в потоке HttpResponse. Далее мы будем использовать эту функцию как базовую и по ходу рассмотрения добавим многие необходимые функциональные возможности, такие как обработка исключительных ситуаций и кэширование картинки. Я определю эту функцию в классе ManifestImageLoader как статическую.

    public class ManifestImageLoader
    {
    public static void RenderImage ( string assembly , string image , HttpContext context )
    {
          Assembly   resourceAssem = Assembly.Load ( assembly ) ;
    
          // Получаем ресурс
          using ( Stream   imageStream = resourceAssem.GetManifestResourceStream ( image ) )
          {
             // И если все OK, выполняем контрольное считывание
             using ( System.Drawing.Image theImage =
                   System.Drawing.Image.FromStream ( imageStream ) )
                response.ContentType = "image/gif" ;
                theImage.Save ( context.Response.OutputStream , ImageFormat.Gif ) ;
          }
    }
    }
    

    Функция RenderImage принимает имя сборки, имя картинки и поток ответа. Получив достоверные имена сборки и картинки, она просто должна загрузить картинку и вернуть ее в выходной поток. Сначала загружается сборка, а затем мы используем функцию Assembly.GetManifestResourceStream, чтобы вернуть поток через именованный ресурс, которым в этом случае является картинка. Вам понадобится добавить оператор using для System.Reflection и System.IO, и также сослаться на сборку System.Drawing.

    Как только мы получили поток картинки, она может быть построена с помощью метода Image.FromStream ( ). Обратите внимание, что мы используем класс Image из пространства имен System.Drawing, а не из класса с аналогичным именем пространства имен System.Web.UI.WebControls, поскольку первый является оболочкой для доступа к функциям Win32 для работы с картинками, тогда как последний является классом для серверного элемента управления <img>.

    Если вы не знакомы с синтаксисом оператора C# using, он заключает код в блок try/finally и обеспечивает вызов Dispose для элемента, заключенного в круглые скобки.

    Теперь мы можем использовать картинку из сборки. Прежде всего, нам нужна возможность создавать URL для этой картинки. Первый способ сделать это — использовать .ASPX-страницу.

    Использование картинки с ASPX-страницы

    В первом способе использования картинки требуется .ASPX страница, которая, естественно, должна будет постоянно храниться где-то на сервере. У самой страницы нет содержимого, ее основная задача — извлечение параметров из URL и использование их для извлечения картинки.

    В качестве примера, скажем, у нас есть страница ImageFromASPX.aspx, хранящаяся в корневом каталоге Web сайта. Тогда, используя следующий синтаксис формирования URL в HTML, вы можете определить, чтобы все ваши картинки использовались с этой страницы. Здесь мы используем картинку баннера winxp.gif.

    <img src="/imagefromASPX.aspx?assem=ImageServer&amp;image=winxp.gif" />
    

    В URL мы задали путь к ASPX странице и два параметра: один является именем сборки (в данном случае ImageServer), другой — имя картинки. В этом есть некоторый риск для безопасности, поэтому позже в данной статье я приведу метод шифрования этих данных.

    В коде для ASPX страницы мы можем записать следующее:

    private void Page_Load ( object sender, System.EventArgs e )
    {
       // Retrieve the parameters
       string assembly = Request.QueryString["assem"] ;
       string image = Request.QueryString["image"] ;
    
       // And load the image
       ManifestImageLoader.RenderImage ( assembly , image ) ;
    }		
    

    Весь этот код проводит синтаксический разбор параметров запроса, а затем вызывает функцию RenderImage, которую мы написали ранее. Как видите, ничего сложного, однако, в этом есть один недостаток — все эти запросы картинок должны проходить через один и тот же URL, т.е., чтобы использовать картинки, каждый специальный элемент управления должен знать местоположение и имя файла imageserver.aspx. Устранение этого ограничения — предмет рассмотрения следующего раздела.

    Использование картинки из специального обработчика

    Если вы никогда ранее не сталкивались с обработчиками, я приведу краткий обзор. Обработчик — это объект, который реализовывает интерфейс IHttpHandler, а в его названии используется определенный глагол (POST, GET и т.д.) или ряд глаголов, когда запрос с данным расширением файла передается через ASP.NET конвейер.

    В общем, ASP.NET проверяет расширение файла запроса и передает запрос в обработчик, ассоциированный с этим запросом.

    Вооруженные этим знанием, мы можем создать обработчик, ассоциировать его с нашим собственным расширением файла (чтобы ASP.NET знал, что мы хотим вызвать наш обработчик, а не какой-то другой), и таким способом использовать картинки.

    Ниже приведен код простого обработчика, который использует функцию RenderImage, объявленную ранее.

    public class ManifestResourceHandler : IHttpHandler
    {
       /// <summary>
       /// Обрабатывает запрос на картинку
       /// </summary>
       /// <param name="context">Текущее содержимое HTTP</param>
       void IHttpHandler.ProcessRequest ( System.Web.HttpContext context )
       {
          // Получает имя сборки и имя ресурса из запроса
          string assembly = context.Request.QueryString["assem"] ;
          string image = context.Request .QueryString["image"] ;
    
          // Затем загружает картинку и возвращает вызывающему
          ManifestImageLoader.RenderImage ( assembly , image ) ;
       }
    
       /// <summary>
       /// Этот обработчик может быть использован повторно, не надо его утилизировать
       /// </summary>
       bool IHttpHandler.IsReusable
       {
          get { return true; }
       }
    }
    

    Код очень похож на приведенный выше для ASPX страницы — считывает параметры из переданного URL, затем передает их в функцию RenderImage.

    Теперь, чтобы использовать картинку с помощью обработчика, нам надо использовать другой URL. В данном случае, чтобы эти запросы направлялись к соответствующему обработчику, надо создать вымышленное расширение файла (т.е. такое, какого еще не существует в IIS). В этом примере я буду использовать "mfr" (manifest resource). Запрос теперь будет выглядеть примерно так.

    <img src=".mfr?assem=MS.Resources&amp;image=winxp.gif" />
    

    Обратите внимание, что я не задал путь к ресурсу, только расширение файла .mfr.

    Основное преимущество использования обработчика — он вызывается для всех запросов, независимо от пути к ним.

    Чтобы обработчик заработал, нужно сделать еще два шага. Во-первых, в вашем web.config надо задать новый обработчик:

    <configuration>
      <system.web>
        ...
        <httpHandlers>
          <add verb="GET" path="*.mfr" type="ImageServer.ManifestResourceHandler, ImageServer" />
        </httpHandlers>
      </system.web>
    </configuration>
    

    Тип в приведенном выше config-файле определяет тип и сборку, в которых реализован обработчик. Обратите внимание, что атрибут verb чувствителен к регистру, поэтому ему должно быть присвоено значение GET, а не какой-то другой стиль капитализации. Сама сборка должна быть помещена или в директорию bin вашего Web-сайта, или может быть установлена в Глобальном кэше сборок (Global Assembly Cache (GAC)).

    Далее надо отредактировать конфигурацию вашего Web-сервера в IIS Admin. Щелкните Properties для Web-сайта, который вы хотите изменить, выберите ярлычок Home Directory и щелкните Configuration. При этом должно открыться такое же, как приведено ниже, окно.

    Рисунок 1. Настройка IIS

    Щелкните кнопку Add, чтобы создать запись для типа файла .mfr. Каждое расширение сопоставляется с ISAPI-фильтром, который обрабатывает запрос для ресурса. Для ASP.NET — это фильтр aspnet_isapi.dll. Эта библиотека на диске находится под установленной вами версией Framework:

    Версия Путь
    1.0 C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\aspnet_isapi.dll
    1.1 C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_isapi.dll

    Версии .Net Framework

    Остальные настройки показаны на следующем рисунке. Обратите внимание, что вы должны очистить флажок Verify that file exists, в противном случае обработчик никогда не будет вызван (поскольку файл с расширением .mfr физически существовать на диске не будет).

    Рисунок 2. Настройка свойств для вашего расширения

    Теперь ваш обработчик должен работать. Наберите в браузере URL, который соответствует ресурсу в вашей сборке:

    Рисунок 3. Использование образа из сборки

    Если вместо запрашиваемой вами картинки, вы получаете исключение, такое как «'null' — недопустимое значение для 'stream'», значит, вы столкнулись с одной из маленьких проблем, которые мы не обработали в коде, возникающих, когда в картинке есть ошибка. Мы исправим это и другие маленькие упущения в следующем разделе.

    Повышение надежности кода

    Первая проблема, которую мы рассмотрим, — это применение заглавных букв в запросах. HTTP все приведенные ниже URL считает идентичными, поскольку URL не чувствительны к регистру:

    <img src=".mfr?assem=ImageServer&amp;image=winxp.gif" />
    <img src=".mfr?assem=ImageServer&amp;image=WINxp.gif" />
    <img src=".mfr?assem=ImageServer&amp;image=WiNxP.gif" />
    

    В настоящее время в нашем коде есть проблема, потому что он не сохраняет нечувствительную к регистру природу исходного HTTP-запроса, поэтому нам надо дописать функцию LoadAndReturnImage.

    Нечувствительность к регистру

    Нам необходимо загружать картинку из сборки, не заботясь о регистре, однако, Assembly.GetManifestResourceStream чувствительна к регистру, поэтому должен быть другой способ сделать это. Функция Assembly.GetManifestResourceNames() будет возвращать список всех ресурсов данной сборки, поэтому все, что нам надо, — это вызвать ее, сделать нечувствительное к регистру сравнение с именами этих ресурсов и использовать любое найденное в списке имя:

    Assembly   resourceAssem = Assembly.Load ( assembly ) ;
    
    // Ищем кэшированные имена
    string[] names = HttpContext.Current.Application [ assembly ] as string[] ;
    
    if ( null == names )
    {
       // Получаем имена всех ресурсов в сборке
       names = resourceAssem.GetManifestResourceNames() ;
       Array.Sort ( names , CaseInsensitiveComparer.Default ) ;
       HttpContext.Current.Application [ assembly ] = names ;
    }
    
    // Если в этой сборке несколько ресурсов, 
    // ищем то, которое нам надо
    if ( names.Length > 0 )
    {
       // Находим картинку в массиве имен
       int pos = Array.BinarySearch ( names , image , 
    CaseInsensitiveComparer.Default ) ;
    
       if ( pos > -1 )
          WriteImage ( resourceAssem , names[pos] , true ) ;
    }
    

    Здесь я загружаю сборку, которая содержит ресурсы, затем просматриваю кэш приложения, чтобы увидеть, был ли список ресурсов этой сборки загружен и кэширован. Если нет, вызывая Assembly.GetManifestResourceNames(), я читаю список ресурсов, сортирую его и сохраняю в массиве Application.

    Затем с помощью метода Array.BinarySearch()я могу провести двоичный поиск в списке имен. Это намного быстрее, чем последовательный перебор строк списка, есть лишь небольшие затраты на сохранение списка ресурсов.

    Так решается проблема чувствительности к регистру, а как насчет производительности? В настоящее время при каждом запросе картинки будет вызываться весь этот код, что на любом самом обычном Web-сайте может привести к значительным проблемам с производительностью. Мы рассмотрим это далее.

    Кэширование

    Так же как с обычными картинками и некоторыми ASPX-страницами, было бы неплохо иметь возможность кэшировать картинки, возвращаемые из сборки, поскольку, если они находятся в сборке, вряд ли они будут часто меняться, если вообще будут.

    Если бы мы писали простую ASPX-страницу, чтобы кэшировать ее, мы могли бы добавить директиву OutputCache; однако в нашем сценарии нам нужен метод программного добавления управляющих заголовков кэширования в ответный поток. К счастью, в ASP.NET это делается очень просто. В функцию, в которой мы записываем картинку в выходной поток, нам просто надо добавить следующие строки:

    response.Cache.SetExpires ( DateTime.Now.AddMinutes ( 60 ) ) ;
    response.Cache.SetCacheability ( HttpCacheability.Public ) ;
    response.Cache.VaryByParams["assem"] = true ;
    response.Cache.VaryByParams["image"] = true ;
    // Пишем картинку в поток ответа...
    

    Этим задается время хранения картинки в кэше в течение часа (это время, конечно же, можно увеличить, чтобы уменьшить загрузку сервера) и определяется, что картинка может быть кэширована где угодно (на клиенте, прокси, сервере и т.д.). Здесь также задаются параметры, изменяющие поведение кэширования.

    Теперь код почти готов, но нам надо решить, что делать в исключительных ситуациях.

    Программирование исключительных ситуаций

    В нашем коде может возникнуть ряд исключительных ситуаций, и на данный момент пользователь будет вознагражден поломанными ссылками на картинки в браузере или еще хуже страницей ошибки ASP.NET. Мы небезосновательно можем ожидать несколько вариантов развития событий:

    • Сборка, возможно, не существует.
    • Сборка, возможно, существует, но в ней нет никаких картинок.
    • В сборке, возможно, нет необходимой картинки.

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

    Возможно, вы, вполне справедливо, захотите заменить эту картинку собственной. Я включил эту картинку в сборку ImageServer, которая будет возвращена браузером в случае исключительных ситуаций. Это поведение можно настроить в секции AppConfig файла web.config.

    Чтобы переопределить поведение в случае возникновения исключительной ситуации, заданное по умолчанию, добавьте в web.config следующее:

    <appSettings>
       <add key="MFRShowBrokenLink" value="true" />
    </appSettings>
    

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

    Рисунок 4. Картинка, возвращаемый при отсутствии ресурса в сборке

    Если вы посмотрите журнал трассировки, для несуществующей картинки вы увидите примерно следующее.

    Рисунок 5. Пример записи в журнале трассировки для неправильного запроса картинки

    Весь обсуждаемый в этой статье код доступен из файла MFRImages.exe, ссылка для скачивания в начале этой страницы. Загрузка включает все дополнения, сделанные в этом разделе. В нее также входит несколько тестовых страниц, на которых вы можете увидеть результаты использования обработчика и ASPX-методов формирования картинок.

    Формирование корректного URL

    Следующее, что должно быть добавлено, — это метод, который может возвращать для картинки, находящейся в сборке, правильно сформированный URL, который затем может быть вызван создателем специального элемента управления (или вами!) для возвращения картинки.

    Если для раскрытия картинки вы выбрали обработчик, все что вам надо — функция, подобная следующей:

    public static string ConstructImageURL ( Assembly assembly, string image )
    {
       return string.Concat ( ".mfr?assem=" , 
                         HttpUtility.UrlEncode ( assembly.FullName.ToString ( ) ) , 
                         "&image=" ,
                         HttpUtility.UrlEncode ( image ) ) ;
    }
    

    Для этого кода я использую string.Concat(), потому что он работает, примерно, в 4 раза быстрее, чем string.Format(). Используем любую самую малую возможность! Затем вы можете использовать ее для задания свойства ImageURL для любых создаваемых вами в вашем специальном элементе управления картинок.

    Безопасность

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

    Чтобы избежать этой потенциальной проблемы, благоразумным будет возвращать зашифрованное значение. Мы можем использовать или некоторый хэш-код, сгенерированный из имени сборки и картинки, или зашифрованную форму имени сборки и имени картинки, которые дешифровываются при получении запроса.

    В первом случае (использование хэш-кода) требуется, чтобы на сервере была таблица соответствия, заполняемая для каждой используемой картинки. Это создает потенциальную проблему на Web кластерах, где один сервер может обслуживать исходный запрос картинки (и кэширует хэш-код), в то время как другой сервер формирует ответ с самой картинкой.

    Поэтому я выбрал второй способ, в котором URL, возвращаемый пользователю, содержит зашифрованные имена сборки и картинки. В этом случае не возникает проблемы Web кластеров; однако это означает, что с браузера на сервер надо передавать немного больше данных, поскольку URL картинки будет немного большим.

    В пример кода входит класс, который зашифровывает и дешифрует строки с помощью алгоритма Triple DES (Стандарт шифрования данных). В общих чертах, перед передачей клиенту имя сборки зашифровывается. При запросе картинки эти значения дешифруются и код становится таким же, как был до вызова.

    Я реализовал это в решение и оставил возможность конфигурирования. Это просто флаг в web.config, если задано значение "true", имена ресурсов будут шифроваться, поскольку они предоставляются клиенту:

       <appSettings>
          <add key="MFRSecure" value="true" />
       </appSettings>
    

    Затем в методе ProcessRequest обработчика я проверяю этот флаг:

    bool   secure = false ;
    string   shouldSecure = ConfigurationSettings.AppSettings["MFRSecure"] ;
    
    if ( null != shouldSecure )
       secure = Convert.ToBoolean ( shouldSecure ) ;
    
    string assembly = context.Request.QueryString["assem"] ;
    string image = context.Request.QueryString["image"] ;
    
    if ( secure )
    {
       assembly = Crypto.Decrypt ( assembly ) ;
       image = Crypto.Decrypt ( image ) ;
    }
    
    ManifestImageLoader.RenderImage ( assembly , image ) ;
    

    Аналогично, в методе ConstructImageURL, представленном ранее, я шифровал имена сборки и картинки перед их передачей клиенту:

    Есть большое количество участков, где код может быть улучшен и расширен. Вот несколько предложений по этому поводу.

    • Вместо жесткого кодирования имени картинки, используемого в случае, если ресурс не обнаружен, укажите URL картинки. Тогда при возникновении исключительной ситуации вы можете загрузить конкретную картинку с диска (или из другой сборки!) и вернуть ее в браузер.
    • Время нахождения в кэше для картинок также может быть определено как настраиваемый элемент.
    • Код может быть расширен, чтобы разрешить использование из сборки картинок любого типа — в настоящее время MIME-тип жестко закодирован в образ/GIF.
    • Нет основания тому, почему код этого примера не мог бы использовать другие ресурсы, находящиеся в сборках — вы можете раскрывать TXT-файлы, WAV файлы и т.д.

    Заключение

    В этой статье представлены два метода для извлечения картинки из сборки в формате, подходящем для включения в Web-сайт. Первый метод — использование картинки из ASPX-страницы — прост и не требует изменений в конфигурации Web-сервера; однако, чтобы картинка была отображена, путь к ASPX-странице, которая использует картинку, должен быть задан верно.

    Другой метод — использование картинки из специального обработчика — устраняет это ограничение с путем, но требует изменений в метабазе IIS, чтобы разрешить использование расширения .mfr расширением aspnet_isapi.dll. Также требуется модификация web.config для данного приложения.

    Моей личной рекомендацией будет использовать метод обработчика, а не ASPX-метод, потому что после настройки Web-сервера он более прост в применении (учитывая тот факт, что путь не нужен).

    Об авторе

    Морган (Morgan) — консультант по разработке приложений, работающий для Microsoft в UK, специализирующийся на Visual C#, элементах управления, WinForms и ASP.NET. Он работает с .NET со времен версии PDC 2000 года, и она ему так понравилась, что он устроился работать в компанию. Его домашняя страничка — http://www.morganskinner.com/ (http://www.morganskinner.com/), где вы найдете ссылки на другие его статьи. В свободное время (которого не так много) он борется с сорняками на своем участке и наслаждается Корнуэльским пирогом с мясом.

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

    Материал, изложенный в данной статье, многократно проверен. Но, поскольку вероятность технических ошибок все равно существует, сообщество не может гарантировать абсолютную точность и правильность приводимых сведений. В связи с этим сообщество не несет ответственности за возможные ошибки, связанные с использованием статьи.
     
         

       
       
         
      VBNet рекомендует