Community Server Source Code - Abstract Classes, Reflection and Data Providers

In my post, Data Access Application Block Revealed - Factory Methods and Reflection, I talked about how the Enterprise Library Data Access Application Block used abstract classes and reflection to instantiate data providers to provide a extensible solution for data storage.  As luck would have it, Community Server also exploits the concepts of abstract classes, reflection, and data providers to pull off a similar architecture for their data storage.  To reinforce these concepts, let's dig into the Community Server source code to show how a post is viewed on a blog.

View-Post.ascx is the user control responsbile for displaying a post, and it basically just hands the work off to the class, EntryViewContainer.  Here is a snippet of EntryViewContainer:

 

EntryViewContainer
public class EntryViewContainer :  WeblogThemedControl
{
    // ...

    void BindData()
    {
        CSContext csContext = CSContext.Current;

        BlogPostQuery query = new BlogPostQuery();
        query.PostID = csContext.PostID;

        string name = Context.Request.QueryString["PostName"];
        if(!Globals.IsNullorEmpty(name))
            query.Name = name;

        query.IncludeCategories = false;
        query.ReturnFullThread = true;
        query.BlogID = CurrentWeblog.SectionID;
        
        PostSet ps =  WeblogPosts.GetPosts(query,true);
 
    // ...
}

 

One of the interesting things here that I won't really go into is the use of a BlogPostQuery object to pass parameters that essentially end up as parameters for stored procedures.  This is a nice way to package up parameters as opposed to having several overloads of GetPosts that take varying parameters.

As you can see, EntryViewContainer just grabs the BlogID and PostID along with a few defaults and calls WeblogPosts.GetPosts to go get the post to be displayed.  It actually returns a PostSet object, which is basically a thread of posts - the main post and the flow of comments to the post.

WeblogPosts is essentially just a set of functions that handle post related activity.  You could call it a Controller class of sorts as it is the first object beyond the UI layer that is responsible for receiving or handling a system operation message (See Applying UML and Patterns: Controller GRASP Pattern - Model View Controller Design Pattern - First Object Beyond UI Layer).

 

WeblogPosts Class
public class WeblogPosts
{
    private WeblogPosts(){}
    
    // ...

    public static PostSet
GetPosts(BlogPostQuery query,
bool cacheable) { string key = cacheable ? query.Key : null; PostSet ps = null; if(cacheable) ps = CSCache.Get(key) as PostSet; if(ps == null) { WeblogDataProvider wdp =
WeblogDataProvider.Instance(); ps = wdp.GetPosts(query); if(cacheable) CSCache.Insert(key,ps,30,
System.Web.Caching.CacheItemPriority.Low); }
return ps; } // ... }

 

Here is where the caching is done as well as where we start to see the data providers coming into action.  WeblogDataProvider is an abstract class with not much more than a single method, Instance(), that grabs the concrete data provider class that handles the storage operations for blogs.  The concrete class returned is the class that actually carries out the operation to get the post and its comments from data storage.  Here is some of the code in WeblogDataProvider:

 

WeblogDataProvider Class
public abstract class WeblogDataProvider
{
    public static readonly string
WeblogDataProviderName = "WeblogDataProvider"; // ... private static WeblogDataProvider _defaultInstance = null; static WeblogDataProvider() { CreateDefaultCommonProvider(); } public static WeblogDataProvider Instance() { return _defaultInstance; } private static void CreateDefaultCommonProvider() { CSConfiguration config = CSConfiguration.GetConfig(); Provider sqlForumsProvider =
(Provider) config.Providers[WeblogDataProviderName]; _defaultInstance =
DataProviders.CreateInstance(sqlForumsProvider)
as WeblogDataProvider; } // ... }

 

The main work happens in the CreateDefaultCommonProvider method.  It goes out and grabs the configuration information which contains the name of the concrete class that will be handling the data storage and retrieval of blog posts.  This information is tucked away in a config file, called communityserver.config, that has the following entry:

 

communityserver.config
<add 
    name = "WeblogDataProvider" 
    type = "CommunityServer.Data.WeblogSqlDataProvider,
CommunityServer.SqlDataProvider" connectionStringName = "SiteSqlServer"
databaseOwnerStringName = "SiteSqlServerOwner" />

 

There is the concrete class that will actually be handling the blog storage and retrieval: WeblogSqlDataProvider.  You can guess that we will need to dynamically create this class during runtime.  This is the job of the DataProviders class, which is just a set of helper functions for just that thing:

 

DataProviders Class
public sealed class DataProviders
{

    private DataProviders()
    {
    }

    // ...

    public static object
CreateInstance(Provider dataProvider) { string connectionString = null; string databaseOwner = null; GetDataStoreParameters(dataProvider,
out connectionString, out databaseOwner); Type type = Type.GetType(dataProvider.Type); object newObject = null; if(type != null) { newObject = Activator.CreateInstance(type,
new object[]{databaseOwner,connectionString}); } if(newObject == null) ProviderException(dataProvider.Name); return newObject; } // ... }

 

The CreateInstance method is the work horse that uses reflection to create an instance of the concrete class, WeblogSqlDataProvider, that will eventually get returned to the controller class, WeblogPosts.

As you can see below, WeblogSqlDataProvider is making use of the BlogPostQuery object to pass out the values for the parameters and essentially just calling the stored procedure that returns a few resultsets of data to be passed back as a PostSet.

 

WeblogSqlDataProvider
public  class WeblogSqlDataProvider : WeblogDataProvider
{

// ...

public override PostSet GetPosts(BlogPostQuery query)
{
    using( SqlConnection connection = GetSqlConnection() ) 
    {
        using(SqlCommand command = new SqlCommand(databaseOwner
+ ".cs_weblog_Postset", connection)) { command.CommandType = CommandType.StoredProcedure; command.Parameters.Add("@SectionID",
SqlDbType.Int).Value
= query.BlogID;
            // ...

            PostSet ps = new PostSet();

            connection.Open();
            
            using(SqlDataReader reader =
command.ExecuteReader()) {
                if(reader.Read())
                {
                    WeblogPost entry = new WeblogPost();
                    PopulateWeblogEntryFromIDataReader
(reader,entry);
if(query.IncludeCategories) { reader.NextResult(); ArrayList categories = new ArrayList(); while(reader.Read()) { categories.Add(reader["Name"]
as string); } entry.Categories = (string[])
categories.ToArray(
typeof(string)); } ps.Posts.Add(entry); //comment list reader.NextResult(); while(reader.Read()) { entry = new WeblogPost(); PopulateWeblogEntryFromIDataReader
(reader,entry); ps.Posts.Add(entry); } reader.NextResult(); ps.TotalRecords
= (int)
command.Parameters[
"@TotalRecords"].Value; reader.Close(); } else { reader.Close(); } } return ps; } } } // ... }
 

When you break down the source code like this it becomes pretty clear how all these fundamental OOP techniques come into play .  There are a few other goodies in Community Server that are probably worth talking about at a later time, too.

posted on Friday, April 01, 2005 7:49 PM

My Links

Post Categories

Article Categories

Archives

Loose-Leaf Tea