File: UI\PartialCachingControl.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="PartialCachingControl.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI {
 
    using System;
    using System.IO;
    using System.Text;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Globalization;
    using System.Web;
    using System.Web.Util;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    using System.Web.Caching;
    using System.Web.Compilation;
    using System.Web.Configuration;
    using System.Security.Permissions;
 
 
    // Keeps track of one call to Page Register* API
    // The semantics of the fields depends to the call type
    [Serializable]
internal class RegisterCallData {
    internal ClientAPIRegisterType Type;
    internal ScriptKey Key;
    internal string StringParam1;
    internal string StringParam2;
    internal string StringParam3;
}
 
// Data that we need to cache
[Serializable]
internal class PartialCachingCacheEntry {
    internal    Guid                      _cachedVaryId;
    internal    string                    _dependenciesKey;
    internal    string[]                  _dependencies; // file dependencies
 
    internal string OutputString;
    internal string CssStyleString;
    internal ArrayList RegisteredClientCalls;
}
 
/// <devdoc>
///    <para>[To be supplied.]</para>
/// </devdoc>
[
ToolboxItem(false)
]
public abstract class BasePartialCachingControl : Control {
 
    internal Control _cachedCtrl;
    private long _nonVaryHashCode;
    internal string _ctrlID;
    internal string _guid;
    internal DateTime _utcExpirationTime;
    internal bool _useSlidingExpiration;
    internal HttpCacheVaryByParams _varyByParamsCollection;
    internal string[] _varyByControlsCollection;
    internal string _varyByCustom;
    internal string _sqlDependency;
    internal string _provider;
    internal bool _cachingDisabled;
    private string _outputString;
    private string _cssStyleString;
    private string _cacheKey;
    private CacheDependency _cacheDependency;
    private PartialCachingCacheEntry _cacheEntry;
    private ControlCachePolicy _cachePolicy;
    private ArrayList _registeredCallDataForEventValidation;
    private ArrayList _registeredStyleInfo = null;
 
    internal const char varySeparator = ';';
    internal const string varySeparatorString = ";";
 
    internal override void InitRecursive(Control namingContainer) {
 
        HashCodeCombiner combinedHashCode = new HashCodeCombiner();
 
        _cacheKey = ComputeNonVaryCacheKey(combinedHashCode);
 
        // Save the non-varying hash, so we don't need to recalculate it later
        _nonVaryHashCode = combinedHashCode.CombinedHash;
 
        PartialCachingCacheEntry cacheEntry = null;
 
        // Check if there is a cache entry for the non-varying key
        object tmpCacheEntry = OutputCache.GetFragment(_cacheKey, _provider);
 
        if (tmpCacheEntry != null) {
            ControlCachedVary cachedVary = tmpCacheEntry as ControlCachedVary;
            if (cachedVary != null) {
                string varyCachedKey = ComputeVaryCacheKey(combinedHashCode, cachedVary);
 
                // Check if there is a cache entry for the varying key
                cacheEntry = (PartialCachingCacheEntry) OutputCache.GetFragment(varyCachedKey, _provider);
                if (cacheEntry != null && cacheEntry._cachedVaryId != cachedVary.CachedVaryId) {
                    cacheEntry = null;
                    // explicitly remove the entry
                    OutputCache.RemoveFragment(varyCachedKey, _provider);
                }
            }
            else {
                // If it wasn't a ControlCachedVary, it must be a PartialCachingCacheEntry
                cacheEntry = (PartialCachingCacheEntry) tmpCacheEntry;
            }
        }
 
        // If it's a cache miss, create the control and make it our child
        if (cacheEntry == null) {
 
            // Cache miss
 
            _cacheEntry = new PartialCachingCacheEntry();
 
            _cachedCtrl = CreateCachedControl();
            Controls.Add(_cachedCtrl);
 
            // Make sure the Page knows about us while the control's OnInit is called
            Page.PushCachingControl(this);
            base.InitRecursive(namingContainer);
            Page.PopCachingControl();
        }
        else {
 
            // Cache hit
 
            _outputString = cacheEntry.OutputString;
            _cssStyleString = cacheEntry.CssStyleString;
 
            // If any calls to Register* API's were made when the control was run,
            // make them now to restore correct behavior (VSWhidbey 80907)
            if (cacheEntry.RegisteredClientCalls != null) {
                foreach (RegisterCallData registerCallData in cacheEntry.RegisteredClientCalls) {
                    switch (registerCallData.Type) {
 
                        case ClientAPIRegisterType.WebFormsScript:
                            Page.RegisterWebFormsScript();
                            break;
 
                        case ClientAPIRegisterType.PostBackScript:
                            Page.RegisterPostBackScript();
                            break;
 
                        case ClientAPIRegisterType.FocusScript:
                            Page.RegisterFocusScript();
                            break;
 
                        case ClientAPIRegisterType.ClientScriptBlocks:
                        case ClientAPIRegisterType.ClientScriptBlocksWithoutTags:
                        case ClientAPIRegisterType.ClientStartupScripts:
                        case ClientAPIRegisterType.ClientStartupScriptsWithoutTags:
                            Page.ClientScript.RegisterScriptBlock(registerCallData.Key,
                                registerCallData.StringParam2, registerCallData.Type);
                            break;
 
                        case ClientAPIRegisterType.OnSubmitStatement:
                            Page.ClientScript.RegisterOnSubmitStatementInternal(registerCallData.Key,
                                registerCallData.StringParam2);
                            break;
 
                        case ClientAPIRegisterType.ArrayDeclaration:
                            Page.ClientScript.RegisterArrayDeclaration(registerCallData.StringParam1,
                                registerCallData.StringParam2);
                            break;
 
                        case ClientAPIRegisterType.HiddenField:
                            Page.ClientScript.RegisterHiddenField(registerCallData.StringParam1,
                                registerCallData.StringParam2);
                            break;
 
                        case ClientAPIRegisterType.ExpandoAttribute:
                            Page.ClientScript.RegisterExpandoAttribute(registerCallData.StringParam1,
                                registerCallData.StringParam2, registerCallData.StringParam3, false);
                            break;
 
                        case ClientAPIRegisterType.EventValidation:
                            if (_registeredCallDataForEventValidation == null) {
                                _registeredCallDataForEventValidation = new ArrayList();
                            }
 
                            _registeredCallDataForEventValidation.Add(registerCallData);
                            break;
                        default:
                            Debug.Assert(false);
                            break;
                    }
                }
            }
 
            base.InitRecursive(namingContainer);
        }
    }
 
    internal override void LoadRecursive() {
 
        // If we're in a cache hit, don't do anything special
        if (_outputString != null) {
            base.LoadRecursive();
            return;
        }
 
        // Make sure the Page knows about us while the control's OnLoad is called
        Page.PushCachingControl(this);
        base.LoadRecursive();
        Page.PopCachingControl();
    }
 
    internal override void PreRenderRecursiveInternal() {
 
        // If we're in a cache hit, don't do anything special
        if (_outputString != null) {
            base.PreRenderRecursiveInternal();
 
            // register the cached styles on the Header control.
            if (_cssStyleString != null && Page.Header != null) {
                Page.Header.RegisterCssStyleString(_cssStyleString);
            }
 
            return;
        }
 
        // Make sure the Page knows about us while the control's OnPreRender is called
        Page.PushCachingControl(this);
        base.PreRenderRecursiveInternal();
        Page.PopCachingControl();
    }
 
 
    /// <internalonly/>
    public override void Dispose() {
        if (_cacheDependency != null) {
            _cacheDependency.Dispose();
            _cacheDependency = null;
        }
 
        base.Dispose();
    }
 
    internal abstract Control CreateCachedControl();
 
 
    /// <devdoc>
    ///    <para> Gets or sets the CacheDependency used to cache the control output.</para>
    /// </devdoc>
    public CacheDependency Dependency {
        get { return _cacheDependency; }
        set { _cacheDependency = value; }
    }
 
 
    public ControlCachePolicy CachePolicy {
        get {
            // Create the ControlCachePolicy object on demand
            if (_cachePolicy == null)
                _cachePolicy = new ControlCachePolicy(this);
 
            return _cachePolicy;
        }
    }
 
    internal HttpCacheVaryByParams VaryByParams {
        get {
            if (_varyByParamsCollection == null) {
                _varyByParamsCollection = new HttpCacheVaryByParams();
                _varyByParamsCollection.IgnoreParams = true;
            }
 
            return _varyByParamsCollection;
        }
    }
    
    internal string VaryByControl {
        get {
            if (_varyByControlsCollection == null)
                return String.Empty;
 
            return String.Join(varySeparatorString, _varyByControlsCollection);
        }
        
        set {
            if (String.IsNullOrEmpty(value)) {
                _varyByControlsCollection = null;
            }
            else {
                _varyByControlsCollection = value.Split(varySeparator);
            }
        }
    }
    
    internal TimeSpan Duration {
        get {
            // Special case MaxValue
            if (_utcExpirationTime == DateTime.MaxValue)
                return TimeSpan.MaxValue;
 
            return _utcExpirationTime - DateTime.UtcNow;
        }
        
        set {
            if (value == TimeSpan.MaxValue) {
                // If it's the max timespan, just make it DateTime.MaxValue to avoid
                // an overflow when adding (VSWhidbey 273271)
                _utcExpirationTime = DateTime.MaxValue;
            }
            else {
                // Compute the expiration time
                _utcExpirationTime = DateTime.UtcNow.Add(value);
            }
        }
    }
 
    private void RegisterValidationEvents() {
        if (_registeredCallDataForEventValidation != null) {
            foreach (RegisterCallData registerCallData in _registeredCallDataForEventValidation) {
                Page.ClientScript.RegisterForEventValidation(registerCallData.StringParam1,
                    registerCallData.StringParam2);
            }
        }
    }
 
    internal void RegisterStyleInfo(SelectorStyleInfo selectorInfo) {
        if (_registeredStyleInfo == null) {
            _registeredStyleInfo = new ArrayList();
        }
 
        _registeredStyleInfo.Add(selectorInfo);
    }
 
    /// <internalonly/>
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    protected internal override void Render(HtmlTextWriter output) {
        CacheDependency sqlCacheDep = null;
 
        // If the output is cached, use it and do nothing else
        if (_outputString != null) {
            output.Write(_outputString);
            RegisterValidationEvents();
            return;
        }
 
        // If caching was turned off, just render the control
        if (_cachingDisabled || !RuntimeConfig.GetAppConfig().OutputCache.EnableFragmentCache) {
            _cachedCtrl.RenderControl(output);
            return;
        }
 
        // Create SQL cache dependency before we render the page
        if (_sqlDependency != null) {
            sqlCacheDep = SqlCacheDependency.CreateOutputCacheDependency(_sqlDependency);
        }
 
        _cacheEntry.CssStyleString = GetCssStyleRenderString(output.GetType());
 
        // Create a new HtmlTextWriter, with the same type as the current one (see ASURT 118922)
        StringWriter tmpWriter = new StringWriter();
        HtmlTextWriter tmpHtmlWriter = Page.CreateHtmlTextWriterFromType(tmpWriter, output.GetType());
        CacheDependency cacheDep;
        TextWriter savedWriter = Context.Response.SwitchWriter(tmpWriter);
 
        try {
            // Make sure the Page knows about us while the control's OnPreRender is called
            Page.PushCachingControl(this);
            _cachedCtrl.RenderControl(tmpHtmlWriter);
            Page.PopCachingControl();
        }
        finally {
            Context.Response.SwitchWriter(savedWriter);
        }
 
        _cacheEntry.OutputString = tmpWriter.ToString();
 
        // Send the output to the response
        output.Write(_cacheEntry.OutputString);
 
        // Cache the output
 
        cacheDep = _cacheDependency;
 
        if (sqlCacheDep != null) {
            if (cacheDep == null) {
                cacheDep = sqlCacheDep;
            }
            else {
                AggregateCacheDependency aggr = new AggregateCacheDependency();
 
                aggr.Add(cacheDep);
                aggr.Add(sqlCacheDep);
                cacheDep = aggr;
            }
        }
 
        ControlCachedVary cachedVary = null;
        string realItemCacheKey;
        // If there are no varies, use the non-varying key
        if (_varyByParamsCollection == null && _varyByControlsCollection == null && _varyByCustom == null) {
            realItemCacheKey = _cacheKey;
        }
        else {
            string[] varyByParams = null;
                if (_varyByParamsCollection != null)
                    varyByParams = _varyByParamsCollection.GetParams();
 
            cachedVary = new ControlCachedVary(varyByParams, _varyByControlsCollection, _varyByCustom);
 
            HashCodeCombiner combinedHashCode = new HashCodeCombiner(_nonVaryHashCode);
            realItemCacheKey = ComputeVaryCacheKey(combinedHashCode, cachedVary);
        }
 
        // Compute the correct expiration, sliding or absolute
        DateTime utcExpirationTime;
        TimeSpan slidingExpiration;
        if (_useSlidingExpiration) {
            utcExpirationTime = Cache.NoAbsoluteExpiration;
            slidingExpiration = _utcExpirationTime - DateTime.UtcNow;
        }
        else {
            utcExpirationTime = _utcExpirationTime;
            slidingExpiration = Cache.NoSlidingExpiration;
        }
        
        try {
            OutputCache.InsertFragment(_cacheKey, cachedVary,
                                       realItemCacheKey, _cacheEntry,
                                       cacheDep /*dependencies*/,
                                       utcExpirationTime, slidingExpiration,
                                       _provider);
        }
        catch {
            if (cacheDep != null) {
                cacheDep.Dispose();
            }
            throw;
        }
    }
 
    // Return the key used to cache the output
    private string ComputeNonVaryCacheKey(HashCodeCombiner combinedHashCode) {
        // Create a cache key by combining various elements
 
        // Start with the guid
        combinedHashCode.AddObject(_guid);
 
        // Make the key vary based on the type of the writer (ASURT 118922)
        HttpBrowserCapabilities browserCap = Context.Request.Browser;
        if (browserCap != null)
            combinedHashCode.AddObject(browserCap.TagWriter);
 
        return CacheInternal.PrefixPartialCachingControl + combinedHashCode.CombinedHashString;
    }
 
    private string ComputeVaryCacheKey(HashCodeCombiner combinedHashCode,
        ControlCachedVary cachedVary) {
 
        // Add something to the has to differentiate it from the non-vary hash.
        // This is needed in case this method doesn't add anything else to the hash (VSWhidbey 194199)
        combinedHashCode.AddInt(1);
 
        // Get the request value collection
        NameValueCollection reqValCollection;
        HttpRequest request = Page.Request;
        if (request != null && request.HttpVerb == HttpVerb.POST) {
            // Bug 6129: Partial cache key should include posted form values in postbacks.
            // Include both QueryString and Form values (but not Cookies or Server Variables like Request.Params does).
            // Per Request.Params behavior, add QueryString values before Form values
            reqValCollection = new NameValueCollection(request.QueryString);
            reqValCollection.Add(request.Form);
        }
        else {
            // Use the existing value if possible to avoid recreating a NameValueCollection
            reqValCollection = Page.RequestValueCollection;
            // If it's not set, get it based on the method
            if (reqValCollection == null) {
                reqValCollection = Page.GetCollectionBasedOnMethod(true /*dontReturnNull*/);
            }
        }
 
        if (cachedVary._varyByParams != null) {
 
            ICollection itemsToUseForHashCode;
 
            // If '*' was specified, use all the items in the request collection.
            // Otherwise, use only those specified.
            if (cachedVary._varyByParams.Length == 1 && cachedVary._varyByParams[0] == "*")
                itemsToUseForHashCode = reqValCollection;
            else
                itemsToUseForHashCode = cachedVary._varyByParams;
 
            // Add the items and their values to compute the hash code
            foreach (string varyByParam in itemsToUseForHashCode) {
 
                // Note: we use to ignore certain system fields here (like VIEWSTATE), but decided
                // not to for consistency with pahe output caching (VSWhidbey 196267, 479252)
 
                combinedHashCode.AddCaseInsensitiveString(varyByParam);
                string val = reqValCollection[varyByParam];
                if (val != null)
                    combinedHashCode.AddObject(val);
            }
        }
 
        if (cachedVary._varyByControls != null) {
 
            // Prepend them with a prefix to make them fully qualified
            string prefix;
            if (NamingContainer == Page) {
                // No prefix if it's the page
                prefix = String.Empty;
            }
            else {
                prefix = NamingContainer.UniqueID;
                Debug.Assert(!String.IsNullOrEmpty(prefix));
                prefix += IdSeparator;
            }
 
            prefix += _ctrlID + IdSeparator;
 
            // Add all the relative vary params and their values to the hash code
            foreach (string varyByParam in cachedVary._varyByControls) {
 
                string temp = prefix + varyByParam.Trim();
                combinedHashCode.AddCaseInsensitiveString(temp);
                string val = reqValCollection[temp];
                if (val != null)
                    combinedHashCode.AddObject(reqValCollection[temp]);
            }
        }
 
        if (cachedVary._varyByCustom != null) {
            string customString = Context.ApplicationInstance.GetVaryByCustomString(
                Context, cachedVary._varyByCustom);
            if (customString != null)
                combinedHashCode.AddObject(customString);
        }
 
        return CacheInternal.PrefixPartialCachingControl + combinedHashCode.CombinedHashString;
    }
 
    private string GetCssStyleRenderString(Type htmlTextWriterType) {
        // Nothing to do if no styles are registered.
        if (_registeredStyleInfo == null) {
            return null;
        }
 
        // Create an empty cssStringWriter
        StringWriter cssStringWriter = new StringWriter(CultureInfo.CurrentCulture);
 
        // Create a new HtmlTextWriter, with the same type as the current one
        HtmlTextWriter cssHtmlTextWriter = 
            Page.CreateHtmlTextWriterFromType(cssStringWriter, htmlTextWriterType);
 
        CssTextWriter cssWriter = new CssTextWriter(cssHtmlTextWriter);
 
        foreach (SelectorStyleInfo si in _registeredStyleInfo) {
            HtmlHead.RenderCssRule(cssWriter, si.selector, si.style, si.urlResolver);
        }
 
        // Return the css style rendered string
        return cssStringWriter.ToString();
    }
 
    internal void SetVaryByParamsCollectionFromString(string varyByParams) {
 
        Debug.Assert(_varyByParamsCollection == null);
 
        if (varyByParams == null)
            return;
 
        string[] varyByParamsStrings = varyByParams.Split(varySeparator);
        _varyByParamsCollection = new HttpCacheVaryByParams();
        _varyByParamsCollection.SetParams(varyByParamsStrings);
    }
 
    internal void RegisterPostBackScript() {
        RegisterClientCall(ClientAPIRegisterType.PostBackScript, String.Empty, null);
    }
 
    internal void RegisterFocusScript() {
        RegisterClientCall(ClientAPIRegisterType.FocusScript, String.Empty, null);
    }
 
    internal void RegisterWebFormsScript() {
        RegisterClientCall(ClientAPIRegisterType.WebFormsScript, String.Empty, null);
    }
 
    private void RegisterClientCall(ClientAPIRegisterType type,
        ScriptKey scriptKey, string stringParam2) {
 
        // Keep track of the call, in order to be able to call it again when there is a cache hit.
 
        RegisterCallData registerCallData = new RegisterCallData();
        registerCallData.Type = type;
        registerCallData.Key = scriptKey;
        registerCallData.StringParam2 = stringParam2;
 
        if (_cacheEntry.RegisteredClientCalls == null)
            _cacheEntry.RegisteredClientCalls = new ArrayList();
 
        _cacheEntry.RegisteredClientCalls.Add(registerCallData);
    }
 
    private void RegisterClientCall(ClientAPIRegisterType type,
        string stringParam1, string stringParam2) {
        RegisterClientCall(type, stringParam1, stringParam2, null);
    }
 
    private void RegisterClientCall(ClientAPIRegisterType type,
        string stringParam1, string stringParam2, string stringParam3) {
 
        // Keep track of the call, in order to be able to call it again when there is a cache hit.
 
        RegisterCallData registerCallData = new RegisterCallData();
        registerCallData.Type = type;
        registerCallData.StringParam1 = stringParam1;
        registerCallData.StringParam2 = stringParam2;
        registerCallData.StringParam3 = stringParam3;
 
        if (_cacheEntry.RegisteredClientCalls == null)
            _cacheEntry.RegisteredClientCalls = new ArrayList();
 
        _cacheEntry.RegisteredClientCalls.Add(registerCallData);
    }
 
    internal void RegisterScriptBlock(ClientAPIRegisterType type, ScriptKey key, string script) {
        RegisterClientCall(type, key, script);
    }
 
    internal void RegisterOnSubmitStatement(ScriptKey key, string script) {
        RegisterClientCall(ClientAPIRegisterType.OnSubmitStatement, key, script);
    }
 
    internal void RegisterArrayDeclaration(string arrayName, string arrayValue) {
        RegisterClientCall(ClientAPIRegisterType.ArrayDeclaration,
            arrayName, arrayValue);
    }
 
    internal void RegisterHiddenField(string hiddenFieldName, string hiddenFieldInitialValue) {
        RegisterClientCall(ClientAPIRegisterType.HiddenField,
            hiddenFieldName, hiddenFieldInitialValue);
    }
 
    internal void RegisterExpandoAttribute(string controlID, string attributeName, string attributeValue) {
        RegisterClientCall(ClientAPIRegisterType.ExpandoAttribute, controlID, attributeName, attributeValue);
    }
 
    internal void RegisterForEventValidation(string uniqueID, string argument) {
        RegisterClientCall(ClientAPIRegisterType.EventValidation, uniqueID, argument);
    }
}
 
 
/// <devdoc>
///    <para>[To be supplied.]</para>
/// </devdoc>
public class StaticPartialCachingControl : BasePartialCachingControl {
 
    private BuildMethod _buildMethod;
 
 
    /// <internalonly/>
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    public StaticPartialCachingControl(string ctrlID, string guid, int duration,
        string varyByParams, string varyByControls, string varyByCustom,
        BuildMethod buildMethod)
        :this(ctrlID, guid, duration, varyByParams, varyByControls,
            varyByCustom, null, buildMethod, null)
    {
    }
 
 
    /// <internalonly/>
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    public StaticPartialCachingControl(string ctrlID, string guid, int duration,
        string varyByParams, string varyByControls, string varyByCustom, string sqlDependency,
        BuildMethod buildMethod)
        :this(ctrlID, guid, duration, varyByParams, varyByControls,
            varyByCustom, sqlDependency, buildMethod, null)
    {
    }
 
    public StaticPartialCachingControl(string ctrlID, string guid, int duration,
        string varyByParams, string varyByControls, string varyByCustom, string sqlDependency, 
        BuildMethod buildMethod, string providerName) {
        _ctrlID = ctrlID;
        Duration = new TimeSpan(0 /*hours*/, 0 /*mins*/, duration /*seconds*/);
 
        SetVaryByParamsCollectionFromString(varyByParams);
 
        if (varyByControls != null)
            _varyByControlsCollection = varyByControls.Split(varySeparator);
        _varyByCustom = varyByCustom;
        _guid = guid;
        _buildMethod = buildMethod;
        _sqlDependency = sqlDependency;
        _provider = providerName;
    }
 
    internal override Control CreateCachedControl() {
        return _buildMethod();
    }
 
    /*
     * Called by generated code (hence must be public).
     * Create a StaticPartialCachingControl and add it as a child
     */
 
    /// <internalonly/>
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    static public void BuildCachedControl(Control parent, string ctrlID, string guid,
        int duration, string varyByParams, string varyByControls, string varyByCustom,
        BuildMethod buildMethod) {
        BuildCachedControl(parent, ctrlID, guid, duration, varyByParams,
            varyByControls, varyByCustom, null, buildMethod, null);
    }
 
 
    /// <internalonly/>
    /// <devdoc>
    ///    <para>[To be supplied.]</para>
    /// </devdoc>
    static public void BuildCachedControl(Control parent, string ctrlID, string guid,
        int duration, string varyByParams, string varyByControls, string varyByCustom, string sqlDependency,
        BuildMethod buildMethod) {
        BuildCachedControl(parent, ctrlID, guid, duration, varyByParams,
            varyByControls, varyByCustom, sqlDependency, buildMethod, null);
    }
 
    static public void BuildCachedControl(Control parent, string ctrlID, string guid,
        int duration, string varyByParams, string varyByControls, string varyByCustom, string sqlDependency, 
        BuildMethod buildMethod, string providerName) {
 
        StaticPartialCachingControl pcc = new StaticPartialCachingControl(
            ctrlID, guid, duration, varyByParams, varyByControls, varyByCustom, sqlDependency,
            buildMethod, providerName);
 
        ((IParserAccessor)parent).AddParsedSubObject(pcc);
    }
}
 
 
/// <devdoc>
///    <para>[To be supplied.]</para>
/// </devdoc>
public class PartialCachingControl : BasePartialCachingControl {
 
    private IWebObjectFactory _objectFactory;
    private Type _createCachedControlType;
    private object[] _args;
 
 
    public Control CachedControl { get { return _cachedCtrl; } }
 
    internal PartialCachingControl(IWebObjectFactory objectFactory, Type createCachedControlType,
        PartialCachingAttribute cacheAttrib, string cacheKey, object[] args) {
        string providerName = cacheAttrib.ProviderName;
        _ctrlID = cacheKey;
        Duration = new TimeSpan(0 /*hours*/, 0 /*mins*/, cacheAttrib.Duration /*seconds*/);
 
        SetVaryByParamsCollectionFromString(cacheAttrib.VaryByParams);
 
        if (cacheAttrib.VaryByControls != null)
            _varyByControlsCollection = cacheAttrib.VaryByControls.Split(varySeparator);
        _varyByCustom = cacheAttrib.VaryByCustom;
        _sqlDependency = cacheAttrib.SqlDependency;
        if (providerName == OutputCache.ASPNET_INTERNAL_PROVIDER_NAME) {
            providerName = null;
        }
        _provider = providerName;
        _guid = cacheKey;
        _objectFactory = objectFactory;
        _createCachedControlType = createCachedControlType;
        _args = args;
    }
 
    internal override Control CreateCachedControl() {
 
        Control cachedControl;
 
        if (_objectFactory != null) {
            cachedControl = (Control) _objectFactory.CreateInstance();
        }
        else {
            // Instantiate the control
            cachedControl = (Control) HttpRuntime.CreatePublicInstance(_createCachedControlType, _args);
        }
 
        // If it's a user control, do some extra initialization
        UserControl uc = cachedControl as UserControl;
        if (uc != null)
            uc.InitializeAsUserControl(Page);
 
        cachedControl.ID = _ctrlID;
 
        return cachedControl;
    }
}
 
/*
 * Holds param names that this cached item varies by.
 */
[Serializable]
internal class ControlCachedVary {
    private           Guid      _cachedVaryId;
    internal readonly string[]  _varyByParams;
    internal readonly string    _varyByCustom;
    internal readonly string[]  _varyByControls;
 
    internal Guid CachedVaryId { get { return _cachedVaryId; } }
 
    internal ControlCachedVary(string[] varyByParams,
        string[] varyByControls, string varyByCustom) {
        _varyByParams = varyByParams;
        _varyByControls = varyByControls;
        _varyByCustom = varyByCustom;
        _cachedVaryId = Guid.NewGuid();
    }
 
    public override bool Equals (Object obj) {
 
        if (!(obj is ControlCachedVary))
            return false;
 
        ControlCachedVary cv = (ControlCachedVary) obj;
 
        return  _varyByCustom == cv._varyByCustom               &&
                StringUtil.StringArrayEquals(_varyByParams, cv._varyByParams) &&
                StringUtil.StringArrayEquals(_varyByControls, cv._varyByControls);
    }
 
    public override int GetHashCode () {
        HashCodeCombiner hashCodeCombiner = new HashCodeCombiner();
        
        // We need non-randomized hash code for _varyByCustom
        hashCodeCombiner.AddInt(StringUtil.GetNonRandomizedHashCode(_varyByCustom));
        
        hashCodeCombiner.AddArray(_varyByParams);
        hashCodeCombiner.AddArray(_varyByControls);
        return hashCodeCombiner.CombinedHash32;
    }
}
 
}