Как я могу добавить результат действия контроллера ASP.NET MVC в Bundle?
У меня есть действие контроллера, которое возвращает файл Javascript. Я могу ссылаться на этот файл с моего представления, и он отлично работает. Я хотел бы поместить его в System.Web.Optimization.Bundle с другими JS файлами.
Я пытаюсь это сделать, по существу:
new Bundle().Include("~/DynamicScript/UrlDictionary");
Другие файлы в моем Bundle получаются просто отлично, но этот игнорируется. Видя это поведение, я полагаю, что связывание обрабатывается до того, как приложение сможет разрешать URL-адреса через инфраструктуру маршрутизации, или, может быть, компонент связывания не запрашивает файлы таким образом, чтобы это разрешение могло произойти.
Если кто-то может подтвердить это для меня и/или указать мне в хорошем направлении, это было бы очень признательно.
Ответы
Ответ 1
Я думаю, что это вполне выполнимо - но прежде, чем я войду в свое решение. Помните, что пакет создан при первом ударе и повторно используется. Это означает, что любой "динамический" script все еще должен быть глобальным (т.е. не может зависеть от конкретного пользователя и т.д.). Вот почему, как правило, он разрешает только статические js файлы. Сказать, что... Я мог представить себе ситуацию, когда вы можете использовать такие переменные, как номер версии или что-то в этом js (хотя в этом случае я лично просто использовал Ajax/JSON для ее получения).
Я думаю, что путь для этого - создать производный тип из Bundle. В нем вы должны перезаписать метод EnumerateFiles. Первоначально вы перечислили бы base.EnumerateFiles, а затем включили свой собственный виртуальный файл.
Что-то вроде (Примечание: не проверенный код):
public class VirtualMethodBundle : Bundle
{
private List<VirtualFile> _virtualContent = new List<VirtualFile>();
public override IEnumerable<VirtualFile> EnumerateFiles(BundleContext context)
{
foreach(var file in base.EnumerateFiles(context))
{
yield return file;
}
foreach(var virtual in _virtualContent)
{
yield return virtual;
}
}
public void AddCustomFile(VirtualFile file)
{
_virtualContent.Add(method);
}
}
Тогда у вас будет специальный тип VirtualFile, который переопределяет метод Open/Name и возвращает туда динамический контент... Что-то как.
public class MethodBasedVirtualFile : VirtualFile
{
private readonly Func<string> _contentFactory;
private readonly string _path;
public MethodBasedVirtualFile(string path, Func<string> contentFactory)
{
_path = path;
_contentFactory = contentFactory;
}
public override string Name { get { return _path; } }
public override Stream Open()
{
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(_contentFactory());
writer.Flush();
stream.Position = 0;
return stream;
}
}
Итак, чтобы использовать все, что у вас было бы...
var bundle = new VirtualMethodBundle();
bundle.Include(... real files ...);
bundle.AddCustomFile(
new MethodBasedVirtualFile("~/DynamicScript/UrlDictionary",
... the method that creates the content of that script...)
);
Если бы вы были умны, вы могли бы просто создать UrlVirtualFile, который берет путь url и использует MVC для автоматического получения контента по мере необходимости.
Ответ 2
Вот что я сделал для этого сценария. Я определил "Bundle Result", а затем в моем методе Controller, просто вернул Bundle по имени. Я использую ETags (заголовок If-None-Match) как оптимизацию для кэширования клиентов.
например:
public ActionResult ReturnFooBundle()
{
return new BundleResult("foo", TimeSpan.FromDays(7));
}
Вот реализация BundleResult:
public class BundleResult
: ActionResult
{
private class BundleInfo
{
public string BundleETag;
public DateTime LastModified;
public Bundle TheBundle;
public BundleResponse Response;
}
private static Dictionary<string, BundleInfo> _bundleCache = new Dictionary<string, BundleInfo>();
public string BundleName { get; private set; }
public TimeSpan CacheExpiry { get; private set; }
public BundleResult(string bundleName, TimeSpan cacheExpiry)
{
BundleName = bundleName;
CacheExpiry = cacheExpiry;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Clear();
BundleInfo bundleInfo = GetBundle(context.HttpContext);
string requestETag = context.HttpContext.Request.Headers["If-None-Match"];
if (!string.IsNullOrEmpty(requestETag) && (requestETag == bundleInfo.BundleETag))
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified;
context.HttpContext.Response.StatusDescription = "Not Modified";
return;
}
else
{
BundleResponse bundleResponse = bundleInfo.Response;
HttpResponseBase response = context.HttpContext.Response;
response.Write(bundleResponse.Content);
response.ContentType = bundleResponse.ContentType;
HttpCachePolicyBase cache = response.Cache;
cache.SetCacheability(HttpCacheability.ServerAndPrivate);
cache.SetLastModified(bundleInfo.LastModified);
cache.SetETag(bundleInfo.BundleETag);
}
}
private BundleInfo GetBundle(HttpContextBase context)
{
// lookup the BundleResponse
BundleInfo retVal;
lock (_bundleCache)
{
_bundleCache.TryGetValue(BundleName, out retVal);
}
if(retVal != null)
{
#if DEBUG
// see if the contents have been modified.
BundleContext bundleContext = new BundleContext(context, BundleTable.Bundles, BundleName);
DateTime lastModified = retVal.TheBundle.EnumerateFiles(bundleContext).Select(fi => fi.LastWriteTimeUtc).Max();
if (lastModified > retVal.LastModified)
{
// regenerate the bundleInfo
retVal = null;
}
#endif
}
if (retVal == null)
{
string rawBundleName = BundleTable.Bundles.ResolveBundleUrl(BundleName);
string hash = rawBundleName.Substring(rawBundleName.IndexOf("?v=") + 3);
Bundle bundle = BundleTable.Bundles.GetBundleFor(BundleName);
BundleContext bundleContext = new BundleContext(context, BundleTable.Bundles, BundleName);
BundleResponse bundleResponse = bundle.GenerateBundleResponse(bundleContext);
DateTime lastModified = bundle.EnumerateFiles(bundleContext).Select(fi => fi.LastWriteTimeUtc).Max();
retVal = new BundleInfo
{
BundleETag = hash,
Response = bundleResponse,
TheBundle = bundle,
LastModified = lastModified,
};
lock (_bundleCache)
{
_bundleCache[BundleName] = retVal;
}
}
return retVal;
}
}
Ответ 3
Система объединения поддерживает только физические файлы, а не маршруты приложений.