Готовим ASP.NET Core: как представить статический контент в виде ресурсов

    Мы продолжаем нашу колонку по теме 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
    Microsoft
    122.93
    Microsoft — мировой лидер в области ПО и ИТ-услуг
    Share post

    Similar posts

    Comments 0

    Only users with full accounts can post comments. Log in, please.