Instead of sock drawer approach, we could organize our code into modules
Here's how we wanted our code organization be like:
With modular approach, the controller and its views are grouped together. Though you will lose the IDE navigation aspect of controller<->view, you won't miss it much as your contoller and its views are near each other
Notice that we didn't include {controller} in the url, the controller and module are the same thing
And notice too that we hardcoded the controller to "_"
"_Controller" won't work, ASP.NET MVC always append Controller to the controller parameter. Hence "_" is enough
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_SandwichModule_default",
url: "Hey/Jude/Dont/Make/SandwichModule/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional, controller = "_" },
// instead of this string-based code: "JustAspNetMvcThing.App.Hey.Jude.Dont.Make.SandwichModule" parameter, we could use typeof:
namespaces: new[] { typeof(JustAspNetMvcThing.App.Hey.Jude.Dont.Make.SandwichModule._Controller).Namespace }
);
context.MapRoute(
"Hey_default",
"Hey/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
using System.Web.Mvc;
namespace JustAspNetMvcThing.Areas.Let
{
public class LetAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Let";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
// Add this:
context.MapRoute(
name: "Let_It_BeModule_default",
url: "Let/It/BeModule/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional, controller = "_" },
namespaces: new[] { typeof(JustAspNetMvcThing.App.Let.It.BeModule._Controller).Namespace }
);
context.MapRoute(
"Let_default",
"Let/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
Here's the base controller for our modules:
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace JustAspNetMvcThing.App
{
public class AppBaseController : 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((object)null, viewName, memberName);
}
ViewResult TheView(object model, string viewName, string memberName)
{
// Skip(1) skips JustAspNetMVcThing
// SkipLastN excludes the controller
// Sample output: App/Hey/Jude/Dont/Make/SandwichModule
string modulePath = string.Join("/", this.GetType().FullName.Split('.').Skip(1).SkipLastN(1));
// Sample output: /App/Hey/Jude/Dont/Make/SandwichModule/Bad.cshtml
string viewFullPath = "/" + modulePath + "/" + memberName + ".cshtml";
return View(viewFullPath, model);
}
}// class AppBaseController
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);
}
}//class Helper
}//namespace
The Sandwich module, notice the _Controller name? We do that, so the controller will always sort first in the folder
using JustAspNetMvcThing.Models;
using System.Web.Mvc;
namespace JustAspNetMvcThing.App.Hey.Jude.Dont.Make.SandwichModule
{
public class _Controller : AppBaseController
{
// GET: /Hey/Jude/Dont/Make/SandwichModule/Bad/1
public ViewResult Bad(int id = 0)
{
var p = new Person { FirstName = "Paul " + id };
return View(p);
}
}
}
Here's one of the SandwichModule's views. Noticed that we can't use @model anymore. @model ModelHere is just a shorthand for @inherits System.Web.Mvc.WebViewPage<ModelHere>
@inherits System.Web.Mvc.WebViewPage<JustAspNetMvcThing.Models.Person> Hello @Model.FirstName
The Be module:
using System.Web.Mvc;
namespace JustAspNetMvcThing.App.Let.It.BeModule
{
public class _Controller : AppBaseController
{
// GET: /Let/It/BeModule/ThePersonYouWantToBe
public ActionResult ThePersonYouWantToBe()
{
return View();
}
}
}
Here's one of the BeModule's views:
@inherits System.Web.Mvc.WebViewPage I am me!
Happy Coding!

Ever heard of areas?
ReplyDeleteYes have used Areas, it has a limitation though, it can't be nested
Delete