Tuve un problema similar y terminé aplicando la excelente respuesta de Kalman con el código a continuación (no tan ordenado, pero podría decirse que más expansible):
namespace MvcHtmlHelpers
{
//http://stackoverflow.com/questions/5110028/add-css-or-js-files-to-layout-head-from-views-or-partial-views#5148224
public static partial class HtmlExtensions
{
public static AssetsHelper Assets(this HtmlHelper htmlHelper)
{
return AssetsHelper.GetInstance(htmlHelper);
}
}
public enum BrowserType { Ie6=1,Ie7=2,Ie8=4,IeLegacy=7,W3cCompliant=8,All=15}
public class AssetsHelper
{
public static AssetsHelper GetInstance(HtmlHelper htmlHelper)
{
var instanceKey = "AssetsHelperInstance";
var context = htmlHelper.ViewContext.HttpContext;
if (context == null) {return null;}
var assetsHelper = (AssetsHelper)context.Items[instanceKey];
if (assetsHelper == null){context.Items.Add(instanceKey, assetsHelper = new AssetsHelper(htmlHelper));}
return assetsHelper;
}
private readonly List<string> _styleRefs = new List<string>();
public AssetsHelper AddStyle(string stylesheet)
{
_styleRefs.Add(stylesheet);
return this;
}
private readonly List<string> _scriptRefs = new List<string>();
public AssetsHelper AddScript(string scriptfile)
{
_scriptRefs.Add(scriptfile);
return this;
}
public IHtmlString RenderStyles()
{
ItemRegistrar styles = new ItemRegistrar(ItemRegistrarFormatters.StyleFormat,_urlHelper);
styles.Add(Libraries.UsedStyles());
styles.Add(_styleRefs);
return styles.Render();
}
public IHtmlString RenderScripts()
{
ItemRegistrar scripts = new ItemRegistrar(ItemRegistrarFormatters.ScriptFormat, _urlHelper);
scripts.Add(Libraries.UsedScripts());
scripts.Add(_scriptRefs);
return scripts.Render();
}
public LibraryRegistrar Libraries { get; private set; }
private UrlHelper _urlHelper;
public AssetsHelper(HtmlHelper htmlHelper)
{
_urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
Libraries = new LibraryRegistrar();
}
}
public class LibraryRegistrar
{
public class Component
{
internal class HtmlReference
{
internal string Url { get; set; }
internal BrowserType ServeTo { get; set; }
}
internal List<HtmlReference> Styles { get; private set; }
internal List<HtmlReference> Scripts { get; private set; }
internal List<string> RequiredLibraries { get; private set; }
public Component()
{
Styles = new List<HtmlReference>();
Scripts = new List<HtmlReference>();
RequiredLibraries = new List<string>();
}
public Component Requires(params string[] libraryNames)
{
foreach (var lib in libraryNames)
{
if (!RequiredLibraries.Contains(lib))
{ RequiredLibraries.Add(lib); }
}
return this;
}
public Component AddStyle(string url, BrowserType serveTo = BrowserType.All)
{
Styles.Add(new HtmlReference { Url = url, ServeTo=serveTo });
return this;
}
public Component AddScript(string url, BrowserType serveTo = BrowserType.All)
{
Scripts.Add(new HtmlReference { Url = url, ServeTo = serveTo });
return this;
}
}
private readonly Dictionary<string, Component> _allLibraries = new Dictionary<string, Component>();
private List<string> _usedLibraries = new List<string>();
internal IEnumerable<string> UsedScripts()
{
SetOrder();
var returnVal = new List<string>();
foreach (var key in _usedLibraries)
{
returnVal.AddRange(from s in _allLibraries[key].Scripts
where IncludesCurrentBrowser(s.ServeTo)
select s.Url);
}
return returnVal;
}
internal IEnumerable<string> UsedStyles()
{
SetOrder();
var returnVal = new List<string>();
foreach (var key in _usedLibraries)
{
returnVal.AddRange(from s in _allLibraries[key].Styles
where IncludesCurrentBrowser(s.ServeTo)
select s.Url);
}
return returnVal;
}
public void Uses(params string[] libraryNames)
{
foreach (var name in libraryNames)
{
if (!_usedLibraries.Contains(name)){_usedLibraries.Add(name);}
}
}
public bool IsUsing(string libraryName)
{
SetOrder();
return _usedLibraries.Contains(libraryName);
}
private List<string> WalkLibraryTree(List<string> libraryNames)
{
var returnList = new List<string>(libraryNames);
int counter = 0;
foreach (string libraryName in libraryNames)
{
WalkLibraryTree(libraryName, ref returnList, ref counter);
}
return returnList;
}
private void WalkLibraryTree(string libraryName, ref List<string> libBuild, ref int counter)
{
if (counter++ > 1000) { throw new System.Exception("Dependancy library appears to be in infinate loop - please check for circular reference"); }
Component library;
if (!_allLibraries.TryGetValue(libraryName, out library))
{ throw new KeyNotFoundException("Cannot find a definition for the required style/script library named: " + libraryName); }
foreach (var childLibraryName in library.RequiredLibraries)
{
int childIndex = libBuild.IndexOf(childLibraryName);
if (childIndex!=-1)
{
//child already exists, so move parent to position before child if it isn't before already
int parentIndex = libBuild.LastIndexOf(libraryName);
if (parentIndex>childIndex)
{
libBuild.RemoveAt(parentIndex);
libBuild.Insert(childIndex, libraryName);
}
}
else
{
libBuild.Add(childLibraryName);
WalkLibraryTree(childLibraryName, ref libBuild, ref counter);
}
}
return;
}
private bool _dependenciesExpanded;
private void SetOrder()
{
if (_dependenciesExpanded){return;}
_usedLibraries = WalkLibraryTree(_usedLibraries);
_usedLibraries.Reverse();
_dependenciesExpanded = true;
}
public Component this[string index]
{
get
{
if (_allLibraries.ContainsKey(index))
{ return _allLibraries[index]; }
var newComponent = new Component();
_allLibraries.Add(index, newComponent);
return newComponent;
}
}
private BrowserType _requestingBrowser;
private BrowserType RequestingBrowser
{
get
{
if (_requestingBrowser == 0)
{
var browser = HttpContext.Current.Request.Browser.Type;
if (browser.Length > 2 && browser.Substring(0, 2) == "IE")
{
switch (browser[2])
{
case '6':
_requestingBrowser = BrowserType.Ie6;
break;
case '7':
_requestingBrowser = BrowserType.Ie7;
break;
case '8':
_requestingBrowser = BrowserType.Ie8;
break;
default:
_requestingBrowser = BrowserType.W3cCompliant;
break;
}
}
else
{
_requestingBrowser = BrowserType.W3cCompliant;
}
}
return _requestingBrowser;
}
}
private bool IncludesCurrentBrowser(BrowserType browserType)
{
if (browserType == BrowserType.All) { return true; }
return (browserType & RequestingBrowser) != 0;
}
}
public class ItemRegistrar
{
private readonly string _format;
private readonly List<string> _items;
private readonly UrlHelper _urlHelper;
public ItemRegistrar(string format, UrlHelper urlHelper)
{
_format = format;
_items = new List<string>();
_urlHelper = urlHelper;
}
internal void Add(IEnumerable<string> urls)
{
foreach (string url in urls)
{
Add(url);
}
}
public ItemRegistrar Add(string url)
{
url = _urlHelper.Content(url);
if (!_items.Contains(url))
{ _items.Add( url); }
return this;
}
public IHtmlString Render()
{
var sb = new StringBuilder();
foreach (var item in _items)
{
var fmt = string.Format(_format, item);
sb.AppendLine(fmt);
}
return new HtmlString(sb.ToString());
}
}
public class ItemRegistrarFormatters
{
public const string StyleFormat = "<link href=\"{0}\" rel=\"stylesheet\" type=\"text/css\" />";
public const string ScriptFormat = "<script src=\"{0}\" type=\"text/javascript\"></script>";
}
}
El proyecto contiene un método estático AssignAllResources:
assets.Libraries["jQuery"]
.AddScript("~/Scripts/jquery-1.10.0.min.js", BrowserType.IeLegacy)
.AddScript("~/Scripts//jquery-2.0.1.min.js",BrowserType.W3cCompliant);
/* NOT HOSTED YET - CHECK SOON
.AddScript("//ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js",BrowserType.W3cCompliant);
*/
assets.Libraries["jQueryUI"].Requires("jQuery")
.AddScript("//ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js",BrowserType.Ie6)
.AddStyle("//ajax.aspnetcdn.com/ajax/jquery.ui/1.9.2/themes/eggplant/jquery-ui.css",BrowserType.Ie6)
.AddScript("//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js", ~BrowserType.Ie6)
.AddStyle("//ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/eggplant/jquery-ui.css", ~BrowserType.Ie6);
assets.Libraries["TimePicker"].Requires("jQueryUI")
.AddScript("~/Scripts/jquery-ui-sliderAccess.min.js")
.AddScript("~/Scripts/jquery-ui-timepicker-addon-1.3.min.js")
.AddStyle("~/Content/jQueryUI/jquery-ui-timepicker-addon.css");
assets.Libraries["Validation"].Requires("jQuery")
.AddScript("//ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js")
.AddScript("~/Scripts/jquery.validate.unobtrusive.min.js")
.AddScript("~/Scripts/mvcfoolproof.unobtrusive.min.js")
.AddScript("~/Scripts/CustomClientValidation-1.0.0.min.js");
assets.Libraries["MyUtilityScripts"].Requires("jQuery")
.AddScript("~/Scripts/GeneralOnLoad-1.0.0.min.js");
assets.Libraries["FormTools"].Requires("Validation", "MyUtilityScripts");
assets.Libraries["AjaxFormTools"].Requires("FormTools", "jQueryUI")
.AddScript("~/Scripts/jquery.unobtrusive-ajax.min.js");
assets.Libraries["DataTables"].Requires("MyUtilityScripts")
.AddScript("//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js")
.AddStyle("//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css")
.AddStyle("//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables_themeroller.css");
assets.Libraries["MvcDataTables"].Requires("DataTables", "jQueryUI")
.AddScript("~/Scripts/jquery.dataTables.columnFilter.min.js");
assets.Libraries["DummyData"].Requires("MyUtilityScripts")
.AddScript("~/Scripts/DummyData.js")
.AddStyle("~/Content/DummyData.css");
en la página _layout
@{
var assets = Html.Assets();
CurrentResources.AssignAllResources(assets);
Html.Assets().RenderStyles()
}
</head>
...
@Html.Assets().RenderScripts()
</body>
y en los parciales y vistas
Html.Assets().Libraries.Uses("DataTables");
Html.Assets().AddScript("~/Scripts/emailGridUtilities.js");