Derek Tomes
Derek Tomes

Reputation: 4007

How do you overload MVC Controllers to avoid repeating common code?

I'm working on a new MVC 5 project. It's a single multi tenanted site that allows many organisations and branches to maintain a page. All pages start with the following url format:

http://mysite.com/{organisation}/{branch}/...

For example:

http://mysite.com/contours/albany/...   
http://mysite.com/contours/birkenhead/...   
http://mysite.com/lifestyle/auckland/...   

I've declared my RouteConfig with the {organisation} and {branch} before the {controller} and {action}:

routes.MapRoute(
    name: "Default",
    url: "{organisation}/{branch}/{controller}/{action}/{id}",
    defaults: new { controller = "TimeTable", 
                    action = "Index", 
                    id = UrlParameter.Optional });

This is working just fine. However EVERY single controller now has identical code in the top of it that examines the organisation and branch.

public ActionResult Index(string organisation, string branch, string name, int id)
{
    // ensure the organisation and branch are valid
    var branchInst = _branchRepository.GetBranchByUrlPath(organisation, branch);
    if (branchInst == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    // start the real code here...
}

I'm keen on the DRY principle (Don't Repeat Yourself) and I'm wondering if it's possible to somehow isolate that common code and change my controller signatures to something like this:

public ActionResult Index(Branch branch, string name, int id)
{
    // start the real code here...
}

Upvotes: 3

Views: 1972

Answers (3)

SuperNES
SuperNES

Reputation: 2810

I had to do something similar in an application where a userid and token were passed in via POST to every controller action (because of some very old legacy authentication we were doing).

I declared a BaseController and had each controller inherit that base controller. So each controller that inherits the base already has access to standard uid and token properties (if they need them), and those properties are already retrieved from the HTTP Context at the start of each action.

public abstract class BaseController : Controller
    {
        #region Properties

        protected string uid;
        protected string token;

        #endregion

        #region Event overloads

        protected override void Initialize(System.Web.Routing.RequestContext requestContext)
        {    
            try
            {
                uid = requestContext.HttpContext.Request["uid"];
                token = requestContext.HttpContext.Request["token"];

                if (uid != null && token != null)
                {
                    ViewBag.uid = uid;

                    if (!token.Contains("%"))
                        ViewBag.token = requestContext.HttpContext.Server.UrlEncode(token);
                    else
                        ViewBag.token = token;

                    base.Initialize(requestContext);
                }
                else
                {
                    requestContext.HttpContext.Response.Redirect(ConfigurationManager.AppSettings.GetValues("rsLocation")[0]);
                }
            }
            // User ID is not in the query string
            catch (Exception ex)
            {
                requestContext.HttpContext.Response.Redirect(ConfigurationManager.AppSettings.GetValues("redirectLocation")[0]);
            }
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }

        #endregion
    }

Now you can inherit your base controller in your "real" controllers.

public class TestController : BaseController
    {
        #region Controller actions

        //
        // GET: /Index/     

        [Authorize]
        public ViewResult Index()
        {
             //blah blah blah
        }
     }

In your case, you'd be looking for organization and branch instead of uid and token, but it's the same idea.

Upvotes: 1

Jun Wei Lee
Jun Wei Lee

Reputation: 1022

Another alternative is to use custom ActionFilters, that way you don't have to remember to inherit your base controller. The AuthorizeAttribute is a good one to overload as that's what you want to do.

http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs

 public class ValidationActionFilter : AuthorizeAttribute

 {
      public override void OnAuthorization(AuthorizationContext filterContext)
      {
           var routeData = filterContext.RouteData;
           var branchInst = _branchRepository.GetBranchByUrlPath(routeData.Values["organisation"], routeData.Values["branch"]);
           if (branchInst == null)
           {
              filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest);
           }   
      }
 }

In your Global.asax or FilterConfig class you can then simply register it

GlobalFilters.Filters.Add(new ValidationActionFilter());

Not tested code but you get the idea..

Upvotes: 0

GvM
GvM

Reputation: 1733

You can create a common controller and have your other controllers inherit from it. For example:

public class YourCommonController : Controller
    {
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var organisation = filterContext.RouteData.Values["organisation"];
            var branch = filterContext.RouteData.Values["branch"];

            // Your validation code here

            base.OnActionExecuting(filterContext);
        }
    }

Now, just inherit from YourCommonController when you want to have this shared code.

Upvotes: 3

Related Questions