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
"Simplicity can't be bought later, it must be earned from the start" -- DB
Wednesday, October 15, 2014
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
Step 2. Add Forms Authentication in web.config:
Step 3: Use an ORM, let's use Dapper, get it from nuget:
Step 4: Create a UserLogin model:
Here's how ReturnUrl looks like:
Step 5: Create a login page (two sub-steps):
Step 5.1 Create Login controller and actions:
5.2 Create the view: /Views/Security/Login.cshtml
6. Detect roles in Global.asax.cs:
Step 7. Setup the home page. Two sub-steps
7.1. Create the Home controller:
7.2. Create the view: /Views/Home/Welcome.cshtml
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:
Final.2. Create the view: /Views/Music/Greet.cshtml:
Happy Coding!
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
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.[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
..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!
Sunday, October 12, 2014
Typical NHibernate SessionFactory Auto-mapping
This auto-mapping uses NHibernate's built-in auto-mapping
The auto-mapping's customizer adapts PostgreSQL lowercase+underscore table and property naming convention to .NET's Pascal-case class and property naming convention
Happy Coding!
The auto-mapping's customizer adapts PostgreSQL lowercase+underscore table and property naming convention to .NET's Pascal-case class and property naming convention
using NHibernate.Cfg; // .DatabaseIntegration extension method
namespace Erp.DomainMapping
{
public static class Mapper
{
static NHibernate.ISessionFactory _sessionFactory = Mapper.BuildSessionFactory();
// call this on production
public static NHibernate.ISessionFactory SessionFactory
{
get { return _sessionFactory; }
}
public static NHibernate.ISessionFactory BuildSessionFactory(bool useUnitTest = false)
{
var mapper = new NHibernate.Mapping.ByCode.ConventionModelMapper();
mapper.IsEntity((t, declared) => t.Namespace == "Erp.Domain.TheModels");
mapper.BeforeMapClass += mapper_BeforeMapClass;
mapper.BeforeMapProperty += mapper_BeforeMapProperty;
mapper.BeforeMapManyToOne += mapper_BeforeMapManyToOne;
mapper.BeforeMapBag += mapper_BeforeMapBag;
var cfg = new NHibernate.Cfg.Configuration();
// .DatabaseIntegration! Y U EXTENSION METHOD?!
cfg.DataBaseIntegration(c =>
{
var cs = System.Configuration.ConfigurationManager.ConnectionStrings["TheErpConnection"].ConnectionString;
//// SQL Server
//c.Driver<NHibernate.Driver.SqlClientDriver>();
//c.Dialect<NHibernate.Dialect.MsSql2008Dialect>();
//c.ConnectionString = "Server=.;Database=TestTheDatabase;Trusted_Connection=True";
// PostgreSQL
c.Driver<NHibernate.Driver.NpgsqlDriver>();
c.Dialect<NHibernate.Dialect.PostgreSQLDialect>();
c.ConnectionString = cs;
if (useUnitTest)
{
c.LogSqlInConsole = true;
c.LogFormattedSql = true;
}
});
NHibernate.Cfg.MappingSchema.HbmMapping mapping =
mapper.CompileMappingFor(typeof(Erp.Domain.TheModels.Company).Assembly.GetExportedTypes());
cfg.AddMapping(mapping);
// http://www.ienablemuch.com/2013/06/multilingual-and-caching-on-nhibernate.html
//var filterDef = new NHibernate.Engine.FilterDefinition("lf", /*default condition*/ null,
// new Dictionary<string, NHibernate.Type.IType>
// {
// { "LanguageCultureCode", NHibernate.NHibernateUtil.String}
// }, useManyToOne: false);
//cfg.AddFilterDefinition(filterDef);
cfg.Cache(x =>
{
// SysCache is not stable on unit testing
if (!useUnitTest)
{
x.Provider<NHibernate.Caches.SysCache.SysCacheProvider>();
// I don't know why SysCacheProvider is not stable on simultaneous unit testing,
// might be SysCacheProvider is just giving one session factory, so simultaneous test see each other caches
// This solution doesn't work: http://stackoverflow.com/questions/700043/mstest-executing-all-my-tests-simultaneously-breaks-tests-what-to-do
}
else
{
// This is more stable in unit testing
x.Provider<NHibernate.Cache.HashtableCacheProvider>();
}
// http://stackoverflow.com/questions/2365234/how-does-query-caching-improves-performance-in-nhibernate
// Need to be explicitly turned on so the .Cacheable directive on Linq will work:
x.UseQueryCache = true;
});
if (useUnitTest)
cfg.SetInterceptor(new NHSQLInterceptor());
//new NHibernate.Tool.hbm2ddl.SchemaUpdate(cfg).Execute(useStdOut: false, doUpdate: true);
//using (var file = new System.IO.FileStream(@"c:\x\ddl.txt",
// System.IO.FileMode.Create,
// System.IO.FileAccess.ReadWrite))
//using (var sw = new System.IO.StreamWriter(file))
//{
// new SchemaUpdate(cfg)
// .Execute(sw.Write, false);
//}
var sf = cfg.BuildSessionFactory();
return sf;
}
static void mapper_BeforeMapProperty(NHibernate.Mapping.ByCode.IModelInspector modelInspector,
NHibernate.Mapping.ByCode.PropertyPath member,
NHibernate.Mapping.ByCode.IPropertyMapper propertyCustomizer)
{
string postgresFriendlyName = member.ToColumnName().ToLowercaseNamingConvention();
propertyCustomizer.Column(postgresFriendlyName);
}
static void mapper_BeforeMapClass(NHibernate.Mapping.ByCode.IModelInspector modelInspector,
System.Type type,
NHibernate.Mapping.ByCode.IClassAttributesMapper classCustomizer)
{
classCustomizer.Cache(cacheMapping => cacheMapping.Usage(NHibernate.Mapping.ByCode.CacheUsage.ReadWrite));
string className = type.Name;
string postgresFriendlyName = className.ToLowercaseNamingConvention();
classCustomizer.Table(postgresFriendlyName);
System.Reflection.MemberInfo mi = type.GetMember(className + "Id",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[0];
classCustomizer.Id(mi,
idMapper =>
{
idMapper.Column(postgresFriendlyName + "_id");
idMapper.Generator(
NHibernate.Mapping.ByCode.Generators.Sequence,
generatorMapping => generatorMapping.Params(new { sequence = postgresFriendlyName + "_" + postgresFriendlyName + "_id_seq" }));
});
}
static void mapper_BeforeMapManyToOne(
NHibernate.Mapping.ByCode.IModelInspector modelInspector,
NHibernate.Mapping.ByCode.PropertyPath member,
NHibernate.Mapping.ByCode.IManyToOneMapper propertyCustomizer)
{
string postgresFriendlyName = member.ToColumnName().ToLowercaseNamingConvention() + "_id";
propertyCustomizer.Column(postgresFriendlyName);
}
static void mapper_BeforeMapBag(
NHibernate.Mapping.ByCode.IModelInspector modelInspector,
NHibernate.Mapping.ByCode.PropertyPath member,
NHibernate.Mapping.ByCode.IBagPropertiesMapper propertyCustomizer)
{
propertyCustomizer.Cache(cacheMapping => cacheMapping.Usage(NHibernate.Mapping.ByCode.CacheUsage.ReadWrite));
propertyCustomizer.Lazy(NHibernate.Mapping.ByCode.CollectionLazy.Extra);
/*
* class Person
* {
* IList<Hobby> Hobbies
* }
*
*/
string parentEntity = member.LocalMember.DeclaringType.Name.ToLowercaseNamingConvention(); // this gets the Person
string foreignKey = parentEntity + "_id";
propertyCustomizer.Key(keyMapping => keyMapping.Column(foreignKey));
// http://www.ienablemuch.com/2014/10/inverse-cascade-variations-on-nhibernate.html
// best persistence approach: Inverse+CascadeAll
propertyCustomizer.Inverse(true);
propertyCustomizer.Cascade(NHibernate.Mapping.ByCode.Cascade.All);
}
class NHSQLInterceptor : NHibernate.EmptyInterceptor
{
// http://stackoverflow.com/questions/2134565/how-to-configure-fluent-nhibernate-to-output-queries-to-trace-or-debug-instead-o
public override NHibernate.SqlCommand.SqlString OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
{
Mapper.NHibernateSQL = sql.ToString();
return sql;
}
}
public static string NHibernateSQL { get; set; }
} // Mapper
static class StringHelper
{
public static string ToLowercaseNamingConvention(this string s, bool toLowercase = true)
{
if (toLowercase)
{
var r = new System.Text.RegularExpressions.Regex(@"
(?<=[A-Z])(?=[A-Z][a-z]) |
(?<=[^A-Z])(?=[A-Z]) |
(?<=[A-Za-z])(?=[^A-Za-z])", System.Text.RegularExpressions.RegexOptions.IgnorePatternWhitespace);
return r.Replace(s, "_").ToLower();
}
else
return s;
}
}
}
Happy Coding!
Saturday, October 11, 2014
Refactoring peace of mind with ASP.NET MVC
If the ASP.NET MVC view has this code:
We can improve it by making it refactoring-friendly:
The refactoring enabler:
No approach is complete if it is not wrapped in a fluent API. Now your code is completely free of string, typos could be avoided:
If we want to detect both controller and action:
The supporting API:
Happy Coding!
// http://stackoverflow.com/questions/6852979/get-current-controller-in-view
string controller = (string)this.ViewContext.RouteData.Values["controller"];
string action = (string)this.ViewContext.RouteData.Values["action"]; // can use this too: (string)this.ViewContext.Controller.ValueProvider.GetValue("action").RawValue;
if (controller == "Companies" && action == "Search")
{
...
}
We can improve it by making it refactoring-friendly:
Type controllerType = ViewContext.Controller.GetType();
string action = (string)this.ViewContext.RouteData.Values["action"]; // can use this too: (string)this.ViewContext.Controller.ValueProvider.GetValue("action").RawValue;
if (controllerType == typeof(Erp.Controllers.CompaniesController)
&& action == StaticReflection.GetMemberName<Erp.Controllers.CompaniesController>(m => m.Search(null))
{
...
}
The refactoring enabler:
namespace Erp.Helper
{
// http://joelabrahamsson.com/getting-property-and-method-names-using-static-reflection-in-c/
public class StaticReflection
{
public static string GetMemberName<T>(System.Linq.Expressions.Expression<Func<T, object>> expression)
{
if (expression == null)
{
throw new ArgumentException(
"The expression cannot be null.");
}
return GetMemberName(expression.Body);
}
public static string GetMemberName<T>(System.Linq.Expressions.Expression<Action<T>> expression)
{
if (expression == null)
{
throw new ArgumentException(
"The expression cannot be null.");
}
return GetMemberName(expression.Body);
}
private static string GetMemberName(System.Linq.Expressions.Expression expression)
{
if (expression == null)
{
throw new ArgumentException(
"The expression cannot be null.");
}
if (expression is System.Linq.Expressions.MemberExpression)
{
// Reference type property or field
var memberExpression =
(System.Linq.Expressions.MemberExpression)expression;
return memberExpression.Member.Name;
}
if (expression is System.Linq.Expressions.MethodCallExpression)
{
// Reference type method
var methodCallExpression = (System.Linq.Expressions.MethodCallExpression)expression;
return methodCallExpression.Method.Name;
}
if (expression is System.Linq.Expressions.UnaryExpression)
{
// Property, field of method returning value type
var unaryExpression = (System.Linq.Expressions.UnaryExpression)expression;
return GetMemberName(unaryExpression);
}
throw new ArgumentException("Invalid expression");
}
}
}
No approach is complete if it is not wrapped in a fluent API. Now your code is completely free of string, typos could be avoided:
if (ViewContext.Controller.Verify<Erp.Controllers.CompaniesController>().IsTheContext())
{
...
}
If we want to detect both controller and action:
if (ViewContext.Controller
.Verify<Erp.Controllers.CompaniesController>().WithAction(m => m.Search(null)).IsTheContext())
{
...
}
The supporting API:
public static class StaticReflectionExtension
{
public static ControllerDetector<T> Verify<T>(this System.Web.Mvc.ControllerBase controller)
where T : System.Web.Mvc.ControllerBase
{
return new ControllerDetector<T>(controller);
}
}
public class ControllerDetector<T> where T : System.Web.Mvc.ControllerBase
{
System.Web.Mvc.ControllerBase _controller;
string _action = "";
public ControllerDetector(System.Web.Mvc.ControllerBase controller)
{
_controller = controller;
}
public bool IsTheContext()
{
return
_controller.GetType() == typeof(T)
&&
(
_action == ""
||
_action == (string)_controller.ValueProvider.GetValue("action").RawValue
);
}
public ControllerDetector<T> WithAction(System.Linq.Expressions.Expression<Func<T, object>> expression)
{
_action = StaticReflection.GetMemberName<T>(expression);
return this;
}
}
Happy Coding!
Subscribe to:
Posts (Atom)
