ASP.NET MVC 4 - Add Bundle from Controller? - asp.net-mvc-4

I have some pages on my site that use certain CSS and JS resources - but they are the only page(s) to use that css or js file - so I don't want to include that CSS and JS reference in every page. Rather than modify each View to reference the CSS/JS it needs, I thought I could create a bundle in the Controller and add it to the Bundles that are already registered, and then it would be included in the bundle references, but this does not appear to be possible, or maybe I'm just going about it the wrong way.
In my Controller for a registration page for example, I thought I could write this:
Bundle styleBundle = new Bundle("~/bundles/registrationStyleBundle");
styleBundle.Include("~/Content/Themes/Default/registration.css");
BundleTable.Bundles.Add(styleBundle);
And then have this in my /Views/Shared/_Layout.cshtml:
#foreach(Bundle b in BundleTable.Bundles)
{
if (b is StyleBundle)
{
<link href="#BundleTable.Bundles.ResolveBundleUrl(b.Path)" rel="stylesheet" type="text/css" />
}
else if (b is ScriptBundle)
{
<script src="#BundleTable.Bundles.ResolveBundleUrl(b.Path)" type="text/javascript"></script>
}
}
But this does not work - the only bundles to get rendered to my page end up being the ones I specified in RegisterBundles (in /App_Start/BundleConfig.cs)
Any idea how to achieve this kind of "dynamic" or "runtime" bundling?
EDIT: Following Jasen's advice, what I ended up doing was taking the bundle creation/registration code out of the controller and adding it to RegisterBundles() in /App_Start/BundleConfig.cs. This way, the bundle is already available and the contents get minified. So:
bundles.Add(
new StyleBundle("~/bundles/registrationStyleBundle")
.Include("~/Content/Themes/default/registration.css"));
Then, in my view, I added this:
#section viewStyles{
<link href="#BundleTable.Bundles.ResolveBundleUrl("~/bundles/registrationStyleBundle")." rel="stylesheet" type="text/css" />
}
Then, in /Views/Shared/_Layout.cshtml, I added this:
#RenderSection("viewStyles", required: false)

Use the #section Scripts { } block to conditionally add bundles.
_Layout.cshtml
<body>
...
#RenderSection("Scripts", required: false)
</body>
FooView.cshtml
#section Scripts {
#Scripts.Render("~/bundles/foo")
}
KungFooView.cshtml
#section Scripts {
#Scripts.Render("~/bundles/kungfoo")
}
In my BundleConfig I typically group resources
bundles.Add(new ScriptBundle("~/bundles/Areas/Admin/js").Include(...);
bundles.Add(new StyleBundle("~/bundles/Areas/Admin/css").Include(...);
bundles.Add(new ScriptBundle("~/bundles/Areas/Home/js").Include(...);
bundles.Add(new StyleBundle("~/bundles/Areas/Home/css").Include(...);
Now I can either define multiple layout files or just selectively add bundles to the views.

Related

How to add Font Awesome to Blazor client (Razor component) app?

I created a Blazor WebAssembly hosted template in .NET Core 3.1. Then right clicked on project.Client/wwwroot/css folder and clicked on Add client side library. Then selected the Font Awesome library and installed it. I added the below line to index.html <head>.
<link href="css/font-awesome/css/fontawesome.css" rel="stylesheet"/>
I have libman.json of:
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "font-awesome#5.11.2",
"destination": "wwwroot/css/font-awesome/"
}
]
}
I added just the below line to the default Blazor template page Counter.razor (Razor component). The IntelliSense finds the font:
#page "/counter"
<h1>Counter</h1>
<span class="fa fa-save"></span>
#code {}
but I only see a square:
You also need to include the JavaScript.
<link rel="stylesheet" href="css/font-awesome/css/fontawesome.min.css" />
<script src="css/font-awesome/js/all.min.js"></script>
You can put the <script> tag below the other one at the bottom of the file but I doubt that you'll notice any speed difference.
From a now deleted comment:
The JS is just one option (the preferred option), but CSS only is still an option as well. Also, you don't use both. It's either CSS or JS
In Blazor I could only get the JS version to work. CSS only didn't work (the file was 200-OK).
The fa prefix has been deprecated in version 5. The new default is the fas solid style and the fab style for brands. ref
add to _hosts.cshtml (for server side)
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css">
Use fas as below:
#page "/counter"
<h1>Counter</h1>
<span class="fas fa-save"></span> <!--fas not fa-->
#code {}
This is tested in blazor Net5
You can use libman (or copy the files manually from the zip available at Fontawesome website). Then install/copy only all.min.css and the whole contents of webfonts folder into wwwroot/css/font-awesome subfolder. Like this:
Then put this into Pages/_Host.cshtml (for Blazor Server) or wwwroot/index.html (Blazor Web Assembly) into the head section:
<link rel="stylesheet" href="css/font-awesome/css/all.min.css" />
Or, as an alternative, add this at the beginning of site.css:
#import url('font-awesome/css/all.min.css');
No need for JS. It works.
You have to actually reference the stylesheet in your HTML page. This is usually done in the layout (_Layout.csthml). You need to add something like the following in your <head>:
<link rel="stylesheet" href="/css/font-awesome/font-awesome.min.css" />
FontAwesome have multiple framework supported (Vue, React, Angular, WordPress, LESS, SCSS). But I don't know why they are not providing it for Blazor.
So that, I have created "Brushtail.FontAweomse.Blazor" nuget package.
Instructions https://www.nuget.org/packages/Brushtail.FontAwesome.Blazor/

How to run SystemJs module when view loads

I have a component that loads a javascript module that builds on Bootstrap.js and Jquery to automatically build a table of contents for a page based on H1,H2,... headers. The component code is as follows:
import { bindable, bindingMode, customElement, noView } from 'aurelia-framework';
#noView()
#customElement('scriptinjector')
export class ScriptInjector {
#bindable public url;
#bindable public isLocal;
#bindable public isAsync;
#bindable({ defaultBindingMode: bindingMode.oneWay }) protected scripttag;
private tagId = 'bootTOCscript';
public attached() {
if (this.url) {
this.scripttag = document.createElement('script');
if (this.isAsync) {
this.scripttag.async = true;
}
if (this.isLocal) {
System.import(this.url);
return;
} else {
this.scripttag.setAttribute('src', this.url);
}
document.body.appendChild(this.scripttag);
}
}
public detached() {
if (this.scripttag) {
this.scripttag.remove();
}
}
}
Essentially for those not familiar with Aurelia, this simply uses SystemJs to load the bootstrap-toc.js module from my app-bundle wherever I put this on my view:
<scriptinjector url="lib/bootstrap-toc.js" is-local.bind='true'></scriptinjector>
My problem is that although this works perfectly when I first load the view, subsequent visits don't generate a TOC (table of contents). I have checked that Aurelia is in fact calling System.Import each time the view is loaded, but it seems that once a module has been imported once, it is never imported again (the code from the bundle never runs a second time).
Does anyone know how I can unload/reload/reset/rerun the module when I re-enter the view?
Ok, so after days of fighting with this I have figured out an acceptable solution that keeps all the functionality of the TOC library and requires as few changes to the skeleton project and the target library as I could manage. Forget the script injector above.
In the index.html, do as follows:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Holdings Manager</title>
<!--The FontAwesome version is locked at 4.6.3 in the package.json file to keep this from breaking.-->
<link rel="stylesheet" href="jspm_packages/npm/font-awesome#4.6.3/css/font-awesome.min.css">
<link rel="stylesheet" href="styles/styles.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body aurelia-app="main" data-spy="scroll" data-target="#toc">
<div class="splash">
<div class="message">Holdings Manager</div>
<i class="fa fa-spinner fa-spin"></i>
</div>
<!-- The bluebird version is locked at 4.6.3 in the package.json file to keep this from breaking -->
<!-- We include bluebird to bypass Edge's very slow Native Promise implementation. The Edge team -->
<!-- has fixed the issues with their implementation with these fixes being distributed with the -->
<!-- Windows 10 Anniversary Update on 2 August 2016. Once that update has pushed out, you may -->
<!-- consider removing bluebird from your project and simply using native promises if you do -->
<!-- not need to support Internet Explorer. -->
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script src="jspm_packages/npm/bluebird#3.4.1/js/browser/bluebird.min.js"></script>
<script src="jspm_packages/npm/jquery#2.2.4/dist/jquery.min.js"></script>
<script src="jspm_packages/github/twbs/bootstrap#3.3.7/js/bootstrap.min.js"></script>
<script>
System.import('core-js').then(function() {
return System.import('polymer/mutationobservers');
}).then(function() {
System.import('aurelia-bootstrapper');
}).then(function() {
System.import('lib/bootstrap-toc.js');
});
</script>
</body>
</html>
This is assuming you have installed bootstrap using jspm (which brings in jquery as a dependency). This also assumes you have put the javascript library (the one you want to incorporate, bootstrap-toc in my case) in your src/lib folder and that you have configured your bundling to include js files from your source folder.
Next, if your library has a self executing anonymous function defined, you need to take that code and move it inside the 'attached' method of the viewmodel where you want the library to be applied. So in this case, I have a 'help' view with a bunch of sections/subsections that I wanted a TOC generated for, so the code looks like:
import { singleton } from 'aurelia-framework';
#singleton()
export class Help {
public attached() {
$('nav[data-toggle="toc"]').each((i, el) => {
const $nav = $(el);
window.Toc.init($nav);
});
}
}
The code inside the 'attached' method above was cut and pasted from the bootstrap-toc.js file and I removed the self-executing anonymous method.
I tried using system.import for the jquery/bootstrap libraries but that made part of the TOC functionality stop working and I have lost my patience to figure out why so those libraries are staying as script references for now.
Also, when you build the project you will get errors :
help.ts(7,7): error TS2304: Cannot find name '$'.
help.ts(9,16): error TS2339: Property 'Toc' does not exist on type 'Window'.
These do not cause problems at runtime since both $ and Toc will be defined before the view is ever instantiated. You can solve these build errors with this solution here.

MVC 4 Bundling causes missing image in Kendo UI

I have created a new MVC 4 application and am trying to migrate an existing MVC 3 application over. Everything works fine until I try to use the new Bundling feature and when I bundle Kendo css files the arrow on dropdowns and numeric textboxes disappear. They function ok, just missing the images. The files seem to bundle just fine. I have researched extensively and have tried renaming the files to remove the "min" and still have the same issue.
Here are the files I am trying to bundle:
<link href="#Url.Content("~/Content/kendo/kendo.common.min.css")" rel="stylesheet" type="text/css" />
<link href="#Url.Content("~/Content/kendo/kendo.default.min.css")" rel="stylesheet" type="text/css" />
<link href="#Url.Content("~/Content/kendo/kendo.blueopal.min.css")" rel="stylesheet" type="text/css" />
When I bundle them like so the issues appear :
bundles.Add(new StyleBundle("~/Content/cssBundle").Include(
"~/Content/kendo/kendo.common.min.css",
"~/Content/kendo/kendo.default.min.css",
"~/Content/kendo/kendo.blueopal.min.css"
));
I faced the same problem.
CssRewriteUrlTransform should do the trick:
.Include("~/Content/kendo/2014.1.318/kendo.common.min.css", new CssRewriteUrlTransform())
First off, there is no need to minify files that have already been minified. The StyleBundle class will try to minify the Kendo .min files again, which is unnecessary. Use the Bundle class instead.
Secondly, the .Include() method takes a second parameter of params IItemTransform[] transforms. You can pass new CssRewriteUrlTransform() as that parameter, so your CSS will have the right paths.
Example:
bundles.Add(new Bundle("~/Content/cssBundle")
.Include("~/Content/kendo/kendo.common.min.css", new CssRewriteUrlTransform()),
.Include("~/Content/kendo/kendo.default.min.css", new CssRewriteUrlTransform()),
.Include("~/Content/kendo/kendo.blueopal.min.css", new CssRewriteUrlTransform())
);
I know it's a pain, but I usually just modify the .css files and do a find/replace to get the correct paths.
Otherwise, you can set the bundle to be the same directory that Kendo is in, like this:
bundles.Add(new StyleBundle("~/Content/kendo").Include(
"~/Content/kendo/kendo.common.min.css",
"~/Content/kendo/kendo.default.min.css",
"~/Content/kendo/kendo.blueopal.min.css"
));
I was able to correct this problem by configuring routes in my application for the problem locations.
// Route for bundles problem.
routes.MapRoute("ResourcesFix", "bundles/{folder}/{path}",
new { controller = "Redirect", action = "Index" });
// Redirect for requests.
public class RedirectController : Controller
{
public ActionResult Index(String folder, String path)
{
return Redirect("~/Content/kendo/" +
WebConfigurationManager.AppSettings["KendoVersion"] + "/" + folder + "/" + path);
}
}
Add the following class extension,
public static class BundleConfigExt
{
public static Bundle CustomCssInclude(this Bundle bundle, params string[] virtualPaths)
{
foreach (var virtualPath in virtualPaths)
{
if (virtualPath.IndexOf("~/Content/kendo/") > -1) //OR
//// if (virtualPath.IndexOf("~/Content/kendo/") > -1 || virtualPath.IndexOf("~/Content/ExternalCss/") > -1)
{
bundle.Include(virtualPath, new CssRewriteUrlTransform());
}
else
{
bundle.Include(virtualPath);
}
}
return bundle;
}
}
Call .CustomCssInclude() extension method instead of .Include(),
bundles.Add(new StyleBundle("~/Bundles/AllArabicCss").CustomCssInclude(
"~/Content/bootstrap.min.css",
"~/Content/kendo/kendo.common.*",
"~/Content/kendo/kendo.default.min.css",
//...
"~/Content/kendo/kendo.bootstrap.min.css",
"~/Content/ExternalCss/custom.css",
"~/Content/ExternalCss/tab-responsive.css",
"~/Content/ExternalCss/mobile-responsive.css"));

Use section in partial view

In my shared layout I would like to have a "scripts" section to stuff it with all the scripts needed for page functionality.
Layout.cshtml
<html>
<head>
<title>Test</title>
<script src="#Url.Content("~/Scripts/jquery-2.0.3.js")" type="text/javascript"> </script>
#RenderSection("Scripts", required: false)
</head>
<body>
#RenderBody()
</body>
</html>
So, my view loads a specific javascript, and I want it to be in "scripts" section, and it's working.
Index.cshtml
#model PlatformaPu.Areas.Inventura.Models.Home.Index
#section Scripts {
<script src="#Url.Content("~/Areas/Inventura/Scripts/Home/Index.js")" type="text/javascript"></script>
}
{CONTENT REMOVED FOR BREVITY}
#section Footer {
#Html.Partial("~/Views/Shared/_AppSelector.cshtml", Model.AppSelector)
}
Finally, my view renders a partial and I have a javascript that this partial loads.
_AppSelector.cshtml
#model PlatformaPu.Models.Shared._AppSelector
#section Scripts {
<script src="#Url.Content("~/Scripts/Shared/_AppSelector.js")" type="text/javascript"></script>
}
{CONTENT REMOVED FOR BREVITY}
...and this is NOT working - javascript tag is NOT rendered in "scripts" section
How can I do this?
As discussed in this question, it is not possible to use sections in a partial view:
Sections don't work in partial views and that's by design. You may use some custom helpers to achieve similar behavior, but honestly it's the view's responsibility to include the necessary scripts, not the partial's responsibility. I would recommend you using the #scripts section of the main view to do that and not have the partials worry about scripts.
You should add the script reference to the main view that references the partial.
This is my first answer!
I've being working with webforms for years and now i'm dealing with MVC 5. Bit hard.
Perhaps is the wrong solution, but works :)
In Layout.cshtml. add second "ScriptsPartial" section
#RenderSection("ScriptsPartial", required: false)
In Index.cshtml, add ", new ViewDataDictionary(ViewData) { { "ViewPage", this } }"
#section Footer {
#Html.Partial("~/Views/Shared/_AppSelector.cshtml", Model.AppSelector, new ViewDataDictionary(ViewData) { { "ViewPage", this } })
}
In _AppSelector.cshtml, remove this
#section Scripts {
<script src="#Url.Content("~/Scripts/Shared/_AppSelector.js")" type="text/javascript"></script>
}
In _AppSelector.cshtml, add this in any place
#{
if (ViewData.ContainsKey("ViewPage"))
{
System.Web.Mvc.WebViewPage viewPage = (System.Web.Mvc.WebViewPage)ViewData["ViewPage"];
viewPage.DefineSection("ScriptsPartial", () =>
{
// viewPage.Write(Scripts.Render("~/AppSelector/Scripts")); // If you use a Bundle
viewPage.WriteLiteral("<script src=\"" + Url.Content("~/Scripts/Shared/_AppSelector.js") + "\" type=\"text/javascript\"></script>");
});
}
}
Just "send" the View to the PartialView (no Parent property like in WebForms?) and use it to add content to "ScriptsPartial" section.
"ScriptsPartial" is needed because DefineSection throws an error "section already defined: Scripts"
So, no more than one PartialView can use "ScriptsPartial" section... not so good solution.
Best regards,
Paco Ferre

Using external plugins in Sencha Touch

I want to use a simple Sencha Touch keypad plugin.
The plugin code can be found over here.
The keypad can be created in an html file under tags as follows:
<script>
Ext.setup({
onReady: function () {
var basic = new Ext.ux.Keypad();
basic.render('keypad');
}
});
</script>
<div id="keypad"/>
Alternatively, it can be used in a Sencha container as follows too:
...
items:[
{
xtype: 'keypad'
}
]
However, I am not able to get it to work the latter way. I'm new to Sencha and I think I'm not placing the files at the right places or not including them properly. I have already included the following in my index.html:
<script type="text/javascript" charset="utf-8" src="js/sencha-touch-1.1.1/sencha-touch.js"></script>
<link rel="stylesheet" type="text/css" href="js/sencha-touch-1.1.1/resources/css/sencha-touch.css">
<script src="js/Keypad.js" type="text/javascript" charset="utf-8"></script>
Can someone let me know what modifications are necessary in which files so that I can use the keypad plugin directly in a container?
in your app.js file you need to set path for the plugin folder in the loader...
put the ux (plugin)folder where your app.js is located...
in app.js set the following
Ext.Loader.setPath('Ext.ux', 'ux');
On the view where you are using the numpad you need to specify a
requires: ['Ext.ux.NumPad' ...] //All plugin related files
Also ensure that the CSS files are in the proper location...
Hope it helps...