HI WELCOME TO SIRIS
Showing posts with label MVC. Show all posts
Showing posts with label MVC. Show all posts

Detailed ASP.NET MVC Pipeline

ASP.NET MVC is an open source framework built on the top of Microsoft .NET Framework to develop the web application that enables a clean separation of code. ASP.NET MVC framework is the most customizable and extensible platform shipped by Microsoft. In this article, you will learn the detailed pipeline of ASP.NET MVC.

Routing

Routing is the first step in ASP.NET MVC pipeline. typically, it is a pattern matching system that matches the incoming request to the registered URL patterns in the Route Table.
The UrlRoutingModule(System.Web.Routing.UrlRoutingModule) is a class which matches an incoming HTTP request to a registered route pattern in the RouteTable(System.Web.Routing.RouteTable).
When ASP.NET MVC application starts at first time, it registers one or more patterns to the RouteTable to tell the routing system what to do with any requests that match these patterns. An application has only one RouteTable and this is setup in the Application_Start event of Global.asax of the application.
  1. public class RouteConfig
  2. {
  3. public static void RegisterRoutes(RouteCollection routes)
  4. {
  5. routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  6.  
  7. routes.MapRoute(
  8. name: "Default",
  9. url: "{controller}/{action}/{id}",
  10. defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  11. );
  12. }
  13. }
  1. protected void Application_Start()
  2. {
  3. //Other code is removed for clarity
  4. RouteConfig.RegisterRoutes(RouteTable.Routes);
  5. }
When the UrlRoutingModule finds a matching route within RouteCollection (RouteTable.Routes), it retrieves the IRouteHandler(System.Web.Mvc.IRouteHandler) instance(default is System.Web.MvcRouteHandler) for that route. From the route handler, the module gets an IHttpHandler(System.Web.IHttpHandler) instance(default is System.Web.MvcHandler).
  1. public interface IRouteHandler
  2. {
  3. IHttpHandler GetHttpHandler(RequestContext requestContext);
  4. }

Controller Initialization

The MvcHandler initiates the real processing inside ASP.NET MVC pipeline by using ProcessRequest method. This method uses the IControllerFactory instance (default is System.Web.Mvc.DefaultControllerFactory) to create corresponding controller.
  1. protected internal virtual void ProcessRequest(HttpContextBase httpContext)
  2. {
  3. SecurityUtil.ProcessInApplicationTrust(delegate {
  4. IController controller;
  5. IControllerFactory factory;
  6. this.ProcessRequestInit(httpContext, out controller, out factory);
  7. try
  8. {
  9. controller.Execute(this.RequestContext);
  10. }
  11. finally
  12. {
  13. factory.ReleaseController(controller);
  14. }
  15. });
  16. }

Action Execution

  1. When the controller is initialized, the controller calls its own InvokeAction() method by passing the details of the chosen action method. This is handled by the IActionInvoker.
    1. public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
  2. After chosen of appropriate action method, model binders(default is System.Web.Mvc.DefaultModelBinder) retrieves the data from incoming HTTP request and do the data type conversion, data validation such as required or date format etc. and also take care of input values mapping to that action method parameters.
  3. Authentication Filter was introduced with ASP.NET MVC5 that run prior to authorization filter. It is used to authenticate a user. Authentication filter process user credentials in the request and provide a corresponding principal. Prior to ASP.NET MVC5, you use authorization filter for authentication and authorization to a user.
    By default, Authenticate attribute is used to perform Authentication. You can easily create your own custom authentication filter by implementing IAuthenticationFilter.
  4. Authorization filter allow you to perform authorization process for an authenticated user. For example, Role based authorization for users to access resources.
    By default, Authorize attribute is used to perform authorization. You can also make your own custom authorization filter by implementing IAuthorizationFilter.
  5. Action filters are executed before(OnActionExecuting) and after(OnActionExecuted) an action is executed. IActionFilter interface provides you two methods OnActionExecuting and OnActionExecuted methods which will be executed before and after an action gets executed respectively. You can also make your own custom ActionFilters filter by implementing IActionFilter. For more about filters refer this article Understanding ASP.NET MVC Filters and Attributes
  6. When action is executed, it process the user inputs with the help of model (Business Model or Data Model) and prepare Action Result.

Result Execution

  1. Result filters are executed before(OnResultnExecuting) and after(OnResultExecuted) the ActionResult is executed. IResultFilter interface provides you two methods OnResultExecuting and OnResultExecuted methods which will be executed before and after an ActionResult gets executed respectively. You can also make your own custom ResultFilters filter by implementing IResultFilter.
  2. Action Result is prepared by performing operations on user inputs with the help of BAL or DAL. The Action Result type can be ViewResult, PartialViewResult, RedirectToRouteResult, RedirectResult, ContentResult, JsonResult, FileResult and EmptyResult.
    Various Result type provided by the ASP.NET MVC can be categorized into two category- ViewResult type and NonViewResult type. The Result type which renders and returns an HTML page to the browser, falls into ViewResult category and other result type which returns only data either in text format, binary format or a JSON format, falls into NonViewResult category.

View Initialization and Rendering

  1. ViewResult type i.e. view and partial view are represented by IView(System.Web.Mvc.IView) interface and rendered by the appropriate View Engine.
    1. public interface IView
    2. {
    3. void Render(ViewContext viewContext, TextWriter writer);
    4. }
  2. This process is handled by IViewEngine(System.Web.Mvc.IViewEngine) interface of the view engine. By default ASP.NET MVC provides WebForm and Razor view engines. You can also create your custom engine by using IViewEngine interface and can registered your custom view engine in to your Asp.Net MVC application as shown below:
    1. protected void Application_Start()
    2. {
    3. //Remove All View Engine including Webform and Razor
    4. ViewEngines.Engines.Clear();
    5. //Register Your Custom View Engine
    6. ViewEngines.Engines.Add(new CustomViewEngine());
    7. //Other code is removed for clarity
    8. }
  3. Html Helpers are used to write input fields, create links based on the routes, AJAX-enabled forms, links and much more. Html Helpers are extension methods of the HtmlHelper class and can be further extended very easily. In more complex scenario, it might render a form with client side validation with the help of JavaScript or jQuery.
What do you think?
I hope you will enjoy the ASP.NET MVC pipeline while extending ASP.NET MVC features. I would like to have feedback from my blog readers. Your valuable feedback, question, or comments about this article are always welcome.

Custom Authentication and Authorization in ASP.NET MVC

When standard types of authentication do not meet your requirements, you need to modify an authentication mechanism to create a custom solution. A user context has a principal which represents the identity and roles for that user. A user is authenticated by its identity and assigned roles to a user determine about authorization or permission to access resources.

ASP.NET provides IPrincipal and IIdentity interfaces to represents the identity and role for a user. You can create a custom solution by evaluating the IPrincipal and IIdentity interfaces which are bound to the HttpContext as well as the current thread.
  1. public class CustomPrincipal : IPrincipal
  2. {
  3. public IIdentity Identity { get; private set; }
  4. public bool IsInRole(string role)
  5. {
  6. if (roles.Any(r => role.Contains(r)))
  7. {
  8. return true;
  9. }
  10. else
  11. {
  12. return false;
  13. }
  14. }
  15.  
  16. public CustomPrincipal(string Username)
  17. {
  18. this.Identity = new GenericIdentity(Username);
  19. }
  20.  
  21. public int UserId { get; set; }
  22. public string FirstName { get; set; }
  23. public string LastName { get; set; }
  24. public string[] roles { get; set; }
  25. }
Now you can put this CustomPrincipal objects into the thread’s currentPrinciple property and into the HttpContext’s User property to accomplish your custom authentication and authorization process.

ASP.NET Forms Authentication

ASP.NET forms authentication occurs after IIS authentication is completed. You can configure forms authentication by using forms element with in web.config file of your application. The default attribute values for forms authentication are shown below:
  1. <system.web>
  2. <authentication mode="Forms">
  3. <forms loginUrl="Login.aspx"
  4. protection="All"
  5. timeout="30"
  6. name=".ASPXAUTH"
  7. path="/"
  8. requireSSL="false"
  9. slidingExpiration="true"
  10. defaultUrl="default.aspx"
  11. cookieless="UseDeviceProfile"
  12. enableCrossAppRedirects="false" />
  13. </authentication>
  14. </system.web>
The FormsAuthentication class creates the authentication cookie automatically when SetAuthCookie() or RedirectFromLoginPage() methods are called. The value of authentication cookie contains a string representation of the encrypted and signed FormsAuthenticationTicket object.


You can create the FormsAuthenticationTicket object by specifying the cookie name, version of the cookie, directory path, issue date of the cookie, expiration date of the cookie, whether the cookie should be persisted, and optionally user-defined data as shown below:
  1. FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
  2. "userName",
  3. DateTime.Now,
  4. DateTime.Now.AddMinutes(30), // value of time out property
  5. false, // Value of IsPersistent property
  6. String.Empty,
  7. FormsAuthentication.FormsCookiePath);
Now, you can encrypt this ticket by using the Encrypt method FormsAuthentication class as given below:
  1. string encryptedTicket = FormsAuthentication.Encrypt(ticket);

Note

To encrypt FormsAuthenticationTicket ticket set the protection attribute of the forms element to All or Encryption.

Custom Authorization

ASP.NET MVC provides Authorization filter to authorize a user. This filter can be applied to an action, a controller, or even globally. This filter is based on AuthorizeAttribute class. You can customize this filter by overriding OnAuthorization() method as shown below:
  1. public class CustomAuthorizeAttribute : AuthorizeAttribute
  2. {
  3. public string UsersConfigKey { get; set; }
  4. public string RolesConfigKey { get; set; }
  5.  
  6. protected virtual CustomPrincipal CurrentUser
  7. {
  8. get { return HttpContext.Current.User as CustomPrincipal; }
  9. }
  10.  
  11. public override void OnAuthorization(AuthorizationContext filterContext)
  12. {
  13. if (filterContext.HttpContext.Request.IsAuthenticated)
  14. {
  15. var authorizedUsers = ConfigurationManager.AppSettings[UsersConfigKey];
  16. var authorizedRoles = ConfigurationManager.AppSettings[RolesConfigKey];
  17.  
  18. Users = String.IsNullOrEmpty(Users) ? authorizedUsers : Users;
  19. Roles = String.IsNullOrEmpty(Roles) ? authorizedRoles : Roles;
  20. if (!String.IsNullOrEmpty(Roles))
  21. {
  22. if (!CurrentUser.IsInRole(Roles))
  23. {
  24. filterContext.Result = new RedirectToRouteResult(new
  25. RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
  26.  
  27. // base.OnAuthorization(filterContext); //returns to login url
  28. }
  29. }
  30.  
  31. if (!String.IsNullOrEmpty(Users))
  32. {
  33. if (!Users.Contains(CurrentUser.UserId.ToString()))
  34. {
  35. filterContext.Result = new RedirectToRouteResult(new
  36. RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
  37.  
  38. // base.OnAuthorization(filterContext); //returns to login url
  39. }
  40. }
  41. }
  42. }
  43. }

User Authentication

A user will be authenticated if IsAuthenticated property returns true. For authenticating a user you can use one of the following two ways:
  1. Thread.CurrentPrincipal.Identity.IsAuthenticated
  2. HttpContext.Current.User.Identity.IsAuthenticated

Designing Data Model

Now it’s time to create data access model classes for creating and accessing Users and Roles as shown below:
  1. public class User
  2. {
  3. public int UserId { get; set; }
  4.  
  5. [Required]
  6. public String Username { get; set; }
  7.  
  8. [Required]
  9. public String Email { get; set; }
  10.  
  11. [Required]
  12. public String Password { get; set; }
  13.  
  14. public String FirstName { get; set; }
  15. public String LastName { get; set; }
  16.  
  17. public Boolean IsActive { get; set; }
  18. public DateTime CreateDate { get; set; }
  19.  
  20. public virtual ICollection<Role> Roles { get; set; }
  21. }
  1. public class Role
  2. {
  3. public int RoleId { get; set; }
  4.  
  5. [Required]
  6. public string RoleName { get; set; }
  7. public string Description { get; set; }
  8.  
  9. public virtual ICollection<User> Users { get; set; }
  10. }

Defining Database Context with Code First Mapping between User and Role

Using Entity Framework code first approach, create a DataContext having User and Role entities with its relational mapping details as shown below:
  1. public class DataContext : DbContext
  2. {
  3. public DataContext()
  4. : base("DefaultConnection")
  5. {
  6.  
  7. }
  8. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  9. {
  10. modelBuilder.Entity<User>()
  11. .HasMany(u => u.Roles)
  12. .WithMany(r=>r.Users)
  13. .Map(m =>
  14. {
  15. m.ToTable("UserRoles");
  16. m.MapLeftKey("UserId");
  17. m.MapRightKey("RoleId");
  18. });
  19. }
  20. public DbSet<User> Users { get; set; }
  21. public DbSet<Role> Roles { get; set; }
  22. }

Code First Database Migrations

With the help of entity framework code first database migrations create the database named as Security in the SQL Server. Run the following command through Visual Studio Package Manager Console to migrate your code into SQL Server database.


After running first command i.e. enabling migrations for your project, add seed data to Configuration.cs file of Migrations folder as shown below:
  1. protected override void Seed(Security.DAL.DataContext context)
  2. {
  3. Role role1 = new Role { RoleName = "Admin" };
  4. Role role2 = new Role { RoleName = "User" };
  5.  
  6. User user1 = new User { Username = "admin", Email = "admin@ymail.com", FirstName = "Admin", Password = "123456", IsActive = true, CreateDate = DateTime.UtcNow, Roles = new List() };
  7. User user2 = new User { Username = "user1", Email = "user1@ymail.com", FirstName = "User1", Password = "123456", IsActive = true, CreateDate = DateTime.UtcNow, Roles = new List() };
  8. user1.Roles.Add(role1);
  9. user2.Roles.Add(role2);
  10. context.Users.Add(user1);
  11. context.Users.Add(user2);
  12. }
When above three commands will be executed successfully as shown above, the following database will be created in your SQL Server.

Solution Structure





Designing View Model

Create a view model class for handing login process as given below:
  1. public class LoginViewModel
  2. {
  3. [Required]
  4. [Display(Name = "User name")]
  5. public string Username { get; set; }
  6.  
  7. [Required]
  8. [DataType(DataType.Password)]
  9. [Display(Name = "Password")]
  10. public string Password { get; set; }
  11.  
  12. [Display(Name = "Remember me?")]
  13. public bool RememberMe { get; set; }
  14. }
  1. public class CustomPrincipalSerializeModel
  2. {
  3. public int UserId { get; set; }
  4. public string FirstName { get; set; }
  5. public string LastName { get; set; }
  6. public string[] roles { get; set; }
  7. }

Forms Authentication Initialization

  1. public class AccountController : Controller
  2. {
  3. DataContext Context = new DataContext();
  4. //
  5. // GET: /Account/
  6. public ActionResult Index()
  7. {
  8. return View();
  9. }
  10.  
  11. [HttpPost]
  12. public ActionResult Index(LoginViewModel model, string returnUrl = "")
  13. {
  14. if (ModelState.IsValid)
  15. {
  16. var user = Context.Users.Where(u => u.Username == model.Username && u.Password == model.Password).FirstOrDefault();
  17. if (user != null)
  18. {
  19. var roles=user.Roles.Select(m => m.RoleName).ToArray();
  20.  
  21. CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
  22. serializeModel.UserId = user.UserId;
  23. serializeModel.FirstName = user.FirstName;
  24. serializeModel.LastName = user.LastName;
  25. serializeModel.roles = roles;
  26.  
  27. string userData = JsonConvert.SerializeObject(serializeModel);
  28. FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
  29. 1,
  30. user.Email,
  31. DateTime.Now,
  32. DateTime.Now.AddMinutes(15),
  33. false, //pass here true, if you want to implement remember me functionality
  34. userData);
  35.  
  36. string encTicket = FormsAuthentication.Encrypt(authTicket);
  37. HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
  38. Response.Cookies.Add(faCookie);
  39.  
  40. if(roles.Contains("Admin"))
  41. {
  42. return RedirectToAction("Index", "Admin");
  43. }
  44. else if (roles.Contains("User"))
  45. {
  46. return RedirectToAction("Index", "User");
  47. }
  48. else
  49. {
  50. return RedirectToAction("Index", "Home");
  51. }
  52. }
  53.  
  54. ModelState.AddModelError("", "Incorrect username and/or password");
  55. }
  56.  
  57. return View(model);
  58. }
  59.  
  60. [AllowAnonymous]
  61. public ActionResult LogOut()
  62. {
  63. FormsAuthentication.SignOut();
  64. return RedirectToAction("Login", "Account", null);
  65. }
  66. }
  1. public class MvcApplication : System.Web.HttpApplication
  2. {
  3. protected void Application_Start()
  4. {
  5. AreaRegistration.RegisterAllAreas();
  6.  
  7. WebApiConfig.Register(GlobalConfiguration.Configuration);
  8. FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
  9. RouteConfig.RegisterRoutes(RouteTable.Routes);
  10. BundleConfig.RegisterBundles(BundleTable.Bundles);
  11.  
  12. Database.SetInitializer<DataContext>(new DataContextInitilizer());
  13. }
  14. protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
  15. {
  16. HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
  17. if (authCookie != null)
  18. {
  19.  
  20. FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
  21.  
  22. CustomPrincipalSerializeModel serializeModel = JsonConvert.DeserializeObject<CustomPrincipalSerializeModel>(authTicket.UserData);
  23. CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
  24. newUser.UserId = serializeModel.UserId;
  25. newUser.FirstName = serializeModel.FirstName;
  26. newUser.LastName = serializeModel.LastName;
  27. newUser.roles = serializeModel.roles;
  28.  
  29. HttpContext.Current.User = newUser;
  30. }
  31.  
  32. }
  33. }

Base Controller for Accessing Current User

Create a base controller for accessing your User data in your all controller. Inherit, your all controller from this base controller to access user information from the UserContext.
  1. public class BaseController : Controller
  2. {
  3. protected virtual new CustomPrincipal User
  4. {
  5. get { return HttpContext.User as CustomPrincipal; }
  6. }
  7. }
  1. public class HomeController : BaseController
  2. {
  3. //
  4. // GET: /Home/
  5. public ActionResult Index()
  6. {
  7. string FullName = User.FirstName + " " + User.LastName;
  8. return View();
  9. }
  10. }

Base View Page for Accessing Current User

Create a base class for all your views for accessing your User data in your all views as shown below:
  1. public abstract class BaseViewPage : WebViewPage
  2. {
  3. public virtual new CustomPrincipal User
  4. {
  5. get { return base.User as CustomPrincipal; }
  6. }
  7. }
  8. public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
  9. {
  10. public virtual new CustomPrincipal User
  11. {
  12. get { return base.User as CustomPrincipal; }
  13. }
  14. }
Register this class with in the \Views\Web.config as base class for all your views as given below:
  1. <system.web.webPages.razor>
  2. <!--Other code has been removed for clarity-->
  3. <pages pageBaseType="Security.DAL.Security.BaseViewPage">
  4. <namespaces>
  5. <!--Other code has been removed for clarity-->
  6. </namespaces>
  7. </pages>
  8. </system.web.webPages.razor>
Now you can access the authenticated user information on all your view in easy and simple way as shown below in Admin View:
  1. @{
  2. ViewBag.Title = "Index";
  3. Layout = "~/Views/Shared/_AdminLayout.cshtml";
  4. }
  5. <h4>Welcome : @User.FirstName</h4>
  6. <h1>Admin DashBoard</h1>

Login View

  1. @model Security.Models.LoginViewModel
  2.  
  3. @{
  4. ViewBag.Title = "Index";
  5. }
  6.  
  7. @using (Html.BeginForm())
  8. {
  9. @Html.AntiForgeryToken()
  10. <div class="form-horizontal">
  11. <h4>User Login</h4>
  12. <hr />
  13. @Html.ValidationSummary(true)
  14.  
  15. <div class="form-group">
  16. @Html.LabelFor(model => model.Username, new { @class = "control-label col-md-2" })
  17. <div class="col-md-10">
  18. @Html.EditorFor(model => model.Username)
  19. @Html.ValidationMessageFor(model => model.Username)
  20. </div>
  21. </div>
  22.  
  23. <div class="form-group">
  24. @Html.LabelFor(model => model.Password, new { @class = "control-label col-md-2" })
  25. <div class="col-md-10">
  26. @Html.EditorFor(model => model.Password)
  27. @Html.ValidationMessageFor(model => model.Password)
  28. </div>
  29. </div>
  30.  
  31. <div class="form-group">
  32. @Html.LabelFor(model => model.RememberMe, new { @class = "control-label col-md-2" })
  33. <div class="col-md-10">
  34. @Html.EditorFor(model => model.RememberMe)
  35. @Html.ValidationMessageFor(model => model.RememberMe)
  36. </div>
  37. </div>
  38.  
  39. <div class="form-group">
  40. <div class="col-md-offset-2 col-md-10">
  41. <input type="submit" value="Login" class="btn btn-default" />
  42. </div>
  43. </div>
  44. </div>
  45. }

Applying CustomAuthorize Attribute

To make secure your admin or user pages, decorate your Admin and User controllers with CustomAuthorize attribute as defined above and specify the uses or roles to access admin and user pages.
  1. [CustomAuthorize(Roles= "Admin")]
  2. // [CustomAuthorize(Users = "1")]
  3. public class AdminController : BaseController
  4. {
  5. //
  6. // GET: /Admin/
  7. public ActionResult Index()
  8. {
  9. return View();
  10. }
  11. }
  1. [CustomAuthorize(Roles= "User")]
  2. // [CustomAuthorize(Users = "1,2")]
  3. public class UserController : BaseController
  4. {
  5. //
  6. // GET: /User/
  7. public ActionResult Index()
  8. {
  9. return View();
  10. }
  11. }
You can also specify the Roles and Users with in your web.config as a key to avoid hard code values for Users and Roles at the controller level.
  1. <add key="RolesConfigKey" value="Admin"/>
  2. <add key="UsersConfigKey" value="2,3"/>
Use one of these keys within the CustomAuthorize attribute as shown below:
  1. //[CustomAuthorize(RolesConfigKey = "RolesConfigKey")]
  2. [CustomAuthorize(UsersConfigKey = "UsersConfigKey")]
  3.  
  4. public class AdminController : BaseController
  5. {
  6. //
  7. // GET: /Admin/
  8. public ActionResult Index()
  9. {
  10. return View();
  11. }
  12. }
  1. [CustomAuthorize(RolesConfigKey = "RolesConfigKey")]
  2. // [CustomAuthorize(UsersConfigKey = "UsersConfigKey")]
  3. public class UserController : BaseController
  4. {
  5. //
  6. // GET: /User/
  7. public ActionResult Index()
  8. {
  9. return View();
  10. }
  11. }

Test your Application

When you will run your application and will login into the application using user1, you will be redirected to User dashboard as shown below:





When user will try to access unauthorized pages such as Admin dashboard using URL: http://localhost:11681/Admin , he will get the custom error page as shown below:

What do you think?
I hope you will enjoy the tips while implmenting role-based or user-based security in your ASP.NET MVC application. I would like to have feedback from my blog readers. Your valuable feedback, question, or comments about this article are always welcome.