Context is King

No pain, no gain.

  • Customize the SimpleMembership in ASP.NET MVC 4.0

    As we know, .NET 4.5 have come up to us, and come along with a lot of new interesting features as well. Visual Studio 2012 was also introduced some days ago. They made us feel very happy with cool improvement along with us. Performance when loading code editor is very good at the moment (immediate after click on the solution). I explore some of cool features at these days. Some of them like Json.NET integrated in ASP.NET MVC 4.0, improvement on asynchronous action, new lightweight theme on Visual Studio, supporting very good on mobile development, improvement on authentication…

  • Skinny controller in ASP.NET MVC 4

    Rails community are always inspire a lot of best ideas. I really love this community by the time. One of these is "Fat models and skinny controllers". I have spent a lot of time on ASP.NET MVC, and really I did some miss-takes, because I made the controller so fat. That make controller is really dirty and very hard to maintain in the future. It is violate seriously SRP principle and KISS as well. But how can we achieve that in ASP.NET MVC? That question is really clear after I read "Manning ASP.NET MVC 4 in Action". It is simple that we can separate it into ActionResult, and try to implementing logic and persistence data inside this. In last 2 years, I have read this from Jimmy Bogard blog, but in that time I never had a consideration about it. That's enough for talking now.

    I just published a sample on ASP.NET MVC 4, implemented on Visual Studio 2012 RC at here. I used EF framework at here for implementing persistence layer, and also use 2 free templates from internet to make the UI for this sample.

    In this sample, I try to implementing the simple magazine website that managing all articles, categories and news. It is not finished at all in this time, but no problems, because I just show you about how can we make the controller skinny. And I wanna hear more about your ideas.

    The first thing, I am abstract the base ActionResult class like this:   

        public abstract class MyActionResult : ActionResult, IEnsureNotNull
        {
            public abstract void EnsureAllInjectInstanceNotNull();
        }
     
        public abstract class ActionResultBase<TController> : MyActionResult where TController : Controller
        {
            protected readonly Expression<Func<TController, ActionResult>> ViewNameExpression;
            protected readonly IExConfigurationManager ConfigurationManager;
     
            protected ActionResultBase (Expression<Func<TController, ActionResult>> expr)
                : this(DependencyResolver.Current.GetService<IExConfigurationManager>(), expr)
            {
            }
     
            protected ActionResultBase(
                IExConfigurationManager configurationManager,
                Expression<Func<TController, ActionResult>> expr)
            {
                Guard.ArgumentNotNull(expr, "ViewNameExpression");
                Guard.ArgumentNotNull(configurationManager, "ConfigurationManager");
     
                ViewNameExpression = expr;
                ConfigurationManager = configurationManager;
            }
     
            protected ViewResult GetViewResult<TViewModel>(TViewModel viewModel)
            {
                var m = (MethodCallExpression)ViewNameExpression.Body;
                if (m.Method.ReturnType != typeof(ActionResult))
                {
                    throw new ArgumentException("ControllerAction method '" + m.Method.Name + "' does not return type ActionResult");
                }
     
                var result = new ViewResult
                {
                    ViewName = m.Method.Name
                };
     
                result.ViewData.Model = viewModel;
     
                return result;
            }
     
            public override void ExecuteResult(ControllerContext context)
            {
                EnsureAllInjectInstanceNotNull();
            }
        }

    I also have an interface for validation all inject objects. This interface make sure all inject objects that I inject using Autofac container are not null. The implementation of this as below

        public interface IEnsureNotNull
        {
            void EnsureAllInjectInstanceNotNull();
        }

    Afterwards, I am just simple implementing the HomePageViewModelActionResult class like this

        public class HomePageViewModelActionResult<TController> : ActionResultBase<TController> where TController : Controller
        {
            #region variables & ctors
     
            private readonly ICategoryRepository _categoryRepository;
            private readonly IItemRepository _itemRepository;
     
            private readonly int _numOfPage;
     
            public HomePageViewModelActionResult(Expression<Func<TController, ActionResult>> viewNameExpression)
                : this(viewNameExpression,
                       DependencyResolver.Current.GetService<ICategoryRepository>(),
                       DependencyResolver.Current.GetService<IItemRepository>())
            {
            }
     
            public HomePageViewModelActionResult(
                Expression<Func<TController, ActionResult>> viewNameExpression,
                ICategoryRepository categoryRepository,
                IItemRepository itemRepository)
                : base(viewNameExpression)
            {
                _categoryRepository = categoryRepository;
                _itemRepository = itemRepository;
     
                _numOfPage = ConfigurationManager.GetAppConfigBy("NumOfPage").ToInteger();
            }
     
            #endregion
     
            #region implementation
     
            public override void ExecuteResult(ControllerContext context)
            {
                base.ExecuteResult(context);
     
                var cats = _categoryRepository.GetCategories();
     
                var mainViewModel = new HomePageViewModel();
                var headerViewModel = new HeaderViewModel();
                var footerViewModel = new FooterViewModel();
                var mainPageViewModel = new MainPageViewModel();
     
                headerViewModel.SiteTitle = "Magazine Website";
                if (cats != null && cats.Any())
                {
                    headerViewModel.Categories = cats.ToList();
                    footerViewModel.Categories = cats.ToList();
                }
     
                mainPageViewModel.LeftColumn = BindingDataForMainPageLeftColumnViewModel();
                mainPageViewModel.RightColumn = BindingDataForMainPageRightColumnViewModel();
     
                mainViewModel.Header = headerViewModel;
                mainViewModel.DashBoard = new DashboardViewModel();
                mainViewModel.Footer = footerViewModel;
                mainViewModel.MainPage = mainPageViewModel;
     
                GetViewResult(mainViewModel).ExecuteResult(context);
            }
     
            public override void EnsureAllInjectInstanceNotNull()
            {
                Guard.ArgumentNotNull(_categoryRepository, "CategoryRepository");
                Guard.ArgumentNotNull(_itemRepository, "ItemRepository");
                Guard.ArgumentMustMoreThanZero(_numOfPage, "NumOfPage");
            }
     
            #endregion
     
            #region private functions
     
            private MainPageRightColumnViewModel BindingDataForMainPageRightColumnViewModel()
            {
                var mainPageRightCol = new MainPageRightColumnViewModel();
     
                mainPageRightCol.LatestNews = _itemRepository.GetNewestItem(_numOfPage).ToList();
                mainPageRightCol.MostViews = _itemRepository.GetMostViews(_numOfPage).ToList();
     
                return mainPageRightCol;
            }
     
            private MainPageLeftColumnViewModel BindingDataForMainPageLeftColumnViewModel()
            {
                var mainPageLeftCol = new MainPageLeftColumnViewModel();
     
                var items = _itemRepository.GetNewestItem(_numOfPage);
     
                if (items != null && items.Any())
                {
                    var firstItem = items.First();
     
                    if (firstItem == null)
                        throw new NoNullAllowedException("First Item".ToNotNullErrorMessage());
     
                    if (firstItem.ItemContent == null)
                        throw new NoNullAllowedException("First ItemContent".ToNotNullErrorMessage());
     
                    mainPageLeftCol.FirstItem = firstItem;
     
                    if (items.Count() > 1)
                    {
                        mainPageLeftCol.RemainItems = items.Where(x => x.ItemContent != null && x.Id != mainPageLeftCol.FirstItem.Id).ToList();
                    }
                }
     
                return mainPageLeftCol;
            }
     
            #endregion
        }

     Final step, I get into HomeController and add some line of codes like this

        [Authorize]
        public class HomeController : BaseController
        {
            [AllowAnonymous]
            public ActionResult Index()
            {
                return new HomePageViewModelActionResult<HomeController>(x=>x.Index());
            }
     
            [AllowAnonymous]
            public ActionResult Details(int id)
            {
                return new DetailsViewModelActionResult<HomeController>(x => x.Details(id), id);
            }
     
            [AllowAnonymous]
            public ActionResult Category(int id)
            {
                return new CategoryViewModelActionResult<HomeController>(x => x.Category(id), id);
            }
        }

    As you see, the code in controller is really skinny, and all the logic I move to the custom ActionResult class. Some people said, it just move the code out of controller and put it to another class, so it is still hard to maintain. Look like it just move the complicate codes from one place to another place. But if you have a look and think it in details, you have to find out if you have code for processing all logic that related to HttpContext or something like this. You can do it on Controller, and try to delegating another logic  (such as processing business requirement, persistence data,...) to custom ActionResult class.

    Tell me more your thinking, I am really willing to hear all of its from you guys.

    All source codes can be find out at here.

  • BDD with Specflow, Moq and Microsoft Unit Testing framework

    BDD (Behavior Driven Development) is very popular in these days. I know a lot of companies used BDD in our projects. So I also had some curios about it and I applied for my current project. I used Specflow, Moq and Microsoft Testing framework for working. I did it and found out some things very interested in. In this post I will not focus on what it BDD? What is Mock, stub or how to write a unit testing? Readers can easy find on internet, just googling it and everything will be okay. I just focus on the some best things I found when I implemented the BDD on my code. I used AAA (Arrange-Act-Assert) and GWT (Given-When-Then) patterns in my code. They are clearly and easily to understand and become standard when you write unit testing and mocking.

  • WCF Web API with Web Activator package

    In these days, I had read some blogs and a lot of post in internet about WCF Web API. And I also have curious about it.  It is not new technology. And after I watched some postcast. I recognize it is a good one that I must to researching. RESTfull is a hot topic and have a lot of argues these days. WCF Web API is built on top of WCF service. How does it good? I don't know we just follow and analyze it later. Do you agree with me?

  • Razor Themed View Engine for multi-themes site

    All sources code for this post can be find at here.

    I have just updated the source codes and the post with the suggest from gbitdiff. Thanks for your comment.

    Have you ever implemented a multi-themes site using Razor view engine? Yes it is really good to do that. Specially, you can do it and make it work well with Razor. I spent a lot of time to investigate this problem. After searched on Google, I had found some of links very useful about this topic. But all of them also implemented in ASP.NET MVC 2.0 and custom a WebformViewEngine. So I know I must work with it from scratch. But no problem, I decided to analyses some examples at here and here. They are good samples for my work. And I can re-use the ViewEngine from them, only change from WebformViewEngine to RazorViewEngine.
    What components do we need to make your site using multi themes? We only need css, images and certainly views for make its working. Basic idea is customize the ViewEngine and when the ASP.NET MVC want to find a image, css, view or partial view, we will point to the valid one in theme folder. That's it. After 4 hours to implemented it, I very happy because it can work very well. And this is my result for default theme:



    red theme



    and green theme



    this is a project structure for multi-themes

     

    As you see, I use the path Content\themes\... is same with the organisation of ASP.NET MVC 3.0, and the path ~\Views\... contain all views. And now I will show you some important line of code to implemented it.
    First thing is RazorThemeViewEngine

        public class RazorThemeViewEngine : RazorViewEngine
        {
            public RazorThemeViewEngine()
            {
                MasterLocationFormats = new[] {
                    "~/Views/{2}/{1}/{0}.cshtml",
                    "~/Views/{2}/{1}/{0}.vbhtml",
                    "~/Views/{2}/Shared/{0}.cshtml",
                    "~/Views/{2}/Shared/{0}.vbhtml"
                };
                ViewLocationFormats = new[] {
                    "~/Views/{2}/{1}/{0}.cshtml",
                    "~/Views/{2}/{1}/{0}.vbhtml",
                    "~/Views/{2}/Shared/{0}.cshtml",
                    "~/Views/{2}/Shared/{0}.vbhtml"
                };
                PartialViewLocationFormats = new[] {
                    "~/Views/{2}/{1}/{0}.cshtml",
                    "~/Views/{2}/{1}/{0}.vbhtml",
                    "~/Views/{2}/Shared/{0}.cshtml",
                    "~/Views/{2}/Shared/{0}.vbhtml"
                };
            }

            protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
            {
                try
                {
                    return File.Exists(controllerContext.HttpContext.Server.MapPath(virtualPath));
                }
                catch (HttpException exception)
                {
                    if (exception.GetHttpCode() != 0x194)
                    {
                        throw;
                    }
                    return false;
                }
                catch
                {
                    return false;
                }
            }

            public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
            {
                string[] strArray;
                string[] strArray2;

                if (controllerContext == null)
                {
                    throw new ArgumentNullException("controllerContext");
                }
                if (string.IsNullOrEmpty(viewName))
                {
                    throw new ArgumentException("viewName must be specified.""viewName");
                }

                var themeName = GetThemeToUse(controllerContext);

                var requiredString = controllerContext.RouteData.GetRequiredString("controller");

                var viewPath = GetPath(controllerContext, ViewLocationFormats, "ViewLocationFormats", viewName, themeName, requiredString, "View", useCache, out strArray);
                var masterPath = GetPath(controllerContext, MasterLocationFormats, "MasterLocationFormats", masterName, themeName, requiredString, "Master", useCache, out strArray2);

                if (!string.IsNullOrEmpty(viewPath) && (!string.IsNullOrEmpty(masterPath) || string.IsNullOrEmpty(masterName)))
                {
                    return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
                }
                return new ViewEngineResult(strArray.Union(strArray2));
            }

            public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
            {
                string[] strArray;
                if (controllerContext == null)
                {
                    throw new ArgumentNullException("controllerContext");
                }
                if (string.IsNullOrEmpty(partialViewName))
                {
                    throw new ArgumentException("partialViewName must be specified.""partialViewName");
                }

                var themeName = GetThemeToUse(controllerContext);

                var requiredString = controllerContext.RouteData.GetRequiredString("controller");
                var partialViewPath = GetPath(controllerContext, PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, themeName, requiredString, "Partial", useCache, out strArray);
                return string.IsNullOrEmpty(partialViewPath) ? new ViewEngineResult(strArray) : new ViewEngineResult(CreatePartialView(controllerContext, partialViewPath), this);
            }

            private static string GetThemeToUse(ControllerContext controllerContext)
            {
                var themeName = controllerContext.HttpContext.Items["themeName"as string ?? "Default";

                return themeName;
            }

            private static readonly string[] _emptyLocations;

            private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName, string name, string themeName, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
            {
                searchedLocations = _emptyLocations;
                if (string.IsNullOrEmpty(name))
                {
                    return string.Empty;
                }
                if ((locations == null) || (locations.Length == 0))
                {
                    throw new InvalidOperationException("locations must not be null or emtpy.");
                }

                bool flag = IsSpecificPath(name);
                string key = CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, themeName);
                if (useCache)
                {
                    var viewLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
                    if (viewLocation != null)
                    {
                        return viewLocation;
                    }
                }
                return !flag ? GetPathFromGeneralName(controllerContext, locations, name, controllerName, themeName, key, ref searchedLocations)
                            : GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
            }

            private static bool IsSpecificPath(string name)
            {
                var ch = name[0];
                if (ch != '~')
                {
                    return (ch == '/');
                }
                return true;
            }

            private string CreateCacheKey(string prefix, string name, string controllerName, string themeName)
            {
                return string.Format(
                    CultureInfo.InvariantCulture,
                    ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}",
                    new object[] { GetType().AssemblyQualifiedName, prefix, name, controllerName, themeName });
            }

            private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string themeName, string cacheKey, ref string[] searchedLocations)
            {
                var virtualPath = string.Empty;
                searchedLocations = new string[locations.Length];
                for (var i = 0; i < locations.Length; i++)
                {
                    var str2 = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName, themeName });

                    if (FileExists(controllerContext, str2))
                    {
                        searchedLocations = _emptyLocations;
                        virtualPath = str2;
                        ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
                        return virtualPath;
                    }
                    searchedLocations[i] = str2;
                }
                return virtualPath;
            }

            private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
            {
                var virtualPath = name;
                if (!FileExists(controllerContext, name))
                {
                    virtualPath = string.Empty;
                    searchedLocations = new[] { name };
                }
                ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
                return virtualPath;
            }
        }

    so lucky because RazorViewEngine is also same with WebformViewEngine, so it is not hard to convert to. :D
    Second is ThemeControllerBase

        public class ThemeControllerBase : Controller
        {
            protected override void Execute(System.Web.Routing.RequestContext requestContext)
            {
                var themeName = ConfigurationManager.AppSettings["ThemeName"];
                var defaultTheme = ConfigurationManager.AppSettings["DefaultTheme"];

                if (requestContext.HttpContext.Items[themeName] == null)
                {
                    //first time load
                    requestContext.HttpContext.Items[themeName] = requestContext.HttpContext.Request.Cookies.Get("theme").Value;
                }
                else
                {
                    requestContext.HttpContext.Items[themeName] = defaultTheme;

                    var previewTheme = requestContext.RouteData.GetRequiredString("theme");

                    if (!string.IsNullOrEmpty(previewTheme))
                    {
                        requestContext.HttpContext.Items[themeName] = previewTheme;
                    }
                }

                base.Execute(requestContext);
            }
        }

    and the DropDownList partial view for choose a list of themes:

    @{ var list = ThemeSample.Helpers.ThemeHelper.GetSelectedList(ViewContext.RequestContext.HttpContext.Items["themeName"].ToString());
    }
    @Html.DropDownList("Theme", list)
    <script language="javascript" type="text/javascript">
        $(function () {
            $('#Theme').change(function () {

                $.post('@Url.Action("Index""ThemeDropdown")', { theme: $(this).val() }, function (result) {
                    $('body').html('');
                    $('body').html(result);
                });
            });
        });
    </script>

    controller for it, after user choose the theme on DropDownList, it will submit to the action in this controller.

        public class ThemeDropdownController : Controller
        {
            [HttpPost]
            public ActionResult Index(string theme)
            {
                var themeName = ConfigurationManager.AppSettings["themeName"];

                ControllerContext.RequestContext.HttpContext.Items[themeName] = theme;
                var themeCookie = new HttpCookie("theme", theme);
                HttpContext.Response.Cookies.Add(themeCookie);

                const string controller = "Home";
                const string action = "Index";

                return Redirect(string.Format("~/{0}/{1}/{2}", theme, controller, action));
            }
        }

    theme helper class

        public static class ThemeHelper
        {
            public static IList<SelectListItem> GetSelectedList(string selectedTheme)
            {
                // TODO: should get from web.config or database
                var list = new List<SelectListItem>
                                {new SelectListItem {Text = "Default", Value = "Default"},
                                new SelectListItem {Text = "Red", Value = "Red"},
                                new SelectListItem {Text = "Green", Value = "Green"}};

                foreach (var selectListItem in list.Where(selectListItem => selectListItem.Value.Equals(selectedTheme)))
                {
                    selectListItem.Selected = true;
                }

                return list;
            }

            public static string GetCssWithTheme(ViewContext viewContext)
            {
                var themeName = ConfigurationManager.AppSettings["themeName"];

                return string.Format("~/Content/themes/{0}/Site.css", viewContext.HttpContext.Items[themeName]);
            }
        }

    some config in Global.asax

                ViewEngines.Engines.Clear();
                ViewEngines.Engines.Add(new RazorThemeViewEngine());

    Notes: In this post I did not write the helper for find images, but it is really easy to implemented. How about your thinking in this topic? I really want to know about that. Leave some messages and we will get more clearly about that. Happy coding and see you next post!

    kick it on DotNetKicks.com Shout it

  • dynamic, Why not?

    In these days, people begin to expertise some new features in .NET 4.0. And I am same to them as well. One of features I like and write to this post is dynamic keyword. It is not new, at least in PHP, Python, Ruby... But in .NET world, I think it is new and ship to .NET 4.0 as a default. And some of projects are ported to .NET based on DLR as IronPython, IronRuby. We can use dynamic keyword in anywhere in our project. How do you think about it? So dynamite, isn’t it? :D. Some of people are didn’t like it, some else are love it. How about you? With me, I only try to utilize a new feature in .NET 4.0 for make my work is easier.