Reducing If/Else and Switch/Case statements for large conditionals

c#  •  mvc  •  .net

I recently had a situation when writing a website that required an SSO implementation to more than ten 3rd party applications. Of course, each of the 3rd party websites required different information in a different manner to process the sign in. In designing a mechanism to send the login information to the different providers I imagined a potentially unmanageable If/Else or Switch/Case Statement. The method I used to get around it is not new, though it is very cool and very under-utilized if you ask me Enter IDictionary<string, Action>.

The Controller

I set up a controller just for SSO actions that will take in the URL and the name of the 3rd party application. This way in our views the developers can create the URLs in views like this

Html.Action("Index", "Sso", new { url="http://third.party/link.jspa", appName = "3rdpartyapp"})

The controller action then looks like this

public class SsoController : Controller
{
    [Authorize]
    public void Index(string url, string appName)
    {
        SsoMethods.Current.SsoAction[appName.ToLowerInvariant()].Invoke(url);
    }
}

This class could have a bunch of if/else or switch/case statements but we have the SsoMethods class, where all the magic happens! You might be able to figure out what we’re doing at this point

SsoMethods

The contents of my SsoMethods class basically just exposes and IDictionary that uses a string for the key and Action<string> as the stored type. The string type of the stored type is so that I can pass each action the requested URL (see the parameter to Invoke in my controller above). I am setting up the contents of IDictionary in the constructor.

public class SsoMethods
{
    private readonly IDictionary<string, Action<string>> _methods;
    private static SsoMethods _current;

    public static SsoMethods Current
    {
        get { return _current ?? (_current = new SsoMethods()); }
        set { _current = value; }
    }

    public SsoMethods()
    {
        _methods = new Dictionary<string, Action<string>>
                       {
                           {"3rdpartyapp", ThirdPartyAppSso},
                           {"another3rdpartyapp", AnotherPartyAppSso}
                       };
    }

    public SsoMethods(IDictionary<string, Action<string>> methods)
    {
        _methods = methods;
    }

    public IDictionary<string, Action<string>> SsoAction
    {
        get
        {
            return _methods;
        }
    }

    #region Actions

    private static void ThirdPartyAppSso(string url)
    {
        // Actions needed to login to 3rdparyapp
    }

    private static void AnotherPartyAppSso(string url)
    {
        // Actions needed to login to other 3rd party app
        HttpContext.Current.Response.Redirect(url, false);
    }

    #endregion
}

We would still have to create a method for each SSO application we add; however, this seems much more maintainable to me than a gigantic if/else or switch case block.

The only other thing I am doing in this class is the static reference to an instance of itself. This is so that I can create unit tests that verify the correct Dictionary action is called based on the input parameter. In the unit tests I would just do something as follows.

// Arrange
IDictionary<string, Action<string>> methods = new Dictionary<string, Action<string>>
       {
           {"test1", x => Assert.AreEqual("testa", x) },
           {"test2", x => Assert.AreEqual("testb", x) }
       };

// Act
SsoMethods.Current = new SsoMethods(methods);

// Assert
SsoMethods.Current.SsoAction["test1"].Invoke("testa");
SsoMethods.Current.SsoAction["test2"].Invoke("testb");

Going just a little further

To take this just a little bit further (and to make it easier on my front end developers) I created a URL helper that wraps the call to create the link to the SsoController. This way all the front end developer needs to know is the link to the 3rd party app and the name of the app. A further enhancement would be an overloaded helper that just took the 3rd party application URL and used some mechanism to determine which app it is – then the View would only contain code like this to link to the other sites.

<a href='<%= Url.SsoLinkUrl("http://third.party/link.jspa")>'>Link to third party</a>

Helper Code

public static class SsoLinkHelper
{
    public static string SsoLinkUrl(this UrlHelper helper, string appUrl, string appName)
    {
        return helper.Action("Index", "Sso", new {area = "", url = appUrl, appName = appName.ToLowerInvariant()});
    }

    public static string SsoLinkUrl(this UrlHelper helper, string appUrl)
    {
        // TODO: look at the appURL and try to determine the app to redirect to.
        return helper.SsoLinkUrl(appUrl, "3rdpartyapp");
    }
}