Я накатил собственное решение, и оно отлично работает! Я создал 4 класса, которые могу использовать в качестве настраиваемых серверных элементов управления:
- ScriptBundle
- Сценарий
- Стиль: Бандл
- Ссылка на сайт
Эти функции вызывают мою настраиваемую библиотеку комплектации, которая сама является оболочкой для API System.Web.Optimization.
Во время рендеринга ScriptBundle
и StyleBundle
я затем проверяю внутреннюю настройку (ту же, что я использую для установки EnableOptimizations
в System.Web.Optimization API), которая сообщает странице либо использовать объединение, либо просто записывать обычные теги script
/ link
. Если объединение включено, он вызывает эту функцию из моей пользовательской библиотеки объединения (для скриптов аналогичный код для стилей tho. Bundler
в приведенном ниже коде является классом для моей пользовательской библиотеки объединения - на всякий случай, если Microsoft изменит API-интерфейс System.Web.Optimization, который я хотел промежуточный слой, чтобы мне не пришлось так сильно менять свой код):
public static void AddScriptBundle(string virtualTargetPath, params string[] virtualSourcePaths)
{
var scriptBundle = new System.Web.Optimization.ScriptBundle(virtualTargetPath);
scriptBundle.Include(virtualSourcePaths);
System.Web.Optimization.BundleTable.Bundles.Add(scriptBundle);
}
Чтобы убедиться, что я создаю Bundle только в том случае, если он еще не существует, я сначала проверяю Bundle с помощью этого метода (перед использованием вышеуказанного метода):
public static bool BundleExists(string virtualTargetPath)
{
return System.Web.Optimization.BundleTable.Bundles.GetBundleFor(virtualTargetPath) != null;
}
Затем я использую эту функцию, чтобы выдать URL-адрес пакета с помощью System.Web.Optimization:
public static System.Web.IHtmlString GetScriptBundleHTML(string virtualTargetPath)
{
return System.Web.Optimization.Scripts.Render(virtualTargetPath);
}
В моих файлах .aspx я делаю следующее:
<%@ Register TagPrefix="cc1" Namespace="AdHocBundler" Assembly="AdHocBundler" %>
...
<cc1:ScriptBundle name="MyBundle" runat="Server">
<cc1:script src='~/js/script1.js'/>
<cc1:script src='~/js/utils/script2.js'/>
</cc1:ScriptBundle>
Уловка для меня заключалась в том, чтобы выяснить, что мне нужно преобразовать теги script
и link
, чтобы они работали как элементы списка в элементах управления ScriptBundle
и StyleBundle
, но после этого он отлично работает И это позволило мне использовать оператор тильды для простых ссылок относительно корня приложения. (с использованием Page.ResolveClientUrl()
, который полезен для создания содержимого модуля).
Спасибо этому SO-ответу за то, что помог мне понять, как создать настраиваемый элемент управления коллекцией: Как создать настраиваемый элемент управления ASP.NET со свойством коллекции?
ОБНОВЛЕНИЕ: в интересах полного раскрытия информации я получил разрешение поделиться кодом для ScriptBundle (StyleBundle практически идентичен, поэтому не включил его):
[DefaultProperty("Name")]
[ParseChildren(true, DefaultProperty = "Scripts")]
public class ScriptBundle : Control
{
public ScriptBundle()
{
this.Enabled = true;
this.Scripts = new List<Script>();
}
[PersistenceMode(PersistenceMode.Attribute)]
public String Name { get; set; }
[PersistenceMode(PersistenceMode.Attribute)]
[DefaultValue(true)]
public Boolean Enabled { get; set; }
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public List<Script> Scripts { get; set; }
protected override void Render(HtmlTextWriter writer)
{
if (String.IsNullOrEmpty(this.Name))
{
// Name is used to generate the bundle; tell dev if he forgot it
throw new Exception("ScriptBundle Name is not defined.");
}
writer.BeginRender();
if (this.Enabled && Bundler.EnableOptimizations)
{
if (this.Scripts.Count > 0)
{
string bundleName = String.Format("~/bundles{0}/{1}.js",
HttpContext.Current.Request.FilePath,
this.Name).ToLower();
// create a bundle if not exists
if (!Bundler.BundleExists(bundleName))
{
string[] scriptPaths = new string[this.Scripts.Count];
int len = scriptPaths.Length;
for (int i = 0; i < len; i++)
{
if (!string.IsNullOrEmpty(this.Scripts[i].Src))
{
// no need for resolve client URL here - bundler already does it for us, so paths like "~/scripts" will already be expanded
scriptPaths[i] = this.Scripts[i].Src;
}
}
Bundler.AddScriptBundle(bundleName, scriptPaths);
}
// spit out a reference to bundle
writer.Write(Bundler.GetScriptBundleHTML(bundleName));
}
}
else
{
// do not use bundling. generate normal script tags for each Script
foreach (Script s in this.Scripts)
{
if (!string.IsNullOrEmpty(s.Src))
{
// render <script type='<type>' src='<src'>/> ... and resolve URL to expand tilde, which lets us use paths relative to app root
// calling writer.Write() directly since it has less overhead than using RenderBeginTag(), etc., assumption is no special/weird chars in the cc1:script attrs
writer.Write(String.Format(Script.TAG_FORMAT_DEFAULT,
s.Type,
Page.ResolveClientUrl(s.Src)));
}
}
}
writer.EndRender();
}
}
public class Script
{
public const String ATTR_TYPE_DEFAULT = "text/javascript";
public const String TAG_FORMAT_DEFAULT = "<script type=\"{0}\" src=\"{1}\"></script>";
public Script()
{
this.Type = ATTR_TYPE_DEFAULT;
this.Src = null;
}
public String Type { get; set; }
public String Src { get; set; }
public String Language { get; set; }
}
person
nothingisnecessary
schedule
17.07.2013