Technical Insights: Azure, .NET, Dynamics 365 & EV Charging Architecture

Category: ASP.NET MVC

Build Custom View Engine to override default views folder in ASP.NET MVC

So here is the situation, I need to build a personalization for the user where they can have different View files based on their user preference or alternatively they can pick their own View folder. To better explain the situation, look at the folder structure below

ViewPersonalisation-FolderStructure

So basically, I don’t want it to be under “Views” folder anymore instead I want my views to be under “Media” folder and by default if the user hasn’t specified which “Views” folder that they want to select then it should use the default one which is “Main” folder

First of all, we need to create our own View Engine that inherits from “RazorViewEngine” and then we need to override the default “ViewLocation“, “PartialViewLocation” and “MasterLocation” with our own default location which is “Media”

Secondly, we need to override “CreateView” method and “CreatePartialView” method to replace the default view location with the one defined in database/user settings

*Note: in the code below “CacheManager.CurrentSite.FolderPath” is storing the “Views” folder that the user has selected in their profile. “UseCustomViewLocation” function is used in this case to not have the personalisation on any of the “Admin Area”
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.IO;</code>

namespace WebUI.Custom.ViewEngine
{
public class WhitelabelViewEngine : RazorViewEngine
{
private const string WHITELABELVIEWFOLDER = "Whitelabel";
private const string MAINFOLDER = "Main";
private const string EXCLUDED_AREA = "admin";

public WhitelabelViewEngine()
{
base.ViewLocationFormats = new[] {
"~/Media/" + MAINFOLDER + "/{1}/{0}.cshtml",
"~/Media/" + MAINFOLDER + "/{1}/{0}.vbhtml",
"~/Media/" + MAINFOLDER + "/Shared/{0}.cshtml",
"~/Media/" + MAINFOLDER + "/Shared/{0}.vbhtml"
};

base.MasterLocationFormats = new[] {
"~/Media/" + MAINFOLDER + "/{1}/{0}.cshtml",
"~/Media/" + MAINFOLDER + "/{1}/{0}.vbhtml",
"~/Media/" + MAINFOLDER + "/Shared/{0}.cshtml",
"~/Media/" + MAINFOLDER + "/Shared/{0}.vbhtml"
};

base.PartialViewLocationFormats = new[] {
"~/Media/" + MAINFOLDER + "/{1}/{0}.cshtml",
"~/Media/" + MAINFOLDER + "/{1}/{0}.vbhtml",
"~/Media/" + MAINFOLDER + "/Shared/{0}.cshtml",
"~/Media/" + MAINFOLDER + "/Shared/{0}.vbhtml"
};
}

protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
IView partialView = base.CreatePartialView(controllerContext, partialPath);

if (UseCustomViewLocation(controllerContext))
{
partialView = base.CreatePartialView(controllerContext, partialPath.Replace(MAINFOLDER, CacheManager.CurrentSite.FolderPath));

if (!File.Exists(controllerContext.HttpContext.Server.MapPath(((RazorView)partialView).ViewPath)))
partialView = base.CreatePartialView(controllerContext, partialPath);
}

return partialView;
}

protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
IView pageView = base.CreateView(controllerContext, viewPath, masterPath);

if (UseCustomViewLocation(controllerContext))
{
pageView = base.CreateView(controllerContext, viewPath.Replace(MAINFOLDER, CacheManager.CurrentSite.FolderPath),
masterPath.Replace(MAINFOLDER, CacheManager.CurrentSite.FolderPath));

if (!File.Exists(controllerContext.HttpContext.Server.MapPath(((RazorView)pageView).ViewPath)))
pageView = base.CreateView(controllerContext, viewPath, masterPath);
}

return pageView;
}

///
/// to check whether in the custom area or not
///

//////
private bool UseCustomViewLocation(ControllerContext controllerContext)
{
bool useCustomViewLocation = false;

if (controllerContext.RouteData.DataTokens["area"] == null || (controllerContext.RouteData.DataTokens["area"] != null &amp;&amp; controllerContext.RouteData.DataTokens["area"] != EXCLUDED_AREA))
{
useCustomViewLocation = true;
}

return useCustomViewLocation;
}
}
}
[/code]

Lastly, We also need to register our custom view engine in Global.asax
[code language=”csharp”]
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
BootStrapper.Setup();

/*Register Whitelabel View Engine logic*/
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new WhitelabelViewEngine());
}
}
[/code]
 

Custom Authorize Attribute and HTTP 403

In this post, I want to outline in how to create your own Authorize tag and to make sure when you call JSON method with your custom authorize attribute to throw HTTP403 – Forbidden

1. You need to create your own Attribute inherits from AuthorizeAttribute

2. AuthorizeCore is the logic that defines whether you are authorized or not

3. OnAuthorization defines the behaviour when you are not authorized. In this case we want to throw HTTP 403 – forbidden. By doing this in your Javascript, you can catch this 403 error and throw friendly error message to the user

Code Snippet
  1. public class CustomAuthorizeAttribute : AuthorizeAttribute
  2.     {
  3.         protected override bool AuthorizeCore(HttpContextBase httpContext)
  4.         {
  5.             if (httpContext == null) throw new ArgumentNullException(“httpContext”);
  6.             return (SessionData.Member != null && SessionData.Member.MemberId > 0);
  7.         }
  8.         public override void OnAuthorization(AuthorizationContext filterContext)
  9.         {
  10.             base.OnAuthorization(filterContext);
  11.             if (filterContext.Result == null)
  12.             {
  13.                 return;
  14.             }
  15.             else if (filterContext.Result.GetType() == typeof(HttpUnauthorizedResult)
  16.                 && filterContext.HttpContext.Request.IsAjaxRequest())
  17.             {
  18.                 filterContext.Result = new ContentResult();
  19.                 filterContext.HttpContext.Response.StatusCode = 403;
  20.             }
  21.         }
  22.     }

You don’t need to do anything in your controller to implement HTTP403, it is all derived from the custom attribute, you just need to use the attribute and everything will be taken care of. Sample usage

Code Snippet
  1. [CustomAuthorize]
  2.         public ActionResult SaveJobJSON(int jobid)
  3.         {
  4.             string message = string.Empty;
  5.             bool successful = false;
  6.             JobsSavedService JobsSavedService = new JobsSavedService();
  7.             successful = JobsSavedService.SavedJobForMember(jobid, ref message);
  8.             JobsSavedService = null;
  9.             return Json(new { successful = successful, message = message }, JsonRequestBehavior.AllowGet);
  10.         }

Pass Model from Javascript to MVC Controller

Initially, I always pass individual object properties through JSON and form the model in the controller as I wasn’t sure how to pass/form a model from Javascript to controller

Controller

Code Snippet
  1. [AllowAnonymous]
  2.         public ActionResult JsonRegister(string username, string password, string confirmpassword,
  3.                                             string email, string confirmemail)
  4.         {
  5.             MemberModel.RegistrationModel model = new MemberModel.RegistrationModel() {
  6.                                                                                         UserName = username,
  7.                                                                                         Password = password,
  8.                                                                                         ConfirmPassword = confirmpassword,
  9.                                                                                         Email = email,
  10.                                                                                         ConfirmEmail = confirmemail };

Javascript

Code Snippet
  1. $.getJSON(@Url.Action(“JsonRegister”), { UserName: $(“#UserName”).val(),
  2.                                                     Password: $(“#Password”).val(),
  3.                                                     ConfirmPassword: $(“#ConfirmPassword”).val(),
  4.                                                     Email: $(“#Email”).val(),
  5.                                                     ConfirmEmail: $(“#ConfirmEmail”).val()
  6.                                                 }, function (RegisterResult) {
  7.                                                         if (RegisterResult.success) {
  8.                                                             $(‘#RegistrationFields’).hide();
  9.                                                             $(‘#ErrorMessage’).text();
  10.                                                             $(‘#RegistrationMessage’).css(‘display’, );
  11.                                                         }
  12.                                                         else {
  13.                                                             $(‘#ErrorMessage’).css(‘display’, ).text(RegisterResult.errors);
  14.                                                         }
  15.         });

the code above is working just fine but I still feel that there is room for improvement. Below is the code that shows how you can still have your controller accepting the model instead of expanding the properties in the model as the parameters to the controller. The solution is just to have the model being assigned to a variable (in this context called as data) before passing it to the JSON, my previous code was forming the actual object in the JSON code

Controller

Code Snippet
  1. [AllowAnonymous]
  2.         public ActionResult JsonRegister(MemberModel.RegistrationModel model)
  3.         {
  4.             string error = string.Empty;
  5.             if (ModelState.IsValid)

Javascript

Code Snippet
  1. var data = { UserName: $(“#UserName”).val(),
  2.             Password: $(“#Password”).val(),
  3.             ConfirmPassword: $(“#ConfirmPassword”).val(),
  4.             Email: $(“#Email”).val(),
  5.             ConfirmEmail: $(“#ConfirmEmail”).val()
  6.         };
  7.         $.getJSON(@Url.Action(“JsonRegister”), data, function (RegisterResult) {
  8.             if (RegisterResult.success) {
  9.                 $(‘#RegistrationFields’).hide();
  10.                 $(‘#ErrorMessage’).text();
  11.                 $(‘#RegistrationMessage’).css(‘display’, );
  12.             }
  13.             else {
  14.                 $(‘#ErrorMessage’).css(‘display’, ).text(RegisterResult.errors);
  15.             }
  16.         });

ModelState Errors in MVC through JSON

Normally, when you used HttpPost/Form submission to post the view through the controller then you can have the model validation applied automatically through @Html.ValidationSummary()

But how do you get the ModelState errors through Json? You can still use LINQ to get the model errors from the ModelState and pass it through JSON

Code Snippet
  1. public ActionResult JsonRegister(MemberModel.RegistrationModel model)
  2.         {
  3.             string error = string.Empty;
  4.             if (!ModelState.IsValid)
  5.             {
  6.                 IEnumerable<System.Web.Mvc.ModelError> modelerrors = ModelState.SelectMany(x => x.Value.Errors);
  7.                 foreach (var modelerror in modelerrors)
  8.                 {
  9.                     error += modelerror.ErrorMessage + “\n”;
  10.                 }
  11.             }
  12.             return Json(new { success = false, errors = error }, JsonRequestBehavior.AllowGet);
  13.         }

Powered by WordPress & Theme by Anders Norén