Monday, September 29, 2014

Modular code organization in ASP.NET MVC

Hated the way ASP.NET MVC imposes the sock drawer approach to your code organization?


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 SkipLastN(this IEnumerable source, int n)
        {
            var it = source.GetEnumerator();
            bool hasRemainingItems = false;
            var cache = new Queue(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!

Nested routing and folder in ASP.NET MVC Redux

Here's a way to make nested routing and folder in ASP.NET MVC: http://www.ienablemuch.com/2014/09/nested-routing-and-folder-in-aspnet-mvc.html


However, I find the code organization of that approach is not symmetrical enough, I wanted the code organization to be 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!

50% Sure

The amusing thing with running ASP.NET MVC in an environment other than Windows






Happy Deploying!

Deploy ASP.NET MVC to Linux

If you received this error when deploying Visual Studio ASP.NET MVC project to Linux/Unix Mono environment:

System.InvalidOperationException
The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Home/Index.aspx
~/Views/Home/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml


Chances are you set the precompiled option when you published your project, just uncheck it then the error will go away:





Tested ASP.NET MVC 4


Note that if you are deploying ASP.NET MVC project instead of ASP.NET one, the fastcgi_index have to be removed, otherwise you'll receive this error:





Happy Deploying!


Sunday, September 28, 2014

Nested routing and folder in ASP.NET MVC

Nested routing and folder is doable in Laravel

Ever wanted to have a nested url and folder in ASP.NET MVC too? e.g., http://example.com/Hey/Jude/Dont/Make/It/Bad/1

It's doable in ASP.NET MVC by straying outside of convention-over-configuration. First, configure the routing, and then be explicit in your controller when specifying your view's path. Voila! you can now place your view anywhere you wanted it to, but most likely you'll want the folder hierarchy be symmetrical with your url

To cut to the chase, here it is in picture:



Note that we can't use @model directive on our view anymore with the approach above. So we have to explicitly use @inherit. For details, see this: http://stackoverflow.com/questions/8127462/the-view-must-derive-from-webviewpage-or-webviewpagetmodel


Visual Studio cannot navigate anymore to your view with the approach above, however with ReSharper it's still able to navigate the view from your controller

Friday, September 26, 2014

SignalR without generated proxy

Without generated proxy, you can just use plain html on your project, typical of SPA applications. So you won't need this anymore in your SPA application: <script src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="/Scripts/jquery-1.6.4.min.js"></script>
    <script src="/Scripts/jquery.signalR-2.1.2.js"></script>
</head>
<body>

    <input type="text" id="theMessage" />

    <button>Loading...</button>

    <div id="messages"></div>

</body>

<script>
    $(function () {
        var tmntConnection = $.hubConnection();

        var tmntHub = tmntConnection.createHubProxy('tmntHub');

        
        tmntHub.connection.start(function () {




            var uuid = generateUUID();
            $('button').text('Ready').click(function () {
                var text = $('#theMessage').val();
                tmntHub.invoke('tellAprilONeil', uuid, text);
            });


            // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
            function generateUUID() {
                var d = new Date().getTime();
                var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                    var r = (d + Math.random() * 16) % 16 | 0;
                    d = Math.floor(d / 16);
                    return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
                });
                return uuid;
            };

        })


        tmntHub.on('cowabungaToTurtles', function (name, message) {

            $('#messages').append("<p>" + name + ": " + message + "</p>");
        });

        
    });
</script>

</html>



Contrast the above to SignalR with generated proxy: http://www.ienablemuch.com/2014/09/signalr-in-three-easy-steps.html


Happy Coding!


Thursday, September 25, 2014

SignalR in three easy steps

Step 0: Create an empty ASP.NET Web Application, get SignalR from Nuget


Step 1, create the start-up class. SignalR look for the name Startup when your app starts

using Owin;

// If you need to use other class name http://www.asp.net/aspnet/overview/owin-and-katana/owin-startup-class-detection
// [assembly: Microsoft.Owin.OwinStartup(typeof(PlaySignalR.StartMeUp))]

namespace PlaySignalR
{

    // By convention, SignalR look for Startup class name. Renaming this would cause runtime error if you don't specify OwinStartup attribute
    public class Startup
    {
        // SignalR also look for this method signature. The name Configuration could also be renamed, it's specified in attribute/web.config
        public void Configuration(Owin.IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}


Step 2, create a communication hub:

namespace PlaySignalR
{
    // Teenage Mutant Ninja Turtles Hub
    public class TmntHub : Microsoft.AspNet.SignalR.Hub
    {
        public void TellAprilONeil(string name, string message)
        {
            Clients.All.cowabungaToTurtles(name, message);
        }
    }
}


Last step, add Default.aspx, then use this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="PlaySignalR.Default" %>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="/Scripts/jquery-1.6.4.min.js"></script>
    <script src="/Scripts/jquery.signalR-2.1.2.js"></script>
    <script src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>
</head>
<body>
    
    <input type="text" id="theMessage" />

    <button>Send message</button>

    <div id="messages"></div>

</body>

<script>
    $(function () {
        var tmntHub = $.connection.tmntHub;

        tmntHub.connection.start();

        tmntHub.client.cowabungaToTurtles = function (name, message) {
            
            $('#messages').append("<p>" + name + ": " + message + "</p>");
        };

        $('button').click(function () {
            var text = $('#theMessage').val();            
            tmntHub.server.tellAprilONeil('<%: Guid.NewGuid()%>', text);            
        });
        

    });
</script>

</html>



Complete code: https://github.com/MichaelBuen/PlaySignalR


Happy Coding!