I would like to do the following:
#Scripts.Render("~/bundles/jquery?version=1"])
the version value has to be dynamic and should match the value defined in a cookie.
How can I add this parameter to Scripts.Render ?
I've tried something like that with jQuery but with no luck:
#Scripts.Render("~/bundles/jquery?version=" + $.cookie('version'))
Replace
#Scripts.Render("~/bundles/jquery?version=1"])
with
#{string version = 1}
#Scripts.RenderFormat("<script type=\"text/javascript\" src=\"{0}?nocache="+ version +"\"></script>", "~/bundles/jquery")
as shown in this post: http://www.jomendez.com/2016/05/26/how-to-avoid-js-files-cache-script-bundle-with-razor/
By default MVC optimisations will automatically add a version parameter to the bundle links for release builds but not for debug. E.g. when you deploy your site, the link to /bundles/modernizr becomes something like /bundles/modernizr?v=inCVuEFe6J4Q07A0AcRsbJic and the JavaScript is minified.
If one of the files in the bundle has changed the parameter changes on next deployment, hence linked files are cached by browsers but reloaded from the server when they have changed in a new release.
For easier debugging optimisations are disabled in debug (= no version parameter added and no minified code). If you want to override this you can set the compilation debug attribute to false in web.config, or you can enable optimizations in code, like so:
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
// Code removed for clarity.
BundleTable.EnableOptimizations = true;
}
For full details see
http://www.asp.net/mvc/overview/performance/bundling-and-minification, in particular the sections "Controlling Bundling and Minification" and "Bundle Caching".
For all I know #Scripts.Render("~/bundles/jquery") is not a path. It's just name. So if you want to use different versions you should create two bundles in your BundleConfig:
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery-ver191").Include(
"~/Scripts/jquery-1.9.1js"));
bundles.Add(new ScriptBundle("~/bundles/jquery-ver202").Include(
"~/Scripts/jquery-2.0.2js"));
}
Than you can call necessary version in your view:
#Scripts.Render("~/bundles/jquery-ver191")
or
#Scripts.Render("~/bundles/jquery-ver202")
EDITED: On your comment: But this default code in BundleConfig is the same you want.
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
Or you don't have an oppurtunity to delete other version exept newest?
Create a mvc helper that changes the html generated and adds the version. The follow example works when a single file or multiple files (debug mode) are generated.
public static HtmlString GetScriptsWithVersion()
{
const string VERSION = "2.0.1"; //or get the version where you want
const string SCRIPT_END = "\"></script>";
string html = Scripts.Render("~/bundles/ui").ToString();
string versionParam = "?v=" + VERSION
html = html.Replace(SCRIPT_END, versionParam + SCRIPT_END);
return new HtmlString(html);
}
Related
What are the options for setting a project version with .NET Core / ASP.NET Core projects?
Found so far:
Set the version property in project.json. Source: DNX Overview, Working with DNX projects. This seems to set the AssemblyVersion, AssemblyFileVersion and AssemblyInformationalVersion unless overridden by an attribute (see next point).
Setting the AssemblyVersion, AssemblyFileVersion, AssemblyInformationalVersion attributes also seems to work and override the version property specified in project.json.
For example, including 'version':'4.1.1-*' in project.json and setting [assembly:AssemblyFileVersion("4.3.5.0")] in a .cs file will result in AssemblyVersion=4.1.1.0, AssemblyInformationalVersion=4.1.1.0 and AssemblyFileVersion=4.3.5.0
Is setting the version number via attributes, e.g. AssemblyFileVersion, still supported?
Have I missed something - are there other ways?
Context
The scenario I'm looking at is sharing a single version number between multiple related projects. Some of the projects are using .NET Core (project.json), others are using the full .NET Framework (.csproj). All are logically part of a single system and versioned together.
The strategy we used up until now is having a SharedAssemblyInfo.cs file at the root of our solution with the AssemblyVersion and AssemblyFileVersion attributes. The projects include a link to the file.
I'm looking for ways to achieve the same result with .NET Core projects, i.e. have a single file to modify.
You can create a Directory.Build.props file in the root/parent folder of your projects and set the version information there.
However, now you can add a new property to every project in one step by defining it in a single file called Directory.Build.props in the root folder that contains your source. When MSBuild runs, Microsoft.Common.props searches your directory structure for the Directory.Build.props file (and Microsoft.Common.targets looks for Directory.Build.targets). If it finds one, it imports the property. Directory.Build.props is a user-defined file that provides customizations to projects under a directory.
For example:
<Project>
<PropertyGroup>
<Version>0.0.0.0</Version>
<FileVersion>0.0.0.0</FileVersion>
<InformationalVersion>0.0.0.0.myversion</InformationalVersion>
</PropertyGroup>
</Project>
Another option for setting version info when calling build or publish is to use the undocumented /p option.
dotnet command internally passes these flags to MSBuild.
Example:
dotnet publish ./MyProject.csproj /p:Version="1.2.3" /p:InformationalVersion="1.2.3-qa"
See here for more information: https://github.com/dotnet/docs/issues/7568
Not sure if this helps, but you can set version suffixes at publish time. Our versions are usually datetime driven, so that developers don't have to remember to update them.
If your json has something like "1.0-*"
"dotnet publish --version-suffix 2016.01.02" will make it "1.0-2016.01.02".
It's important to stick to "semvar" standards, or else you'll get errors. Dotnet publish will tell you.
Why not just change the value in the project.json file. Using CakeBuild you could do something like this (optimizations probably possible)
Task("Bump").Does(() => {
var files = GetFiles(config.SrcDir + "**/project.json");
foreach(var file in files)
{
Information("Processing: {0}", file);
var path = file.ToString();
var trg = new StringBuilder();
var regExVersion = new System.Text.RegularExpressions.Regex("\"version\":(\\s)?\"0.0.0-\\*\",");
using (var src = System.IO.File.OpenRead(path))
{
using (var reader = new StreamReader(src))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if(line == null)
continue;
line = regExVersion.Replace(line, string.Format("\"version\": \"{0}\",", config.SemVer));
trg.AppendLine(line);
}
}
}
System.IO.File.WriteAllText(path, trg.ToString());
}
});
Then if you have e.g. a UnitTest project that takes a dependency on the project, use "*" for dependency resolution.
Also, do the bump before doing dotnet restore. My order is as follows:
Task("Default")
.IsDependentOn("InitOutDir")
.IsDependentOn("Bump")
.IsDependentOn("Restore")
.IsDependentOn("Build")
.IsDependentOn("UnitTest");
Task("CI")
.IsDependentOn("Default")
.IsDependentOn("Pack");
Link to full build script: https://github.com/danielwertheim/Ensure.That/blob/3a278f05d940d9994f0fde9266c6f2c41900a884/build.cake
The actual values, e.g. the version is coming from importing a separate build.config file, in the build script:
#load "./buildconfig.cake"
var config = BuildConfig.Create(Context, BuildSystem);
The config file looks like this (taken from https://github.com/danielwertheim/Ensure.That/blob/3a278f05d940d9994f0fde9266c6f2c41900a884/buildconfig.cake):
public class BuildConfig
{
private const string Version = "5.0.0";
public readonly string SrcDir = "./src/";
public readonly string OutDir = "./build/";
public string Target { get; private set; }
public string Branch { get; private set; }
public string SemVer { get; private set; }
public string BuildProfile { get; private set; }
public bool IsTeamCityBuild { get; private set; }
public static BuildConfig Create(
ICakeContext context,
BuildSystem buildSystem)
{
if (context == null)
throw new ArgumentNullException("context");
var target = context.Argument("target", "Default");
var branch = context.Argument("branch", string.Empty);
var branchIsRelease = branch.ToLower() == "release";
var buildRevision = context.Argument("buildrevision", "0");
return new BuildConfig
{
Target = target,
Branch = branch,
SemVer = Version + (branchIsRelease ? string.Empty : "-b" + buildRevision),
BuildProfile = context.Argument("configuration", "Release"),
IsTeamCityBuild = buildSystem.TeamCity.IsRunningOnTeamCity
};
}
}
If you still want to have the Solution Level SharedVersionInfo.cs you can do it by adding these lines to your project.json file:
"buildOptions": {
"compile": {
"includeFiles": [
"../../SharedVersionInfo.cs"
]
}
}
Your relative path may vary, of course.
use external version.txt file with version, and prebuild step to publish this version in projects
I feel a Little stuck right now. First I used nuget to
install-package Bootstrap.less
as well as
install-package dotless
Then, as shown in Rick Andersons Blogpost about bundling and minification in asp.net mvc, I created a LessTransform-Class. I set up 2 nearly empty .less files and created a new bundle packaging them...
var lessBundle = new Bundle("~/MyLess").IncludeDirectory("~/Content/MyLess", "*.less", true);
lessBundle.Transforms.Add(new LessTransformer());
lessBundle.Transforms.Add(new CssMinify());
bundles.Add(lessBundle);
That worked well. Then I added a new StyleBundle to the main bootstrap.less file (which basically uses #import to include all the other .less files that bootstrap.less ships)...
bundles.Add(new StyleBundle("~/Bootstrap").Include("~/Content/Bootstrap/less/bootstrap.less"));
and a ScriptBundle to the bootstrap JavaScripts...
bundles.Add(new ScriptBundle("~/bundles/Bootstrap").Include("~/Scripts/bootstrap/js/bootstrap-*"));
to include all shipped bootstrap-*.js files and TADAA everything worked fine. The CSS got compiled including all imported JavaScript files were properly loaded.
But ... all that only worked for development mode with
<compilation debug="true" targetFramework="4.5"/>
As soon as I disable debug to see if the bundling into one file and the minification works properly I encounter the Problem.
The bundling process seems to fail to import all those .less files imported into bootstrap.less
/* Minification failed. Returning unminified contents.
(11,1): run-time error CSS1019: Unexpected token, found '/'
(11,2): run-time error CSS1019: Unexpected token, found '/'
(12,1): run-time error CSS1031: Expected selector, found '#import'
(12,1): run-time error CSS1025: Expected comma or open brace, found '#import'
(12,27): run-time error CSS1019: Unexpected token, found '/'
(12,28): run-time error CSS1019: Unexpected token, found '/'
... here go many many lines like these
(60,25): run-time error CSS1019: Unexpected token, found ';'
(62,1): run-time error CSS1019: Unexpected token, found '/'
(62,2): run-time error CSS1019: Unexpected token, found '/'
(63,1): run-time error CSS1031: Expected selector, found '#import'
(63,1): run-time error CSS1025: Expected comma or open brace, found '#import'
(63,27): run-time error CSS1019: Unexpected token, found '/'
(63,28): run-time error CSS1019: Unexpected token, found '/'
: run-time error CSS1067: Unexpected end of file encountered
*/
/*!
* Bootstrap v2.3.1
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world #twitter by #mdo and #fat.
*/
// Core variables and mixins
#import "variables.less"; // Modify this for custom colors, font-sizes, etc
#import "mixins.less";
... and the rest of the original bootstrap.less... no style definitions
having a look at the minified bootstrap.javascript bundle also boggles me. in dev there was no Problem after loading the page, now after the bootstrap.javascript was bundled and minified in Google the JavaScript console states
Uncaught TypeError: Cannot read property 'Constructor' of undefined
I have had a look at several Topics that seemed closely related to my Problem, and I tried a few things, but so far without success.
Many thanks in advance to anyone who could shed some light into my Situation and who would point out what I am missing or doing wrong. Best regards, Ingo
If you want to use bootstrap as less-files and in addition want to stop worrying about bundling and minification on your development machine as well as on your production machine, you might consider using the following approach.
Note: you don't need all this if you only play around with Less-Files while DEBUGging is enabled; But as soon as you want your application to go live on a production server like Windows Azure, and still want to just modify your less files without having to take care about the bundling and minification procedures... well... then this approach will work
So in order to solve the problem I felt a little stuck in, I had to approach the problem differently and had to modify (see Modification 2 further down the post) the "BundleSource" I thought I'd like to have.
SO DONT FORGET TO READ THE 2nd Modification/Warning close to the bottom of this answer!
MODIFICATION 1)
So the first and bigger part of the job is to get the bundling of the bootstrap-less files working. In order to do that I took the liberty to fork a piece of code I found in the web that (if you only need one less-file bundle) itself solves my problem... unless you might want to use or be able to use multiple less-bundles with several base directories... So that is where I actually found the approach that helped me a lot ...
... wherefore I award many thanks to Kristof Claes for his Blog-Entry "Using ASP.NET bundling and minification with LESS files" which I accidently and gladly stumbled over.
Like me he tried to use the LessMinify.cs that Scott Hanselman was showing in his speeches to work with 1 LESS-file instead of just bundling every single file in 1 directory full of LESS-files.
But he had to extend the whole bundling procedure slightly as he shows in his Blog-Entry. That way the solution he proposes can bundle 1 less file that uses imports to load other less files. But as he statically implements the path that is added to the source directory in which to find the less files... whichever less bundle you define has to pick a less file in the same directory...
That is where I took the liberty to extend his solution a bit further. I created a file LessBundling.cs with the following content:
using dotless.Core.configuration;
using dotless.Core.Input;
using MvcApplication2.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Web;
using System.Web.Hosting;
using System.Web.Optimization;
namespace MvcApplication2.Extensions
{
// create Less-Minifier (use Type to define source directory of less files [see below at BootstrapFileReader])
public class LessMinify<TFileReader> : CssMinify
where TFileReader : IFileReader
{
public LessMinify() {}
public override void Process(BundleContext context, BundleResponse response)
{
var config = new DotlessConfiguration()
{
MinifyOutput = true,
ImportAllFilesAsLess = true,
CacheEnabled = false,
LessSource = typeof(TFileReader)
};
response.Content = dotless.Core.Less.Parse(response.Content, config);
base.Process(context, response);
}
}
// create a LessStyleBundler to allow initializing LessBundle with a single less file that uses imports
public class LessStyleBundle<TFileReader> : Bundle
where TFileReader : IFileReader
{
public LessStyleBundle(string virtualPath)
: base(virtualPath, new LessMinify<TFileReader>()) {}
public LessStyleBundle(string virtualPath, string cdnPath)
: base(virtualPath, cdnPath, new LessMinify<TFileReader>()) { }
}
// create abstract VirtualFileReader from dotless-IFileReader as a Base for localized
internal abstract class VirtualFileReader : IFileReader
{
public byte[] GetBinaryFileContents(string fileName)
{
fileName = GetFullPath(fileName);
return File.ReadAllBytes(fileName);
}
public string GetFileContents(string fileName)
{
fileName = GetFullPath(fileName);
return File.ReadAllText(fileName);
}
public bool DoesFileExist(string fileName)
{
fileName = GetFullPath(fileName);
return File.Exists(fileName);
}
public string GetFullPath(string path)
{
return HostingEnvironment.MapPath(SourceDirectory + path);
}
public abstract string SourceDirectory {get;}
// implement to return Path to location of less files
// e. g. return "~/Content/bootstrap/less/";
}
// create BootstrapFileReader overwriting the Path where to find the Bootstrap-Less-Files
internal sealed class BootstrapFileReader : VirtualFileReader
{
public override string SourceDirectory
{
get { return "~/Content/bootstrap/less/"; }
}
}
}
So what does this actually do?
LessMinify extends the CssMinify class and therefore brings everything needed to minify css files
The important difference to "usual" bundling is that you create a new Dotless-Configuration with the LessSource defined as typeof(TFileReader) ...
By using <TFileReader> you can define a class that will contain the source directory in which the bundler/minifier will look for the less files to be taken into account
LessStyleBundle extends Bundle and therefore brings everything needed to bundle the files
In this class I again use TFileReader as this is where the LessMinify(er) will be instantiated
VirtualFileReader implements IFileReader which is a dotless interface defining all methods required to parse less files and give information where to find files to be imported
In order to extend Kristof's solution to the problem I added the abstract property SourceDirectory... requiring me to also make the VirtualFileReader abstract class
Now with that setup you can create as many LessFileReaders as you want. You just have to extend the abstract VirtualFileReader as can be seen in
BootstrapFileReader extends VirtualFileReader
The only purpose of the BootstrapFileReader is to have a property-getter for the SourceDirectory in which the bundler/minifier will find the less files that are to be imported
Well in my case Bootstraps Less-Files where lying in ~/Content/bootstrap/less which should be the default location if you install the "twitter.bootstrap.less"-nugget.
If you'd have another directory in your application, which contained a less file which again has multiple imports you just create a new class extending VirtualFileReader and define the property-getter for the SourceDirectory to return the corresponding path
If you then want to use this Bundling method to actually bundle and minify less files in a production environment you just add the LessStyleBundle-instantion to the BundlerConfig.cs:
bundles.Add(new LessStyleBundle<BootstrapFileReader>("~/bundles/BootstrapCSS")
.Include("~/Content/bootstrap/less/bootstrap.less"));
and of course your _Layout.cshtml should also be aware of the readily prepared bundle
#Styles.Render("~/bundles/BootstrapCSS")
MODIFICATION 2)
now the minor Modification which I also had to add to get this working
In my first attempt to bundle bootstrap.less I used this
bundles.Add(new LessStyleBundle<BootstrapFileReader>("~/Content/BootstrapCSS")
.Include("~/Content/bootstrap/less/bootstrap.less"));
I thought I would use Content in the routes for CSS/Less and Bundles in the routes for Javascript.
But that does not work out of the box. ASP.net doesnt permit the creation of a Bundle that starts with ~/Content. You will get a 403 authorization failure. Therefore the easiest solution to that is to use ~/bundles instead:
bundles.Add(new LessStyleBundle<BootstrapFileReader>("~/bundles/BootstrapCSS")
.Include("~/Content/bootstrap/less/bootstrap.less"));
As there aren't many real solutions to this problem I hope this will help at least some of you if you plan to integrate twitter bootstrap into your asp.net mvc4 application.
best regards,
Ingo
I've modified Ingo workaround to get rid of custom classes for each directory.
Also, I've added proper exception output (because otherwise all exceptions was silent and you just got empty less file in case of error).
public class LessTransform : IItemTransform
{
[ThreadStatic]
internal static string CurrentParsedFileDirectory;
public string Process (string includedVirtualPath, string input)
{
CurrentParsedFileDirectory = Path.GetDirectoryName (includedVirtualPath);
var config = new DotlessConfiguration
{
MinifyOutput = false,
CacheEnabled = false,
MapPathsToWeb = true,
ImportAllFilesAsLess = true,
LessSource = typeof (VirtualFileReader),
Logger = typeof (ThrowExceptionLogger)
};
return Less.Parse (input, config);
}
}
internal class VirtualFileReader : IFileReader
{
public bool UseCacheDependencies
{
get { return false; }
}
public byte[] GetBinaryFileContents (string fileName)
{
return File.ReadAllBytes (GetFullPath (fileName));
}
public string GetFileContents (string fileName)
{
return File.ReadAllText (GetFullPath (fileName));
}
public bool DoesFileExist (string fileName)
{
return File.Exists (GetFullPath (fileName));
}
public string GetFullPath (string path)
{
if (string.IsNullOrEmpty (path))
return string.Empty;
return HostingEnvironment.MapPath (path[0] != '~' && path[0] != '/'
? Path.Combine (LessTransform.CurrentParsedFileDirectory, path)
: path);
}
}
public class ThrowExceptionLogger : Logger
{
public ThrowExceptionLogger (LogLevel level) : base (level)
{
}
protected override void Log (string message)
{
if (string.IsNullOrEmpty (message))
return;
if (message.Length > 100)
message = message.Substring (0, 100) + "...";
throw new LessTransformException (message);
}
}
[Serializable]
public sealed class LessTransformException : Exception
{
public LessTransformException (string message) : base (message)
{
}
}
Usage:
bundles.Add (new StyleBundle ("~/styles-bundle/common")
.Include ("~/content/bootstrap/bootstrap.less", new LessTransform ()));
I was having the same issue today, I found a work around but I'd like a better solution as well. I was also trying to use dotless and a custom transform like what you have.
Workaround:
Pre-build event:
"$(SolutionDir)packages\dotless.1.3.1.0\tool\dotless.compiler.exe" "$(ProjectDir)Content\less\bootstrap.less"
That will create a bootstrap.css file which you can then include as regular CSS instead of LESS.
This solution isn't ideal, as you'd have to update the build event each time you update dotless, and having the bundle handle it is cleaner as well.
I really, really recommend installing WebEssentials 2012 instead.
It will generate a css-file AND a minified css-file from your .less and you can reference the css instead. It will automatically update the css everytime you make a change to your .less so there is no need to remember any pre-build steps or anything...
When installing WebEssentials you'll also get other sweet features like preview of CoffeeScript, TypeScript and LESS. JSHint, automatic minification and lots and lots more "goodies"!
How to minify JavaScript inside a view page's script block with minimal effort?
I have some page specific scripts that would like to put on specific view pages. But the ASP.NET MVC4 bundling and minification only works with script files, not script code inside a view page.
UPDATE
I took Sohnee's advice to extract the scripts into files. But I need to use them on specific pages so what I end up doing is:
on layout page, i created an optional section for page specific javascript block:
#RenderSection("js", required: false)
</body>
then in the view page, let's say Index.cshtml, i render the script section like such:
#section js{
#Scripts.Render("~/bundles/js/" + Path.GetFileNameWithoutExtension(this.VirtualPath))
}
as you can see, it assumes the javascript filename (index.js) is the same as the view page name (index.cshtml). then in the bundle config, i have:
var jsFiles = Directory.GetFiles(HttpContext.Current.Server.MapPath("Scripts/Pages"), "*.js");
foreach (var jsFile in jsFiles)
{
var bundleName = Path.GetFileNameWithoutExtension(jsFile);
bundles.Add(new ScriptBundle("~/bundles/js/" + bundleName).Include(
"~/Scripts/pages/" + Path.GetFileName(jsFile)));
}
then, if you are on index page, the HTML output will be:
<script src="/bundles/js/Index?v=ydlmxiUb9gTRm508o0SaIcc8LJwGpVk-V9iUQwxZGCg1"></script>
</body>
and if you are on products page, the HTML output will be:
<script src="/bundles/js/Products?v=ydlmxiUb9gTRm508o0SaIcc8LJwGpVk-V9iUQwxZGCg1"></script>
</body>
You can minify inline scripts using this HTML helper
using Microsoft.Ajax.Utilities;
using System;
namespace System.Web.Mvc
{
public class HtmlHelperExtensions
{
public static MvcHtmlString JsMinify(
this HtmlHelper helper, Func<object, object> markup)
{
string notMinifiedJs =
markup.Invoke(helper.ViewContext)?.ToString() ?? "";
var minifier = new Minifier();
var minifiedJs = minifier.MinifyJavaScript(notMinifiedJs, new CodeSettings
{
EvalTreatment = EvalTreatment.MakeImmediateSafe,
PreserveImportantComments = false
});
return new MvcHtmlString(minifiedJs);
}
}
}
And inside your Razor View use it like this
<script type="text/javascript">
#Html.JsMinify(#<text>
window.Yk = window.Yk || {};
Yk.__load = [];
window.$ = function (f) {
Yk.__load.push(f);
}
</text>)
</script>
If you use System.Web.Optimization than all necessary dlls are already referenced otherwise you can install WebGrease NuGet package.
Some additional details available here: http://www.cleansoft.lv/minify-inline-javascript-in-asp-net-mvc-with-webgrease/
EDIT:
Replaced DynamicInvoke() with Invoke(). No need for runtime checks here, Invoke is much faster than DynamicInvoke. Added .? to check for possible null.
The way to do this with minimal effort is to extract it into a script file. Then you can use bundling and minification just as you want.
If you want to minify it inline, it will be a much greater effort than simply moving the script off-page.
Based on #samfromlv's answer, I created an extension to handle CSS as well. It also takes BundleTable.EnableOptimizations into consideration.
OptimizationExtensions.cs
Adding in an answer for ASP.NET MVC Core. The solution I used to minify inline JS and razor generated html was WebMarkupMin.
It ultimately boiled down to adding these two minuscule changes to my project:
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
//added
app.UseWebMarkupMin();
app.UseMvc(.....
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//added
services.AddWebMarkupMin(
options =>
{
//i comment these two lines out after testing locally
options.AllowMinificationInDevelopmentEnvironment = true;
options.AllowCompressionInDevelopmentEnvironment = true;
})
.AddHttpCompression();
}
There's a great blog post by Andrew Lock (author of ASP.NET Core in Action) about using WebMarkupMin https://andrewlock.net/html-minification-using-webmarkupmin-in-asp-net-core/ WebMarkupMin is highly configurable and Andrew's post goes way more indepth, highly recommended reading it intently before just copying and pasting.
A little late for the party, but for .NET Core you could use a TagHelper to minify the content of a script tag like this:
[HtmlTargetElement("script", Attributes = MinifyAttributeName)]
public class ScriptTagHelper : TagHelper
{
private const string MinifyAttributeName = "minify";
[HtmlAttributeName(MinifyAttributeName)]
public bool ShouldMinify { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (!ShouldMinify)
{
await base.ProcessAsync(context, output);
return;
}
var textChildContent = await output.GetChildContentAsync();
var scriptContent = textChildContent.GetContent();
// or use any other minifier here
var minifiedContent = NUglify.Uglify.Js(scriptContent).Code;
output.Content.SetHtmlContent(minifiedContent);
}
}
and then use it in your views:
<script minify="true">
...
</script>
Fenton had a great answer about this: "rather than minify inline JavaScript code, externalize the inline JavaScript code and then you can minify with any standard JavaScript minifiers / bundlers."
Here is how you externalize the JavaScript: https://webdesign.tutsplus.com/tutorials/how-to-externalize-and-minify-javascript--cms-30718
Here is my direct answer to minify the inline JavaScript code (require a bit of manual work).
Copy the inline JavaScript code snippet and paste them into a separate JavaScript file and save it, e.g. inline.js
Use esbuild to minify the inline code snippet in inline.js, see more details about minification here
esbuild --minify < inline.js > inline-minified.js
Copy the minified JavaScript code snippet in inline-minified.js and paste it back into the original HTML to replace the original code inside of the tag.
Done.
Is there any way I can do this?
Some of the benefits of bundling are:
Minimization
Gzip compression
The request has a token parameter for handling files versiones (cache).
In my site I use a lot of bundles, but in some pages I only have 1 script and I don't think I should create a bundle only for 1 script. Is there any way I can use this three benefits with Url.Content method.
My utopic solution would be to set up something (maybe in the web.config) and whenever Url.Content is called it adds this functionality. Using it in either of this ways:
<script type="text/javascript" src="#Url.Content("~/Scripts/...")"></script>
<script type="text/javascript" src="~/Scripts/..."></script>
(The second one is because I'm using Razor 2)
If that is not possible I can make an extension method to UrlHelper to add this functionality.
Thanks!
There's nothing really wrong with creating a bundle with one file to get the benefits of minification and versioning. You would have to use the Scripts.Render helper as well, there's no support for this in the UrlHelper currently, but as you mentioned already you could write an extension method to call into the Scripts helper.
Update (by OP)
Here are my extension method for anyone who want to use it:
public static IHtmlString DynamicScriptsBundle(this HtmlHelper htmlHelper, string nombre, params string[] urls)
{
string path = string.Format("~/{0}", nombre);
if (BundleTable.Bundles.GetBundleFor(path) == null)
BundleTable.Bundles.Add(new ScriptBundle(path).Include(urls));
return Scripts.Render(path);
}
public static IHtmlString DynamicStylesBundle(this HtmlHelper htmlHelper, string nombre, params string[] urls)
{
string path = string.Format("~/{0}", nombre);
if (BundleTable.Bundles.GetBundleFor(path) == null)
BundleTable.Bundles.Add(new StyleBundle(path).Include(urls));
return Styles.Render(path);
}
I'm using Monorail in my C# web application. Since I upgrated it (.Net Framework 2 to 4 and Monorail 1.0.3 to 2.1RC), my ViewComponent class is not found. All my controllers seem to work fine. I'm using nVelocity View Engine. I'm not using Windsor, but maybe now I'm suppose to register it in a certain way?
In the .vm file, I experimented the following lines (without success, the first one was working before I upgraded the project) :
#component(MenuComponent)
#component(MenuComponent with "role=admins")
#blockcomponent(MenuComponent with "role=admins")
Did anyone experiment that?
The full error message is:
ViewComponent 'MenuComponent' could
not be found. Was it registered? If
you have enabled Windsor Integration,
then it's likely that you have forgot
to register the view component as a
Windsor component. If you are sure you
did it, then make sure the name used
is the component id or the key passed
to ViewComponentDetailsAttribute
Many thanks!
I finally found a clue to my problem. I used 'Castle.Monorail.Framework.dll' source code to see what happen inside : it seems that assemblies specified in the Web.Config file (in <Controllers> or even in <viewcomponents>) are not 'inspected' as they are supposed to be because the variable which contains it is initialized too late.
I builded a new version of the dll and now it's working fine. I will submit my 'fixed' code to the Castle Project Community to be sure it's not the consequence of something else (like bad settings).
Til then here is my 'fix', I just moved a portion of code. You can find the original source code here : http://www.symbolsource.org/Public/Metadata/Default/Project/Castle/1.0-RC3/Debug/All/Castle.MonoRail.Framework/Castle.MonoRail.Framework/Services/DefaultViewComponentFactory.cs
*Assembly:* Castle.MonoRail.Framework
*Class:* Castle.MonoRail.Framework.Services.**DefaultViewComponentFactory**
public override void Service(IServiceProvider provider)
{
/* Here is the section I moved */
var config = (IMonoRailConfiguration)provider.GetService(typeof(IMonoRailConfiguration));
if (config != null)
{
assemblies = config.ViewComponentsConfig.Assemblies;
if (assemblies == null || assemblies.Length == 0)
{
// Convention: uses the controller assemblies in this case
assemblies = config.ControllersConfig.Assemblies.ToArray();
}
}
/*******************************/
base.Service(provider); // Assemblies inspection is done there
var loggerFactory = (ILoggerFactory) provider.GetService(typeof(ILoggerFactory));
if (loggerFactory != null)
{
logger = loggerFactory.Create(typeof(DefaultViewComponentFactory));
}
/* The moved section was here */
}
I'm curious, without your fix, if you rename MenuComponent to just Menu, does it work?