Friday, October 17, 2014

ASP.NET MVC - Return JSON without using JsonResult

After using ServiceStack and ASP.NET Web API, and then using ASP.NET MVC again, you'll be wanting to just return the model as-is and just forget the explicit infrastructure to return the model as JSON

public JsonResult GetTopSellingProduct(int year)
{
    using(IDataStore ds = new DataStore(_nhibernateSessionFactory))
    {
        return Json(Product.GetTopSellingProduct(ds, year), JsonRequestBehavior.AllowGet);
    }    
}


This is how we wanted to return models as JSON even in ASP.NET MVC controller:
public Product GetTopSellingProduct(int year)
{
    using(IDataStore ds = new DataStore(_nhSessionFactory))
    {
        return Product.GetTopSellingProduct(ds, year);
    }    
}


Turns out that is possible, Adam Bar noticed ASP.NET MVC can be made to return JSON object implicitly


Here's a complete code that enables ASP.NET Web API-like controller action to ASP.NET MVC. This is using NetJSON for JSON serialization, and DryIoc for IoC/DI needs. DryIoc is the fastest IoC/DI and NetJSON is the fastest JSON serializer

using Erp.Controllers;

using DryIoc; // http://www.palmmedia.de/blog/2011/8/30/ioc-container-benchmark-performance-comparison
using NJ = NetJSON; // http://theburningmonk.com/2014/08/json-serializers-benchmarks-updated-2/

using System;

using System.Linq;



namespace Erp
{
    public class MvcApplication : System.Web.HttpApplication
    {
        DryIocDependencyResolver _ioc = new DryIocDependencyResolver(); // Customized DefaultControllerFactory
        
        protected void Application_Start()
        {
            System.Web.Mvc.AreaRegistration.RegisterAllAreas();

            
            FilterConfig.RegisterGlobalFilters(System.Web.Mvc.GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(System.Web.Routing.RouteTable.Routes);

                        
            System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(_ioc);
            
        }
    
    }
       

    class DryIocDependencyResolver : System.Web.Mvc.DefaultControllerFactory
    {
        internal DryIoc.Container _container;

        public DryIocDependencyResolver()
        {
            _container = new DryIoc.Container();
            RegisterTheIocs();
        }


        protected override System.Web.Mvc.IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            System.Web.Mvc.IController ic = controllerType == null
                ? null
                : (System.Web.Mvc.IController)_container.Resolve(controllerType);


            // _container.ResolvePropertiesAndFields(controller);  // uncomment this if you want to use DI on controller's properties

            var mvcController = ic as System.Web.Mvc.Controller;

            if (mvcController != null)
                mvcController.ActionInvoker = new SimulateWebApi();

            return mvcController;
        }


        class SimulateWebApi : System.Web.Mvc.ControllerActionInvoker
        {
            const string JsonContentType = "application/json";

            protected override System.Web.Mvc.ActionResult CreateActionResult(
                System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ActionDescriptor actionDescriptor, object actionReturnValue)
            {
                if (actionReturnValue == null)
                    return new System.Web.Mvc.EmptyResult();

                return (actionReturnValue as System.Web.Mvc.ActionResult) ?? new System.Web.Mvc.ContentResult()
                {
                    ContentType = JsonContentType,
                    Content = NJ.NetJSON.Serialize (actionReturnValue)
                };
            }
        }


        void RegisterTheIocs()
        {            
            System.Reflection.Assembly assembly = typeof(HomeController).Assembly;

            foreach (var controller in assembly.GetTypes().Where(t => typeof(System.Web.Mvc.Controller).IsAssignableFrom(t)))
            {
                _container.Register(controller, DryIoc.Reuse.InResolutionScope);                
            }
           

            _container.Register(typeof(Elmah.Mvc.ElmahController), DryIoc.Reuse.InResolutionScope);

            // http://ayende.com/blog/153701/ask-ayende-life-without-repositories-are-they-worth-living
            // http://www.ienablemuch.com/2014/10/typical-nhibernate-sessionfactory-auto-mapping.html
            _container.RegisterDelegate<NHibernate.ISessionFactory>(x => Erp.DomainMapping.Mapper.SessionFactory, DryIoc.Reuse.Singleton);            
        }

    }//DefaultControllerFactory

    

}//namespace



JSON Serializers benchmark: http://www.ienablemuch.com/2014/10/benchmarking-net-json-serializers.html


Happy Coding!

Wednesday, October 15, 2014

Auto-mapping domain models with schemas using NHibernate

Auto-mapping domain models with schema is easy with NHibernate's built-in auto-mapping

Here's how an auto-mapping of domain models with schema could be built with NHibernate: https://github.com/MichaelBuen/PlayNHibernateAutomapping/blob/master/DomainMapping/Mapper.cs

DDL of the sample domain models with schema: https://github.com/MichaelBuen/PlayNHibernateAutomapping/blob/master/DomainMapping/DDL.txt


The domain models' properties don't have virtual in them, Virtuosity.Fody automates that for us. Our program automates manual and boring work for the user, developers should be recipient of automations too :-) Here are the sample domain models with schemas:
https://github.com/MichaelBuen/PlayNHibernateAutomapping/tree/master/PlayAutomapping


For schema enforcement on domain models, it's preferrable to enclose them in a static class rather than in a namespace. namespace could be bypassed



Happy Coding!

Mapping many-to-many

Is too much information hiding necessary?

Three ways to map a many-to-many relationship:
http://lostechies.com/jimmybogard/2014/03/12/avoid-many-to-many-mappings-in-orms/


And there is such thing as exotic mapping, avoid it if it is not needed

Tuesday, October 14, 2014

ASP.NET MVC Forms Authentication in Eight Easy Steps

Step 0. Create an Empty ASP.NET MVC project

Step 1. Create a database

/*
use master;
drop database ReadyAspNetMvc;
*/


create database ReadyAspNetMvc;
go


use ReadyAspNetMvc;
go

create table Person
(
    PersonId int identity(1,1) primary key,
    UserName nvarchar(50) not null unique,
    PlainTextPassword nvarchar(100) not null, -- should use hashing: http://www.ienablemuch.com/2014/10/bcrypt-primer.html    
    Roles nvarchar(100) not null default '' -- In actual application, this is relational not comma-delimited
);


insert into Person(UserName, PlainTextPassword,Roles) values
('John', 'L', 'Beatles,Musician'),
('Paul', 'M', 'Beatles,Musician'),
('George', 'H', 'Beatles,Musician'),
('Ringo', 'S', 'Beatles,Musician'),
('Kurt', 'C', 'Nirvana,Musician'),
('Dave', 'G', 'Nirvana,Musician'),
('Krist', 'N', 'Nirvana,Musician'),
('Elvis', 'P', 'Musician'),
('Michael', 'J', ''),
('Freddie', 'M', '');


go


Step 2. Add Forms Authentication in web.config:
<configuration>

    <system.web>
    
        <httpRuntime targetFramework="4.5.1" />
        
        <compilation debug="true" targetFramework="4.5.1" />

        <authentication mode="Forms">
            <forms loginUrl="~/Security/Login" timeout="2880" />
        </authentication>


Step 3: Use an ORM, let's use Dapper, get it from nuget:

Step 4: Create a UserLogin model:
namespace ReadyAspNetMvc.Models
{
    public class UserLogin
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string ReturnUrl { get; set; } // when an action accessed is not authorized, this is where the url to return to is binded 
    }
}

Here's how ReturnUrl looks like:



Step 5: Create a login page (two sub-steps):

Step 5.1 Create Login controller and actions:

using System.Web.Mvc;

using System.Linq;


using Dapper;


namespace ReadyAspNetMvc.Controllers
{
    public class SecurityController : Controller
    {       
        public ViewResult Login()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Login(ReadyAspNetMvc.Models.UserLogin login)
        {
            System.Action authorize = () => System.Web.Security.FormsAuthentication.SetAuthCookie(userName: login.UserName, createPersistentCookie: true);
            

            using (var con = new System.Data.SqlClient.SqlConnection("Server=.; Database=ReadyAspNetMvc; Trusted_Connection=true;"))
            {
                var persons = con.Query("select UserName, PlainTextPassword from Person where UserName = @UserName", new { UserName = login.UserName });

                if (!persons.Any())
                    return View(login);


                var person = persons.Single();


                if (login.Password == person.PlainTextPassword)
                {                    

                    if (string.IsNullOrWhiteSpace(login.ReturnUrl))
                    {
                        authorize();
                        return RedirectToAction(controllerName: "Home", actionName: "Welcome");
                    }
                    else
                    {
                        if (Url.IsReallyLocalUrl(login.ReturnUrl))
                        {
                            authorize();
                            return Redirect(login.ReturnUrl);
                        }
                        else
                        {
                            TempData["warning_message"] = "Url was altered";
                            return RedirectToAction(controllerName: "Security", actionName: "Login");
                        }
                    }
                    

                    //// another way, but it's better to use ASP.NET MVC-proper by using return Redirect(...), so use the above
                    //else
                    //{
                    //    System.Web.Security.FormsAuthentication.RedirectFromLoginPage(userName: login.UserName, createPersistentCookie: true);
                    //    return null;
                    //}
                }
                else
                {  
                    TempData["warning_message"] = "Invalid username or password";                  
                    return View();
                }
            }
            
        }//Login action


        public RedirectToRouteResult SignOut()
        {
            System.Web.Security.FormsAuthentication.SignOut();
            return RedirectToAction(controllerName: "Home", actionName: "Welcome");
        }

    }//SecurityController
}


...

    public static class UrlExtension
    {
        // Thanks وحيد نصيري
        public static bool IsReallyLocalUrl(this UrlHelper url, string returnUrl)
        {
            var shouldRedirect = !string.IsNullOrWhiteSpace(returnUrl) &&
                url.IsLocalUrl(returnUrl) &&
                returnUrl.Length > 1 &&
                returnUrl.StartsWith("/", System.StringComparison.InvariantCultureIgnoreCase) &&
                !returnUrl.StartsWith("//", System.StringComparison.InvariantCultureIgnoreCase) &&
                !returnUrl.StartsWith("/\\", System.StringComparison.InvariantCultureIgnoreCase);
            return shouldRedirect;
        }
    }




5.2 Create the view: /Views/Security/Login.cshtml

@model ReadyAspNetMvc.Models.UserLogin

@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>



@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.UserName)
    </div>
    <div>
        @Html.TextBoxFor(x => x.UserName)
    </div>

    <div>
        @Html.LabelFor(x => x.Password)
    </div>

    <div>
        @Html.PasswordFor(x => x.Password)
    </div>

    
    <p>
        <input type="submit" />
    </p>
            
}


<p>
    <a href="@Url.RouteUrl(new { controller = "Home", action = "Welcome" })">Back to Home Welcome</a>
</p>



<p style="color: red">@this.TempData["warning_message"]</p>




6. Detect roles in Global.asax.cs:
using System;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;


using System.Linq;
using Dapper;

namespace ReadyAspNetMvc
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801
    public class MvcApplication : System.Web.HttpApplication
    {        
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);                       
        }

     
        // Auto-wired-up event
        // http://stackoverflow.com/questions/4677866/how-does-global-asax-postauthenticaterequest-event-binding-happe
        void Application_PostAuthenticateRequest(object sender, EventArgs e)
        {
            if (!System.Web.Security.FormsAuthentication.CookiesSupported) 
                return;

            string cookieName = System.Web.Security.FormsAuthentication.FormsCookieName;            
            System.Web.HttpCookie cookie = this.Request.Cookies[cookieName]; 

            if (cookie == null) 
                return;

            string encryptedCookieValue = cookie.Value;
            System.Web.Security.FormsAuthenticationTicket ticket = System.Web.Security.FormsAuthentication.Decrypt(encryptedCookieValue);

            string userName = ticket.Name;
            string[] roles = null;


            using (var con = new System.Data.SqlClient.SqlConnection("Server=.; Database=ReadyAspNetMvc; Trusted_Connection=true;"))
            {
                var persons = con.Query("select UserName, Roles from Person where UserName = @UserName", new { UserName = userName });

                var person = persons.Single();


                roles = ((string)person.Roles).Split(',');


                System.Security.Principal.IIdentity identity = new System.Security.Principal.GenericIdentity(name: userName, type: "Forms");

                System.Web.HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, roles);
            }


        }// Application_PostAuthenticateRequest

        
    }//class MvcApplication
}


Step 7. Setup the home page. Two sub-steps

7.1. Create the Home controller:
using System.Web.Mvc;

namespace ReadyAspNetMvc.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return RedirectToAction(actionName: "Welcome");
        }


        public ViewResult Welcome()
        {
            string userName = System.Web.HttpContext.Current.User.Identity.Name;

            ViewBag.UserName = userName;
            
            return View();
        }

    }
}

7.2. Create the view: /Views/Home/Welcome.cshtml
@{
    ViewBag.Title = "Welcome";
}



@if (!string.IsNullOrWhiteSpace(ViewBag.UserName)) 
{
    <h2>Welcome @ViewBag.UserName</h2>
}



<div>
    <a href="@Url.RouteUrl(new { controller = "Music", action = "AboutBeatles" })">About Beatles</a>    
</div>

<div>
    <a href="@Url.RouteUrl(new { controller = "Music", action = "AboutNirvana" })">About Nirvana</a>
</div>

<div>
    <a href="@Url.RouteUrl(new { controller = "Music", action = "AboutGrungeRock" })">About Grunge Rock</a>
</div>

<div>
    <a href="@Url.RouteUrl(new { controller = "Music", action = "AboutMusician" })">About Musician</a>
</div>

<div>
    <a href="@Url.RouteUrl(new { controller = "Music", action = "AmILogged" })">Am I Logged?</a>
</div>

<div>
    <a href="@Url.RouteUrl(new { controller = "Music", action = "Anyone" })">Anyone</a>
</div>


<p>
    <div>
        @if (!string.IsNullOrWhiteSpace(ViewBag.UserName)) 
        { 
            <a href="@Url.RouteUrl(new { controller = "Security", action = "SignOut" })">Sign Out</a>
        }
        else
        {
            <a href="@Url.RouteUrl(new { controller = "Security", action = "Login" })">Login</a>
        }        
    </div>
</p>



Final Step. Create a controller that will test the authorization. Note that while GenericPrincipal's roles parameter is array-based, the Authorize's Roles property is comma-delimited

Final.1. Controller:
using System.Web.Mvc;

namespace ReadyAspNetMvc.Controllers
{
    public class MusicController : Controller
    {
        
        
        [Authorize(Roles="Beatles")]
        public ViewResult AboutBeatles()
        {
            string userName = System.Web.HttpContext.Current.User.Identity.Name;
            ViewBag.Message = string.Format("Hello {0}! Beatles is the greatest rock band", userName);
            return Greet();
        }


        [Authorize(Roles = "Nirvana")]
        public ViewResult AboutNirvana()
        {
            string userName = System.Web.HttpContext.Current.User.Identity.Name;
            ViewBag.Message = string.Format("Hello {0}! Nirvana is the greatest grunge band", userName);
            return Greet();
        }

        [Authorize(Roles = "Beatles,Nirvana")]
        public ViewResult AboutGrungeRock()
        {
            string userName = System.Web.HttpContext.Current.User.Identity.Name;
            ViewBag.Message = string.Format("Hello {0}! This is grunge rock", userName);
            return Greet();
        }


        [Authorize(Roles = "Musician")]
        public ViewResult AboutMusician()
        {
            string userName = System.Web.HttpContext.Current.User.Identity.Name;
            ViewBag.Message = string.Format("Hello {0}! You are a music inventor", userName);
            return Greet();
        }
                

        [Authorize]
        public ViewResult AmILogged()
        {
            string userName = System.Web.HttpContext.Current.User.Identity.Name;
            ViewBag.Message = string.Format("Yes {0}!", userName);
            return Greet();
        }

        public string Anyone()
        {
            return "<b>Anyone</b>";
        }

        public ViewResult Greet()
        {
            return View("Greet"); 

            // return View(); // If we do this, when we visit /Music/AboutMusician ASP.NET MVC will try to find AboutMusician.cshtml instead of Greet.cshtml
        }


    }
}

Final.2. Create the view: /Views/Music/Greet.cshtml:
<h2>@ViewBag.Message</h2>


<a href="@Url.RouteUrl(new { controller = "Home", action = "Welcome" })">Back to Home Welcome</a>




Happy Coding!

Monday, October 13, 2014

Head-scratching auto-wired-up event


If you received this kind of error..

Server Error in '/' Application.


Object reference not set to an instance of an object.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:


[NullReferenceException: Object reference not set to an instance of an object.]
   System.Web.PipelineModuleStepContainer.GetStepArray(RequestNotification notification, Boolean isPostEvent) +22
   System.Web.PipelineStepManager.ResumeSteps(Exception error) +1324
   System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb) +95
   System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +186


Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.34212




..chances are that explicitness in code is important to you too, hence when following a how-to post, you'll be inclined to believe that when an event is missing a subscription process the author just forgot to mention it in his post. Naturally, when trying out those code, we will wire the method to the event:

using System;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace ReadyAspNetMvc
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);


            this.PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest;
        }

        void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Hello");
        }
    }
}


Alas, the world is not gracious to folks who wants explicitness. Doing that code results to yellow screen of death above. Good thing there is stackoverflow, there are two solutions to the problem. One is to just accept the magical auto-wired-up events based on the exact method signature, doing that, we have to remove the event subscription from our Global.asax.cs:

using System;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace ReadyAspNetMvc
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);           
        }

        void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Hello");
        }
    }
}


Oops.. not so fast, that still has an error. The magical signature is not dependent on the class name, the magical method signature for PostAuthenticateRequest is exactly Application_PostAuthenticateRequest:
using System;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace ReadyAspNetMvc
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);           
        }

        void Application_PostAuthenticateRequest(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Hello");
        }
    }
}


Another method is we wire the event explicitly, it can't be done on Application_Start though, it must be done on Init:

using System;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace ReadyAspNetMvc
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);           
        }

        public override void Init()
        {
            this.PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest;
            base.Init();
        }

        void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Hello");
        }

        
    }
}


Application_Start is an auto-wired-up event, it looks like we can't make it explicit though. See: http://stackoverflow.com/questions/4677866/how-does-global-asax-postauthenticaterequest-event-binding-happen#comment5156961_4677905



Happy Coding!