The Account Component in the Unifico Framework which is serving as a sample component now has an admin. Two supporting developments for the admin are also up: the paging HtmlHelper and overridable embedded views. Allowing for the embedded views to be overridden by placing new views in App.Web greatly eases development and should help in Component maintenance and versioning (new views can be made without rebuilding the component). I am also rather happy with the eas to which views are made overridable.
The paging turned out quite well, a few changes to the PageResponse<T> to implement IPageable made things fit really nicely. For example if PageResponse<User> is passed to the view from the controller, the paging can be generated with default settings in the snippet below. I also made use of my QueryString extension methods to make it work with populated QueryStrings (and have support for many pagers).
<%= Html.PageListing(Users) %>
http://www.codeplex.com/unifico
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 }