Question

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.

Was it helpful?

Solution

This is bug with OutputCaching and Script Manager, which officially was recognized by Microsoft

There is workaround to add scripts to scriptmanager manually:

 <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Name="AjaxControlToolkit.Common.Common.js" Assembly="AjaxControlToolkit" />
            <asp:ScriptReference Name="AjaxControlToolkit.ExtenderBase.BaseScripts.js" Assembly="AjaxControlToolkit" />
            <asp:ScriptReference Name="AjaxControlToolkit.TextboxWatermark.TextboxWatermark.js"
                Assembly="AjaxControlToolkit" />
        </Scripts>
    </asp:ScriptManager>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top