However, I find the code organization of that approach is not symmetrical enough, I wanted the code organization of controller and views be mirror image of each other, like this:
Here's the supporting code:
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace JustAspNetMvcThing.App
{
public class AreaBaseController : Controller
{
public ViewResult View(object model, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
return TheView(model, /*viewName*/ null, memberName);
}
public ViewResult View(string viewName = null, string masterName = null, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
return TheView(/*model*/ (object)null, viewName, memberName);
}
ViewResult TheView(object model, string viewName, string memberName)
{
if (viewName != null && model == null)
return View(viewName, model: null);
string origName = this.GetType().Name;
// "Controller" string is stripped from "ItController"
// "Controller" is stringly-typed, there's a better way, just wanted to make a short code
// Sample output: It
string controllerName = origName.Substring(0, origName.Length - "Controller".Length);
// Skip(3) skips JustAspNetMVcThing.App.Controllers
// SkipLastN excludes the controller, example: Hey/Jude/Dont/Make
// Sample output: Hey/Jude/Dont/Make/It
string controllerPath = string.Join("/", this.GetType().FullName.Split('.').Skip(3).SkipLastN(1)) + "/" + controllerName;
// Sample output: /App/Views/Hey/Jude/Dont/Make/It/Bad.cshtml
string viewFullPath = "/App/Views/" + controllerPath + "/" + memberName + ".cshtml";
return View(viewFullPath, model);
}
}
static class Helper
{
// Because .Reverse() is bad: http://stackoverflow.com/questions/4166493/drop-the-last-item-with-linq#comment4498849_4166546
public static IEnumerable<T> SkipLastN<T>(this IEnumerable<T> source, int n)
{
var it = source.GetEnumerator();
bool hasRemainingItems = false;
var cache = new Queue<T>(n + 1);
do
{
if (hasRemainingItems = it.MoveNext())
{
cache.Enqueue(it.Current);
if (cache.Count > n)
yield return cache.Dequeue();
}
} while (hasRemainingItems);
}
}
}
Then do this on the Area registration:
using System.Web.Mvc;
namespace JustAspNetMvcThing.Areas.Hey
{
public class HeyAreaRegistration : AreaRegistration
{
public override string AreaName { get{ return "Hey"; } }
public override void RegisterArea(AreaRegistrationContext context)
{
// Add this:
context.MapRoute(
name: "Hey_Jude_Dont_Make_default",
url: "Hey/Jude/Dont/Make/{controller}/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "JustAspNetMvcThing.App.Controllers.Hey.Jude.Dont.Make" }
);
context.MapRoute(
"Hey_default",
"Hey/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
Voila! Your controller can continue its normal business. The View(object) resolves to View(object,memberName="") hence we are able to hook the normal View signature from the controller that inherits from AreaBaseController
using JustAspNetMvcThing.Models;
using System.Web.Mvc;
namespace JustAspNetMvcThing.App.Controllers.Hey.Jude.Dont.Make
{
public class ItController : AreaBaseController
{
// GET: /Hey/Jude/Dont/Make/It/Bad/1
public ViewResult Bad(int id = 0)
{
var p = new Person { FirstName = "Paul " + id };
return View(p);
}
}
}
Lastly, we must use @inherits instead of @model on our views. @model is a shorthand for @inherits
@inherits System.Web.Mvc.WebViewPage<JustAspNetMvcThing.Models.Person> Hello @Model.FirstName
Happy Coding!

No comments:
Post a Comment