Aspnetcore: Embedded Content (how static assets work with multi-project scenarios)

Created on 4 Jan 2019  路  26Comments  路  Source: dotnet/aspnetcore

Scope

We want to support building and shipping class libraries that include static content. This can include support for discovering and injecting these static assets at runtime where appropriate.

We also want to make it possible to do local development with multiple projects containing static files. This means that in development, it would be possible to serve files from multiple projects wwwroot folders.

These are both features that Blazor had prior to mondo-ization.

Components Big Rock Done area-blazor feature

Most helpful comment

We've done some thinking on this, and here's an overview:

  • Libraries (whether consumed as projects or packages) can expose static files using some syntax in their .csproj.

    • We might do it on a per-file basis, via an itemgroup like <StaticFile Include="dist/**/*.js" />

    • ... but there are good reasons why we might prefer to do it on a per-directory basis, e.g., with a property like <StaticFilesRoot>dist</StaticFilesRoot>

  • With this alone, apps consuming that project/package (including transitively) will be able to reference all such files via URLs in some standard format, e.g., _content/{packagename}/{filepath}.

    • Example: <script src="_content/YourCompany.SuperMaps/js/main.js"></script>

    • Example: <link rel="_content/YourCompany.SuperMaps/styles.css">

    • Important: We are not planning to auto-add these tags at runtime like we did in the past for Blazor component libraries. You will have to put in the relevant <script>/<link>/etc tags manually. This is the only reasonable design, because in general you have to be able to control ordering, and to be able to select which of the static assets you want (consider if a package provided 15 different localized versions of a given .js file, or different CSS files for different themes). As such, library authors will need to document what tags users should add.

  • In development,

    • To make your server actually serve those static files, we need either some new middleware (e.g., app.UseDeveloperStaticFiles, or we need to extend UseStaticFiles to do this internally.

    • The point of this is so we can serve the static files directly from their original locations on disk in the source project, and don't have to copy them to some separate location on build. This is important so that you can freely edit those static files and refresh the browser to get changes immediately without having to run a .NET build.

  • In production,

    • On publish, we'll copy all the static files to locations within your content root (typically, wwwroot), so that UseStaticFiles will serve them without any extra code or config. The file paths we copy to will preserve the same URL formats, so you don't have to change any of your references to those files.

    • For Blazor apps, the fact that we already publish content root files to dist means this will automatically work with non-.NET servers without any extra steps

    • In case you don't want to include the static content from any given projects/packages in your publish output, there will be some syntax on the project/package reference item to say the static content should be ignored.

  • For developers who prefer to bundle static assets using something like Webpack, we can extend this system with either of the following.

    • We could have optional MSBuild targets that, on build, copy any static files exposed by package (not project) references into a well-known staging location in your app (e.g., under obj). Then you can have a Webpack entrypoint that references files in there, as well as in your other projects via relative filenames.
    • Or, more advanced, we could build a Webpack file resolver that knows how to locate static files exposed by NuGet package references. Then you Webpack entrypoint could reference things like import '@YourCompany.SuperMaps/js/main.js';, and you can reference static content from NuGet packages just as easily as from NPM packages.

    Either of these designs preserve's Webpack's ability to do immediate incremental rebuilds when you edit files in referenced projects. However we're not finalising this design yet and won't implement it in the first phase.


That's all there is to it. It's a minimal set of new concepts, and should work equally for Razor Components, Blazor, or any other ASP.NET Core web application that wants to consume static content from packages/projects.

All 26 comments

Can you share some details around what is the current idea to handle the scenario? Will the _content approach up to version 0.8 remain? Is there any chance this goes to preview 3 instead of 4?

We've done some thinking on this, and here's an overview:

  • Libraries (whether consumed as projects or packages) can expose static files using some syntax in their .csproj.

    • We might do it on a per-file basis, via an itemgroup like <StaticFile Include="dist/**/*.js" />

    • ... but there are good reasons why we might prefer to do it on a per-directory basis, e.g., with a property like <StaticFilesRoot>dist</StaticFilesRoot>

  • With this alone, apps consuming that project/package (including transitively) will be able to reference all such files via URLs in some standard format, e.g., _content/{packagename}/{filepath}.

    • Example: <script src="_content/YourCompany.SuperMaps/js/main.js"></script>

    • Example: <link rel="_content/YourCompany.SuperMaps/styles.css">

    • Important: We are not planning to auto-add these tags at runtime like we did in the past for Blazor component libraries. You will have to put in the relevant <script>/<link>/etc tags manually. This is the only reasonable design, because in general you have to be able to control ordering, and to be able to select which of the static assets you want (consider if a package provided 15 different localized versions of a given .js file, or different CSS files for different themes). As such, library authors will need to document what tags users should add.

  • In development,

    • To make your server actually serve those static files, we need either some new middleware (e.g., app.UseDeveloperStaticFiles, or we need to extend UseStaticFiles to do this internally.

    • The point of this is so we can serve the static files directly from their original locations on disk in the source project, and don't have to copy them to some separate location on build. This is important so that you can freely edit those static files and refresh the browser to get changes immediately without having to run a .NET build.

  • In production,

    • On publish, we'll copy all the static files to locations within your content root (typically, wwwroot), so that UseStaticFiles will serve them without any extra code or config. The file paths we copy to will preserve the same URL formats, so you don't have to change any of your references to those files.

    • For Blazor apps, the fact that we already publish content root files to dist means this will automatically work with non-.NET servers without any extra steps

    • In case you don't want to include the static content from any given projects/packages in your publish output, there will be some syntax on the project/package reference item to say the static content should be ignored.

  • For developers who prefer to bundle static assets using something like Webpack, we can extend this system with either of the following.

    • We could have optional MSBuild targets that, on build, copy any static files exposed by package (not project) references into a well-known staging location in your app (e.g., under obj). Then you can have a Webpack entrypoint that references files in there, as well as in your other projects via relative filenames.
    • Or, more advanced, we could build a Webpack file resolver that knows how to locate static files exposed by NuGet package references. Then you Webpack entrypoint could reference things like import '@YourCompany.SuperMaps/js/main.js';, and you can reference static content from NuGet packages just as easily as from NPM packages.

    Either of these designs preserve's Webpack's ability to do immediate incremental rebuilds when you edit files in referenced projects. However we're not finalising this design yet and won't implement it in the first phase.


That's all there is to it. It's a minimal set of new concepts, and should work equally for Razor Components, Blazor, or any other ASP.NET Core web application that wants to consume static content from packages/projects.

Feel free to edit the top post.

Mechanically - how does this work for a nuget package. So if I'm building a library, what happens?

The point of this is so we can serve the static files directly from their original locations on disk in the source project, and don't have to copy them to some separate location on build. This is important so that you can freely edit those static files and refresh the browser to get changes immediately without having to run a .NET build.

I also want to see more detail about how this will work

Thought: Take into account what the Identity UI does today for the files in https://github.com/aspnet/AspNetCore/tree/master/src/Identity/UI/src/wwwroot and that we should update Identity UI to use this new pattern once it's available. cc @javiercn @HaoK

@Eilon Steve an I already chatted about this. We have a new plan in mind and I'm hoping we also move Identity UI to this model too.

TL;DR They will end up as published content during publish and they will be served from their original locations during development.

Assigning myself as I'm going to be doing the work here. (AFAIK)

In MatBlazor, I just created component MatBlazorInstall, where if now I am in RazorComponents (ServerSide Blazor) just make inline injection if js and css.
So, all you need - put somewhere in the beginning of your application <MatBlazorInstall />.
Of course this is temporary resolution and not good, while we will have better solution.
I just add <style>content of css</style> and <script>content of js</script>

BUT
In Blazor (client side) we have problem - doubled content loading. Bacause - content files loaded from server throw http, because build porcess copied them in another folder and then downloaded inside dll.
That's really not good.

@SamProf - if you think you have found a bug, please log a separate issue and provide a clear description.

Hey folks! I liked the design for this feature however, I have to drop my 2c:

Important: We are not planning to auto-add these tags at runtime like we did in the past for Blazor component libraries.

I understand that in some cases ordering of loading static resources matters. But we should have a way allow auto-add.

As one of the maintainers of BlazorExtensions, it would be a pretty bad dev experience if I have to ask everyone to not just install the nuget packages we provide, but also to read the docs/code to know which .css/.jss each component exports (some cases there are many) and ask them to import it manually...

Perhaps we could create a taghelper/element or something that the developer woud add along with other <style>/<script> tags to the index.cshtml like this:

<StaticResource Include="Blazor.Extensions.SignalR" />

So all the static resources exposed by the package would be added in an order defined by the component developer and respect the rest of the entries' order.

What do you think?

BUT
In Blazor (client side) we have problem - doubled content loading. Bacause - content files loaded from server throw http, because build porcess copied them in another folder and then downloaded inside dll.
That's really not good.
@SamProf If you look at my BlazorEmbedLibrary you'll see I do the same, but it protects against double-injection

I also agree with @galvesribeiro but would go one step further and ask could we have the old style Blazor content embedded automatically, but with an option to opt out of content from any libraries?
that way component producers will not be swamped with support requests.

Perhaps we could create a taghelper/element

You can certainly implement a tag helper that renders the tags needed for your library. You can bundle that tag helper into your package if you want.

We're not planning to make any framework-supplied one for the initial version. It's entirely possible that we might in the future, but that would be a new area for us to design. It's important that what we do put in the box is well designed and robust.

know which .css/.jss each component exports (some cases there are many)

I think that's your problem. Don't have many .css/.js files, since the runtime perf will be bad even when people do correctly import them all. Either:

  • If you're splitting things up into lots of files so users can pick which subset they want, then obviously it doesn't help to have the framework auto-reference them all. The developer still needs to pick which subset they want.
  • Or, if people should reference them all, then use something like Webpack to bundle them into a single file (or at least one .js file and one .css file). That will be easier to consume and will perform much better.

    • If you really don't want to bundle, then at least have some mechanism whereby some top-level script auto-adds references to all the others instead of requiring the consumer to add lots of tags.

My way is https://github.com/SamProf/EmbeddedBlazorContent

1) In server Startup.cs configure server to host embedded files

app.UseEmbeddedBlazorContent(typeof(MatBlazor.BaseMatComponent).Assembly);
app.UseEmbeddedBlazorContent(typeof(MatBlazor.Demo.Pages.Index).Assembly);

2) For @Html Razor helper in _Host.cshtml i write helper to add inside <head> section section with links to resources.

@Html.EmbeddedBlazorContent()

This will produce code like this:

<script src="/EmbeddedBlazorFile/dist/matBlazor.js"></script>
<script src="/EmbeddedBlazorFile/blazorFiddleLoader.js"></script>
<link href="/EmbeddedBlazorFile/site.css" rel="stylesheet" />

In action I use this method in my http://www.matblazor.com

@javiercn
Can you add some information/documentation about marking a file in a class library as a static asset and how it will be referenced from a sample app? For example, is the syntax mentioned in https://github.com/aspnet/AspNetCore/issues/6349#issuecomment-469676381 be supported (or any variety of it):

  1. <StaticFile Include="dist/**/*.js" /> or
  2. <StaticFilesRoot>dist</StaticFilesRoot>

We will be handling the documentation piece next week, but essentially you just need to put the files from the library inside a wwwroot folder and they will get exposed under the _content/<<library>>/ path.
For example, if your library name is MyLibrary and you have in your library wwwroot\sample.js it will get exposed on consuming apps at _content/mylibrary/sample.js (the contents of your library wwwroot folder will end up when you publish your app in the wwwroot folder of the published app, under _content/mylibrary

@javiercn Is it expected for a class library, whose package id contains dots to have its static assets path generated without the dots? If there is a library called MyCustomLib and a js file called script.js in the wwwroot folder and its PackagedId is specified as:

// in MyCustomLib.csproj
  ...
  <PropertyGroup>
    <PackageId>my.custom.lib</PackageId>
  </PropertyGroup>

then the path to the file is _content/mycustomlib/script.js instead of _content/my.custom.lib/script.js. Is there a way to actually generate it as _content/my.custom.lib/script.js?

@Stamo-Gochev Yes. We do some work to sanitize the url by removing dots and lower-casing everything (to minimize differences across OSs).

Is there a reason for you to want dots in the url? They can be problematic when combined with other things like redirect rules, file system providers, etc.

Did you take that into account?

The main concern is that the name of the class library consists of several words and combining them without the dots results in a long word that is harder to read. I cannot replaces the dots with dashes as it will be a breaking change. Can there be an option that can control if the dots will be stripped?

@Stamo-Gochev We do have some guidance coming up on how to properly author razor class libraries with static web assets. I don't remember if we have a switch for it, but I'll make sure it gets included in the docs if there is one.

The main concern is that the name of the class library consists of several words and combining them without the dots results in a long word that is harder to read.

If we were to replace the dots with something else (like _) would that ease your concerns?

I wouldn't be too concerned about this for a few reasons.

  • We expect library authors to provide code snippets in the instructions detailing how to use their library, so that users can simply copy/paste your script/link tags into their page's body/header tags.
  • You can write a partial view/tag-helper to inject the scripts on the page.
  • We might have tooling in the future that will give you completions, which greatly reduces errors.

We don't think it'll be very practical/common for people to be typing script urls by hand compared to copying them from the instructions for a specific library.

@javiercn Yes, the class library provides instructions on how to consume its static assets, so it is a copy and paste step.

The PackageId can also be changed to include dashes instead of dots, but this will be a breaking change for us as this is connected with the name of the package at nuger.org. In addition, as most nuget packages use dots for delimiters instead of dashed, this might affect other vendors as well.

If a new class library is created now, it might not have such a problem as we are now aware of how its name will be exposed and it is up to us to decide how to name the new lib, so the problem only affects existing libraries. This is why having an option for displaying the name as is (without sanitization) might work for existing libs as the developer will be responsible for using the option.

I'm really liking this feature. I'd also like to maintain the dots in the library name. Although I think it would be better if there was MSBuild property to set it to cater for everyone. It would also be great if we could override the destination folder from "_content" to something else e.g. "assets".

@nfplee Seems like this will be updated with https://github.com/aspnet/AspNetCore/issues/11763

I'm using preview8 and static files(inside component libraries) doesn't seem to work in server-side Blazor. Am I right? Any eta?

Hi @arivoir .

It looks like you are posting on a closed issue!

We're very likely to lose track of your bug/feedback/question unless you:

  1. Open a new issue
  2. Explain very clearly what you need help with
  3. If you think you have found a bug, include detailed repro steps and a repro project so that we can investigate the problem
Was this page helpful?
0 / 5 - 0 ratings