CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

David Hayden [MVP C#]

         .NET Tutorials, Patterns, and Practices

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;
            command.Parameters.Add("@PostID", SqlDbType.Int).Value = query.PostID;
            command.Parameters.Add("@PostName", SqlDbType.NVarChar).Value = query.Name;
            command.Parameters.Add("@PageIndex", SqlDbType.Int, 4).Value = query.PageIndex;
            command.Parameters.Add("@PageSize", SqlDbType.Int, 4).Value = query.PageSize;
            command.Parameters.Add("@ReturnFullThread", SqlDbType.Bit, 1).Value = query.ReturnFullThread;
            command.Parameters.Add("@UserID", SqlDbType.Int).Value = query.UserID;
            command.Parameters.Add("@IncludeCategories", SqlDbType.Bit).Value = query.IncludeCategories;
            command.Parameters.Add("@TotalRecords", SqlDbType.Int).Direction = ParameterDirection.Output;

            PostSet ps = new PostSet();

            connection.Open();

            //NOTE TO SELF: We return the results as seperate sets so that we can avoid some of the more
            //expensive lookups done ont the main posts!
            
            using(SqlDataReader reader = command.ExecuteReader())
            {
                //this will return just one post, forget the rest if nothing found here
                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));
                    }
                    
                    //we could be setting the thread starter right here?
                    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.



Comments

Brendan Tompkins said:

This is a nice explanation. I've not delved this deep into the actual data provider implementation yet, but I have a question for you..

Is this a variation on the Abstract Factory pattern? If so, what is the benefit of doing it this way? With a traditional AF pattern you wouldn't need the Activator.CreateInstance stuff, which is does have performance issues.

I'm sure there are very good reasons for doing it this way - a "method to the madness."

Community server is a pretty large app.. there are all kinds of neat things to see once you get under the covers, huh?
# April 1, 2005 11:24 AM

Brendan Tompkins said:

I may be a little confused about Abstract Factory... I thought you could do it without the reflection stuff, but now that I think about it, (and read your first post) I see that this is standard.
# April 1, 2005 12:21 PM

David Hayden said:

Hey Brendan,

There is no abstract factory pattern used in the above example.

In this piece of code:

WeblogDataProvider wdp = WeblogDataProvider.Instance();

the Instance() method is often referred to as a Factory Method. It essentially hides the actual class that is being instantiated from the client code.

There is no requirement that the concrete class being created is done via reflection or not.

Enterprise Library and Community Server use reflection only to support instantiating classes in 3rd party assemblies. This creates more of a true plug-in framework.

The Abstract Factory Pattern has to do with creating families of classes, which is not what is being done here.

A good series of articles on design patterns would be a phenomenal contribution to the developer community. So many things to talk about, so little time :)
# April 1, 2005 5:45 PM

Jeff Perrin said:

"With a traditional AF pattern you wouldn't need the Activator.CreateInstance stuff, which is does have performance issues."

Since it looks like the instance will only ever be created once, this isn't really an issue.
# April 2, 2005 8:31 AM

David Hayden said:

In&amp;nbsp;the following two&amp;nbsp;posts:

Community Server Source Code - Abstract Classes, Reflection...
# April 2, 2005 5:59 PM

David Hayden said:

I installed DotNetNuke (DNN) this weekend for the Sarasota, Florida .NET Developer Group website.&amp;nbsp;...
# April 3, 2005 3:59 PM

Liang said:

That is very nice, David!
Quick question: how do you get the source code? I downloaded the project as well from their site. But only ASPX files and DLLs, there has no source code for those DLLs.
# April 4, 2005 7:52 AM

David Hayden said:

Here is the link to download the community server source code for version 1.0:

http://www.telligentsystems.com/Solutions/license.aspx?File=cs_1.0_src.exe
# April 4, 2005 9:08 AM

Liang said:

Very appreciate, David! It is very interesting to look at the source code. The first time I saw this Provider Pattern was from Rainbowportal.
# April 4, 2005 9:46 AM

David Hayden said:

As I&amp;nbsp;mentioned before,&amp;nbsp;Applying UML and Patterns by Craig Larman has an extensive description...
# April 8, 2005 11:25 AM

Jeff Perrin said:

# April 14, 2005 8:26 PM

David Hayden said:

Jeff Perrin has some excellent comments regarding my previous post describing the GRASP Controller Pattern,...
# April 15, 2005 10:22 AM

David Hayden said:

Let's dig&amp;nbsp;a little deeper into Codus,&amp;nbsp;an O/R Mapper from Adapdev Technologies, which I originally...
# May 11, 2005 4:44 PM
Check out Devlicio.us!

Our Sponsors

Free Tech Publications

This Blog

Syndication

News

CodeBetter.Com Home