Мы продолжаем нашу колонку по теме ASP.NET Core очередной публикацией от Дмитрия Сикорского ( DmitrySikorsky) — руководителя компании «Юбрейнианс» из Украины. В своей статье Дмитрий рассказывает об опыте работы со статическим контентом в виде ресурсов вне основной сборки проекта в ASP.NET Core. Предыдущие статьи из колонки всегда можно прочитать по ссылке #aspnetcolumn — Владимир Юнев
Иногда необходимо, чтобы статический контент (вроде JS-, CSS-файлов или картинок) располагался, например, вне основной сборки веб-приложения в виде ресурсов. В этой небольшой статье я расскажу о двух подходах к решению этой задачи.

Подготовка проекта с ресурсами


Во-первых, нам необходим проект с ресурсами. Для примера, добавим в ресурсы один CSS-файл (который будет делать весь текст на странице красным) и одну картинку. Для этого нам понадобятся сами файлы, а также, примерно следующая строка в файле project.json нашего проекта:

"resource": [ "Styles/**", "Images/**" ]

Вот и все, теперь после сборки проекта все содержимое папок Styles и Images превратится в ресурсы (очевидно, что можно указать действительно конкретные файлы, а не целые папки, если в этом есть необходимость).
aspnetcolumngithubСовет! Вы можете попробовать все самостоятельно или загрузив исходный код из GitHub https://github.com/DmitrySikorsky/AspNet5Resources.
Кстати, при добавлении файлов в ресурсы «древовидность» их расположения становится «плоской», и все символы «\» в пути к файлу превращаются в точки. Т. е. информация об исходном расположении утрачивается (учитывая, что имена файлов могут содержать точки). Например, добавленный в ресурсы файл \Styles\test.css в проекте AspNet5Resources.Resources будет иметь следующее имя (регистр имеет значение):

AspNet5Resources.Resources.Styles.test.css

К счастью, нам не понадобится каждый раз писать имя сборки (в данном случае это AspNet5Resources.Resources) при получении контента из ресурсов. Для этого при создании EmbeddedFileProvider оно указывается в качестве базового пространства имен (об этом ниже).

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


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

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

public class ResourceController : Controller
  {
    public ActionResult Index(string name)
    {
      Assembly assembly = Assembly.Load(new AssemblyName("AspNet5Resources.Resources"));
      string fullName = assembly.GetName().Name + "." + name;

      if (assembly.GetManifestResourceNames().Contains(fullName))
      {
        Stream stream = assembly.GetManifestResourceStream(fullName);

        return this.Stream(stream);
      }

      return this.HttpNotFound();
    }
}

Для упрощения работы с выходным потоком тут используется наш собственный класс StreamResult, унаследованный от ActionResult:

public class StreamResult : ActionResult
{
    private Stream stream;

    public StreamResult(Stream stream)
    {
      this.stream = stream;
    }

    public async override Task ExecuteResultAsync(ActionContext actionContext)
    {
      HttpResponse httpResponse = actionContext.HttpContext.Response;

      await this.stream.CopyToAsync(httpResponse.Body);
    }
}

Этого достаточно, чтобы иметь возможность отобразить картинку из ресурсов таким вот образом:

<img src="/resource?name=Images.test.png" />

Теперь воспользуемся готовой реализацией «из коробки».

Первым делом нам необходимо реализовать интерфейс IFileProvider, чтобы в результате наш класс (назовем его CompositeFileProvider) умел объединять в себе несколько разных провайдеров. Класс целиком можно посмотреть в исходниках (ссылка в конце статьи), но ключевой момент следующий:

public IFileInfo GetFileInfo(string subpath)
{
      foreach (IFileProvider fileProvider in this.fileProviders)
      {
        IFileInfo fileInfo = fileProvider.GetFileInfo(subpath);

        if (fileInfo != null && fileInfo.Exists)
          return fileInfo;
      }

      return new NonexistentFileInfo(subpath);
}

Т. е. по сути наш класс при поиске файла просто перебирает все доступные провайдеры в поисках того, в котором этот файл есть.

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

public IFileProvider GetFileProvider(string path)
{
      IEnumerable<IFileProvider> fileProviders = new IFileProvider[] { new PhysicalFileProvider(path) };

      return new CompositeFileProvider(
        fileProviders.Concat(
          new Assembly[] { Assembly.Load(new AssemblyName("AspNet5Resources.Resources")) }.Select(a => new EmbeddedFileProvider(a, a.GetName().Name))
        )
      );
}

Далее, нам необходимо «зарегистрировать» наш провайдер при старте приложения в классе Startup:

public Startup(IApplicationEnvironment applicationEnvironment, IHostingEnvironment hostingEnvironment)
{
      this.applicationBasePath = applicationEnvironment.ApplicationBasePath;

      hostingEnvironment.WebRootFileProvider = this.GetFileProvider(this.applicationBasePath);
}

После этого мы можем использовать контент из ресурсов более привычным способом:

<link href="/Styles.test.css" rel="stylesheet" />

Выводы


Лично мне больше нравится второй вариант, т. к. он сильнее напоминает использование обычных файлов (несмотря на то, что файлы извлекаются из ресурсов). Если, например, не использовать точки в названиях файлов, то можно даже заменять в именах ресурсов все точки, кроме последней, символом «\» и таким образом «восстанавливать» исходное расположение и иметь более наглядный URL, но это не так уж важно.

Как и всегда, я подготовил небольшой тестовый проект, чтобы можно было сразу все запустить и увидеть своими глазами: github.com/DmitrySikorsky/AspNet5Resources.

Авторам


Друзья, если вам интересно поддержать колонку своим собственным материалом, то прошу написать мне на vyunev@microsoft.com для того чтобы обсудить все детали. Мы разыскиваем авторов, которые могут интересно рассказать про ASP.NET и другие темы.

Об авторе


Сикорский Дмитрий Александрович
Компания «Юбрейнианс» (http://ubrainians.com/)
Владелец, руководитель
DmitrySikorsky

Комментарии (0)