Preparing ASP.NET Core: How to Present Static Content as Resources

    We continue our column on ASP.NET Core with another publication by Dmitry Sikorsky ( DmitrySikorsky ) - the head of the Ubranians company from Ukraine. In his article, Dmitry talks about his experience working with static content in the form of resources outside the main project assembly in ASP.NET Core. Previous articles from the column can always be read at #aspnetcolumn - Vladimir Yunev
    Sometimes it is necessary that static content (such as JS-, CSS-files or images) is located, for example, outside the main assembly of the web application in the form of resources. In this short article I will talk about two approaches to solving this problem.

    Preparing a project with resources


    First, we need a project with resources. For example, we’ll add one CSS file to the resources (which will make all the text on the page red) and one image. To do this, we need the files themselves, as well as about the following line in the project.json file of our project:

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

    That's all, now after the project is built, the entire contents of the Styles and Images folders will turn into resources (it is obvious that you can specify really specific files, and not entire folders, if necessary).
    aspnetcolumngithubAdvice! You can try everything yourself or by downloading the source code from GitHub https://github.com/DmitrySikorsky/AspNet5Resources .
    By the way, when adding files to the resources, the “tree” of their location becomes “flat”, and all the characters “\” in the path to the file turn into dots. That is, information about the original location is lost (given that the file names may contain periods). For example, the file \ Styles \ test.css added to the resources in the AspNet5Resources.Resources project will have the following name (case-sensitive):

    AspNet5Resources.Resources.Styles.test.css

    Fortunately, we do not need to write the assembly name every time (in this the case is AspNet5Resources.Resources) when retrieving content from resources. To do this, when creating an EmbeddedFileProvider, it is specified as the base namespace (see below).

    Resource usage


    We can use the content added to the project in the form of resources, at least in two ways: to implement everything independently or use a ready-made implementation. Both methods are very simple.

    To implement everything yourself, you need to add to the project that uses content from resources (it does not matter if the resources are located in this assembly or in another), a controller that will extract the requested resources by their names and write them to the output stream:

    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();
        }
    }
    

    To simplify the work with the output stream, here we use our own StreamResult class, inherited from 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);
        }
    }
    

    This is enough to be able to display a picture from resources in this way:


    Now we will use the out-of-box ready-made implementation.

    First of all, we need to implement the IFileProvider interface so that as a result, our class (let's call it CompositeFileProvider) can combine several different providers. The whole class can be viewed in the sources (link at the end of the article), but the key point is as follows:

    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);
    }
    

    That is, in fact, our class, when searching for a file, simply iterates over all available providers in search of the one in which this file is.

    So that our application can use both physically existing files and files from resources, we will create an instance of our CompositeFileProvider in this way:

    public IFileProvider GetFileProvider(string path)
    {
          IEnumerable 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))
            )
          );
    }
    

    Next, we need to "register" our provider when starting the application in the Startup class:

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

    After that, we can use content from resources in a more familiar way:


    conclusions


    Personally, I prefer the second option, because it is more reminiscent of the use of ordinary files (despite the fact that the files are extracted from resources). If, for example, you do not use periods in file names, you can even replace all points in the resource names, except the last, with a "\" symbol and thus "restore" the original location and have a more visual URL, but this is not so important.

    As always, I prepared a small test project so that you can immediately start everything and see with my own eyes: github.com/DmitrySikorsky/AspNet5Resources .

    For authors


    Friends, if you are interested in supporting the column with your own material, please write to me at vyunev@microsoft.com in order to discuss all the details. We are looking for authors who can interestingly talk about ASP.NET and other topics.

    about the author


    Sikorsky Dmitry Aleksandrovich
    Ubreinians Company (http://ubrainians.com/)
    Owner, Director
    DmitrySikorsky

    Also popular now: