由于WEB请求是无状态的,在请求过程中需要保存的共享信息保存在HttpContext中,在Http请求过程中HttpContext始终贯穿整个请求过程,在CS2中相当于对HTTPContext进行了扩展,除了包含HTTPContext中的信息外,CSContext还包含了在CS的整个请求过程中需要共享的自己的数据,如当前请求的用户、Section、Post等等公用数据。
由于WEB请求是无状态的,在请求过程中需要保存的共享信息保存在HttpContext中,在Http请求过程中HttpContext始终贯穿整个请求过程,在CS2中相当于对HTTPContext进行了扩展,除了包含HTTPContext中的信息外,CSContext还包含了在CS的整个请求过程中需要共享的自己的数据,如当前请求的用户、Section、Post等等公用数据。
在CS中,一个页面往往涉及到很多用户控件,每个用户控件对应一个类,类和类之间没有明显的联系,这就需要我们提供一个公用数据的类来保存在整个请求过程中的用户数据,在CS的CSContext就是这个作用,这样的好处是,在整个请求过程中公用数据在获取第一次后就保存到CSContext中了,当前请求的其他地方用的时候就不需要重复获取了。让我们来看看CSContext都保存了哪些数据。
打开CSContext类时候会看到类中的字段都分门别类的集合在一起,可以轻易的查看到每类的相关数据,这里简单介绍一下这些数据:

CSContext
//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
// Copyright (c) Telligent Systems Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections.Specialized;
using System.IO;
using System.Threading;
using System.Web;
using System.Collections;
using CommunityServer.Configuration;

namespace CommunityServer.Components


{
public delegate bool UrlReWriterDelegate(HttpContext context);


/**//// <summary>
/// The CSContext represents common properties and settings used through out of a Request. All data stored
/// in the context will be cleared at the end of the request
///
/// This object should be safe to use outside of a web request, but querystring and other values should be prepopulated
///
/// Each CS thread must create an instance of the CSContext using one of the Three Create overloads. In some cases,
/// the CreateEmptyContext method may be used, but it is NOT recommended.
/// </summary>
public sealed class CSContext

{


Private Containers#region Private Containers

//Generally expect 10 or less items
private HybridDictionary _items = new HybridDictionary();
private NameValueCollection _queryString = null;
private string _siteUrl = null, _rewrittenUrlName = null;
private Uri _currentUri;

string rolesCacheKey = null;

string authenticationType = "forms";
bool _isUrlReWritten = false;
bool _isEmpty = false;
string _rawUrl;

HttpContext _httpContext = null;
DateTime requestStartTime = DateTime.Now;

private PageTimeStatus pageTimeStatus = new PageTimeStatus();
#endregion


Initialize and cnstr.'s#region Initialize and cnstr.'s


/**//// <summary>
/// Create/Instatiate items that will vary based on where this object
/// is first created
///
/// We could wire up Path, encoding, etc as necessary
/// </summary>
private void Initialize(NameValueCollection qs, Uri uri, string rawUrl, string siteUrl)

{
_queryString = qs;
_siteUrl = siteUrl;
_currentUri = uri;
_rawUrl = rawUrl;
}


/**//// <summary>
/// cntr called when no HttpContext is available
/// </summary>
private CSContext(Uri uri, string siteUrl)

{
Initialize(new NameValueCollection(), uri, uri.ToString(), siteUrl);
}


/**//// <summary>
/// cnst called when HttpContext is avaiable
/// </summary>
/// <param name="context"></param>
private CSContext(HttpContext context, bool includeQS)

{
this._httpContext = context;

if(includeQS)

{
Initialize(new NameValueCollection(context.Request.QueryString), context.Request.Url, context.Request.RawUrl, GetSiteUrl());
}
else

{
Initialize(null, context.Request.Url, context.Request.RawUrl, GetSiteUrl());
}
}

#endregion


Create#region Create


/**//// <summary>
/// Creates a empty context to be used for thread data storage. This instance of context will not be able to look up a SiteSettings or User.
/// This method is NOT recommended since most of the API relies on a valid User and SiteSettings objects.
/// </summary>
/// <returns></returns>
public static CSContext CreateEmptyContext()

{
CSContext csContext = new CSContext(new Uri("http://CreateEmptyContext"),"http://CreateEmptyContext");
csContext._isEmpty = true;
SaveContextToStore(csContext);
return csContext;
}


/**//// <summary>
/// Creates a new context and sets the SiteSettings to the specific SettingsID. It's primary usage will be for background threads/tasks/offline.
/// </summary>
/// <param name="settingsID">Settings to look up and bind to.</param>
/// <returns></returns>
public static CSContext Create(int settingsID)

{
CSContext csContext = new CSContext(new Uri("http://CreateContextBySettingsID"),"http://CreateContextBySettingsID");
csContext.SiteSettings = SiteSettingsManager.GetSiteSettings(settingsID);
SaveContextToStore(csContext);
return csContext;
}



/**//// <summary>
/// Creates a Context instance based on HttpContext. Generally, this
/// method should be called via Begin_Request in an HttpModule
/// </summary>
public static CSContext Create(HttpContext context)

{
return Create(context,false);
}


/**//// <summary>
/// Creates a Context instance based on HttpContext. Generally, this
/// method should be called via Begin_Request in an HttpModule
/// </summary>
public static CSContext Create(HttpContext context, bool isReWritten)

{
CSContext csContext = new CSContext(context,true);
csContext.IsUrlReWritten = isReWritten;
SaveContextToStore(csContext);

return csContext;
}


/**//// <summary>
/// On occasion, it may be necessary to use the CSContext during UrlRewriting. By default, it is generally not
/// in the correct state and should not be accessed. The signature below will enable the CSContext to be created,
/// saved (so it is available globally) and then perform the UrlRewritng via a delegate.
///
/// Except for QueryString values, the full CSContext should be availble during UrlRewriting.
/// </summary>
/// <param name="context"></param>
/// <param name="rewriter"></param>
/// <returns></returns>
public static CSContext Create(HttpContext context, UrlReWriterDelegate rewriter)

{
CSContext csContext = new CSContext(context,false);
SaveContextToStore(csContext);
csContext.IsUrlReWritten = rewriter(context);
csContext._queryString = new NameValueCollection(context.Request.QueryString);
return csContext;
}


/**//// <summary>
/// Creates a Context instance based. If the HttpContext is available it will be used.
/// Generally this method should be used when working with CS outside of a valid Web Request
/// </summary>
public static CSContext Create(Uri uri, string appName)

{
CSContext csContext = new CSContext(uri,appName);
SaveContextToStore(csContext);
return csContext;
}
#endregion


Core Properties#region Core Properties

/**//// <summary>
/// Simulates Context.Items and provides a per request/instance storage bag
/// </summary>
public IDictionary Items

{

get
{ return _items;}
}

public PageTimeStatus PageTimeStatus

{

get
{ return pageTimeStatus; }

set
{ pageTimeStatus = value; }
}


/**//// <summary>
/// Provides direct access to the .Items property
/// </summary>
public object this[string key]

{
get

{
return this.Items[key];
}
set

{
this.Items[key] = value;
}
}


/**//// <summary>
/// Allows access to QueryString values
/// </summary>
public NameValueCollection QueryString

{

get
{return _queryString;}
}


/**//// <summary>
/// Quick check to see if we have a valid web reqeust. Returns false if HttpContext == null
/// </summary>
public bool IsWebRequest

{

get
{ return this.Context != null;}
}

public bool IsAuthenticated

{

get
{ return !User.IsAnonymous;}
}

public string AuthenticationType

{

get
{ return authenticationType; }

set
{ authenticationType = value.ToLower(); }
}

public string RewrittenUrlName

{

get
{return _rewrittenUrlName;}

set
{_rewrittenUrlName = value;}
}

public HttpContext Context

{
get

{
return _httpContext;
}
}

public string SiteUrl

{

get
{ return _siteUrl; }
}

private Guid _anonUserId = Guid.Empty;
public Guid AnonymousUserID

{
get

{
if(_anonUserId == Guid.Empty && this.IsWebRequest)
_anonUserId = Users.GetAnonyousUserTrackingID(this.Context);

return _anonUserId;
}
}

#endregion


Helpers#region Helpers

public bool IsEmpty

{

get
{ return _isEmpty;}
}

public bool IsUserTokenRequest

{

get
{return !Globals.IsNullorEmpty(this.Token); }
}

public double AddTimeEvent(object obj)

{
return this.PageTimeStatus.AddTimeEvent(obj.GetType());
}

// *********************************************************************
// GetGuidFromQueryString
//

/**//// <summary>
/// Retrieves a value from the query string and returns it as an int.
/// </summary>
// ***********************************************************************/
public Guid GetGuidFromQueryString(string key)

{
Guid returnValue = Guid.Empty;
string queryStringValue;

// Attempt to get the value from the query string
//
queryStringValue = QueryString[key];

// If we didn't find anything, just return
//
if (queryStringValue == null)
return returnValue;

// Found a value, attempt to conver to integer
//
try

{

// Special case if we find a # in the value
//
if (queryStringValue.IndexOf("#") > 0)
queryStringValue = queryStringValue.Substring(0, queryStringValue.IndexOf("#"));

returnValue = new Guid(queryStringValue);
}

catch
{}

return returnValue;

}

// *********************************************************************
// GetIntFromQueryString
//

/**//// <summary>
/// Retrieves a value from the query string and returns it as an int.
/// </summary>
// ***********************************************************************/
public int GetIntFromQueryString(string key, int defaultValue)

{
string queryStringValue;


// Attempt to get the value from the query string
//
queryStringValue = this.QueryString[key];

// If we didn't find anything, just return
//
if (queryStringValue == null)
return defaultValue;

// Found a value, attempt to conver to integer
//
try

{

// Special case if we find a # in the value
//
if (queryStringValue.IndexOf("#") > 0)
queryStringValue = queryStringValue.Substring(0, queryStringValue.IndexOf("#"));

defaultValue = Convert.ToInt32(queryStringValue);
}

catch
{}

return defaultValue;

}

public string MapPath(string path)

{
if(_httpContext != null)
return _httpContext.Server.MapPath(path);
else
// Returns System\WOW for non web // return Directory.GetCurrentDirectory() + path.Replace("/", @"\").Replace("~", "");
return PhysicalPath(path.Replace("/", Path.DirectorySeparatorChar.ToString()).Replace("~", ""));
}

public string PhysicalPath(string path)

{
return string.Concat(RootPath().TrimEnd(Path.DirectorySeparatorChar), Path.DirectorySeparatorChar.ToString() , path.TrimStart(Path.DirectorySeparatorChar));
}

private string _rootPath = null;

private string RootPath()

{
if(_rootPath == null)

{
_rootPath = AppDomain.CurrentDomain.BaseDirectory;
string dirSep = Path.DirectorySeparatorChar.ToString();

_rootPath = _rootPath.Replace("/", dirSep);

string filePath = Config.FilesPath;
if(filePath != null)

{
filePath = filePath.Replace("/", dirSep);

if(filePath.Length > 0 && filePath.StartsWith(dirSep) && _rootPath.EndsWith(dirSep))

{
_rootPath = _rootPath + filePath.Substring(1);
}
else

{
_rootPath = _rootPath + filePath;
}
}
}
return _rootPath;
}

private string GetSiteUrl()

{
string appOverride = this.Config.ApplicationOverride;
if(appOverride != null)
return appOverride;

//NOTE: Watch this change. Should be safe, but not tested.
//Virtualization means urls must be very precise.
string hostName = _httpContext.Request.Url.Host.Replace("www.",string.Empty);
string applicationPath = _httpContext.Request.ApplicationPath;

if(applicationPath.EndsWith("/"))
applicationPath = applicationPath.Remove(applicationPath.Length-1,1);

return hostName + applicationPath;
}
#endregion


CS Data#region CS Data
private User _currentUser = null;
private SiteSettings _currentSettings = null;
private CSConfiguration _config = null;
private SiteStatistics _stats = null;
private Section _section = null;
private Group _group = null;
private Post _post = null;
private int _settingsID = -1;


/**//// <summary>
/// Return the current logged in User. This user value to be anonymous if no user is logged in.
///
/// If this context is IsEmpty (ie, SiteSettings == null) then null will be returned by default.
///
/// This value can be set if necessary
/// </summary>
public User User

{
get

{
//We can not look up a user without SiteSettings
if(_currentUser == null && SiteSettings != null)

{
_currentUser = TokenUser();
if(_currentUser == null)
_currentUser= Users.GetUser(true);
}
return _currentUser;
}

set
{ _currentUser = value;}
}

private User TokenUser()

{
if(!Globals.IsNullorEmpty(this.Token) && !Globals.IsNullorEmpty(this.UserName))

{
Guid g = new Guid(this.Token);
User user = Users.GetUser(0,this.UserName,true,true);
if(user != null && !user.IsAnonymous && user.PublicToken == g)
return user;
}

return null;
}


/**//// <summary>
/// Settings for the current site. This by default, it will use the value found in this.SiteUrl.
///
/// This value is set by ID when using .Create(int settingsID). If IsEmpty == false, this method will
/// return false unless the SiteSettings is explicitly set.
/// </summary>
public SiteSettings SiteSettings

{
get

{
//If this context is empty, we can not look up a SiteSetting.
//We can still set the setting if necessary
if(_currentSettings == null && !IsEmpty)
_currentSettings = SiteSettingsManager.GetSiteSettings(this.SiteUrl);

return _currentSettings;
}

set
{ _currentSettings = value;}
}


/**//// <summary>
/// Returnt the current configuration found in the communityserver.config file
/// </summary>
public CSConfiguration Config

{
get

{
if(_config == null)
_config = CSConfiguration.GetConfig();

return _config;
}
}


/**//// <summary>
/// Return the site statistics for the current SiteSettings. If SiteSettings is null (usually because of using the Emtpy setting)
/// this propety will return null. In other words, there needs to be a valid SiteSettings first.
/// </summary>
public SiteStatistics Statistics

{
get

{
//Same as user. No SiteSettings, no Statistics
if(_stats == null && SiteSettings != null)
_stats = SiteStatistics.LoadSiteStatistics(this.SiteSettings.SettingsID,true,3);

return _stats;

}
}


/**//// <summary>
/// Container for the current post. This must be explicitly or it will always bee null
/// </summary>
public Post Post

{

get
{ return _post;}

set
{_post = value;}
}


/**//// <summary>
/// Container for the current section. This must be explicitly or it will always bee null
/// </summary>
public Section Section

{

get
{ return _section;}

set
{_section = value;}
}


/**//// <summary>
/// Container for the current group. This must be explicitly or it will always bee null
/// </summary>
public Group Group

{

get
{ return _group;}

set
{_group = value;}
}


/**//// <summary>
/// Shortcut to SiteSettings.SettingsID. This proprty will return -1 if
/// the SiteSettings can not be found (or IsEmtpy == true)
/// </summary>
public int SettingsID

{
get

{
if(_settingsID == -1 && !IsEmpty)
_settingsID = this.SiteSettings.SettingsID;

return _settingsID;

}
}

#endregion


Status Properties#region Status Properties

public DateTime RequestStartTime
{ get
{ return requestStartTime; } }

public string RolesCacheKey
{ get
{ return rolesCacheKey; } set
{ rolesCacheKey = value; } }

public bool IsUrlReWritten
{ get
{ return _isUrlReWritten; } set
{ _isUrlReWritten = value; } }

public string RawUrl
{ get
{ return _rawUrl; } set
{ _rawUrl = value; } }

public ApplicationType ApplicationType
{ get
{return Config.AppLocation.CurrentApplicationType;}}
public Uri CurrentUri

{
get

{
if(_currentUri == null)
_currentUri = new Uri("http://localhost/cs");

return _currentUri;


} set
{_currentUri = value;}
}
private string _hostPath = null;
public string HostPath

{
get

{
if(_hostPath == null)

{
string portInfo = CurrentUri.Port == 80 ? string.Empty : ":" + CurrentUri.Port.ToString();
_hostPath = string.Format("{0}://{1}{2}",CurrentUri.Scheme,CurrentUri.Host, portInfo);
}
return _hostPath;
}
}

private bool _isModal = false;
public bool IsModal

{
get

{
return _isModal;
}
set

{
_isModal = value;
}
}

#endregion


Common QueryString Properties#region Common QueryString Properties


Private Members#region Private Members

int sectionID = -2;
int categoryID = -2;
int messageID = -2;
int groupID = -2;
int postID = -2;
int threadID = -2;
int userID = -2;
string userName = null;
int pageIndex = -2;
int blogGroupID = -2;
Guid roleID = Guid.Empty;
string queryText = null;
string returnUrl = null;
string appKey = null;
string url = null;
string args = null;

#endregion

public int MessageID

{
get

{
if(messageID == -2)
messageID = this.GetIntFromQueryString("MessageID", -1);

return messageID;
}

set
{messageID = value;}
}

// [Obsolete("ForumID is obsolete, use the SectionID property")]
// public int ForumID
// {
// get
// {
// if(sectionID == -2)
// sectionID = this.GetIntFromQueryString("ForumID", -1);
//
// return sectionID;
// }
// set {sectionID = value;}
// }

public int SectionID

{
get

{
if(sectionID == -2)

{
//Phasing out calls to ForumID. For now, if SectioID fails to be found, we default to ForumID
sectionID = GetIntFromQueryString("SectionID", GetIntFromQueryString("ForumID",-1));
}
return sectionID;
}

set
{sectionID = value;}
}



public int GroupID

{
get

{
if(groupID == -2)
groupID = this.GetIntFromQueryString("GroupID", GetIntFromQueryString("ForumGroupID", -1));

return groupID;
}

set
{groupID = value;}
}


public int CategoryID

{
get

{
if(categoryID == -2)
categoryID = this.GetIntFromQueryString("CategoryID", -1);

return categoryID;
}

set
{categoryID = value;}
}

public int BlogGroupID

{
get

{
if(blogGroupID == -2)
blogGroupID = this.GetIntFromQueryString("BlogGroupID", -1);

return blogGroupID;
}

set
{blogGroupID = value;}
}


public int PostID

{
get

{
if(postID == -2)
postID = this.GetIntFromQueryString("PostID", -1);

return postID;
}

set
{postID = value;}
}

public int ThreadID

{
get

{
if(threadID == -2)
threadID = this.GetIntFromQueryString("ThreadID", -1);

return threadID;
}

set
{threadID = value;}
}

public int UserID

{
get

{
if(userID == -2)
userID = this.GetIntFromQueryString("UserID", -1);

return userID;
}

set
{userID = value;}
}

public string UserName

{
get

{
if(userName == null)

{
userName = QueryString["UserName"];
}

return userName;
}
set

{
userName = value;
}
}

public string Token

{

get
{ return QueryString["Token"];}
}

public Guid RoleID

{
get

{
if(roleID == Guid.Empty)
roleID = GetGuidFromQueryString("RoleID");

return roleID;
}

set
{roleID = value;}
}



public string QueryText

{
get

{
if(queryText == null)
queryText = QueryString["q"];

return queryText;
}

set
{queryText = value;}
}

public string ReturnUrl

{
get

{
if(returnUrl == null)
returnUrl = QueryString["returnUrl"];

return returnUrl;
}

set
{returnUrl = value;}
}

public string Url

{
get

{
if(url == null)
url = QueryString["url"];

return url;
}

set
{url = value;}
}

public string Args

{
get

{
if(args == null)
args = QueryString["args"];

return args;
}

set
{args = value;}
}

public int PageIndex

{
get

{
if(pageIndex == -2)

{
// load page number from AJAX parameter first
if (this.Context != null && AjaxManager.IsCallBack && AjaxManager.CallBackMethod == "GetPage" && !Globals.IsNullorEmpty(this.Context.Request.Form["Ajax_CallBackArgument0"]))
pageIndex = int.Parse(this.Context.Request.Form["Ajax_CallBackArgument0"]) - 1;
else

{
pageIndex = this.GetIntFromQueryString("PageIndex", GetIntFromQueryString("p",-1));
if(pageIndex != -1)
pageIndex = pageIndex - 1;
else if(pageIndex < 0)
pageIndex = 0;
}
}
return pageIndex;
}

set
{pageIndex = value;}
}


public string ApplicationKey

{
get

{
if(appKey == null)

{
appKey = ApplicationKeyProvider.Instance().GetKey();
}
return appKey;
}

set
{appKey = value;}
}


#endregion


State#region State

private static readonly string dataKey = "CSContextStore";


/**//// <summary>
/// Returns the current instance of the CSContext from the ThreadData Slot. If one is not found and a valid HttpContext can be found,
/// it will be used. Otherwise, an exception will be thrown.
/// </summary>
public static CSContext Current

{
get

{
HttpContext httpContext = HttpContext.Current;
CSContext context = null;
if(httpContext != null)

{
context = httpContext.Items[dataKey] as CSContext;
}
else

{
context = Thread.GetData(GetSlot()) as CSContext;
}

if (context == null)

{
if(httpContext == null)
throw new Exception("No CSContext exists in the Current Application. AutoCreate fails since HttpContext.Current is not accessible.");

context = new CSContext(httpContext,true);
SaveContextToStore(context);
}
return context;
}
}

private static LocalDataStoreSlot GetSlot()

{
return Thread.GetNamedDataSlot(dataKey);
}

private static void SaveContextToStore(CSContext context)

{
if(context.IsWebRequest)

{
context.Context.Items[dataKey] = context;
}
else

{
Thread.SetData(GetSlot(), context);
}
}

public static void Unload()

{
Thread.FreeNamedDataSlot(dataKey);
}
#endregion
}
}

Private Containers :一些简单的内部字段;
Initialize and cnstr.'s:初始化和私有的构造函数;
Create:静态的Create方法,通过这些冲载的Create方法,在最先调用的时候就构造了CSContext的一个对象;
Core Properties:一些核心的属性,这些属性提供了CSContext最常用的功能。其中Items这个的作用是在当前请求过程中,如果有那些常用的针对请求者的数据保存在此,避免在一次请求多次处理,保存在此后,当前请求的其他地方需要用到此数据时就不需要再处理了。我们可以看到在解决方案里搜索“csContext.Items”这样的关键字找到这些应用。
Helpers:为此类的其他方法提供处理程序的一组方法。
CS Data:保存CS特有的公用数据,比如User、SiteSettings、Post、Section等等,这些都是可以公用的数据,所以统一放在这里,一般在用户控件的基类会有这些数据的处理,所以在我们使用的时候调用这些公用数据很方便。
Status Properties:请求状态的一组属性。
Common QueryString Properties:通过请求参数获取一些公用的数据,这些都是在CS中非常常用的参数,如:sectionID、GroupID、postID等等,在应用过程中我们子需要直接使用这些属性就可以了。
State:一组静态属性和方法,在第一次请求的时候通过调用Create方法创建CSContext对象并将对象保存到HttpContext,当以后需要获取CSContext对象的时候再从HttpContext获取,同时CSContext也保存有HttpContext对象的引用。在这个组里还有一个很重要的方法,可以把CSContext保存到其他区域(非HttpContext的地方),这主要是为了提供非Http请求时用的,比如单元测试等等。
CSContext在CS中的作用很重要,理解它是理解CS工作原理的前提,说到低它就是为了共享数据而出现的,在用户控件组成页面的CS中共享数据显得尤为重要,这样的设计方法借鉴到自己的项目也是个很好的选择。