1
\$\begingroup\$

I've been trying to learn different patterns of programming, and the "Chain of Responsibility" sort of eludes me. I've been told that my specific code snippet would be a good candidate for chain of responsibility, and I'm wondering if someone could show me how to get there?

Public Overrides Sub OnActionExecuting(ByVal filterContext As ActionExecutingContext)

    ''# Set a local variable for the HttpContext.Request. This is going to
    ''# be used several times in the subsequent actions, so it needs to be
    ''# at the top of the method.
    Dim request = filterContext.HttpContext.Request
    Dim url As Uri = request.Url

    ''# Now we get the referring page
    Dim referrer As Uri = If(Not request.UrlReferrer Is Nothing, request.UrlReferrer, Nothing)

    ''# If the referring host name is the same as the current host name,
    ''# then we want to get out of here and not touch anything else. This
    ''# is because we've already set the appropriate domain in a previous
    ''# call.
    If (Not referrer Is Nothing) AndAlso
       (Not url.Subdomain = "") AndAlso
       (Not url.Subdomain = "www") AndAlso
       (referrer.Host = url.Host) Then
        Return
    End If

    ''# If we've made it this far, it's because the referring host does
    ''# not match the current host. This means the user came here from
    ''# another site or entered the address manually.  We'll need to hit
    ''# the database a time or two in order to get all the right
    ''# information.

    ''# This is here just in case the site is running on an alternate
    ''# port. (especially useful on the Visual Studio built in web server
    ''# / debugger)
    Dim newPort As String = If(url.IsDefaultPort, String.Empty, ":" + url.Port.ToString())

    ''# Initialize the Services that we're going to need now that we're
    ''# planning on hitting the database.
    Dim RegionService As Domain.IRegionService = New Domain.RegionService(New Domain.RegionRepository)

    ''# Right now we're getting the requested region from the URI.  This
    ''# is when a user requests something like
    ''# http://calgary.example.com, whereby we extract "calgary" out of
    ''# the address.
    Dim region As Domain.Region = RegionService.GetRegionByName(url.Subdomain)

    ''# If the RegionService returned a region from it's query, then we
    ''# want to exit the method and allow the user to continue on using
    ''# this region.
    If Not region Is Nothing Then
        Return
    End If

    ''# If we've made it this far, it means that the user either entered
    ''# an Invalid Region (yes, we already know the region is invalid) or
    ''# used www. or nothing as a subdomain.  Up until this point, we
    ''# haven't cared if the user is authenticated or not, nor have we
    ''# cared what the full address in their address bar is.  Now we're
    ''# probably going to start redirecting them somewhere.

    ''# First off we need to check if they're authenticated users. If they
    ''# are, we'll just send them on over to their default region.
    If filterContext.HttpContext.User.Identity.IsAuthenticated Then
        Dim userService As New Domain.UserService(New Domain.UserRepository)
        Dim userRegion = userService.GetUserByID(AuthenticationHelper.RetrieveAuthUser.ID).Region.Name

        filterContext.HttpContext.Response.Redirect(url.Scheme + "://" + userRegion + "." + url.PrimaryDomain + newPort + request.RawUrl)

    End If


    ''# Now we know that the user is not Authenticated. So here we check for
    ''# www. If the host has www in it, then we just strip the www and
    ''# bounce the user to the original request.
    If request.Url.Host.StartsWith("www") Then
        Dim newUrl As String = url.Scheme + "://" + url.Host.Replace("www.", "") + newPort + request.RawUrl
        ''# The redirect is permanent because we NEVER want to see www in the domain.
        filterContext.HttpContext.Response.RedirectPermanent(newUrl)

        ''# It's ok for an annonymous browser to view the "Details" of an
        ''# Event/User/Badge/Tag without being assigned to a regions. So
        ''# this is why we strip the www but don't redirect the visitor
        ''# directly over to the "Choose Your Region" view.

    End If

    ''# If we've gone this far, we know the region is invalid, and the
    ''# user needs to be directed to a "choose your region" page.  We're
    ''# not going to do the redirecting here because we want to allow for
    ''# browsing to specific Users/Tags/Badges/Events that are Region
    ''# Agnostic. But if a user tries to view an event listing of any
    ''# sort, we're going to fire them over to the "Choose Your Region"
    ''# page via a separate Attribute attached to only the Actions that
    ''# require it.

End Sub

Here's an uncommented C# version:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var request = filterContext.HttpContext.Request;
    Uri url = request.Url;
    Uri referrer = (request.UrlReferrer != null) ? request.UrlReferrer : null;



    if (((referrer != null)) && (!string.IsNullOrEmpty(url.Subdomain)) && (!(url.Subdomain == "www")) && (referrer.Host == url.Host)) {
        return;
    }


    string newPort = url.IsDefaultPort ? string.Empty : ":" + url.Port.ToString();


    Domain.IRegionService RegionService = new Domain.RegionService(new Domain.RegionRepository());
    Domain.Region region = RegionService.GetRegionByName(url.Subdomain);


    if ((region != null)) {
        return;
    }


    if (filterContext.HttpContext.User.Identity.IsAuthenticated) {
        Domain.UserService userService = new Domain.UserService(new Domain.UserRepository());
        dynamic userRegion = userService.GetUserByID(AuthenticationHelper.RetrieveAuthUser.ID).Region.Name;

        filterContext.HttpContext.Response.Redirect(url.Scheme + "://" + userRegion + "." + url.PrimaryDomain + newPort + request.RawUrl);

    }




    if (request.Url.Host.StartsWith("www")) {
        string newUrl = url.Scheme + "://" + url.Host.Replace("www.", "") + newPort + request.RawUrl;
        //'# The redirect is permanent because we NEVER want to see www in the domain.
        filterContext.HttpContext.Response.RedirectPermanent(newUrl);

    }


}
\$\endgroup\$
2
\$\begingroup\$

This method is not a good candidate for the Chain of Responsibility pattern, but it definitely can be implemented using it (just for the educational purpose):

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        //Init
        var referrerRequestHandler = new ReferrerRequestHandler();
        var regionRequestHandler = new RegionRequestHandler();
        var authenticatedRequestHandler = new AuthenticatedRequestHandler(filterContext);
        var wwwRequestHandler = new WwwRequestHandler(filterContext);

        referrerRequestHandler.SetNextHandler(regionRequestHandler);
        regionRequestHandler.SetNextHandler(authenticatedRequestHandler);
        authenticatedRequestHandler.SetNextHandler(wwwRequestHandler);

        //Run
        var request = filterContext.HttpContext.Request;
        referrerRequestHandler.Redirect(request);
    }
    public abstract class RequestHandler
    {
        public void SetNextHandler(RequestHandler nextHandler)
        {
            _nextHandler = nextHandler;
        }

        public void Redirect(HttpRequestBase request)
        {                
            bool handeled = HandleRedirect(request);
            if (!handeled)
            {
                if (_nextHandler != null)
                {
                    _nextHandler.Redirect(request);
                }
            }
        }

        protected abstract bool HandleRedirect(HttpRequestBase request);

        private RequestHandler _nextHandler;
    }
    public class ReferrerRequestHandler : RequestHandler
    {
        protected override bool HandleRedirect(HttpRequestBase request)
        {
            Uri url = request.Url;
            Uri referrer = (request.UrlReferrer != null) ? request.UrlReferrer : null;
            if (((referrer != null)) && (!string.IsNullOrEmpty(url.Subdomain)) && (!(url.Subdomain == "www")) && (referrer.Host == url.Host))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    public class RegionRequestHandler: RequestHandler
    {
        protected override bool HandleRedirect(HttpRequestBase request)
        {
            Domain.IRegionService RegionService = new Domain.RegionService(new Domain.RegionRepository());
            Domain.Region region = RegionService.GetRegionByName(url.Subdomain);
            if ((region != null))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    public class AuthenticatedRequestHandler: RequestHandler
    {
        public AuthenticatedRequestHandler(ActionExecutingContext filterContext)
        {
            _filterContext = filterContext;
        }

        protected override bool HandleRedirect(HttpRequestBase request)
        {
            Uri url = request.Url;
            string newPort = url.IsDefaultPort ? string.Empty : ":" + url.Port.ToString();
            if (_filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                Domain.UserService userService = new Domain.UserService(new Domain.UserRepository());
                dynamic userRegion = userService.GetUserByID(AuthenticationHelper.RetrieveAuthUser.ID).Region.Name;
                _filterContext.HttpContext.Response.Redirect(url.Scheme + "://" + userRegion + "." + url.PrimaryDomain + newPort + request.RawUrl);
                return true;
            }
            else
            {
                return false;
            }
        }            

        private readonly ActionExecutingContext _filterContext;
    }
    public class WwwRequestHandler : RequestHandler
    {
        public WwwRequestHandler(ActionExecutingContext filterContext)
        {
            _filterContext = filterContext;
        }

        protected override bool HandleRedirect(HttpRequestBase request)
        {
            Uri url = request.Url;
            if (request.Url.Host.StartsWith("www"))
            {
                string newUrl = url.Scheme + "://" + url.Host.Replace("www.", "") + newPort + request.RawUrl;
                //'# The redirect is permanent because we NEVER want to see www in the domain.
                _filterContext.HttpContext.Response.RedirectPermanent(newUrl);
                return true;
            }
            else
            {
                return false;
            }
        }
        private readonly ActionExecutingContext _filterContext;
    }
\$\endgroup\$
  • \$\begingroup\$ Thanks for that. Can you explain why it's not a good candidate for CoR? \$\endgroup\$ – Chase Florell Aug 20 '11 at 15:28
  • 1
    \$\begingroup\$ Just compare the amount of code in your original implementation and CoR implementation. Your original variant is much more compact. And we don't gain any advantages by using CoR. Moreover, CoR makes it even worse: 1. The code becomes too complicated. 2. If you need to add a new 'handler' in the original variant it will take only a couple of lines of code. With the CoR you'll have to add a class and change the code in two places. Designe patterns are useful, but they should be used really carefully or else you'll end up with overcomplicated code. \$\endgroup\$ – Ivan Aug 20 '11 at 16:03
  • \$\begingroup\$ Good call... Like I said, a friend just told me this would be a good example to learn from. \$\endgroup\$ – Chase Florell Aug 20 '11 at 17:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.