A while back I found an interesting post on embedding resources in MVC at "The Glass is Too Big" (http://www.wynia.org/wordpress/2008/12/05/aspnet-mvc-plugins/). Following the method was straightforward and allowed views to be embedded within a Component Class Library. Well, two extra items would be nice. It would be nice to not have to write that large plug-in path, and if a view was defined in the views folder it would be returned in lieu of the embedded view.
Adding this support required two modifications. First the AssemblyResourceProvider was modified to check for the view's existence by adding a Regex pattern match and checking for the file. If the file is found within the view folder, the file is returned, if not the embedded resource will return.
58 // Check to see if a file has been added to the views folder. If so, return that view.
59 Match m = Regex.Match(path, @"~/Plugin/[\w\.]+.dll/([\w\.]+).Views.([\w\.]+).aspx");
60 if (m.Success)
61 {
62 string assemblyMatch = m.Groups[1].Value;
63 string viewPath = m.Groups[2].Value;
64 string physicalPath = HttpContext.Current.Server.MapPath(String.Format("~/Views/{0}/{1}.aspx",
65 assemblyMatch, viewPath.Replace(".", "/")));
66 if (File.Exists(physicalPath))
67 return
File.Open(physicalPath,FileMode.Open);
68 }
http://www.codeplex.com/unifico/SourceControl/changeset/view/1491#9855
To avoid having to write out the plug-in strings I wrote an extension method to write it and return the ViewResult. Reflection is used to gather the assembly name.
9 public
static
class
ControllerPluginPathExtender
10 {
11 public
static
ViewResult PluginView(this
Controller controller)
12 {
13 string controllerName = controller.RouteData.GetRequiredString("controller");
14 string viewName = controller.RouteData.GetRequiredString("action");
15 string assemblyName = controller.GetType().Assembly.FullName.Split(',')[0];
16 string pluginPath = String.Format(
17 @"~/Plugin/{0}.dll/{0}.Views.{1}.{2}.aspx",
18 assemblyName,controllerName,viewName
19 );
20 return
new
ViewResult{
21 ViewName = pluginPath
22 };
23 }
24 }
http://www.codeplex.com/unifico/SourceControl/changeset/view/1491#52892
Now using the embedded option is less tedious, for example:
33
34 [Authorize(Roles="Admin")]
35 public
ActionResult Index()
36 {
37 ViewData["Title"] = "Account Admin Home";
38 ViewData["Message"] = "Welcome to the account admin";
39
40 return
this.PluginView();
41 //return View("~/Plugin/App.Account.dll/App.Account.Views.Admin.Index.aspx");
42 }
It would be nice to not have to use 'this', but I can't see a way around it without rebuilding MVC, not something I want to start doing. Also some caching and less reflection might be nice.
Update: The VewData and TempData have to be added to pass the models to the view
11 public static ViewResult PluginView(this Controller controller)
12 {
13 string controllerName = controller.RouteData.GetRequiredString("controller");
14 string viewName = controller.RouteData.GetRequiredString("action");
15 string assemblyName = controller.GetType().Assembly.FullName.Split(',')[0];
16 string pluginPath = String.Format(
17 @"~/Plugin/{0}.dll/{0}.Views.{1}.{2}.aspx",
18 assemblyName,controllerName,viewName
19 );
20 return new ViewResult{
21 ViewName = pluginPath,
22 ViewData = controller.ViewData,
23 TempData = controller.TempData
24 };
25 }