This is a follow-up question to this: ASP.Net MVC 5 - Deploy separate CDN site with bundled JavaScipt and CSS
I want to serve up my JavaScript and CSS bundles from a second ASP.Net website, so I have done the following.
Main website (ASP.Net MVC website that has not JS or CSS resources)
CDN website (ASP.Net MVC website that has all JS and CSS resources but not much else)
CDN Website Web.config extract
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
</system.webServer>
CDN Website Bundle Config
public static class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
}
}
Main Website Bundle Config
public static class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new StyleBundle("~/Content/css", "http://[CDN website]/Content/css"));
//BundleTable.EnableOptimizations = true;
}
}
Main Website Layout View
<!DOCTYPE html>
<html>
<head>
#Styles.Render("~/Content/css")
</head>
<body>
#RenderBody()
</body>
</html>
Result
The HTML produced by the main website doesn't render the <link> tag for the CSS. However, if I turn on bundle optimizations (see commented out code in Main Website Bundle Config), then the <link> tag appears but looks like this:
<link href="/Content/css?v=" rel="stylesheet">
Navigating to http://[CDN website]/Content/css in the browser loads the appropriate CSS.
Navigating to http://[Main website]/Content/css in the browser loads an empty page.
Am I doing this the incorrectly? I don't want to reference the CDN website URL directly because I want the versioning that comes with the MVC bundler.
I ended-up using the following solution for the CDN website.
https://stackoverflow.com/a/26402383/2663033
The main website then used the appropriate MVC route of the CDN website for the style "href" and the script "src", which redirected to the appropriately versioned content or script bundle.
Related
Is it possible technically to have a full webassembly Blazor application as a Razor Class Library and then consume it in another ASP.NET project regardless of the consumer be MVC, Razor Pages, or Blazor app? Is it possible to define the routing within the Razor Class library?
I'm working on a project that is going to be published as a Nuget package. This package should be used in a variety of ASP.NET projects which are implemented as MVC, Razor Pages, or even Blazor.
I figured out how to get it running. I am using .NET 5.0.
First create a new Solution with a Razor Class Library project (Check the checkbox Support pages and views).
And create a MVC project.
In Startups.csadd the following:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddRazorPages();
services.AddServerSideBlazor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Also make sure you have app.UseStaticFiles(); in your Configure method.
Then, in your Razor Class Library, you can copy and paste the Pages, Shared folders and all other razor files from the example Blazor webassembly project. Also don't forget the wwwroot css folder and add your own wwwroot folder to your RCL.
In the Pages folder also create a new cshtml file. This will be the entry point to your Blazor app.
Example code:
#page
#using PROJECTNAME
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Blazor WebAssembly Prerendered</title>
<base href="~/" />
<link href="/_content/PROJECTID/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="/_content/PROJECTID/css/app.css" rel="stylesheet" />
#*https://learn.microsoft.com/de-de/aspnet/core/blazor/components/css-isolation?view=aspnetcore-5.0#razor-class-library-rcl-support*#
<link href="/_content/PROJECTID/PROJECTNAME.bundle.scp.css" rel="stylesheet" />
</head>
<body>
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
</app>
</body>
</html>
<script src="_framework/blazor.server.js"></script> <!--blazor.webassembly.js didn't work-->
The important parts are the <base href="~/" /> and the _framework/blazor.server.js.
If you don't map this page to be at the root, like #page "/" you have to make sure all the static content is mapped to the project-id correctly.
Also make sure the paths in the example projects NavMenu.razor are correct if you don't use / as the root. Has to be correct in the Razor Components too.
If you have problems with the _Imports.razor file, try adding the NuGet package Microsoft.AspNetCore.Components.WebAssembly
Also add the correct namespace for your shared folder, in the example project it's PROJECTNAME.Shared. Change it accordingly.
Here's a blogpost that helped me get things the right way: https://blog.joelving.dk/2020/02/reusable-ui-and-interchangable-hosting-models-in-blazor/
I have ASP.NET MVC application and im using Telerik's UI for ASP.NET MVC framework for few controls.
1> The application is using bootstrap 3.3.6
2> Telerik also provides bootstrap styling for their controls. so i have bundled those styles as well.
3> I also have my own site.css so i can override some of the styles.
Here is my BundleConfig.cs
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
// jQuery, bootstrap and kendo JavaScripts are bundled here
// kendo bootstrap-theme CSS
bundles.Add(new StyleBundle("~/Content/kendo-bootstrap/css").Include(
"~/Content/kendo/2016.1.412/kendo.common-bootstrap.min.css",
"~/Content/kendo/2016.1.412/kendo.bootstrap.min.css"));
// bootstrap CSS
bundles.Add(new StyleBundle("~/Content/bootstrap/css").Include(
"~/Content/bootstrap.min.css"));
// site CSS
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/site.css"));
}
}
This is how im referencing them in layout.cshtml
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
#Styles.Render("~/Content/kendo-bootstrap/css")
#Styles.Render("~/Content/bootstrap/css")
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/kendo/2016.1.412")
#Scripts.Render("~/bundles/bootstrap")
#Scripts.Render("~/bundles/modernizr")
</head>
<body>
</body>
</html>
Do we need to refer CSS in particular order? Whats the recommended order?
This would be the order in your razor (cshtml) file:
// Generic bootstrap css
#Styles.Render("~/Content/bootstrap/css")
// Css that kendo overwrites or extends based on bootstrap
#Styles.Render("~/Content/kendo-bootstrap/css")
// Your own css that can override the previous css files
#Styles.Render("~/Content/css")
that way bootstrap doesn't overwrite the style that was set by the kendo-ui library.
I installed bootstrap using nuget package manager and the css files are now in my /Content/ folder. However, when trying to reference them using:
<link rel="stylesheet" href="∼/Content/bootstrap.min.css" />
It doesn't work. But when referencing them using a CDN like:
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
It does work. I can't seem to figure out what is wrong as I have never had this problem before. I'm using an Empty template with MVC.
EDIT: After some playing around, I found that it was failing to load /%E2%88%BC/Content/bootstrap.css but after removing the tilda (~) it works fine. Anybody got any ideas?
This is not a correct path, it uses the tilda, which is used on the server when rendering links in server controls in asp.net.
Instead of this:
<link rel="stylesheet" href="∼/Content/bootstrap.min.css" />
Try this:
<link rel="stylesheet" href="#Url.Content("∼/Content/bootstrap.min.css")" />
Assuming that you are using Razor.
Alternatively, consider looking into style and script bundling that you get with new asp.net sites. Can be very useful.
Adding "app.UseStaticFiles()" to Startup.cs worked for me:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseBrowserLink();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
Now only Bootsrap 3 works with local links in .Net. Terrible. :( For 4, you must use CDN :
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
I am trying to get Angular UI Directives for bootstrap working with ASP MVC.
I have created a new, basic project and have used Nuget to add Twitter Bootstrap, AngularJS and UI Bootstrap.
I am registering these as bundles like so:
bundles.Add(new StyleBundle("~/Content/bootstrap")
.Include("~/Content/bootstrap.css")
.Include("~/Content/bootstrap-theme.css"));
bundles.Add(new ScriptBundle("~/bundles/angular")
.Include("~/Scripts/angular.js"));
bundles.Add(new ScriptBundle("~/bundles/angularUiDirectives")
.Include("~/Scripts/ui-bootstrap-{version}.js"));
My shared _Layout.cshtml page looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Testing Angular - #ViewBag.Title</title>
#Styles.Render("~/Content/css")
#Styles.Render("~/Content/bootstrap")
#Scripts.Render("~/bundles/modernizr")
</head>
<body>
#RenderBody()
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/angular")
#Scripts.Render("~/bundles/angularUiDirectives")
#RenderSection("scripts", required: false)
</body>
</html>
So I have added the libraries for Angular UI directives, along with the dependencies listed here via Nuget, I am registering all of these libraries as bundles and then I am rendering these bundles on my shared master page.
So far so good, but then I get to the next instruction:
As soon as you've got all the files downloaded and included in your page you just need to declare a dependency on the ui.bootstrap module
What does 'declare a dependency' mean in regards to HTML and CSS? The page on github suggests I need to put the following somewhere:
angular.module('myModule', ['ui.bootstrap']);
Where do I put this, and when do I run it?
Thanks
I solve this by doing the following:
I added the following to the opening HTML tag of my master page (replacing appName with the name of your application):
ng-app="appName"
I created a seperate Javascript class which I called test.js, registered this as a bundle and added the following:
angular.module('appName', []);
angular.module('appName', ['ui.bootstrap']);
The first line defines the angular module, the second wires it up to the UI Bootstrap library, and you need both.
Updated code to register my bundles is below:
using System.Web.Optimization;
namespace MvcApplication2
{
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new StyleBundle("~/Content/bootstrap")
.Include("~/Content/bootstrap.css")
.Include("~/Content/bootstrap-theme.css"));
bundles.Add(new ScriptBundle("~/bundles/angular")
.Include("~/Scripts/angular.js"));
bundles.Add(new ScriptBundle("~/bundles/angularUiDirectives")
.Include("~/Scripts/ui-bootstrap-tpls-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/test").Include("~/Scripts/Test.js"));
}
}
}
Here is the code for Test.js:
angular.module('appName', []);
angular.module('appName', ['ui.bootstrap']);
Here is the code for my master page:
<!DOCTYPE html>
<html ng-app="appName">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Testing Angular - #ViewBag.Title</title>
#Scripts.Render("~/bundles/angular")
#Scripts.Render("~/bundles/angularUiDirectives")
#Scripts.Render("~/bundles/test")
#Styles.Render("~/Content/bootstrap")
</head>
<body>
#RenderBody()
#RenderSection("scripts", required: false)
</body>
</html>
At this point we are fully setup with AngularJS, and Twitter Bootstrap and Angular Directives for Bootstrap in an ASP MVC application.
I have read lots of article on how to auto-version your CSS/JS files - but none of these really provide an elegant way to do this in ASP.NET MVC.
This link - How to force browser to reload cached CSS/JS files? - provides a solution for Apache - but I'm a little confused how this could be implemented via ASP.NET MVC ?
Would anyone be able to provide some advice how to do this on IIS7 and ASP.NET MVC - so that CSS/JS files automatically have a version number inserted in the URL without changing the location of the file ?
That is, so links come out link this etc presumably using the URL Rewrite or ?
<link rel="stylesheet" href="/css/structure.1194900443.css" type="text/css" />
<script type="text/javascript" src="/scripts/prototype.1197993206.js"></script>
Thx
When faced with this problem I wrote a series of wrapper functions around the UrlHelper's Content method:
EDIT:
Following the discussions in the comments below I updated this code:
public static class UrlHelperExtensions
{
private readonly static string _version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
private static string GetAssetsRoot()
{
string root = ConfigurationManager.AppSettings["AssetsRoot"];
return root.IsNullOrEmpty() ? "~" : root;
}
public static string Image(this UrlHelper helper, string fileName)
{
return helper.Content(string.Format("{0}/v{2}/assets/img/{1}", GetAssetsRoot(), fileName, _version));
}
public static string Asset(this UrlHelper helper, string fileName)
{
return helper.Content(string.Format("{0}/v{2}/assets/{1}", GetAssetsRoot(), fileName, _version));
}
public static string Stylesheet(this UrlHelper helper, string fileName)
{
return helper.Content(string.Format("{0}/v{2}/assets/css/{1}", GetAssetsRoot(), fileName, _version));
}
public static string Script(this UrlHelper helper, string fileName)
{
return helper.Content(string.Format("{0}/v{2}/assets/js/{1}", GetAssetsRoot(), fileName, _version));
}
}
Using these functions in conjunction with the following rewrite rule should work:
<rewrite>
<rules>
<rule name="Rewrite assets">
<match url="^v(.*?)/assets/(.*?)" />
<action type="Rewrite" url="/assets/{R:2}" />
</rule>
</rules>
</rewrite>
This article discusses how to create rewrite rules on IIS7.
This code uses the version number of the current assembly as a query string parameter on the file path's it emits. When I do an update to the site and the build number increments, so does the querystring parameter on the file, and so the user agent will re-download the file.
UPDATE: The previous version did not work on Azure, I have simplified and corrected below. (Note, for this to work in development mode with IIS Express, you will need to install URL Rewrite 2.0 from Microsoft http://www.iis.net/downloads/microsoft/url-rewrite - it uses the WebPi installer, make sure to close Visual Studio first)
UPDATE: Fixed rule for .min files
I recently spent an entirely fruitless day trying to get automatic bundling (to support auto-versioning) in C# / Net 4.6 / MVC 5 / Razor to work. I read many articles both on StackOverflow and elsewhere, yet I could not find an end-to-end walk through of how to set it up. I also do not care for the way files are version-ed (by appending a query string with version to the static file request - ie. somefile.js?v=1234) because I have been told by some that certain proxy servers ignore query strings when caching static resources.
So after a short trip down the rabbit hole, I've rolled my own version for auto-versioning and included full instructions on how to get it working below.
Full discussion #: Simplified Auto-Versioning of Javascript / CSS in ASP.NET MVC 5 to stop caching issues (works in Azure and Locally) With or Without URL Rewrite
THE PROBLEM: You generally have 2 types of javascript / css files in a project.
1) 3 party libraries (such as jquery or mustache) that very rarely change (and when they do, the version on the file generally changes) - these can be bundled / minified on an "as-needed" basis using WebGrease or JSCompress.com (just include the bundled file/version in your _Layout.cshtml)
2) page specific css/js files that should be refreshed whenever a new build is pushed. (without having the user clear thier cache or do multiple refreshes)
My Solution: Auto-increment the assembly version every time the project is built, and use that number for a routed static file on the specific resources you would like to keep refreshed. (so something.js is included as something.v1234.js with 1234 automatically changing every time the project is built) - I also added some additional functionality to ensure that .min.js files are used in production and regular.js files are used when debugging (I am using WebGrease to automate the minify process) One nice thing about this solution is that it works in local / dev mode as well as production.
How to do it: Auto-increment the assembly version every time the project is built, and use that number for a routed static file on the specific resources you would like to keep refreshed. (so something.js is included as something.v1234.js with 1234 automatically changing every time the project is built) - I also added some additional functionality to ensure that .min.js files are used in production and regular.js files are used when debugging (I am using WebGrease to automate the minify process) One nice thing about this solution is that it works in local / dev mode as well as production. (I am using Visual Studio 2015 / Net 4.6, but I believe this will work in earlier versions as well.
Step 1: Enable auto-increment on the assembly when built
In the AssemblyInfo.cs file (found under the "properties" section of your project change the following lines:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
to
[assembly: AssemblyVersion("1.0.*")]
//[assembly: AssemblyFileVersion("1.0.0.0")]
Step 2: Set up url rewrite in web.config for files with embedded version slugs (see step 3)
In web.config (the main one for the project) add the follwing rules in the system.webServer.
<rewrite>
<rules>
<rule name="static-autoversion">
<match url="^(.*)([.]v[0-9]+)([.](js|css))$" />
<action type="Rewrite" url="{R:1}{R:3}" />
</rule>
<rule name="static-autoversion-min">
<match url="^(.*)([.]v[0-9]+)([.]min[.](js|css))$" />
<action type="Rewrite" url="{R:1}{R:3}" />
</rule>
</rules>
</rewrite>
Step 3: Setup Application Variables to read your current assembly version and create version slugs in your js and css files.
in Global.asax.cs (found in the root of the project) add the following code to protected void Application_Start() (after the Register lines)
// setup application variables to write versions in razor (including .min extension when not debugging)
string addMin = ".min";
if (System.Diagnostics.Debugger.IsAttached) { addMin = ""; } // don't use minified files when executing locally
Application["JSVer"] = "v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString().Replace('.','0') + addMin + ".js";
Application["CSSVer"] = "v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString().Replace('.', '0') + addMin + ".css";
Step 4: Change src links in Razor views using the application variables we set up in Global.asax.cs
#HttpContext.Current.Application["CSSVer"]
#HttpContext.Current.Application["JSVer"]
For example, in my _Layout.cshtml, in my head section, I have the following block of code for stylesheets:
<!-- Load all stylesheets -->
<link rel='stylesheet' href='https://fontastic.s3.amazonaws.com/8NNKTYdfdJLQS3D4kHqhLT/icons.css' />
<link rel='stylesheet' href='/Content/css/main-small.#HttpContext.Current.Application["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/medium.#HttpContext.Current.Application["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/large.#HttpContext.Current.Application["CSSVer"]' />
#RenderSection("PageCSS", required: false)
A couple things to notice here: 1) there is no extension on the file. 2) there is no .min either. Both of these are handled by the code in Global.asax.cs
Likewise, (also in _Layout.cs) in my javascript section: I have the following code:
<script src="~/Scripts/all3bnd100.min.js" type="text/javascript"></script>
<script src="~/Scripts/ui.#HttpContext.Current.Application["JSVer"]" type="text/javascript"></script>
#RenderSection("scripts", required: false)
The first file is a bundle of all my 3rd party libraries I've created manually with WebGrease. If I add or change any of the files in the bundle (which is rare) then I manually rename the file to all3bnd101.min.js, all3bnd102.min.js, etc... This file does not match the rewrite handler, so will remain cached on the client browser until you manually re-bundle / change the name.
The second file is ui.js (which will be written as ui.v12345123.js or ui.v12345123.min.js depending on if you are running in debug mode or not) This will be handled / rewritten. (you can set a breakpoint in Application_OnBeginRequest of Global.asax.cs to watch it work)
Full discussion on this at: Simplified Auto-Versioning of Javascript / CSS in ASP.NET MVC 5 to stop caching issues (works in Azure and Locally) With or Without URL Rewrite (Including a way to do it WITHOUT URL Rewrite)
I usually append a fake query string to my resource files.. i.e
<link rel="stylesheet" href="/css/structure.css?v=1194900443" type="text/css" />
<script type="text/javascript" src="/scripts/prototype.js?v=1197993206"></script>
It doesn't require any url helpers and works no matter what's running the in background. To be honest I haven't throughly tested this method, but I've found it always fixes any resource caching issues people were experiencing.
You'd probably have to update the v= manually, but it wouldn't be terribly hard to append a version parameter to the resources from a config file somewhere.
Edit:
I went back and throughly read through the content of the link above and realize you've probably already discarded this method. Apologies for suggesting it again.
I suppose next solution with advanced options (debug/release mode, versions):
Js or Css files included by such way:
<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />
Global.JsPostfix and Global.CssPostfix is calculated by the following way in Global.asax:
protected void Application_Start(object sender, EventArgs e)
{
...
string jsVersion = ConfigurationManager.AppSettings["JsVersion"];
bool updateEveryAppStart = Convert.ToBoolean(ConfigurationManager.AppSettings["UpdateJsEveryAppStart"]);
int buildNumber = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Revision;
JsPostfix = "";
#if !DEBUG
JsPostfix += ".min";
#endif
JsPostfix += ".js?" + jsVersion + "_" + buildNumber;
if (updateEveryAppStart)
{
Random rand = new Random();
JsPosfix += "_" + rand.Next();
}
...
}