Hello,
I wrote a Simple Control, that implements ScriptControl. This is holder for JQuery framework:
/// <summary>
/// Generic control with client behavior handled via jQuery
/// </summary>
public abstract partial class JQControl : ScriptControl, INamingContainer
{
/// <summary>
/// Client method to be called after jQuery initialization
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public JQRaw AfterInit
{
get;
set;
}
/// <summary>
/// Client method to be called before jQuery initialization
/// </summary>
[PersistenceMode(PersistenceMode.InnerProperty)]
public JQRaw PreInit
{
get;
set;
}
/// <summary>
/// Any data to initialize the control with (name-value pairs)
/// </summary>
public IDictionary<string, object> InitData
{
get; set;
}
/// <summary>
/// Authorization templates to be registered and used by privilege manager
/// </summary>
public IDictionary<string, AuthorizationTemplate> AuthorizationTemplates
{
get; set;
}
/// <summary>
/// If ThemePath is specified, this css file will be looked for and loaded from the theme folder
/// </summary>
protected string ThemeCssName
{
get; set;
}
private string _themePath;
/// <summary>
/// Specifies path to look for custom css and images to enable theming.
/// </summary>
public string ThemePath
{
get
{
return _themePath == null ? DefaultThemeHelper.ThemeName : ResolveClientUrl(_themePath);
}
set
{
_themePath = value;
}
}
/// <summary>
/// Collection of streamed javascript files
/// </summary>
private readonly List<ScriptReference> scriptRefs = new List<ScriptReference>();
/// <summary>
/// Collection of streamed stylesheet files
/// </summary>
private readonly List<string> cssRefs = new List<string>();
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
FillScriptReferences();
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
}
protected bool useDynamicCss = true;
private bool dynamicCssEnabled;
protected void EnableDynamicCss()
{
if(!dynamicCssEnabled)
{
//enable dynamic css loading
addScriptReference(typeof(JQControl), "LWM.Implementation.Controls.DynamicStylesheet.css.js");
dynamicCssEnabled = true;
}
}
protected virtual void FillScriptReferences()
{
Type t = typeof(JQControl);
addScriptReference(t, "LWM.Implementation.Controls.JScripts.Jquery.js");
addScriptReference(t, "LWM.Implementation.Controls.JScripts.ExtendJQuery.js");
addScriptReference(t, "LWM.Implementation.Controls.JScripts.ExtendAJAXDotNet.js");
addScriptReference(t, "LWM.Implementation.Controls.JQ.ChainRequests.js");
addScriptReference(t, "LWM.Implementation.Controls.JScripts.jquery.jcache.js");
addScriptReference(t, "LWM.Implementation.Controls.JScripts.jquery.cookie.js");
addScriptReference(t, "LWM.Implementation.Controls.JScripts.jquery.offset.js");
//if ((_themePath != null && ThemeCssName != null))
{
EnableDynamicCss();
}
}
//added to render automatically assigned id, otherwise escaped
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
const string extCss = "jqcontrol";
this.CssClass = this.CssClass != null ? this.CssClass + " " + extCss : extCss;
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);
//writer.AddAttribute(HtmlTextWriterAttribute.Class, CssClass);
}
/// <summary>
/// Ataches an embedded script refernnce using its full name
/// </summary>
/// <param name="name">Name of the reference</param>
/// <param name="useBaseClassAssembly">True if using base class assembly</param>
protected void AttachScriptRefernceByName(string name, bool useBaseClassAssembly)
{
//current type
Type type = this.GetType();
if (useBaseClassAssembly)
{
//base type
type = type.BaseType;
}
addScriptReference(type, name);
}
private void _addCssReference(Type type, string name)
{
string assembly = type.Assembly.FullName;
Type t = null;
if (BoostHelper.AdjustResourceParts(this, type.Assembly, ref assembly, ref name, ref t))
{
// LoggerHelper.LogInfo(String.Format("CSS init with- Client Script:{0}, Type:{1}, Name:{2} ", this.Page.ClientScript.ToString(),t.ToString(),name),"LWMSiteResourceBooster");
var sr = new DynamicStylesheetScriptReference(this.Page.ClientScript, t, new[] { name }); //(name, assembly);
if (!scriptRefs.Contains(sr))
{
scriptRefs.Add(sr);
}
}
else
{
string url = name; // GetEmbeddedURL(type, name);
if (!cssRefs.Contains(url))
{
cssRefs.Add(url);
}
}
}
//protected void addCssReference(string name, Type type)
//{
// _addCssReference(type, name);
// if (cssRefs.Count > 0)
// {
// scriptRefs.Add(new DynamicStylesheetScriptReference(this.Page.ClientScript, type, cssRefs.ToArray()));
// cssRefs.Clear();
// }
//}
protected void addCssReference(Type type, params string[] names)
{
if (names == null) return;
foreach (string name in names)
{
_addCssReference(type, name);
}
if(cssRefs.Count > 0)
{
scriptRefs.Add(new DynamicStylesheetScriptReference(this.Page.ClientScript, type, cssRefs.ToArray()));
cssRefs.Clear();
}
}
protected void removeCssReference(Type type, string name)
{
//TODO
//if (cssRefs.Contains(url))
//{
// cssRefs.Remove(url);
//}
}
protected void addScriptReference(Type type, string name)
{
//full name of the current assembly
string assembly = type.Assembly.FullName;
Type t = null;
BoostHelper.AdjustResourceParts(this, type.Assembly, ref assembly, ref name, ref t);
var sr = new HumanReadableScriptReference(name, assembly);
if (!scriptRefs.Contains(sr))
{
scriptRefs.Add(sr);
}
}
protected void removeScriptReference(Type type, string name)
{
//full name of the current assembly
string assembly = type.Assembly.FullName;
Type t = null;
BoostHelper.AdjustResourceParts(this, type.Assembly, ref assembly, ref name, ref t);
var sr = new HumanReadableScriptReference(name, assembly);
if (scriptRefs.Contains(sr))
{
scriptRefs.Remove(sr);
}
}
private string _jqBreadCrumb;
/// <summary>
/// Unique jQuery pattern that identifies this control
/// </summary>
protected string JQBreadCrumb
{
get
{
if (_jqBreadCrumb == null)
{
//StringBuilder sb = new StringBuilder();
//Control c = this;
Control c = this.NamingContainer;
//int step = 1;
while (c != null && c.ClientID != "__Page")
{
if (c is JQControl) // || step > 1)
{
_jqBreadCrumb = c.ClientID;
break;
//sb.Insert(0, " ");
//sb.Insert(0, "#" + c.ClientID);
}
c = c.NamingContainer;
//step++;
}
//_jqBreadCrumb = sb.ToString();
}
return _jqBreadCrumb;
}
}
//regular expression to look for tokens
private static readonly Regex tokenRegex = new Regex(
@"##(?<tokenName>[\w\:]+)"
);
//actually replaces tokens
private string tokenReplacer(Match m, bool useContext)
{
//token found
string tokenName = m.Groups["tokenName"].Value;
string ctlName = null;
//check if we have a resource token
if (ResourceHelper.CheckStringForToken(tokenName))
{
return "\"" + ResourceHelper.GetStringByToken(tokenName, ResourceType.Portal) + "\"";
}
switch (tokenName)
{
case "this":
//special case: seek for the current control
if (useContext) return "$(__$)";
ctlName = "#" + this.ClientID; // JQBreadCrumb;
break;
case "parent":
//special case: seek for the direct parent control
ctlName = "#" + JQBreadCrumb;
// this.NamingContainer.ClientID; //NOTE: use parent breadcrumb here
break;
default:
//seek for the child control with the given name
if (!useContext)
{
Control ctl = getChildByName(tokenName);
if (ctl != null) ctlName = "#" + ctl.ClientID;
}
//else ctlName = "[id^='" + this.ClientID + "_']" + "[id$='_" + tokenName + "']:first";
break;
}
if(ctlName != null) return "$(\"" + ctlName + "\")";
if (!useContext) return "$(this)._cc('" + tokenName + "', '" + this.ClientID + "')";
return "$(__$)._cc('" + tokenName + "')";
}
private string tokenReplacerWithContext(Match m)
{
return tokenReplacer(m, true);
}
private string tokenReplacerWithoutContext(Match m)
{
return tokenReplacer(m, false);
}
protected virtual Control getChildByName(string name)
{
return this.Controls.Cast<Control>().SingleOrDefault(c => c.ID == name);
}
//regular expression to insert context holder
private static readonly Regex contextRegex = new Regex(
@"^(\s*function\(\s*\)\s*{)", RegexOptions.Multiline
);
private static int replacedNum;
private static string contextReplacer(Match m)
{
replacedNum++;
return "function() { var __$ = this; \r\n";
}
/// <summary>
/// Substitue occurences of tokens of type ##[token] into corresponding jQuery calls
/// </summary>
/// <param name="callbackMethod">Callback method definition that contains tokens</param>
/// <param name="useContext">True if local context is to be used on the client</param>
protected void PrepareCallbackMethod(JQRaw callbackMethod, bool useContext)
{
if(useContext)
{
replacedNum = 0;
callbackMethod.JQRawContent = contextRegex.Replace(callbackMethod.JQRawContent, contextReplacer);
if(replacedNum == 0) useContext = false;
}
if (callbackMethod != null)
{
MatchEvaluator me;
if(useContext) me = tokenReplacerWithContext;
else me = tokenReplacerWithoutContext;
//find tokens in callback body and replace them
callbackMethod.JQRawContent = tokenRegex.Replace(callbackMethod.JQRawContent, me);
}
}
protected void PrepareCallbackMethod(JQRaw callbackMethod)
{
PrepareCallbackMethod(callbackMethod, false);
}
protected virtual void PrepareCallbackMethods(object @params)
{
foreach (PropertyInfo pi in @params.GetType().GetProperties().Where(
p => p.PropertyType.Equals(typeof(JQRaw))
))
{
if (pi.GetCustomAttributes(typeof(CallbackMethodAttribute), false).Length > 0)
{
//property has the attribute: prepare
JQRaw pty = (JQRaw)pi.GetValue(@params, null);
if (pty != null && pty.JQRawContent != null)
{
PrepareCallbackMethod(pty);
}
}
}
}
/// <summary>
/// Returns the script files for the control
/// </summary>
/// <returns>Collection that contains ECMAScript (JavaScript) files that have been registered as embedded resources</returns>
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
return scriptRefs;
}
private const string selfAlias = "__$";
protected virtual string GetDescriptorSelector()
{
//return this.ClientID;
return selfAlias;
}
protected virtual IEnumerable<ScriptDescriptor> _getScriptDescriptors()
{
yield break;
}
//private const string afterInitTemplate = "function() {{ var _method = {0}; _method(); $('#{1}').trigger('_loadComplete'); }}";
protected sealed override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
var result = new List<ScriptDescriptor>();
//yield return new JQSelfDescriptor(this.ClientID, selfAlias);
result.Add(new JQSelfDescriptor(this.ClientID, selfAlias));
if (PreInit != null)
{
//to be executed before initialization
PrepareCallbackMethod(PreInit, true);
//yield return new JQDescriptor(GetDescriptorSelector(), "each", true, PreInit);
result.Add(new JQDescriptor(GetDescriptorSelector(), "each", true, PreInit));
}
if(InitData != null)
{
foreach (string key in InitData.Keys)
{
//initialize with the given object as data
object data;
bool useStd = false;
if (InitData[key] is NativeContainer)
{
data = ((NativeContainer)InitData[key]).Content;
useStd = true;
}
else data = InitData[key];
JQDescriptor jdesc = new JQDescriptor(GetDescriptorSelector(), "data", true, key, data);
if(useStd) jdesc.UseStandardSerializer = true;
//yield return jdesc;
result.Add(jdesc);
}
}
if (AuthorizationTemplates != null)
{
//register authorization templates
//yield return new JQDescriptor(GetDescriptorSelector(), "pm_registerAuthTemplates", true, AuthorizationTemplates);
result.Add(new JQDescriptor(GetDescriptorSelector(), "pm_registerAuthTemplates", true, AuthorizationTemplates));
}
//enumerate overriden method results
foreach(ScriptDescriptor sd in _getScriptDescriptors())
{
// yield return sd;
result.Add(sd);
}
if (AfterInit != null)
{
//to be executed after initialization
PrepareCallbackMethod(AfterInit, true);
//AfterInit.JQRawContent = String.Format(afterInitTemplate, AfterInit.JQRawContent, GetDescriptorSelector());
//yield return new JQDescriptor(GetDescriptorSelector(), "each", true, AfterInit);
result.Add(new JQDescriptor(GetDescriptorSelector(), "each", true, AfterInit));
}
if (ThemePath != null && ThemeCssName != null)
{
//load css file dynamically
string cssURL = VirtualURLHelper.Combine(ThemePath, ThemeCssName);
DynamicStylesheetDescriptor dsdesc = new DynamicStylesheetDescriptor(cssURL);
//yield return dsdesc;
result.Add(dsdesc);
}
if (cssRefs.Count > 0)
{
DynamicStylesheetDescriptor dsdesc = new DynamicStylesheetDescriptor(cssRefs);
//yield return dsdesc;
result.Add(dsdesc);
}
//final trigger
//yield return new JQDescriptor(GetDescriptorSelector(), "trigger", "_loadComplete");
//yield break;
return result;
}
/// <summary>
/// Helper method to create URL to an embedded resource
/// </summary>
/// <param name="type">The type of the server-side resource</param>
/// <param name="resourceName">The name of the server-side resource</param>
/// <returns>The URL reference to the resource</returns>
protected string GetEmbeddedURL(Type type, string resourceName)
{
//get base URL
string url = Page.ClientScript.GetWebResourceUrl(type, resourceName);
//attach name of the resource
return url.Replace("?", "?name=" + resourceName + "&");
}
}
I place it on page into ascx Control: Page: `<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="p2.aspx.cs" Inherits="LWM.Implementation.Portal.Sample.TestOutputCache.p2" %>
<%@ Register TagPrefix="LWM" Src="~/Sample/TestOutputCache/testControl2.ascx" TagName="TestControl2"%>
<LWM:TestControl2 ID="testCached" runat="server" />
</div>
</form>
`
ascx: `<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="testControl2.ascx.cs" Inherits="LWM.Implementation.Portal.Sample.testControl2" %> <%@ OutputCache Duration="600" VaryByParam="None" %> <%@ Register TagPrefix="Controls" Assembly="LWM.Implementation.Controls" Namespace="LWM.Implementation.Controls.JQComposite" %> Test 2 function (evt) {
console.log("afterInit 2");
}
</AfterInit>
`
Also i enable ascx control cache. When page first loaded all ok, but when page getted from server cache all script references are missing...
I search a lot and can't find any idea. So, problem is that scriptmanager do not generate script reference when control loaded from server cache.