Extreme ASP.NET Makeover: Singleton - AuthChecker Class

Download

Right click “Save as…”

  • Mid Quality WMV (Lo-band, Mobile)
  • MP3 (Audio only)
  • MP4 (iPhone, Android)

Extreme ASP.NET Makeover: Death of a Singleton-AuthChecker Class

I’ve Got Some Seeds, Right Away

So where should we start?  How about the AuthChecker class, since that’s what we’ve discussed through the first part of this article.  Earlier, we decided that there was no reason for it to continue to be a singleton, since there was no compelling reason for one, and only one, instance of it to exist for the life of the application.

The process that we’re going to go through will see us move from using the existing AuthChecker class to using instances of an AuthorizationChecker class.  Once we’ve made the transition from AuthChecker to AuthorizationChecker, we will delete the AuthChecker Singleton from our codebase.

The first challenge that we have to face in this process is how do we create a new class (AuthorizationChecker) which we can guarantee to expose the same functionality as the original (AuthChecker) class.  There are two reasonable options for this: abstract base class and interface.  Abstract base class is a good solution, if we were going to need to use the same AuthChecker functionality in multiple class implementation for the distant future.  In this case though, we’ve already determined that we’re going to be deleting the AuthChecker Singleton implementation once we’ve completed our refactoring.  As a result, using Interfaces makes more sense.

That makes our first step in the process to extract an interface from the AuthChecker class.  As you can see below, it’s really a simple interface, called IAuthorizationChecker, that defines the four publically exposed methods on the AuthChecker class:

public interface IAuthenticationChecker
{
    bool CheckActionForGlobals(string action, string currentUser,
        string[] groups);
    bool CheckActionForNamespace(NamespaceInfo nspace, string action,
        string currentUser, string[] groups);
    bool CheckActionForPage(PageInfo page, string action,
        string currentUser, string[] groups);
    bool CheckActionForDirectory(IFilesStorageProviderV30 provider, 
        string directory, string action, string currentUser, string[] groups);
}

Now that we have that interface we need to get the AuthChecker to implement it.  This is pretty easy, but has a couple of small twists.  Because we’re applying the interface to a Singleton class, we also have to ensure that the .Instance property is returning a type of the IAuthenticationChecker interface.  To get the code to compile we also have to change the type of the properties underlying module level variable so that it too is of type IAuthorizationChecker:

public class AuthorizationChecker : IAuthorizationChecker
{
    public bool CheckActionForGlobals(string action, 
        string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
 
    public bool CheckActionForNamespace(NamespaceInfo nspace, 
        string action, string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
 
    public bool CheckActionForPage(PageInfo page, string action, 
        string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
 
    public bool CheckActionForDirectory(
        IFilesStorageProviderV30 provider, string directory, 
        string action, string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
}

Interestingly, this is the last of the changes that you’ll have to make to AuthChecker before you delete it from the project.

Now that we’ve established how calling code should expect us to behave (via the IAuthorizationChecker interface), we can go ahead and write a new class that meets that expectation.  In this case we’re going to call the class AuthorizationChecker and we’ll make it implement the interface.  The result, as shown in Figure 6, is a class with the same four public methods on it that the AuthChecker class has.

Figure 6 Class With Four Public Methods

public class AuthorizationChecker : IAuthorizationChecker
{
    public bool CheckActionForGlobals(string action, 
        string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
 
    public bool CheckActionForNamespace(NamespaceInfo nspace, 
        string action, string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
 
    public bool CheckActionForPage(PageInfo page, string action, 
        string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
 
    public bool CheckActionForDirectory(
        IFilesStorageProviderV30 provider, 
        string directory, string action, 
        string currentUser, string[] groups)
    {
        throw new NotImplementedException();
    }
}

Now that our new AuthorizationChecker class has a basic shell, we need to get it working with the same functionality as the original AuthChecker singleton.   One of the key things when doing refactorings like this is to be able to do them incrementally.   Big-bang approaches can be successful, but rarely do you have the ability to impose the constraints, such as time and cost, on your project’s current needs.  Instead, we want to be able to nibble away at the refactoring as time permits and in a way that won’t affect the project.

To do this, we’re going to take our new AuthenticationChecker class and simply pass through the calls to the original AuthChecker code.   This may seem like an extraneous step, but it’s one that is necessary to achieve the goal of refactoring little by little.  To make this happen, we need to get an instance of AuthChecker into the local scope of the newly created AuthenticationChecker class:

private IAuthorizationChecker _authChecker;
 
public AuthorizationChecker()
{
    AuthChecker.Instance = new AuthChecker(Settings.Instance.Provider);
    _authChecker = AuthChecker.Instance;
}

Once we have the AuthChecker instance in place, we can have the methods in the class simply delegate execution to the existing methods in the AuthChecker class.  Like we said before, this may seem extraneous, but trust us, we’re going somewhere here, as shown in Figure 7.

Figure 7 Execution of Existing Methods in the AuthChecker Class

public bool CheckActionForGlobals(string action, string currentUser, 
    string[] groups)
{
    return _authChecker.CheckActionForGlobals(action, currentUser,
        groups);
}
 
public bool CheckActionForNamespace(NamespaceInfo nspace, 
    string action, string currentUser, string[] groups)
{
    return _authChecker.CheckActionForNamespace(nspace, action, 
        currentUser, groups);
}
 
public bool CheckActionForPage(PageInfo page, string action, 
    string currentUser, string[] groups)
{
    return _authChecker.CheckActionForPage(page, action, 
        currentUser, groups);
}
 
public bool CheckActionForDirectory(IFilesStorageProviderV30 provider, 
    string directory, string action, string currentUser, string[] groups)
{
    return _authChecker.CheckActionForDirectory(provider, directory, 
        action, currentUser, groups);
}

One of the key things to note with this approach is that we’ve maintained a single place of change for the code that is related to the AuthChecker/AuthorizationChecker functionality.  If we’re going to be tackling this refactoring over a period of time, this is key, since we can’t be confidently maintaining multiple pieces of code that represent the same functionality.  Don’t be fooled that the maintenance cost of this approach is zero.  If you were to change the arguments in the methods, you’d have multiple places to change, but, on a positive note, you’d get compile errors from that due to the use of an interface enforcing the publically exposed contract.

Other videos from this article

Read the full article at http://msdn.microsoft.com/magazine/ee343987.aspx

Tags:

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.