Skapa en egen ControllerFactory
När vi skapar nya projekt i ASP.NET MVC så får vi med två Controllers, HomeController och AccountController. Vi kan även enkelt skapa egna Controllers, vilka alltid måste ha suffixet Controller, samt ligga i mappen ”Controllers”.
Men hur kan vi göra om vi inte vill ha suffixet Controller, eller om vi vill ha våra controllers i någon annan mapp eller assembly? Hur gör vi om vi vill använda dependency injection för att kunna byta Controllers mer dynamiskt? Svaret är att skapa en egen ControllerFactory. Det är här ASP.NET MVC hämtar en controller och sedan släpper den.
För att skapa en egen ControllerFactory så har vi två val, antingen implementerar vi IControllerFactory, eller så ärver vi DefaultControllerFactory vilket är standard-klassen för det i ASP.NET MVC. Vi måste sedan registrera vår ControllerFactory så att den läses in när vi surfar in på sidan.
I det här exemplet kommer jag att ha en solution i Visual Studio 2010 med två projekt, ett ASP.NET MVC-projekt, och ett klassbibliotek. I klassbiblioteket skapar jag referenser till System.Web.Mvc.dll samt System.Web.Routing.dll.
I klassbiblioteket skapar vi en ny klass som heter ”New” (observera avsaknaden av Controller-suffixet) och som ärver från Controller. Den har dessutom två ActionResult-metoder.
using System.Web.Mvc;
namespace ExternalControllers
{
public class New : Controller
{
public ActionResult Index()
{
return new ContentResult() { Content = "Hämtat från New!" };
}
public ActionResult Test(string parameter)
{
return new ContentResult() { Content = parameter };
}
}
}
Vi behöver även en liknande controller i ASP.NET MVC-projektet, så vi skapar en vid namn ”OldController” där.
using System.Web.Mvc;
namespace CustomControllerFactoryTest.Controllers
{
public class OldController : Controller
{
public ActionResult Index()
{
return new ContentResult() { Content = "Hämtat från OldController!" };
}
public ActionResult Test(string parameter)
{
return new ContentResult() { Content = parameter };
}
}
}
Surfar vi nu till /Old så kan vi se att vi får fram texten ”Hämtat från OldController!” då den ligger i Controllers-mappen, men surfar vi till /New så får vi ett 404-meddelanden då vi inte har någon NewController där. Hur ska vi då göra för att läsa in den och exekvera på samma sätt som OldController?
För skapar vi en klass som implementerar IControllerFactory. Vi väljer även att skapa de metoder som finns specificerade i interfacet. Vi kan sedan börja med ReleaseController(…) då den är enklast.
public void ReleaseController(IController controller)
{
var c = controller as IDisposable;
if (c == null)
c.Dispose();
}
Det den här metoden gör är att den tar emot en IController. Vi kollar sedan om den även implementerar IDisposable, och om så är fallet så väljer vi att disposa objektet. Den här metoden anropas när en action-metod har körts på sidan och kopplingen mot controllern inte behövs längre.
Vi har även en annan metod i klassen, nämligen CreateController(…). Den här metoden gör tvärtom, den skapar upp en instans till en controller. Det vi kommer att göra nu är att kolla om användaren försöker köra controllern ”Old” eller något annat. Om någon annan controller anropas så vill vi anropa controllern New, vilken vi har i vår externa assembly.
För att veta vilken controller som har efterfrågats så kan vi kolla i RequestContext-objektet vi får som parameter. Här finns alla RouteData-värden med, vilka innehåller bl.a. namnet på Controllern. För att kunna veta vilka värden som skickas in så kan vi kolla i global.asax.
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Här kan vi se att det är tre värden som skickas med vår RouteData, controller, action och id. Vi kommer alltså åt dessa värden från vår ControllerFactory, men vi kan även modifiera dessa samt lägga till nya värden programmatiskt där innan vi anropar en controller. Om vi ser på de controllers vi har skapat upp så har vi en metod i varje controller med namnet ”Test”. De tar emot en parameter vid namn ”parameter”. Då detta värde inte finns med i vår MapRoute så måste den skapas upp i vår ControllerFactory för att vi skall ha tillgång till den.
Då vi kan ändra i de RouteData-värden vi får så ska vi även se till att ändra från Index till Test som action när användaren surfar till vår sida.
För att sedan kunna visa var aktuell controller finns så kommer vi att läsa in assemblyn och skapa en ny instans av den aktuella controllern, vilken vi sedan returnerar från metoden.
När vi har fått ned dessa steg i kod, kan vår metod se ut ungefär så här:
public IController CreateController(RequestContext requestContext, string controllerName)
{
string type = requestContext.RouteData.Values["controller"] as string;
requestContext.RouteData.Values["action"] = "Test";
requestContext.RouteData.Values["parameter"] = type;
Type controllerType;
if (type == "Old")
controllerType = Type.GetType("CustomControllerFactoryTest.Controllers.OldController, CustomControllerFactoryTest");
else
controllerType = Type.GetType("ExternalControllers.New, ExternalControllers");
var controller = Activator.CreateInstance(controllerType) as IController;
return controller;
}
Om vi surfar till sidan nu så ser vi dock ingen skillnad från tidigare. Det vi har kvar att göra nu är att registrera vår ControllerFactory i global.asax.
protected void Application_Start()
{
//…
ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));
}
Genom att ange vilken typ vår ControllerFactory är kan en instans skapas av ASP.NET MVC.
Om vi surfar till sidan nu så får vi upp texten ”Home”. Anledningen till det är för att ASP.NET MVC förväntar sig HomeController. Vi har dock hoppat in mellan och skickat iväg användaren till den nya controllern kallad ”New”, vilket vi kan se om vi debuggar. Surfar vi till /Old så kan vi även se att vi kommer till OldController, vilken ligger i en annan assembly än New.
Genom att skapa en egen anpassad ControllerFactory så får vi alltså större möjlighet att påverka vilken controller som skall laddas in, än om vi hade kört med DefaultControllerFactory.