Click here to Skip to main content
15,860,972 members
Articles / Web Development / HTML

sBlog.Net - A Minimalistic Blog Engine - Using ASP.NET MVC 3

Rate me:
Please Sign up or sign in to vote.
4.94/5 (55 votes)
16 Aug 2013CPOL51 min read 195.8K   6.4K   182   47
sBlog.Net is a minimalistic blog engine created using the ASP.NET MVC 3 framework.

Default home screen

Agenda

Introduction

In a very few words, sBlog.Net is a minimalistic blog engine. This project was heavily inspired by wordpress, though there are a lot more to come in the following days! You will be able to do all of the normal activities that you could do in a blog like adding a post or page, adding categories or tags, adding additional authors and many more. If I have to describe sBlog.Net in a single sentence, I would say, “For the love of asp.net mvc and wordpress”! I could already foresee a lot of why reinvent the wheel questions. But this is just an attempt at something really simplistic, which gives the ability for others to easily modify and play with the code, instead of using a massive system without understanding the inner working. Also refer to the faq for my attempt at explaining why reinvent the wheel! I am pretty sure, it would take very little to modify and extend this blog engine to something you need within a few hours! To get started, you just need an instance of Visual Studio 2010 with ASP.Net MVC 3, MS SQL server (express should also be fine) and optionally IIS.

Before I perform a deep-dive inside the code, let me first give you a series of steps to get started with setting up a development environment and a test environment, because, getting an idea of how the blog engine looks would definitely be helpful in understanding the discussion below. In case you want to look at an online demo, check it out here! Also, this is the project page, where you could find more screen shots and a brief description of about the project!!!

Setting up the Development Environment

This section quickly lists the steps to get started with the development version. You need to have Visual Studio 2010 (or 2008/2012) and MS SQL Server (full / express).

  • Download the sBlog.Net sources and open it in Visual Studio
  • Launch MS SQL server, create a new database for use by your blog - as an administrator or a user who has db_creator  privileges. If the database was created by the user that is going to be used in the web.config file, skip the following  steps to give db_owner access.
    • Then, if you don't already have a login, create a login
    • Now, you need to provide access to the newly created database for the user you created in the previous step
    • To do this, expand Security, then expand Logins, right click on the user your created, choose Properties 
    • In the dialog that appears, choose User Mapping
    • In the right pane, select the database you created, check the checkbox corresponding to the new database
    • In the Database role membership section, choose db_owner, click on OK
  • Go to visual studio, expand the sBlog.Net project, open the web.config file and modify the connection string corresponding to your server
  • Hit on Ctrl + F5, you will get to the setup screen. Just follow the on-screen instructions from now on!

Setting Up a Test Environment - IIS

This section deals with the part of setting up a test environment. To use these steps your copy of windows must have IIS enabled with all the necessary features (IIS can be installed/enabled from the windows features in the control panel, however, it may vary according to the type of windows operating system).

  • Download the sBlog.Net binaries zip file and extract the files.
  • Launch MS SQL server, create a new database for use by your blog - as an administrator or a user who has db_creator  privileges. If the database was created by the user that is going to be used in the web.config file, skip the following  steps to give db_owner access.
    • Then, if you don't already have a login, create a login
    • Now, you need to provide access to the newly created database for the user you created in the previous step
    • To do this, expand Security, then expand Logins, right click on the user your created, choose Properties
    • In the dialog that appears, choose User Mapping
    • In the right pane, select the database you created, check the checkbox corresponding to the new database
    • In the Database role membership section, choose db_owner, click on OK
  • Now (assuming your IIS directory is C:\inetpub\wwwroot), create a folder called sblog
  • Copy the contents of the extracted folder to the folder you created in the previous step
  • Now, right click on the "Uploads" folder, choose Properties. Then select the "Security" tab
  • Click on "Edit", if you do not find IIS_IUSRS add it. Then select the user, give "Full Control" for the  Uploads folder, click on OK in the dialogs opened
  • Then, open the web.config file and modify the connection string to use the new database, user you created
  • Now open IIS manager, from the Start menu or by entering inetmgr in your Run dialog
  • Create a new website by right clicking on the Sites node and choosing Add Web Site
  • In the Add Web Site dialog, 
    • Enter a site name
    • Select ASP.Net 4.0 application pool (or) create a new app pool that uses the ASP.Net 4.0 framework
    • In the Physical path text box, choose the folder (sblog), created in the previous step
    • Now select OK
    Now your website is all set to be used
  • To launch the web site, right click on it, choose "Manage Web Site" and then "Browse"

The on-screen instructions are easy to follow. But if you need more information refer to this blog post!

Organization of the Project

The sBlog.Net solution consists of 8 projects altogether. I have listed them below with a short description for every item:

  • sBlog.Net - This project has the blog engine's core. The framework used is ASP.Net MVC 3
  • sBlog.Net.DB - This project contains the files relevant to managing the sBlog.Net database. This includes things like managing what scripts have been run and what have not been. Changes to this project is part of the version 2.0 release and this would make your life a lot easier, since you do not have run the scripts manually unlike before!
  • sBlog.Net.Domain - This project contains the abstract interfaces, their concrete implementations, some extensions and utilities shared by other projects in the solution
  • sBlog.Net.MetaData - The metadata related to various view models used in the sBlog.Net project is managed here. Also, the attributes used by the metadata classes are also added to this project
  • sBlog.Net.Akismet - Comment spam is a major issue for blog engines. So sBlog.Net has Akismet support in-built to validate comments submitted. However, by default the comments are not validated using akismet. You will have to enable this from the settings section, once the blog setup is complete
  • MVCSocialHelper - Social sharing is very much required in order to gain exposure. So sBlog.Net has social sharing in-built with the help of the open source project http://mvcsocialhelper.codeplex.com/
  • sBlog.Net.Tests - Do I even have to explain this project?! Quintessential unit test project!
  • sBlog.Net.Tools - This project contains tools/utilities that will be be used during the pre and post-build event(s). As of now the project contains a javascript minifier which is used during the post-build event, where the administration section javascript files are minified. The minified script is used when the project is run in the release mode

Saving/Restoring the Settings Related to the Blog

For a project of this scale, it is really necessary to provide the blog owner ability to update things at ease, without having to modify the source, as you have to remember this is not just a php application where you could just go on to the server hosting your application and modify it (even if it is php, I am not advicing you do this though!). So, "one size fits all" is not appropriate for a blog engine as it has to be highly configurable. Thus we need the ability to save and restore settings related to an instance of the blog. There are number of ways to do this. You could just have an xml file or a custom file for this. But this will also reside on the file system and so if the web application is compromised, it also compromises the settings of the blog. So I decided to go with the choice of saving the settings in the database. Another aspect to consider is the fact that this is going to be an ever-changing table. So a flat table with a column each for a setting won't work. So I went for a very simple table based on key / value pairs, so that the changes to this table will be minimal, without having to add columns infinitely as the project might grow in to a stage where there are numerous setting items to be stored (as of now there are 19 entries). Given below is the create table statement corresponding to this table (named sBlog_Settings).

SQL
CREATE TABLE [dbo].[sBlog_Settings](
    [KeyName] [varchar](50) NOT NULL,
    [KeyValue] [varchar](max) NULL
) ON [PRIMARY]

The administration side has a corresponding settings page where you get to manage these settings. A screenshot of this page is shown below:

Settings page

Identifying the Status of the Setup

One of my main intention was to make sure that even people who do not know anything about ASP.Net to use it. So I wanted the process of setting up the blog to be as simple as possible. So when they follow the method to set up using the binaries, the on-screen instructions should help them setup the blog without having to drill down to the code. So, it is important that during the initial run the blog owner to be able to just follow the on-screen intructions to setup the blog. 

In order to deal with identifying whether the setup for the blog is complete or not, wea may have to use the Application_Start and Session_Start events that are part of the Global.asax.cs file. If you recall, Application_Start method is invoked whenever the application starts for the first time or when the application restarts. Some activities that can cause an application to restart are (not limited to):

  • Modifications to the web.config file
  • Recycling the application pool assigned to the website
  • Restarting the web site
  • Restarting the IIS server itself
C#
protected void Application_Start()
{
    // -- Snip --
    VerifyInstallation();
}

private void VerifyInstallation()
{
    var settingsRepository = InstanceFactory.CreateSettingsInstance();
    var pathMapper = InstanceFactory.CreatePathMapperInstance();
    var dbStatusGenerator = new SetupStatusGenerator(schemaInstance, pathMapper);
    Application["Installation_Status"] = dbStatusGenerator.GetSetupStatus();
}

If you notice in the above snippet, there is a call to the VerifyInstallation method during the Application_Start event. This method does something very simple - First an instance of the settings repository and path mapper instance is created using the instance factory. Note that this is in the Global.asax.cs and we do not have the application running fully. So we cannot use dependency injection here. However, as this instance is created using the ninject dependency management module, disposing off this instance will also be taken care by it. Version 1's method of handling installation status was pretty simple and straight-forward, but, since v2.0 does a lot more like web based database management, the current method is more involved. To begin with I have a SetupStatusGenerator helper class that frees the Global.asax.cs file from lifting heavy stuff! Here is the most important method of this class:

C#
public SetupStatus GetSetupStatus()
{
    var setupStatus = new SetupStatus();

    try
    {
        var allEntries = GetSchemaVersions();
        var sortedList = _pathMapper.GetAvailableScripts();

        var schemaInstance = allEntries.LastOrDefault();
        var lastInstance = sortedList.LastOrDefault();

        if (lastInstance != null && lastInstance.Equals(schemaInstance))
        {
           setupStatus.StatusCode = SetupStatusCode.NoUpdates;
           setupStatus.Message = "Your instance is up to date!";
        }
        else
        {
            setupStatus.StatusCode = SetupStatusCode.HasUpdates;
            setupStatus.Message = "Your instance has some updates";
        }
     }
     catch (Exception exception)
     {
          if (exception.Message == "Invalid object name 'Schema'.")
          {
             setupStatus.StatusCode = SetupStatusCode.DatabaseNotSetup;
             setupStatus.Message = "Database has not been setup";
          }
          else
          {
              setupStatus.StatusCode = SetupStatusCode.DatabaseError;
              setupStatus.Message = exception.Message;
          }
      }

      return setupStatus;
}

Since this is the core method that decides the status of the installation, I cannot just throw exceptions occurring, since there is no other module to handle this. Thus, when there is an exception, I check if the exception is because the application cannot find the Schema table. In that case, this database has never been setup, so you get to see the installation screen. However, other database related errors like invalid connection string would take you to the maintenance screen giving you some instructions on what could have gone wrong.

If there are no exceptions, first I get the list of scripts run already using GetSchemaVersions. The sBlog.Net project contains the .sql files to be run within the Sql folder in the root. These scrips are now translated in to a list in the next line. Now it is just a matter of few steps to check if there are any pending scripts and if any inform the user and present the update form! I am going to leave this part to you, since this could itself be a separate article!

This method could fail for any number of reasons, such as, invalid connection string, invalid database name etc etc. Either ways, the status of the installation is stored in an application variable called Installation_Status. More discussion about dependency injection follows next!

Now that we have seen how the application status is determined, lets now see what happens once the application is started. After the application is started, among other activities that takes place, we are more worried about when a session starts. This happens when a user has accessed the website and the first request arrives. Whenever a new session is started, the application variable I discussed earlier is checked to make sure the user is redirected to a setup page, instead of loading the application, as shown below: 

C#
protected void Session_Start()
{
    var databaseStatus = (SetupStatus)Application["Installation_Status"];
    var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);

    if (databaseStatus != null && databaseStatus.StatusCode == SetupStatusCode.NoUpdates)
    {
       var installationStatus = GetInstallationStatus();

       if (installationStatus == false)
       {
             Response.Redirect(urlHelper.RouteUrl("SetupIndex"), true);
       }
     }
     else
     {
          if (databaseStatus != null && databaseStatus.StatusCode == SetupStatusCode.DatabaseError)
          {
              Response.Redirect(urlHelper.RouteUrl("SetupError"), true);
          }

          if (databaseStatus != null && databaseStatus.StatusCode == SetupStatusCode.DatabaseNotSetup)
          {
              Response.Redirect(urlHelper.RouteUrl("InitializeDatabase"));
          }
      }
}

Depending on the setup status in the application variable, user is either redirected to the installation page, update page or the error page. Remember, you cannot redirect users to a certain page from the Application_Start event. Here is the "install" page for the blog!

Install screen

The setup consists of two steps. In the first step, to make sure you are the owner, you are asked to enter the connection string for the application. Once it is verified, you get to see the Continue >> button to move on to step 2. Note that the text box to enter the connection string and the button do not appear until 2 criteria are satisified - (1) The connection string in the database should be valid (2) The Uploads folder should be "writeable", i.e., IIS_IUSRS user should have full access to this folder (and nothing else!). In the second page of the setup, you get to choose the blog's name, root url and the administrator password (default user name is admin).

Dependency Injection

Managing connections is an integral part in dealing with an application that has anything and everything to do with databases. So managing them yourself may not be advisable. Adding a dependency injection framework definitely helps, as the process of disposing of instances created are managed by itself. sBlog.Net project uses NInject for managing dependencies. This facilitates loose coupling between various modules, which enables you to replace the modules independent of each other. The NinjectControllerFactory class takes care of the process by which a controller instance gets the required dependencies. This class extends the DefaultControllerFactory class provided by the mvc framework. Application_Start method is used to setup ninject as shown below:

C#
protected void Application_Start()
{
    // -- Snip --

    SetupDependencyManagement();

    // -- Snip --
}

// -- Snip --

private void SetupDependencyManagement()
{
    var ninjectControllerFactory = new NinjectControllerFactory();
    ControllerBuilder.Current.SetControllerFactory(ninjectControllerFactory);
    DependencyResolver.SetResolver(new NinjectDependencyResolver(ninjectControllerFactory.GetKernel()));
}

The SetupDependencyManagement method handles the process of setting up ninject. The first step is to create the ninject controller factory as discussed earlier. Then this instance passed on to the SetControllerFactory method of the current ControllerBuilder object. In the next step, the GetKernel method of NinjectControllerFactory is used to get a reference to the kernel in order to create a dependency resolver using the NinjectDependencyResolver class, which is then passed to the SetResolver. I will elaborate on dependency resolver towards the end of this section!

The GetControllerInstance method in order to return an instance of the required controller. Given below is the method:

C#
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
    {
        try
        {
            var defaultController = base.GetControllerInstance(requestContext, null);
            return defaultController;
        }
        catch (HttpException httpException)
        {
            if (httpException.GetHttpCode() == (int) HttpStatusCode.NotFound)
                throw new UrlNotFoundException("Unable to find a controller");
            throw;
        }
    }

    return (IController)_kernel.Get(controllerType);
}

In line 3, if the framework was unable to create a controller instance, it passes a null. If not, NInject takes the responsibility of creating an instance of the controller by passing in the required dependencies. Apart from the GetControllerInstance method, there is a private class that is used as a "kernel" by the ninject module. The first line of the class creates a kernel as shown below: 

C#
private readonly IKernel _kernel = new StandardKernel(new ApplicationIocServices());

The kernel above will be used by the ninject module to find/create instances based on the dependency required. The ApplicationIocServices class is the one that serves as the provider of a binding between an abstract instance and a concrete instance. inherit's from the NinjectModule class and implements the Load method. This is the method where an interface is bound to a concrete class. 

C#
private class ApplicationIocServices : NinjectModule
{
    public override void Load()
    {
        Bind<IUser>().To<User>();
        Bind<IPost>().To<Post>();
        // -- snip --
    }
}

In the above snippet, you can observe that the interfaces IUser and IPost are bound to the concrete entities User and Post. These dependencies in the form of an interface can be used as shown below, in the HomeController.

C#
public class HomeController : BlogController
{
    private readonly int _postsPerPage;
    private readonly IPost _postRepository;
    private readonly IUser _userRepository;
    private readonly ICategory _categoryRepository;
    private readonly ITag _tagRepository;
    private readonly ICacheService _cacheService;

    public HomeController(IPost postRepository, IUser userRepository, 
           ICategory categoryRepository, ITag tagRepository, 
           ISettings settingsRepository, ICacheService cacheService)
        : base (settingsRepository)
    {
        _postRepository = postRepository;
        _userRepository = userRepository;
        _categoryRepository = categoryRepository;
        _tagRepository = tagRepository;
        _postsPerPage = settingsRepository.BlogPostsPerPage;
        _cacheService = cacheService;
    }

    // -- snip --
}

Every interface such as IPost, IUser etc., also forces the user to implement the IDisposable interface, thereby safely disposing the database connections. When NInject disposes an instance it created, it also calls the Dispose method, thereby automatically disposing the sql connections created. Here is the definition for the IUser interface.

C#
public interface IUser : IDisposable
{
    UserEntity GetUserObjByUserID(int userID);
    UserEntity GetUserObjByUserName(string userName, string passWord);
    // -- snip --
}

I know there has been a lot of discussion about DI, but there is one more thing I need to point out as promised in the previous section. As far as I have to inject the dependencies to a controller, I am in a good shape as ninject takes care of that automatically once I set the controller factory for the application. But I also have to consider situations where ninject cannot inject the dependencies automatically. An instance is the html helpers where ninject has no control. So, at these scenarios I use a dependency resolver. Earlier, I introduced you to the method which binds an abstract type to a concrete type. So ninject already knows about all of the bindings. So, when you request a service by passing in a abstract type, it will be able to return an instance of the concrete type. To enable ninject to do this, I have created a dependency resolver called NinjectDependencyResolver that implements the IDependencyResolver interface provided by ninject, as given below:

C#
public class NinjectDependencyResolver : IDependencyResolver
{
    private readonly IKernel _kernel;

    public NinjectDependencyResolver(IKernel kernel)
    {
        _kernel = kernel;
    }

    public object GetService(Type serviceType)
    {
        return _kernel.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return _kernel.GetAll(serviceType);
    }
}

I guess you remember the line below, which informs ninject that it should use NinjectDependencyResolver as the dependency resolver:

C#
DependencyResolver.SetResolver(new NinjectDependencyResolver(ninjectControllerFactory.GetKernel()));

After this step, I have the ability to get a dependency anytime and anywhere even if the class is not a controller class. Consider a section of the GetCommonScriptsAndStyles html extension to get the scripts and styles for the blog.

C#
public static MvcHtmlString GetCommonScriptsAndStyles(this HtmlHelper htmlHelper)
{
    // -- Snip --    
    var settingsRepository = InstanceFactory.CreateSettingsInstance();
    
    // -- Snip --
}

Notice line 2, where I get an instance of the settings repository using the CreateSettingsInstance method in the InstanceFactory class. Given below is the content of this method:

C#
public static ISettings CreateSettingsInstance()
{
    return DependencyResolver.Current.GetService<ISettings>();
}

An instance of the concrete type for ISettings abstract type is created in this method using the GetService method which is a method provided by the current instance of the application's dependency resolver. Thus, instead of adding a method to the NinjectControllerFactory class, and using the kernel to create an instance, I just let ninject itself to handle the process of creating instances for classes other than the controller.

Ability to Cache Data to Reduce the Number of Database Calls

Let us consider the scenario where a user browses to our blog engine. By default, the blog engine picks the first 5 posts and displays it. The user now has the option to either view the previous 5 posts or click on an individual post or filter posts by categories/ tags/ month & year combination. Irrespective of the type, note that we have to go to the database every time to get the posts. In order to avoid this and make the blog more responsive, I have added the ability to get the posts from the cache. By default the cache duration is set to 5 minutes in the ApplicationConfiguration class as shown below:

C#
private const int DefaultCacheDuration = 5;
public static int CacheDuration
{
    get
    {
        var cacheDuration = ConfigurationManager.AppSettings["CacheDuration"];
        int parsedDuration;
        return int.TryParse(cacheDuration, out parsedDuration) ? parsedDuration : DefaultCacheDuration;
    }
}

Note the property CacheDuration which is used by the application to get the cache duration. In the first line, I attempt to get the cache duration from <appSettings /> section, which is the value corresponding to the CacheDuration key.

XML
<appSettings>
    // -- Snip --
    <add key="CacheDuration" value="5"/>
</appSettings>

In the last step, I try to parse the value as an integer. If at all it fails, I return the default cache duration of 5. Let us now see how this cache duration is put in to use. If you recall, I earlier discussed about the ApplicationIocServices.Load method that bound an abstract instance to a concrete instance. This method also binds the ICacheService type to the CacheService concrete type as shown below:

C#
private class ApplicationIocServices : NinjectModule
{
    public override void Load()
    {
        // -- Snip --
        Bind<ICacheService>().To<CacheService>();
    }
}

This dependency is requested only in the controllers that would use them. For instance, controllers in the administration section does not need the cache service, because once the user is logged in, he/she should be able to view up to date content. Given below is a part of the home controller.

C#
public class HomeController : BlogController
{
    private readonly int _postsPerPage;
    private readonly IPost _postRepository;
    private readonly IUser _userRepository;
    private readonly ICategory _categoryRepository;
    private readonly ITag _tagRepository;
    private readonly ICacheService _cacheService;

    public HomeController(IPost postRepository, IUser userRepository, 
      ICategory categoryRepository, ITag tagRepository, 
      ISettings settingsRepository, ICacheService cacheService)
        : base (settingsRepository)
    {
        // -- Snip --
    }

    // -- Snip --

    private List<PostEntity> GetPostsInternal()
    {
        var posts = Request.IsAuthenticated ? GetProcessedPosts(
          _postRepository.GetPosts(GetUserId())) : 
          _cacheService.GetPostsFromCache(_postRepository, CachePostsUnauthKey);
        return posts;
    }
}

Notice the constructor - it gets an ICacheService instance which is used in the GetPostsInternal method. GetPostsFromCache is a fluent extension for the ICacheService interface. It is given below:

C#
public static List<PostEntity> GetPostsFromCache(
    this ICacheService cacheService, IPost postRepository, string keyName)
{
    return cacheService.Get(keyName, () => postRepository.GetPosts());
}

This method takes in a Post repository and a key name, while being invoked with an instance of cache service. This method invokes the Get method by passing in the keyname and a callback for the method to execute if the cahce does not contain this item identified by the key passed. It will be clear once I introduce you to the Get method.

C#
public class CacheService : ICacheService
{
    public T Get<t>(string cacheID, Func<t> getItemCallback) where T : class
    {
        var item = HttpRuntime.Cache.Get(cacheID) as T;
        if (item == null)
        {
            item = getItemCallback();
            HttpContext.Current.Cache.Insert(cacheID,item,null,DateTime.Now.AddMinutes(
              ApplicationConfiguration.CacheDuration),Cache.NoSlidingExpiration);
        }
        return item;
    }
}

In the previous paragraph, I discussed about the callback that the method would have to use to update the cache if the data is not available. Non-availability of data happens either during the initial launch of the application or when the cache duration expires. This is where the CacheDuration property comes in to the picture. Let us drill in to the method even more for me to explain this. The first line in this method first attempts to get the data in the cache by using the HttpRuntime.Cache.Get method and casts it to the generic type parameter T. If the item is null, the callback passed is used to get the data and is first inserted in to the cache. Then the data is returned to the user. Notice the 4th parameter which uses the cahce duration we discussed earlier. This is a fixed cache duration, after which the contents of the cache identified by the key passed is invalidated. Last parameter is used to pass a sliding duration, which implies that the duration passed will renew every time the data from the cache is accessed. So assuming it is set to 10, when the data is accessed for the first time the cache item is created. Then after 5 minutes, if the cache item is accessed, the duration is again set to 10 minutes and so on.

You may think why I chose to have the cache duration key in the web.config file instead of the settings, thereby providing the ability to update the cache time from administration pages. Before I get in to that, I am sure you know that an application will be restarted if you make changes to the web.config file corresponding to that application by this time. This in turn clears out all the items in the cache, clears out application variables, session variables and so on. This is one reason I wanted to have the cache duration key in the web.config file, thereby clearing out the cache whenever you update it.

Authenticating the Users

This project uses a custom membership provider in order to assume complete control of how a user is authenticated. The first step in implementing a custom membership provider is to create a class that extends the MembershipProvider class. This class has a number of methods, but at this point, the emphasis is on few methods and properties - create a user, get a user entity by passing the username, minimum password length, whether a unique email is required and validating a user. Then you have to modify the web.config to inform the mvc framework to use the custom membership provider to deal with logins and logouts. The sBlog.Net's web.config file already has the custom membership element added with the help of the <membership /> element. Note the fully qualified name in the type attribute, and the connectionStringName attribute.

XML
<membership defaultProvider="CustomMembershipProvider">
  <providers>
    <clear/>
    <add name="CustomMembershipProvider" 
        type="sBlog.Net.Infrastructure.CustomMembershipProvider"
        connectionStringName="AppDb"
        enablePasswordRetrieval="false"
        enablePasswordReset="true"
        requiresQuestionAndAnswer="false"
        requiresUniqueEmail="false"
        maxInvalidPasswordAttempts="5"
        minRequiredPasswordLength="6"
        minRequiredNonalphanumericCharacters="0"
        passwordAttemptWindow="10"
        applicationName="/" />
  </providers>
</membership>

This article of mine discusses more about custom membership providers. Feel free to have a look at this as adding that content over here would make this article too long, that you would give up reading it Smile | <img src=

The Base Controller

All the controllers in this project inherits from the BlogController which inherits from the Controller class, which is what all the controllers should inherit, in order to be usable as a "controller". This base controller provides number of properties and methods that will be shared across other controllers in the project. It also does the most important part of deciding which layout page to use depending on the theme chosen by the user. Consider the OnActionExecuted method given below:

C#
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
    var action = filterContext.Result as ViewResult;

    if (action != null && !string.IsNullOrEmpty(ExpectedMasterName))
    {
        var themeName = SettingsRepository.BlogTheme;

        if (ThemeExists(themeName))
        {
            action.MasterName = MasterExists(themeName) ? string.Format(LayoutFormat, themeName,                                    
                   ExpectedMasterName):string.Format(DefaultLayoutFormat,ExpectedMasterName);
        }
        else
        {
            throw new InvalidThemeException("Invalid theme {0}", themeName);
        }
    }
    
    base.OnActionExecuted(filterContext);
}

The base controller has a property called ExpectedMasterName. This property is set only in the controllers that serve the main blog. The admin section controllers do not set this property. So this method first checks if this property is set. If so, it gets the blog's theme from the settings and checks if the theme exists - whether a folder with the specified name exists in the "Themes" folder. Then, it checks whether the theme folder contains a layout file. If so, that layout file is used. Otherwise, the common layout available in the root's Views folder is used. If the ExpectedMasterName is set, but the theme folder does not exist, the application throws an exception and halts.

Let me also discuss a few more methods that are integral to the project. Given below is the GetUserId method:

C#
protected int GetUserId()
{
    var userId = -1;
    if (Request.IsAuthenticated)
    {
        var userIdentity = (IUserInfo)User.Identity;
        userId = Int32.Parse(userIdentity.UserId);
    }
    return userId;
}

This method returns the user id for the user logged in. If nobody is logged in it just returns -1. Every controller exposes a User property, which implements the IPrincipal interface and this object exposes a property called IIdentity. This object hsa a lot of useful information that could be used, if the current user is logged in. The User object is initialized after a user is authenticated in the Global.asax.cs file during the PostAuthenticateRequest event, as given below:

C#
public override void Init()
{
    PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest;
    base.Init();
}

void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
    var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie != null)
    {
        var encTicket = authCookie.Value;
        if (!String.IsNullOrEmpty(encTicket))
        {
            var ticket = FormsAuthentication.Decrypt(encTicket);
            var id = new UserIdentity(ticket);
            var prin = new GenericPrincipal(id, null);
            HttpContext.Current.User = prin;
        }
    }
}

Note lines 16 - 18 in the code snippet above. Once a user is successfully authenticated, this method first verifies the authentication cookie, and then creates an instance of UserIdentity, which implements the IIdentity (required) interface provided by the framework and also I have created an interface called IUserInfo, which has 2 properties - UserId and UserToken. These 2 properties are used across the application to get information related to the user. The usage of UserId should be immediately obvious to you. But, UserToken may not be. In sBlog.Net, a number of activities are carried out using ajax requests. In order to safely serve these requests, apart from checking if the request is authenticated, I also validate the token passed from the client side. The request is served only if the user is authenticated and the user's token matches the token of the user in the database. This token is set/reset during every login and is unique for a particular user. Given below is a section of the code in the manage comments view. Whenever a page has to issue ajax requests, this page also has to pass the one time token for the user. So it is rendered as shown below:

XML
@Html.HiddenFor(model => Model.OneTimeCode)

Once the token is rendered in the view, it can be used as shown below. This is part where an ajax request is performed to delete a comment. Line 9 is of prime interest. Data passed to the url also includes the one time token along with the id of the element to be deleted.

JavaScript
$('.trashComment').click(function (e) {
    e.preventDefault();
    var anchor = this;
    var commentId = $(anchor).next('.trashCommentID').val();

    $.ajax({
        type: 'GET',
        url: siteRoot + 'Admin/CommentAdmin/TrashComment',
        data: { 'commentId': parseInt(commentId), 'token': $('#OneTimeCode').val() },
        dataType: 'json',
        success: function (data) {
            if (data.DeleteStatusString == "Delete succeeded") {
                var row = $(anchor).parent().parent().parent();
                $(row).remove();
            }
        },
        error: function (req, status, err) {
            alert('an error occurred while trying to delete the selected comment, please try again.');
        }
    });
});

All of this discussion was to give you a background for the following method, which provides a way for the controllers to get the one time token for the user logged in.

C#
protected string GetToken()
{
    var userIdentity = (UserIdentity)User.Identity;
    return userIdentity.UserToken;
}

Replacing The Default String Hashing Mechanism

Now that we have discussed about how a user is authenticated, let us now see how it can be customized to our needs. The web.config has a very important key hasher, which decides how your strings are hashed. The value for this key by default is sBlog.Net.Domain.Hashers.Md5Hasher which is the fully qualified name for a class that implements the IHasher interface. Here is the interface definition:

C#
public interface IHasher
{
    string HashString(string srcString);
}

The default hasher, Md5Hasher just returns the calculated md5 hash of the string entered using a utility class already available in the project, as shown below. Note that it implements the IHasher interface.

C#
public class Md5Hasher : IHasher
{
   public string HashString(string srcString)
   {
       return HashExtensions.GetMD5Hash(srcString);
   }
}

As you all might have heard, MD5 is not the most safest way to hash a string. So, I would always advice replacing this with a more secure hasher, such as SHA-1. In order to replace the default hasher, first you have to create a new hasher that implements the IHasher interface. For example, create a new class ShaHasher under the same namespace as the Md5Hasher. Then implement the IHasher interface and implement the method to hash a string passed. In order to instruct the blog engine to use this, update the web.config key to the fully qualified name of the new type you created, say, sBlog.Net.Domain.Hashers.ShaHasher.

Few Words About the Password Helper

The PasswordHelper class uses this hasher to hash passwords which are combined with the user code's, so that the username and passwords will not be vulnerable to dictionary attacks. Let us divert our attention to the PasswordHelper class for a few minutes. The class definition is given below:

C#
public static class PasswordHelper
{
    public static string GenerateHashedPassword(string userPassword, string randomCode)
    {
        var hasher = Hasher.Instance;
        var hashedPassword = hasher.HashString(string.Format("{0}{1}", userPassword, randomCode));
        return hashedPassword;
    }
}

sBlog.Net hashes passwords along with a salt, which is generated by the blog engine itself and is stored in the database along with other information for a user. As an additional layer of security, the user code is encrypted in the database. However, the passwords are hashed and not encrypted, as that's the recommended way to store a password. The GenerateHashedPassword method thus takes in the password entered by the user and a decrypted user code. This method is used whenever there is a need to validate the user name and password entered by a user. An important thing to remember is that, once you complete the setup of the blog, you can no longer change this key as this would cause all password validations to fail.

Now that we have seen all about updating the hashing mechanism for the blog, let me briefly get in to how all of this is connected. If you notice, I never discussed about the following line:

C#
var hasher = Hasher.Instance;

Instance is a static property in the Hasher static class. The function of the Hasher class is to create an instance of the hasher defined in web.config using the Hasher key, explained in the previous section. Let us have a look at this class. I find it quite interesting Smile | <img src= ! 

C#
public static class Hasher
{
    private const string DefaultHasher = "sBlog.Net.Domain.Hashers.Md5Hasher";

    public static IHasher Instance
    {
        get
        {
            IHasher iHasher;
            var assemblyName = Assembly.Load("sBlog.Net.Domain").CodeBase;
            try
            {
                var hasher = ApplicationConfiguration.HasherTypeName;
                iHasher = (IHasher)Activator.CreateInstanceFrom(assemblyName, hasher).Unwrap();
            }
            catch
            {
                var instance = Activator.CreateInstanceFrom(assemblyName, DefaultHasher).Unwrap();
                iHasher = (IHasher)instance;
            }
            
            return iHasher;
        }
    }
}

ApplicationConfiguration, as the name implies contains properties that could be shared across the application. It contains a property called HasherType which returns the fully qualified type name specified in the web.config file. The Instance property first tries to get this value and tries to create an instance of that type. Note that it attempts to load from the sBlog.Net.Domain assembly only for now. If the process of creating an instance fails, a default md5 hasher instance is created and retruned. CreateInstanceFrom method is used to create an instance of ObjectHandle by passing the assembly name where this type's metadata could be identified along with the fully qualified type name of the hasher. This ObjectHandle is then converted in to form which could be cast in to a known type using the Unwrap method. So, throughout the blog engine, when hashing is required, we just get an instance from this static class, so that we don't have to worry about intricate details of looking through various assemblies and creating instances for our use.

Custom Binding Complex Data Types - Managing Post/Page Add and Edits

Custom model binding is a great feature of mvc 3 that could be used to bind a complex c# object. In the case of sBlog.Net custom model binding is used to bind the contents of the data posted to a PostViewModel, which also includes a CheckBoxListViewModel property. Let us analyze one of them, which is the binder for PostViewModel. This is called as the PostViewModelBinder and is present in the Binders folder within the sBlog.Net (core) project.

C#
public class PostViewModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var postModel = new PostViewModel
                {
                   Post = new PostEntity {PostID = int.Parse(bindingContext.GetValue("Post.PostID"))    
                   // -- Snip -- 
                };

        postModel.Post.Order = postModel.Post.EntryType == 2 ? 
          (int?)GetOrder(bindingContext.GetValue("Post.Order")) : null;

        IModelBinder ckBinder = new CheckBoxListViewModelBinder();
        postModel.Categories = 
          (CheckBoxListViewModel)ckBinder.BindModel(controllerContext, bindingContext);

        if (postModel.Post.EntryType == 1)
        {
            if (!postModel.Categories.Items.Any(c => c.IsChecked))
            {
                var general = postModel.Categories.Items.SingleOrDefault(c => c.Value == "1");
                if (general != null)
                {
                    general.IsChecked = true;
                }
            }

            postModel.Tags = bindingContext.GetValue("hdnAddedTags");
        }

                return postModel;
    }
        
    private static int GetOrder(object value)
    {
        int parsedValue;
        if (value == null || value.ToString() == string.Empty)
            return int.MaxValue;
        return int.TryParse(value.ToString(), out parsedValue) ? parsedValue : int.MaxValue;
    }
}

I don't want to test your patience by explaining every line in detail. So let me just point out some important points. Note that the class implements the IModelBinder interface and this includes the BindModel method. When the mvc framework invokes this method, it passes a ControllerContext object and a ModelBindingContext object. The ModelBindingContext object contains all the fields posted and the subsequent lines attempts to get the values and creates a new PostViewModel object. A model binder comes in to the picture when we are attempting bind a complex model to an object. As far as the types in the parameter of an action method are simple C# objects, we don't have to worry about model binders and let the mvc framework take care of it. But in this case, one look at the model we are binding will prove that it is not just a simple class that requires primitive binding. Hence the need to create a custom model binder. Given below is the Add method that utilizes model binders.

C#
public ActionResult Add()
{
    // -- Snip --
}

[HttpPost]
[ValidateInput(false)]
public ActionResult Add(PostViewModel postModel)
{
    // -- Snip --
}

Note the PostViewModel parameter in the Add method decorated with the HttpPost attribute. Model binders comes in to the picture at this point. When a post request is issued for the Add method, PostViewModelBinder.BindModel method is called with the necessary parameters. But this method is not called "magically" and you have to do the necessary changes to make this happen. This "magic" is done in the Global.asax.cs file as shown below:

C#
protected void Application_Start()
{
    // -- Snip --

    SetupCustomModelBinders();

    // -- Snip --
}

// -- Snip --

private void SetupCustomModelBinders()
{
    ModelBinders.Binders.Add(typeof(CheckBoxListViewModel), new CheckBoxListViewModelBinder());
    ModelBinders.Binders.Add(typeof(PostViewModel), new PostViewModelBinder());
}

In the Application_Start method in the Global.asax.cs file, the binders have to be registered, in order for the framework to invoke the binders, as shown above. At this point, the first line in the SetupCustomModelBinders method that registers the CheckBoxListViewModel is not invoked by the framework, but by the PostViewModelBinder.BindModel method, as you saw earlier [corresponding line of code is given below]:

C#
postModel.Categories = (CheckBoxListViewModel)ckBinder.BindModel(controllerContext, bindingContext);

Even though the model binder for check box list is not called by the framework, in case you are using a CheckBoxListViewModel in an action method's parameter, decorated with the HttpPost attribute, the framework will call the BindModel method of our custom binder.

Saving a Few Cycles

When you install ASP.Net MVC 3, you have the ability to create your views either using the razor view engine (the default for mvc 3) or the WebForms view engine (default for mvc 2). If you notice, in this project, I only use razor views. So, while thinking about the performance improvements, I read an article on how mvc 3 looks for a view. Consider a simple action method as given below:

C#
public class HomeController
{
   public ActionMethod Index()
   {
      return View();
   }
}

When a user requests this method by entering the url /home/index, asp.net mvc looks for the "Index" view. But notice we do not specify the extension here, that would imply what view engine it uses. So asp.net mvc will look for the Index view created using the razor model and the webforms model. In the case of sBlog.Net, it will find Index.cshtml (razor) and render it. But in order to do this, asp.net mvc first searches for the Index.aspx view and then search for the Index.cshtml view. In this case a cycle of the framework is lost looking for the Index.aspx view. In order to save this cycle, I am going to instruct asp.net mvc to only consider the razor view engine. Before I show that code, consider the screen shot below. In this case the action method returns a view that does not exist and notice the order in which the file is searched for:

Order used for searching a view

From the above screen shot, you would have noticed that first a webforms based view is looked up and then a razor view is looked up. Now, let me introduce you to how I could save a few cycles, with the help of the code snippet below:

C#
protected void Application_Start()
{
   // -- Snip --

   SetupViewEngines();

   // -- Snip --
}

private void SetupViewEngines()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new RazorViewEngine());
}

In the Application_Start method, I call the SetupViewEngines method. This method first clears all the engines added in the Engines property provided by the framework's ViewEngines class (which are the webforms and razor view engines). Then I just add the RazorViewEngine alone, thereby saving a few cycles for every action method call, since asp.net mvc now knows that it need not look for a webforms view when a view is requested. Here is a screen shot showing what happens when the above modification is added and a view that does not exist is rendered. Note that now it is just looking for razor view engine based views!

Order used for searching a view

Generating Unique Slugs

In a blog engine, its extremely critical to have the ability to generate unique slugs for the url of a post or page, tags and categories. For urls, not all strings are valid, as these slugs act as a part of the url. For example, web.config as a part of the url is not valid. So we should make sure it does not get to be part of the url. To make it simple and less prone to errors, before generating a slug, I first strip of any character that is not one of a-z, A-Z, 0-9, space, period and the hypen symbol. Let me first show you some code before I explain in a simple set of steps as to what is going on:

C#
public static string GetUniqueSlug(this string srcString, List<string> allItems)
{
    var regex = new Regex(@"[^a-zA-Z 0-9\.\-]+");
    
    var slug = regex.Replace(srcString.ToLower(), string.Empty);

    slug = ReplaceMatches(slug, @"[ ]{2,}").Replace(" ", "-");
    slug = ReplaceMatches(slug, @"[\.]{2,}").Replace(".", "-");
    slug = ReplaceMatches(slug, @"[\-]{2,}");

    if (slug.StartsWith("-") && !slug.EndsWith("-"))
        slug = string.Format("0{0}", slug);

    if (!slug.StartsWith("-") && slug.EndsWith("-"))
        slug = string.Format("{0}0", slug);

    return allItems.Any(s => s == slug) ? GetUniqueSlugInternal(slug, allItems)  : slug;
}

private static string ReplaceMatches(string srcString, string pattern)
{
    var removalPattern = new Regex(pattern);
    return removalPattern.Replace(srcString, "-");
}

Now that you have seen some code, let me describe very shortly what I am attempting.

  • First, I remove any character that is not an alphabet (lower/upper), number (0-9) and one of space, period, hyphen, along with converting it to lower case, as case does not matter in urls
  • Then I replace 2 or more spaces with a hyphen and a single space with a hyphen
  • Then I replace 2 or more periods with a hyphen and a single period with a hyphen
  • Then I replace multiple hyphens with a single hyphen
  • In the next step, if the slug begins / ends with a hypen (even though not an issue), it is prepended / appended with a "0"
  • Finally, if the slug was never used (identified using allItems, the existing list of slugs), it is returned as it is. Else an internal method to find a slug in the format <slug>-<number> is called by passing the slug formed so far. Its given below:
C#
private static string GetUniqueSlugInternal(string srcString, List<string> srcList)
{
    var slugRegex = new Regex(string.Format(@"^{0}-([0-9]+)$", srcString));
    var matchingSlugs = new List<int>();
    srcList.ForEach(s =>
    {
        var match = slugRegex.Match(s);
        if (match.Success)
        {
            var number = int.Parse(match.Groups[1].Captures[0].Value);
            matchingSlugs.Add(number);
        }
    });
    if (matchingSlugs.Any())
    {
        var max = matchingSlugs.Max();
        return string.Format("{0}-{1}", srcString, max + 1);
    }
    return string.Format("{0}-2", srcString);
}

I find this method to be quite interesting as I need to find an unused url. I cannot just use a finite number to generate a slug in the format I specified before. For example, assume there is already a tag called "f" corresponding to the tag name "F#". Now if the user enters a new tag with name "F", this will also boil down to the slug "f", but it is already used. So we have to find a new slug that does not conflict with the existing tag. So you will have to start looking for availability in the order "f-2","f-3","f-4"... and so on. So it's not advisable to have have a fixed number, say i=99999 and try sequentially. Hence I follow the approach where I define a regex which identifies any tag starting with the conflicting slug, followed by a hyphen and then any number of digits. Then, for all the matches I create a list of numbers and then find the maximum of it. Adding 1 to this number gives a number that is never used. In the case of the situation I described before, there won't be any numbers to look for and this is why I have the default case where I just return the slug in the format <slug>-2.

Minifying the JavaScript Files (Admin Section)

With all the amount of ajax calls involved and a number of other activities, the administration section of this blog engine has a whole lot of javascript content. Even though browsers do not get the javascript files everytime, it is very important to reduce the size of the javascript file that the browser has to save. That's why I thought automatically minifying the javascript files during the build process is favorable. Also, it is necessary to use the minified version of the script files only when the project is running in the release mode. The current mode under which an application is running under IIS can be identified using the <compilation /> element in the web.config file. It's given below:

XML
<compilation debug="true" targetFramework="4.0">
</compilation>

Before I get in to how the application itself identifies whether it is running in debug/release mode, let me discuss about how I handle the minification process. I use jsmin.exe (jsmin.exe by Douglas Crockford) during the build event. To enable easy minification without any complicated logic, the scripts are organized in the following fashion. Within the "Scripts" folder within the sBlog.Net project, there is a folder called "Required". Within this folder there are 2 more folders - "debug" and "minified". The "debug" folder has all the required scripts with the prefix number denoting the order in which they should appear in the minified file. When build is carried out, the minified file is copied on to the "minified" folder. Given below is the command I use to build the minified script.

type "$(ProjectDir)Scripts\Required\debug\*.admin.js" |  "$(SolutionDir)sBlog.Net.Tools\Minifiers\jsmin" > "$(ProjectDir)Scripts\Required\minified\script-bundle.min.js"

To find this command in the project, right click on the sBlog.Net project, select "Properties", then select the "Build Events" tab and in the right pane you will find a text area for "Post-build event command line". This is where this command is entered. This command just lists the contents of each file, redirects it to the jsmin.exe file. The jsmin.exe file in turn minifies it and redirects the output to be appended to the script-bundle.min.js file. Now let us get to the next part of this section where how the application determines the mode it is running under. Even though the web.config file has the compilation element with the debug attribute, you don't have to parse the web.config file or do something fancy to get this information. ASP.Net already takes care of this using the IsDebuggingEnabled property in HttpContext.Current as shown below (from AdminScriptProviderHelpers.cs):

C#
private static bool IsDebug()
{
    return HttpContext.Current.IsDebuggingEnabled;
}

Managing Site Errors

Every error that goes uncaught and unhandled by top level classes are logged in to a table called Errors. The blog owner also has the ability to choose whether or not to receive emails for every global exception that occurs in the blog engine from the settings section. Just registering a HandleErrorAttribute in Global.asax.cs won't be enough in the case of a blog engine. We also have to handle the Application_Error method, in order to catch errors that may not be caught by the handle error attribute, as it happens even before the mvc framework comes in to the picture. Let me drill down more in to this. Consider the following snippet:

C#
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new BlogErrorAttribute());
}

The Application_Start method calls the RegisterGlobalFilters in order to register the error filter. This attribute overrides the OnException method and logs the exception first. Then depending on various properties the GetRedirectResultByExceptionType method decides which error page to redirect the user to. For example, if the action method belonged to an admin level controller, identified by the IsAdminController property which is set in the BlogController base class, part of the IControllerProperties interface. However, life is not as easy as you would wish it to be Smile | <img src= " /> Errors might happen even before the mvc framework has kicked in. For example, in the Application_Start method, or SQL connection errors and so on. That's why I also handle the errors in the Application_Error method as shown below:

C#
protected void Application_Error()
{
    var exception = Server.GetLastError();
    var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);

    if (exception is UrlNotFoundException)
    {
        Log(exception);
        Response.Redirect(urlHelper.RouteUrl("Error404"), true);
    }
    else if (exception is SqlException)
    {
        Response.Redirect(urlHelper.RouteUrl("SetupError"), true);
    }
}

Note that if the exception is of type SqlException, there is no point attempting to log it. So we give up logging and just go on to redirecting the user to a page that informs the user of the issue and halts execution.

Rich Text Editor to Create Posts/ Pages

Okay, we have seen a lot of "backend" stuff and its time for some "frontend" stuff, that a normal user or an administrator of the blog engine views. To begin with let us see a few things on the admin side. I am going to begin with discussing the most important feature of a blog engine - a rich text editor to deal with posts/pages). A rich text editor is an integral part of a blog engine as without one, composing html based content could become really tedious. I use CKEditor for providing this ability. Apart from providing basic ability of composing html based content, it also provides a number of other features such as proiving the ability to hook in to features enabling users to upload files and also provide links in the html content they produce. Using ckeditor is pretty simple. Following are the steps to create one:

  • Add jQuery to the page
  • Add ckeditor.js within the CKEdoitor folder to the page
  • Add jquery.js within the CKEditor\adapters folder to the page
  • As of now, all the files referred to are added to the _LayoutAdmin.cshtml layout page, that will be used for the administration section
  • CKEditor enabled textareas are used across a number of pages - for adding/editing a post, adding/editing a page. So I created a user control called AddEditPost.cshtml that will be shared across all of these pages, enabling reuse without repetitivenes.
  • This user control consists of a textarea input field for which ckeditor plugin will be applied, with the css class specified as adminRichText
  • Finally the site's admin javascript file applies the plugin as shown below:
JavaScript
$(document).ready(function () {
    jQuery('.adminRichText').ckeditor();
}

By following these steps a fully functional rich text/html editor can be created, thereby, enabling the blog authors to easily create html based posts. Given below is a screenshot of how the add page for a post looks like:

Creating a post

And given below is a screenshot of how the add page for a page looks like:

Creating a page

Commenting in sBlog.Net

What is a blog without the ability for the readers to comment and voice their opinions? Its equivalent to a book according to my opinion! So, needless to say, sBlog.Net has commenting included by default! But a widespread issue blogs face is the unstoppable influx of comment spam! I took in to account all of this while designing the commenting system of sBlog.Net.

Getting back to the native commenting system of sBlog.Net, it is quite simple. Every post/page can optionally choose to have a form where users can enter their comments. It asks for basic information like the commenter's name, email (optional) and the comment itself. Simple enough, right? But how do I limit spam without the use of CAPTCHA or some other fancy tool? I resorted to using 2-tiers of securing the commenting system, with one being optional. Let me discuss the 1st tier. The idea is deeply influenced by Growmap anti spam-bot plugin. In this method, apart from the fields specified, a check box is added from the client side using javascript. Thus the checkbox added is not visible to a spam bot as the check box element is not present in the html generated for the comment form. But a "normal" user who wishes to comment can see the checkbox and the label (again added using javascript) associated to this checkbox would instruct the user to "select" this check box to indicate that he is not a spam bot!

Without selecting this check box, the form cannot be submitted and thus a spam bot that operates based on the html input elements cannot submit this form at all! But wait, what if the browser has javascript disabled? Yes, this would bypass this check causing spam to be submitted. That's why I have added akismet support (turned off by default). With akismet, a comment can be verified on the server side and identified whether it qualifies as "ham" (a valid comment) or "spam". This cannot be bypassed by the spam bot, thereby taking care of comment spam!

Blog comments can be enabled / disabled site-wide and can also be disabled on a per post/page basis. This could be useful when you wish to close comments just for a single post / page (say, the "about" page). Every author can manage comments posted to his/her posts and the blog administrator can manage comments site-wide. Refer to the previous section for screen shots indicating how comments can be enabled/disabled per post/page.

And, given below are the screenshots of how the comment management page looks for a blog administrator and a "normal" author.

Admin view of other author's comments

Admin view of other author's comments

Admin view of comments for (admin's) posts

Admin view of comments for (admin's) posts

Author's view of comments section for author's posts

Author's view of comments section for author's posts

With the release of v2.0, sBlog.Net has disqus support! Disqus is a free commenting system that does all the heavy weight lifting of managing comments! Once you register with disqus and get the "disqus short name", you are all set! You can now go to the settings page of your blog, enable disqus and provide this short name. From this point, sBlog.Net takes charge! Every blog post/page gets the disqus commenting system replacing the native commenting of the blog engine. While signing up for disqus you could also provide your existing akismet key for automatic spam protection! Given below is a screen shot that shows disqus in action!

Disqus Integration

Syntax Highlighter & Social Sharing

sBlog.Net has syntax highligheter (by Alex Gorbatchev) and social sharing (with the help of MVC Social Helper). As a developer I always found it helpful by explaining something with the corresponding code, as it makes the life of the reader easier as looking in to small snippets of code can really be helpful in understanding stuff and may also reduce the amount of explanation required in order convey what you intend. Given below is a post the contains some C# code.

Syntax highlighter enabled page

With the advent of various social networks that enable sharing, I also thought its crucial to have social sharing available by default. So, sBlog.Net also has social sharing capability built in. Given below is a screenshot that shows a page which social sharing icons.

Social sharing enabled page

Both syntax highlighter & social sharing can be enabled/disabled site wide from the settings page. Once either of them is enabled, authors also control to enable / disable them just for a single post / page.

Creating a New Theme Using Only Css Files

Now, its time to discuss one of the great feature of sBlog.Net blogging engine. This and the subsequent section will discuss about creating themes in order to customize how the blog's layout is. There are a couple of ways by which you could do this, so, lets start with the "easy" way! By following this method you can quickly create a custom designed theme, instead of using the themes provided by default. This method will work either when using Visual Studio or IIS. The following are the steps to create a theme: 

  • Select the Themes folder 
  • Right click on the Themes folder, select New Folder, enter a name for your theme 
  • At a minimum, you should atleast create a css file. So, within your new folder, create a folder called css 
  • Add a css file, let's name it as style.css 
  • The layouts used for posts (_Layout.cshtml) and pages (_LayoutPage.cshtml) are located in the ~/View/Shared folder. In your style.css file add all of the css you need.       
  • Now, save the changes, build / publish your project (if you are doing this from visual studio) and launch it       
  • Login to the administration section as the administrator and go to the Settings section        
  • Now, if you select the blog theme drop-down you will notice your new theme 
  • Select your new theme and save the changes 
  • You should now be able to see that your theme was applied! 

Creating a New Theme Using Layout Files

This section discusses a much more elaborate way by which you can create a custom designed theme, instead of using the themes      provided by deault. This method will work either when using Visual Studio or IIS. The following are the steps to create a theme that also defines the layout:       

  • Select the Themes folder 
  • Right click on the Themes folder, select New Folder, enter a name for your theme 
  • In this method, you have to create the layouts of your post, page and also the css 
  • Now create 2 files: _Layout.cshtml and _LayoutPage.cshtml 
  • Create a folder called css, add a file called style.css and add the styles you like 
  • Refer to any of the default themes that has these 2 files, add the html to your new files similar to the ones present in the default themes 
  • You can also copy/paste existing layout and modify them as per your needs 
  • Just make sure you have all the sections, like the categories section, recent posts section etc! 
  • Now, save the changes, build / publish your project (if you are doing this from visual studio) and launch it 
  • Login to the administration section as the administrator and go to the Settings section 
  • Now, if you select the blog theme drop-down you will notice your new theme 
  • Select your new theme and save the changes 
  • You should now be able to see that your theme was applied! 

Generating the Rss Feed For the Blog 

Finally, let me talk about rss feeds! Rss feeds are an integral part of any blog engine. It helps users keep track of the status of a blog, without having to visit the blog every time to check if there are any new posts. Every major browser supports subscribing to rss feeds and so its a very important feature in a blog engine. ASP.Net MVC 3 has a number of ActionResult types such as one for returning html, json, content etc, but does not have one for returning rss data. The following class satisfies the issue of the missing link! You could see that it inherits from the ActionResult class and overrides the ExectuteResult method. Notice the 1st line. This line sets the content type to "application/rss+xml", as without this, the browser cannot interpret this page as a rss feed. In the next line an instance of Rss20FeedFormatter is created. Using this formatter, the actual content received from the Output property of the response object and written to the rss formatter.

C#
public class RssActionResult : ActionResult
{
    public SyndicationFeed Feed { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ContentType = "application/rss+xml";
        var rssFormatter = new Rss20FeedFormatter(Feed);
        using (var writer = XmlWriter.Create(context.HttpContext.Response.Output))
        {
            rssFormatter.WriteTo(writer);
        }
    }
}

Let us now see how I am putting this class in to use.

C#
public ActionResult Index()
{
    var rssFeedViewModel = GetRssFeedViewModel();
    var feed = RssFeedGenerator.GetRssFeedData(rssFeedViewModel, Url);
    return new RssActionResult { Feed = feed };
}

Second line in the above method is of more importance. Some properties required to generate the rss feed is passed on to a generator, RssFeedGenerator. This class has a method called GetRssFeedData to generate the feeds. This method generates a SyndicationFeed item, which contains a list of SyndicationItem instances. Every item corresponds to a post in the blog ordered by the most recent item. Note that this feed does not include pages. Finally in the last line of the method an action result of type RssActionResult, we created earlier is returned, by passing in the feed item.

Faqs

Why reinvent the wheel?

Yes, of course, we already have wordpress and a few blog engines in ASP.Net. But, I love wordpress' approach to blogging and so wanted to create something that gives the same experience, using ASP.Net MVC 3, which according to my opinion is one of the best creations of Microsoft! This has been a very good learning experience and I believe it would be the same for all others who are going to experiment with this project. A promise to all of you - this project isn't/won't just be a replica all along and you can expect some interesting features to be added in the near future! This project is highly modularized, so that you can easily plug out something and modify them with your own code. Navigating through the poject would also be simple, as it does not attempt to implement a huge number of features or does not use any complicated logic / code. A number of asp.net based blog engines / cms are created using webforms and quite a few blog engines/cms use mvc 3. So I wanted to create a "simple" blogging experience like wordpress, instead of creating something complex along the lines of orchard (I do agree that it's awesome, but for a beginner in orchard, like me, it was painful to setup and debug issues that arose).

Why didn't you use the Entity Framework

At the point I started working on this, I was not very proficient/ confident with the Entity Framework. (I am getting better though!). But I did provide ways to replace the current database module with anything else - for instance the entity framework or MySQL or even XML!

Okay, fair enough. Is the current commenting system thread-based?

Sorry, at this point no. But, this is also in the roadmap for tasks to be done in the next release!

Should we drill in to the code myself or are there going to be any "knowledge base"?

There is a blog, where I periodically plan to put some stuff about the design and also about how some of the stuff in here could be changed.

Do you have any plans for the next release?

Of course! some of the features I have planned to add in the next release are (apart from the ones mentioned in the earlier questions)...

  • A tag listing page based on usage
  • Threading support for the native commenting system
  • Globalization/localization support
  • Sticky "posts"

Points of Interest

I did have a lot of interesting moments while working on creating this blog engine. One of them is the part where I generate the unique slug. I had to update the method a few times, since at every step I found issue that I had not foreseen, for example, I was generating "web.config" as a valid slug, even though it wasn't!

Another interesting issue I had to tackle was when I was designing the commenting system, as it's crucial to a blog. Commenting enables the blog owner to receive readers' opinions. The commenting section above covered my thoughts regarding this module in detail. Nowadays, at work, I have been working on a performance critical application as it operates on very large datasets. I would like to put that in to use for this blog engine too as I am hell-bent on improving the performance of this blog engine. Even now, the performance is pretty good, but would definitely love to make the start-up times even better with the help of some of the most advanced techniques used in CMS engines such as Orchard. 

Other New Features

One of the newest features is a page that lists the authors of the blog with the provision to list out the posts by a certain author when the name of the author is clicked. The link to the authors page is provided in the side bar, where you have the login/dashboard and other links.

sBlog.Net now supports segregating users by "roles". By default there are 3 roles from which an administrator could choose from for a user - they are: "Super Admin", "Admin" and "Author". A super admin can do anything and everything as the name suggests. An admin can do things like managing users, managing public posts and comments apart from the normal activities like creating a post. An author is limited to "only" creating and managing their own posts and comments.

History

  • Article updated to match the v2.0 release
  • Section about recent developments, updated agenda 
  • Updated the project and demo links
  • Added a new section about the commenting system
  • Added a new section about view engines
  • Updated the dependency injection and custom model binding sections
  • Version 1 of the article released  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Just another passionate software developer!

Some of the contributions to the open source world - a blog engine written in MVC 4 - sBlog.Net. Check it out here. For the codeproject article regarding sBlog.Net click here!

(Figuring out this section!)

Comments and Discussions

 
QuestionGreat Post Pin
Member 820324413-Aug-15 4:40
Member 820324413-Aug-15 4:40 
SuggestionA little complex i think. Pin
zamkinos20-May-15 4:24
zamkinos20-May-15 4:24 
General.net Online Training with Real time project in India | UK | Canada | USA Pin
Member 1109613319-Sep-14 19:41
Member 1109613319-Sep-14 19:41 
QuestionConnection string problem Pin
Nitu Samdani10-Jul-14 19:19
Nitu Samdani10-Jul-14 19:19 
Questionwhat's the system requirement if used in a shared virtual host? Pin
flyingxu31-Mar-14 16:53
flyingxu31-Mar-14 16:53 
QuestionWhy the custom Membership/Role providers? Pin
fuzz_ball24-Jan-14 7:19
fuzz_ball24-Jan-14 7:19 
AnswerRe: Why the custom Membership/Role providers? Pin
Karthik. A6-Feb-14 6:08
Karthik. A6-Feb-14 6:08 
QuestionPls help me add memcache! Pin
philvaptech11-Dec-13 18:13
philvaptech11-Dec-13 18:13 
GeneralMy vote of 5 Pin
ayhantb19-Aug-13 20:50
ayhantb19-Aug-13 20:50 
GeneralRe: My vote of 5 Pin
Karthik. A29-Aug-13 4:53
Karthik. A29-Aug-13 4:53 
QuestionA big problem in article Pin
abdurahman ibn hattab12-Aug-13 21:35
abdurahman ibn hattab12-Aug-13 21:35 
AnswerRe: A big problem in article Pin
Karthik. A16-Aug-13 10:31
Karthik. A16-Aug-13 10:31 
GeneralMy vote of 5 Pin
emrebeysungu23-Jul-13 19:33
emrebeysungu23-Jul-13 19:33 
GeneralRe: My vote of 5 Pin
Karthik. A12-Aug-13 14:35
Karthik. A12-Aug-13 14:35 
GeneralMy vote of 5 Pin
Daniel Chernenkov22-Jul-13 11:56
Daniel Chernenkov22-Jul-13 11:56 
GeneralRe: My vote of 5 Pin
Karthik. A12-Aug-13 14:34
Karthik. A12-Aug-13 14:34 
SuggestionGreat Effort Pin
David Rogers Dev11-May-13 4:33
David Rogers Dev11-May-13 4:33 
GeneralRe: Great Effort Pin
Karthik. A11-May-13 19:32
Karthik. A11-May-13 19:32 
QuestionMy vote of 5! Pin
lalitkale7-May-13 5:44
lalitkale7-May-13 5:44 
AnswerRe: My vote of 5! Pin
Karthik. A7-May-13 6:21
Karthik. A7-May-13 6:21 
GeneralRe: My vote of 5! Pin
lalitkale7-May-13 8:09
lalitkale7-May-13 8:09 
QuestionFive Stars Pin
MaskedDev2-May-13 20:43
professionalMaskedDev2-May-13 20:43 
AnswerRe: Five Stars Pin
Karthik. A7-May-13 6:12
Karthik. A7-May-13 6:12 
GeneralMy vote of 5 Pin
vivekthangaswamy2-Apr-13 20:15
professionalvivekthangaswamy2-Apr-13 20:15 
GeneralRe: My vote of 5 Pin
Karthik. A1-May-13 3:27
Karthik. A1-May-13 3:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.