MVC Razor, Include JS / CSS files from another project - c#

I've got a C# MVC project that uses Razor syntax.
To be able to reuse some code, I want to put some of my JavaScript and CSS files in a different project and include them somehow.
This is how my scripts are included at the moment:
<script src="#Url.Content("~/Scripts/bootstrap-typeahead.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/bootstrap-dropdown.js")" type="text/javascript"></script>
At the moment, the scripts are in the same project as the cshtml file but they should be placed in the Common.Web project instead...
What I want to do is this (doesn't work though):
<script src="#Url.Content("Common.Web/Scripts/bootstrap-typeahead.js")" type="text/javascript"></script>
<script src="#Url.Content("Common.Web/Scripts/bootstrap-dropdown.js")" type="text/javascript"></script>

I do this very thing. However I embed the Javascript files and other content in another DLL and then call them from my razor syntax like so. Here is the code I use.
In the View:
Script example:
<script src=#Url.Action("GetEmbeddedResource", "Shared", new { resourceName = "Namespace.Scripts.jquery.qtip.min.js", pluginAssemblyName = #Url.Content("~/bin/Namespace.dll") }) type="text/javascript" ></script>
Image Example:
#Html.EmbeddedImage("corporate.gif", new { width = 150, height = 50})
Here is my helper methods:
public static MvcHtmlString EmbeddedImage(this HtmlHelper htmlHelper, string imageName, dynamic htmlAttributes)
{
UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
var anchor = new TagBuilder("img");
anchor.Attributes["src"] = url.Action("GetEmbeddedResource", "Shared",
new
{
resourceName = "Namespace.Content.Images." + imageName,
pluginAssemblyName = url.Content("~/bin/Namespace.dll")
});
if (htmlAttributes != null)
{
string width = "";
string height = "";
PropertyInfo pi = htmlAttributes.GetType().GetProperty("width");
if (pi != null)
width = pi.GetValue(htmlAttributes, null).ToString();
pi = htmlAttributes.GetType().GetProperty("height");
if (pi != null)
height = pi.GetValue(htmlAttributes, null).ToString();
if (!string.IsNullOrEmpty(height))
anchor.Attributes["height"] = height;
if (!string.IsNullOrEmpty(width))
anchor.Attributes["width"] = width;
}
return MvcHtmlString.Create(anchor.ToString());
}
Lastly my shared Controller:
[HttpGet]
public FileStreamResult GetEmbeddedResource(string pluginAssemblyName, string resourceName)
{
try
{
string physicalPath = Server.MapPath(pluginAssemblyName);
Stream stream = ResourceHelper.GetEmbeddedResource(physicalPath, resourceName);
return new FileStreamResult(stream, GetMediaType(resourceName));
//return new FileStreamResult(stream, GetMediaType(tempResourceName));
}
catch (Exception)
{
return new FileStreamResult(new MemoryStream(), GetMediaType(resourceName));
}
}
private string GetMediaType(string fileId)
{
if (fileId.EndsWith(".js"))
{
return "text/javascript";
}
else if (fileId.EndsWith(".css"))
{
return "text/css";
}
else if (fileId.EndsWith(".jpg"))
{
return "image/jpeg";
}
else if (fileId.EndsWith(".gif"))
{
return "image/gif";
}
else if (fileId.EndsWith(".png"))
{
return "image/png";
}
return "text";
}
Resource Helper:
public static class ResourceHelper
{
public static Stream GetEmbeddedResource(string physicalPath, string resourceName)
{
try
{
Assembly assembly = PluginHelper.LoadPluginByPathName<Assembly>(physicalPath);
if (assembly != null)
{
string tempResourceName = assembly.GetManifestResourceNames().ToList().FirstOrDefault(f => f.EndsWith(resourceName));
if (tempResourceName == null)
return null;
return assembly.GetManifestResourceStream(tempResourceName);
}
}
catch (Exception)
{
}
return null;
}
}
Plugin Helper
public static T LoadPluginByPathName<T>(string pathName)
{
string viewType = typeof(T).GUID.ToString();
if (HttpRuntime.Cache[viewType] != null)
return HttpRuntime.Cache[viewType] is T ? (T)HttpRuntime.Cache[viewType] : default(T);
object plugin = Assembly.LoadFrom(pathName);
if (plugin != null)
{
//Cache this object as we want to only load this assembly into memory once.
HttpRuntime.Cache.Insert(viewType, plugin);
return (T)plugin;
}
return default(T);
}
Remember that I am using these as embedded content!

To my knowledge you can't do this as the path would lie outside of the website.
You can however do the following:
1) Put all the scripts you want to share in Common.Web\Scripts
2) For each script file in your Web application 'Add as Link' to your Common.Web Scripts (you don't even need to do this step; it is however nice to see what scripts your web app uses in VS)
3) Add a post-build event to your Web application that copies the scripts from Common.Web\Scripts to your WebApp\Scripts folder:
copy $(ProjectDir)..\Common.Web\Scripts* $(ProjectDir)\Scripts
so from your perspective in Visual Studio you will only have a single place to update your .js files that can be used by multiple projects.

Instead of using Url helper use relative addressing. What you tried to do makes no sense, as helper is used to resolve paths relative to it's project.
Since you are trying to use resources of another project, it's ok to assume that you know upfront where you're going to deploy each project. Even though I don't like this practice, I can think of a pragmatic solution for this.
If your two applications are at urls:
http://www.mysite.com/app1
http://www.mysite.com/Common.Web
you could address like this:
<script src="#Url.Content("~")/../Common.Web/Scripts/bootstrap-typeahead.js")" type="text/javascript"></script>
meaning, resolve my app root folder, go up a level, and go down rest of the path.

I just found this question while looking for the same answer. Thanks to the previous posters, who made it clear that this cannot be done using Visual Studio alone: you will always end up with a copy of the original file, or an incomplete set of shipping files.
I do not wish to reinvent the wheel whenever I have simple common functionality, so I have a folder for common scripts on my hard drive:
/Common/Javascript
My web project is located in:
/ProjectName
so my common scripts lie outside my web project.
I solved this via source control. Most source control repositories will have the functionality to show a file in another directory. My steps were:
Create empty folder under my VS project: /ProjectName/Scripts/Common
Committed the empty folder to source control
Using Subversion (my source control), I set up an "extern" so that my common file was linked to the new folder. Other source control software may call this a "link" or some other such thing.
Updated the new folder, and my common Javascript files came in, and could now be added to my web project in Visual Studio.
Obviously, I tested this by changing the files within Visual Studio and committing them. The changes were indeed reflected in the original files, sitting in /Common/Javascript.
I can now simply add an extern to any other web project which needs to use the same functionality: though, of course, changing those files comes with additional risk now, as I may unexpectedly break other projects which use them. Though I can configure an extern to use a specific revision of a file, that's not what I want to do at this point; I shall await such complexity as and when it occurs.

Related

Ignoring files in MVC Bundles

We are using feature flags (set in Web.Config) to toggle the visibility in the UI of certain features that aren't yet complete. I'd also like my MVC bundles to NOT include the related JS files since they would just be useless files the client would have to download when the feature isn't enabled.
So far I've found IgnoreList.Ignore but I can't seem to get it to work. This is basically what I'm doing:
public static void RegisterBundles(BundleCollection bundles)
{
if (!appConfiguration.FeatureXEnabled)
{
//Skip these files if the Feature X is not enabled!
//When this flag is removed, these lines can be deleted and the files will be included like normal
bundles.IgnoreList.Ignore("~/App/Organization/SomeCtrl.js", OptimizationMode.Always);
bundles.IgnoreList.Ignore("~/App/Filters/SomeFilter.js", OptimizationMode.Always);
}
var globalBundle = new ScriptBundle("~/bundles/app-global").Include(
"~/App/RootCtrl.js",
"~/App/Directives/*.js",
"~/App/Services/*.js",
"~/App/Application.js"
);
bundles.Add(globalBundle);
var appOrgBundle = new ScriptBundle("~/bundles/app-org");
appOrgBundle.Include(
"~/App/Filters/*.js",
"~/App/Organization/*.js"
);
bundles.Add(appOrgBundle);
}
With the above code, the files in the ignore list are still being included! What am I doing wrong here?
I have fought the ignore list when using expressions as well. A simple work around I have found is to implement an IBundleOrderer that will exclude the files I do not want, and apply some ordering to how the files get included. Although this is not really its intended use, I find it fills the gap.
The IBundleOrderer gets access to the full list of files (expression expanded to what files it matched).
public class ApplicationOrderer : IBundleOrderer {
public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
{
if (!AppSettings.FeatureFlag_ServiceIntegrationsEnabled)
{
//Skip these files if the Service Integrations Feature is not enabled!
//When this flag is removed, these lines can be deleted and the files will be included like normal
var serviceIntegrationPathsToIgnore = new[]
{
"/App/ServiceIntegrations/IntegrationSettingsModel.js",
"/App/ServiceIntegrations/IntegrationSettingsService.js",
"/App/ServiceIntegrations/ServiceIntegrationsCtrl.js"
};
files = files.Where(x => !serviceIntegrationPathsToIgnore.Contains(x.VirtualFile.VirtualPath));
}
return files;
}
}
Example Usage:
public static void RegisterBundles(BundleCollection bundles)
{
var appBundle = new ScriptBundle("~/bundles/app")
.IncludeDirectory("~/App/", "*.js", true)
appBundle.Orderer = new ApplicationOrderer();
bundles.Add(appBundle);
}
I think that the bundling code only happens once - during the initialisation of the website. If you are switching the appConfiguration.FeatureXEnabled flag without restarting/republishing the website then your change will be ignored.
One solution would be to create 2 separate bundles and then use Razor to display the correct bundle reference in your HTML.
e.g.
#{if (appConfiguration.FeatureXEnabled){
<script type="text/javascript" src="~/bundles/bundle-large.js"></script>
}else{
<script type="text/javascript" src="~/bundles/bundle-small.js"></script>
}
I upvoted Gent's answer above because it helped me accomplish what I wanted.
But then I expanded upon it and created a generic IBundleOrderer that uses lambdas to exclude whatever items you want to exclude very simply.
Note the excludes parameter is a params list so you are not limited to a single exclusion pattern. This doesn't do any ordering, but it could be easily added with another parameter.
public class ExcludeItemsOrderer : IBundleOrderer
{
private Func<BundleFile, bool>[] _excludes;
public ExcludeItemsOrderer(params Func<BundleFile, bool>[] excludes)
{
_excludes = excludes;
}
public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
{
if(_excludes == null || _excludes.Length == 0)
{
return files;
}
foreach(var exclude in _excludes)
{
var exclusions = files.Where(exclude);
files = files.Except(exclusions);
}
return files;
}
}
And then I created an extension method to simplify using it:
public static class BundleExtensions
{
public static Bundle AsIsOrderExcluding(this Bundle bundle, params Func<BundleFile, bool>[] excludes)
{
bundle.Orderer = new ExcludeItemsOrderer(excludes);
return bundle;
}
}
So that it can be easily applied as you create your bundles, like this:
bundles.Add(
new ScriptBundle("~/Bundles/App/js")
.IncludeDirectory("~/App", "*.js", true)
.AsIsOrderExcluding(bf => bf.VirtualFile.VirtualPath.IndexOf("/skip_me/") >= 0)
);
Try defining the ignore list as follows (using wildcards instead of relative paths):
bundles.IgnoreList.Ignore("*/App/Organization/SomeCtrl.js", OptimizationMode.Always);
bundles.IgnoreList.Ignore("*/App/Filters/SomeFilter.js", OptimizationMode.Always);
Bundles.IgnoreList.Ignore(pattern) doesn't work like that. It compares just the Name part of files (i.e not the full (virtual)path ) with pattern in any bundle going to be returned.
For example you can ignore files starting with name old_style prefix. This filtering will be applied for files under any folder.
public IEnumerable<BundleFile> FilterIgnoredFiles(BundleContext context, IEnumerable<BundleFile> files) {
return files.Where(f => !ShouldIgnore(context, f.VirtualFile.Name));
}
See:
https://aspnetoptimization.codeplex.com/SourceControl/latest#src/System.Web.Optimization/IgnoreList.cs

WPF Awesomium - custom ResourceInterceptor wont allow local files to be loaded from disk

i have added my own custom Resource Interceptor to Awesomiums Webcore. I use it to load a local html file, manipulate it and then return it.
However this HTML file references other files on disk and I always get the error - something like
Not allowed to load local resource: file:///C:/awesomium project/bin/Debug/Resources/Html/css/myCss.css
This happens whether I return a file or a byte[] as my ResourceResponse. I have the WebPreferences set up as follows
return new WebPreferences()
{
UniversalAccessFromFileURL = true,
FileAccessFromFileURL = true,
SmoothScrolling = true,
};
I had this working using Awesomiums custom data sources but I need to provide our own prefix i.e. I can't use asset://. i'm not sure why this isn't working from the IResourceInterceptor. Any ideas?
Here is my imlementation of the Resource interceptor
public bool OnFilterNavigation(NavigationRequest request)
{
return false;//not handled
}
public ResourceResponse OnRequest(ResourceRequest request)
{
var path = request.Url.ToString();
ResourceResponse response = null;
if (path.StartsWith(#"myCustomPrefix://"))
{
response = _firstHandler.HandleRequest(path.Replace(#"myCustomPrefix://", string.Empty));
}
return response;
}
Edit: _firstHandler is a chain of command pattern. I could potentially do many things with the request so I have a handler for each one. I would like to say the handlers work. If I set them to create a file on disk they create that file - if I load up the html file from disk directly in the awesomium browser it is loaded correctly. It is only an issue when I return it as a ResourceResponse(filepath) or ResourceResponse(byte[], intPtr, mimetype) that it says it can't load the other files the HTML references locally.

Is there a way to programmatically export/save generated javascript and css bundles?

I am using System.Web.Optimization BundleTable for CSS and JavaScript file bundling. I was able to save bundle files manually by going to Chrome Developer Tools, Sources tab then clicking on bundle I would like to save and then clicking right mouse button on code and Save as (screenshot below)
I was wondering is there a way to save them programmatically (javascript or .NET I don't mind ether) as I need to test changes in bundle files by comparing them with old versions to see if they have changed or not. The end product will be selenium test most likely.
Sorted it out myself.
public static string GetBundleContents(string virtualPath)
{
OptimizationSettings config = new OptimizationSettings()
{
ApplicationPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath,
BundleTable = BundleTable.Bundles
};
BundleResponse response = Optimizer.BuildBundle(virtualPath, config);
return response.Content;
}
public static void WriteBundlesToDisk(string path)
{
foreach (var bundle in BundleTable.Bundles)
{
var bundleContents = BundleConfig.GetBundleContents(bundle.Path);
File.WriteAllText(string.Format("{0}/{1}.{2}", path, bundle.Path.Split('/').Last(), BundleConfig.GetFileExtensionByBundleType(bundle)), bundleContents);
}
}
public static string GetFileExtensionByBundleType(Bundle bundle)
{
if (bundle is ScriptBundle)
return "js";
else if (bundle is StyleBundle)
return "css";
return "folderBundle";
}
usage:
BundleConfig.WriteBundlesToDisk("c://bundles");
sidenote: there's a type of bundle called System.Web.Optimization.DynamicFolderBundle that is not being handled properly by the solution above, it will save it as .folderBundle file type.

Get access to the URL's being used in System.Web.Optimization

Background: I'm using the HTML 5 Offline App Cache and dynamically building the manifest file. Basically, the manifest file needs to list each of the static files that your page will request. Works great when the files are actually static, but I'm using Bundling and Minification in System.Web.Optimization, so my files are not static.
When in the DEBUG symbol is loaded (i.e. debugging in VS) then the actual physical files are called from the MVC View. However, when in Release mode, it calls a virtual file that could look something like this: /bundles/scripts/jquery?v=FVs3ACwOLIVInrAl5sdzR2jrCDmVOWFbZMY6g6Q0ulE1
So my question: How can I get that URL in the code to add it to the offline app manifest?
I've tried:
var paths = new List<string>()
{
"~/bundles/styles/common",
"~/bundles/styles/common1024",
"~/bundles/styles/common768",
"~/bundles/styles/common480",
"~/bundles/styles/frontend",
"~/bundles/scripts/jquery",
"~/bundles/scripts/common",
"~/bundles/scripts/frontend"
};
var bundleTable = BundleTable.Bundles;
foreach (var bundle in bundleTable.Where(b => paths.Contains(b.Path)))
{
var bundleContext = new BundleContext(this.HttpContext, bundleTable, bundle.Path);
IEnumerable<BundleFile> files = bundle.GenerateBundleResponse(bundleContext).Files;
foreach (var file in files)
{
var filePath = file.IncludedVirtualPath.TrimStart(new[] { '~' });
sb.AppendFormat(formatFullDomain, filePath);
}
}
As well as replacing GenerateBundleResponse() with EnumerateFiles(), but it just always returns the original file paths.
I'm open to alternative implementation suggestions as well. Thanks.
UPDATE: (7/7/14 13:45)
As well as the answer below I also added this Bundles Registry class to keep a list of the required static files so that it works in debug mode in all browsers. (See comments below)
public class Registry
{
public bool Debug = false;
public Registry()
{
SetDebug();
}
[Conditional("DEBUG")]
private void SetDebug()
{
Debug = true;
}
public IEnumerable<string> CommonScripts
{
get
{
if (Debug)
{
return new string[]{
"/scripts/common/jquery.validate.js",
"/scripts/common/jquery.validate.unobtrusive.js",
"/scripts/common/knockout-3.1.0.debug.js",
"/scripts/common/jquery.timepicker.js",
"/scripts/common/datepicker.js",
"/scripts/common/utils.js",
"/scripts/common/jquery.minicolors.js",
"/scripts/common/chosen.jquery.custom.js"
};
}
else
{
return new string[]{
"/scripts/common/commonbundle.js"
};
}
}
}
}
I'm by no means happy with this solution. Please make suggestions if you can improve on this.
I can suggest an alternative from this blog post create your own token.
In summary the author suggests using web essentials to create the bundled file and then creating a razor helper to generate the token, in this case based on the last changed date and time.
public static class StaticFile
{
public static string Version(string rootRelativePath)
{
if (HttpRuntime.Cache[rootRelativePath] == null)
{
var absolutePath = HostingEnvironment.MapPath(rootRelativePath);
var lastChangedDateTime = File.GetLastWriteTime(absolutePath);
if (rootRelativePath.StartsWith("~"))
{
rootRelativePath = rootRelativePath.Substring(1);
}
var versionedUrl = rootRelativePath + "?v=" + lastChangedDateTime.Ticks;
HttpRuntime.Cache.Insert(rootRelativePath, versionedUrl, new CacheDependency(absolutePath));
}
return HttpRuntime.Cache[rootRelativePath] as string;
}
}
Then you can reference the bundled file like so...
#section scripts {
<script src="#StaticFile.Version("~/Scripts/app/myAppBundle.min.js")"></script>}
Then you have control of the token and can do what you want with it.

How to use Actionmailer.Net.Standalone in a console application?

Is is possible to use Actionmailer.Net.Standalone in a console application? I keep getting an error that:
Could not find any CSHTML or VBHTML views named [CRD.html.cshtml] in the path [EmailTemplates]. Ensure that you specify the format in the file name (ie: CRD.html.cshtml.txt.cshtml or CRD.html.cshtml.html.cshtml)
Code
public class Mailer : RazorMailerBase
{
public override string ViewPath
{
get { return "EmailTemplates"; }
}
public RazorEmailResult Processed(string f)
{
From = group;
To.Add(user);
Subject = "CRD Process Server has processed file: " + f;
return Email("CRD.html.cshtml");
}
}
Do I need to implement a RazorViewEngine somewhere since it isn't standard with a console application?
A little late but maybe it still helps:
Try to change
return Email("CRD.html.cshtml");
to
return Email("CRD");
The extensions are appenden automatically.
There is a open source project as a general templating engine called RazorEngine
A templating engine built upon Microsoft's Razor parsing technology.
The RazorEngine allows you to use Razor syntax to build robust
templates.
Simply;
string template = "Hello #Model.Name! Welcome to Razor!";
string result = Razor.Parse(template, new { Name = "World" });
Also available in NuGet;
Install-Package RazorEngine

Categories

Resources