This post has been migrated from www.experimentsincode.com, we apologise if some of the images or content is missing

I recently wrote a blog post about how to create a simple commenting component using Sitecore MVC and Controller Renderings. While doing this work it came apparent that there might be some interesting side affects of combining multiple Controller Renderings onto a single item, in this blog post I will explorer this (yes I did say in the first one I would look at IOC however that will have to wait until the next post). So lets look at a potential problem, say we have the following two controllers:
    public class TestController : SitecoreController
    {
        [HttpGet]
        public override System.Web.Mvc.ActionResult Index()
        {
            Response.Write("Hello from Test1 Get");
            return View("Index");

        }
        [HttpPost]
        public  System.Web.Mvc.ActionResult Index(string value)
        {
            Response.Write("Hello from Test1 Post");
            return View("Index");

        }
    }

    public class Test2Controller : SitecoreController
    {
        [HttpGet]
        public override System.Web.Mvc.ActionResult Index()
        {
            Response.Write("Hello from Test2 Get");
            return View("Index");
        }

        [HttpPost]
        public System.Web.Mvc.ActionResult Index(string value)
        {
            Response.Write("Hello from Test2 Post");
            return View("Index");
        }
    }
Both controller are straight forward and have method to handle different type os Http request. Now lets associate both controllers and index actions to an item in Sitecore:
In a standard MVC solution this typically doesn't occur, you normally map a single route to a single controller and action. However in Sitecore you don't have to do that, it will run both controllers and actions. This feature actually gives us some great flexibility:
Notice that I have created a submit button for each view as well, if I click the "Test1 Submit" button I get the following output:
Well we get something that we may not have expected, despite the forms being separate both Post actions were called on each controller. This means that if we have two forms on a page that use conditional renderings both will be submitted. Just imagine if you have a search form at the top of a page and contact form on the page, both would be submitted. Ok so we need a way to tell which form was submitted and only allow that controller and action to run. Well first off, if we give each button a name we can look to see which button was clicked. To do this we just need to create a custom action that inherits from ActionMethodSelectorAttribute. It looks something like this:
    public class FormButtonAttribute : ActionMethodSelectorAttribute  
    {
        string _name;

        public FormButtonAttribute(string name)
        {
            _name = name;
        }
        public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
        {
            return !string.IsNullOrEmpty(controllerContext.HttpContext.Request.Form[_name]);
        }
    }
Ok so this attribute will check the forms collection for a specific key and will only allow the method to run if the form value is exists. Lets update the controllers to use this new attribute:
    public class TestController : SitecoreController
    {
        [HttpGet]
        public override System.Web.Mvc.ActionResult Index()
        {
            Response.Write("Hello from Test1 Get");
            return View("Index");

        }
        [HttpPost]
        [FormButton("TestSubmit")]
        public System.Web.Mvc.ActionResult Index(string value)
        {
            Response.Write("Hello from Test1 Post");
            return View("Index");

        }
    }
    public class Test2Controller : SitecoreController
    {
        [HttpGet]
        
        public override System.Web.Mvc.ActionResult Index()
        {
            Response.Write("Hello from Test2 Get");
            return View("Index");
        }

        [HttpPost]
        [FormButton("Test2Submit")]
        public System.Web.Mvc.ActionResult Index(string value)
        {
            Response.Write("Hello from Test2 Post");
            return View("Index");
        }
    }
Ok lets see what happens if we click the "Test Submit" button now:
Ahh.. what happened here? Well because we clicked the first button and it was a HTTP Post we had no actions on the the Test2Controller that was authorised to run. Sitecore always want to run at least one method on a controller. Let see if we can get around this, lets update the controllers to remove the HttpGet attributes, this should ensure that they run every time:
    public class TestController : SitecoreController
    {
        public override System.Web.Mvc.ActionResult Index()
        {
            Response.Write("Hello from Test1 Get");
            return View("Index");

        }
        [HttpPost]
        [FormButton("TestSubmit")]
        public System.Web.Mvc.ActionResult Index(string value)
        {
            Response.Write("Hello from Test1 Post");
            return View("Index");

        }
    }
    public class Test2Controller : SitecoreController
    {
      
        public override System.Web.Mvc.ActionResult Index()
        {
            Response.Write("Hello from Test2 Get");
            return View("Index");
        }

        [HttpPost]
        [FormButton("Test2Submit")]
        public System.Web.Mvc.ActionResult Index(string value)
        {
            Response.Write("Hello from Test2 Post");
            return View("Index");
        }
    }
And with this we get the following result:
Now this looks like it has succeed, however this isn't really what I was expecting to happen. I would have expected that both Index methods for TestController were called since they both match the request. Lets see if we can get two methods to run within a controller, lets update TestController to look like this:
    public class TestController : SitecoreController
    {
        public override System.Web.Mvc.ActionResult Index()
        {
            Response.Write("Hello from Test1 Get");
            return View("Index");

        }


        [HttpPost]
        [FormButton("TestSubmit")]
        public System.Web.Mvc.ActionResult Index(string value)
        {
            Response.Write("Hello from Test1 Post Method 1");
            return View("Index");
        }

        [HttpPost]
        [FormButton("TestSubmit")]
        public System.Web.Mvc.ActionResult Index(string value, string value2)
        {
            Response.Write("Hello from Test1 Post Method 2");
            return View("Index");
        }
    }
And if we run it
Hum.. well that was a shame, it didn't work, it would have been awesome to call multiple methods on a single controller. So the question is, how does Sitecore decide which methods to call on a controller? Well this actually isn't controlled by Sitecore but is actually part of the standard .NET MVC library, in particular the System.Web.Mvc.RunSelectionFilters method:
    private static List<MethodInfo> RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos)
    {
      List<MethodInfo> list1 = new List<MethodInfo>();
      List<MethodInfo> list2 = new List<MethodInfo>();
      using (List<MethodInfo>.Enumerator enumerator = methodInfos.GetEnumerator())
      {
        while (enumerator.MoveNext())
        {
          MethodInfo methodInfo = enumerator.Current;
          ICollection<ActionMethodSelectorAttribute> selectorAttributes = ReflectedAttributeCache.GetActionMethodSelectorAttributes(methodInfo);
          if (selectorAttributes.Count == 0)
            list2.Add(methodInfo);
          else if (Enumerable.All<ActionMethodSelectorAttribute>((IEnumerable<ActionMethodSelectorAttribute>) selectorAttributes, (Func<ActionMethodSelectorAttribute, bool>) (attr => attr.IsValidForRequest(controllerContext, methodInfo))))
            list1.Add(methodInfo);
        }
      }
      if (list1.Count <= 0)
        return list2;
      else
        return list1;
    }
You can see that this method actually priorities methods that have ActionMethodSelectorAttributes over those methods that don't, this explains why in our earlier test we didn't get both methods firing. So what does this mean? Well it means that you should probably always have a method on you controller that doesn't have any attributes attached to it. This method will be the default method that will always run when no other method is matched, you can then use attributes to specify when another method should be run instead of the default method.
comments powered by Disqus