European ASP.NET MVC 4 and MVC 5 Hosting

BLOG about ASP.NET MVC 3, ASP.NET MVC 4, and ASP.NET MVC 5 Hosting and Its Technology - Dedicated to European Windows Hosting Customer

European ASP.NET MVC 4 Hosting - Amsterdam :: Implementing a Custom IPrincipal in ASP.NET MVC 4 Internet Project

clock April 16, 2013 10:49 by author Scott

This article explains a simple tip on how to customized the IPrincipal used in ASP.NET MVC4 internet application project template. You can try this tip if you want to attach additional information on the IPrincipal (Controller.User) for some purposes.

This tip is based from the solution I used in implementing custom identity in my ASP.NET MVC 3 project which I got from this thread: http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal.

The main solution is almost the same from the said thread but with just a few tweaks required to set data to additional IPrincipal properties when OAuthWebSecurity is used as authentication method.  

Initially ASP.NET MVC 4 internet project template is configured to use both WebMatrix.WebSecurity (for local accounts) and OAuthWebSecurity (for external site accounts) for authentication. Also accounts data are getting saved in a UserProfile table which only have properties for user ID and username, and some predefined webpages_TABLES. 

This initial setup is not enough for us to achieve our goal: that is to attach additional information in the IPrincipal. In this example we will going to need to add the first name and last name info of the user but you can add any data to suit your needs.

We will need first a storage of the additional data we want to attach. To do this you can just simply add properties on the UserProfile class defined in AccountModels.cs. Or use any table then modify the InitializeSimpleMembershipAttribute.cs from the Filters folder and set your DBContext and table name:

public SimpleMembershipInitializer()
{
    Database.SetInitializer<YourDBContext>(null);

    try
    {
      using (var context = new UsersContext())
      {
        if (!context.Database.Exists())
        {
          // Create the SimpleMembership database without Entity Framework migration schema
          ((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
        }
      }

      WebSecurity.InitializeDatabaseConnection("DefaultConnection", "YourDesiredTable",
              "UserId", "UserName", autoCreateTables: true);
    }
    catch (Exception ex)
    {
      throw new InvalidOperationException("The ASP.NET Simple Membership database could " +
        "not be initialized. For more information, please see " +
        "http://go.microsoft.com/fwlink/?LinkId=256588", ex);
    }
}

Another way is to leave the SimpleMembershipInitializer as it is and check this tutorial: http://www.asp.net/mvc/tutorials/mvc-4/using-oauth-providers-with-mvc 

If your data storage is now ready we can now start creating custom IPrincipal: 

public interface ICustomPrincipal : System.Security.Principal.Iprincipal
{
    string FirstName { get; set; }

    string LastName { get; set; }
}
public class CustomPrincipal : IcustomPrincipal
{
    public IIdentity Identity { get; private set; }

    public CustomPrincipal(string username)
      {
            this.Identity = new GenericIdentity(username);
      }

      public bool IsInRole(string role)
      {
            return Identity != null && Identity.IsAuthenticated &&
               !string.IsNullOrWhiteSpace(role) && Roles.IsUserInRole(Identity.Name, role);
      }

      public string FirstName { get; set; }

      public string LastName { get; set; }

      public string FullName { get { return FirstName + " " + LastName; } }
}

public class CustomPrincipalSerializedModel
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }
}

Then in the AccountController class, add this method. We will need this method to serialize the user data and attach it in a cookie: 

public void CreateAuthenticationTicket(string username) {      

      var authUser = Repository.Find(u => u.Username == username); 
      CustomPrincipalSerializedModel serializeModel = new CustomPrincipalSerializedModel();     

      serializeModel.FirstName = authUser.FirstName;
      serializeModel.LastName = authUser.LastName;
      JavaScriptSerializer serializer = new JavaScriptSerializer();
      string userData = serializer.Serialize(serializeModel);     

      FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
        1,username,DateTime.Now,DateTime.Now.AddHours(8),false,userData);
      string encTicket = FormsAuthentication.Encrypt(authTicket);
      HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
      Response.Cookies.Add(faCookie);
}

Call the above method: From the ExternalLoginCallback method: 

public ActionResult ExternalLoginCallback(string returnUrl)
{
      AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
        Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
      if (!result.IsSuccessful)
      {
        return RedirectToAction("ExternalLoginFailure");
      }

      if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: true))
      {
        CreateAuthenticationTicket(OAuthWebSecurity.GetUserName(
                        result.Provider, result.ProviderUserId));
        return RedirectToLocal(returnUrl);
      }

      if (User.Identity.IsAuthenticated)
      {
        // If the current user is logged in add the new account
        OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
        CreateAuthenticationTicket(User.Identity.Name);
        return RedirectToLocal(returnUrl);
      }
      else
      {
        // User is new, ask for their desired membership name
        string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
        ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
        ViewBag.ReturnUrl = returnUrl;
        return View("ExternalLoginConfirmation",
          new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
      }
}

In Register method: 

public ActionResult Register(RegisterModel model)
{
  if (ModelState.IsValid)
  {
    // Attempt to register the user
    try
    {
      WebSecurity.CreateUserAndAccount(
        model.UserName,
        model.Password,
        new {            
            UpdatedBy = 0,
            UpdatedDate = DateTime.Today,
            CreatedBy = 0,
            CreatedDate = DateTime.Today
          }
       );

      WebSecurity.Login(model.UserName, model.Password);
      CreateAuthenticationTicket(model.UserName);
      return RedirectToAction("Index", "Home");
    }
    catch (MembershipCreateUserException e)
    {
      ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
    }
}

In ExternalLoginConfirmation method: 

...
OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
 OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);
 CreateAuthenticationTicket(model.UserName);
 return RedirectToLocal(returnUrl);   
... 

And in the Login method: 

public ActionResult Login(LoginModel model, string returnUrl)
{
      if (ModelState.IsValid && WebSecurity.Login(model.UserName,
                model.Password, persistCookie: model.RememberMe))
      {
        CreateAuthenticationTicket(model.UserName);
        return RedirectToLocal(returnUrl);
      }

      // If we got this far, something failed, redisplay form
      ModelState.AddModelError("", "The user name or password provided is incorrect.");
      return View(model);
}

It's now time to read the serialized data from our cookie and replace the HttpContext.Current.User. Do this by overriding the Application_PostAuthenticateRequest method in project's Global.asax.cs . 

protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
      HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
      if (authCookie != null)
      {
        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        if (authTicket.UserData == "OAuth") return;
        CustomPrincipalSerializedModel serializeModel =           serializer.Deserialize<CustomPrincipalSerializedModel>(authTicket.UserData);
        CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
        newUser.Id = serializeModel.Id;
        newUser.FirstName = serializeModel.FirstName;
        newUser.LastName = serializeModel.LastName;
        HttpContext.Current.User = newUser;
      }
}

To access the attached data from pages:

@(User as CustomPrincipal).FullName

And from server: 

@(User as CustomPrincipal).FullName



European ASP.NET MVC 4 Hosting - Amsterdam :: How to Add Metatags on .cshtml Pages in MVC

clock April 8, 2013 09:08 by author Scott

This quick article is a response to a question I received today on Facebook. Please use the following procedure to add metatags on .cshtml pages.

Step 1

When we create a MVC4 Application using an Internet Template we get a "Shared" folder inside the "Views" folder on the root and in the "Shared" folder you will find a layout page named "_Layout.cshtml". Open that file.

Step 2

In the "_Layout.cshtml" page add a new section call inside the <head> tag, as given below:

In the above image you can see that a section call is not required; in other words whenever we need metatags on a page we can define.

Step 3

Now, open you .cshtml page where you wish to add metatags and add the following section reference:

Step 4

Now, open the page in a browser and you will see your metatags in action.

Advanced

We can also make these metatags dynamic, in other words we can control them from controllers.

Controller

public ActionResult Index()
{
    ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
    ViewBag.MetaKeywords = "abc";
    ViewBag.MetaDescription = "abc";

    return View();
}

Section on .cshtml page

@section metatags {
    <meta name='keywords' content='@ViewBag.MetaKeywords'/>
    <meta name='description' content='@ViewBag.MetaDescription'/>
}

Hope this helps.

 



European ASP.NET MVC 4 Hosting - Amsterdam :: Error Handling and Logging in MVC4

clock April 2, 2013 10:19 by author Scott

Error handing is the main concern in any application, whether it is web application or desktop application. Usually, we catch the exception and log its details to database or text,xml file and also display a user friendly message to end user in-place of error.

Asp.Net MVC has some bulit-in exception filters. HandleError is the default bulit-in exception filter. Let's see how to use this filter with in your application.

HandleError Attribute

The HandleErrorAttribute filter works only when custom errors are enabled in the Web.config file of your application. You can enable custom errors by adding a customErrors attribute inside the <system.web> node, as shown below:

</system.web>
...
<customErrors mode="On" defaultRedirect="Error.htm"/>
</system.web>

HandleError Attribute can be used to handle error at Action Method level, Controller level and Global level.

HandleError Attribute at Action Method Level

Simply put this attribute to the action method to tell MVC how to react when an exception occurs.

[HandleError(ExceptionType = typeof(System.Data.DataException), View = "DatabaseError")]
public ActionResult Index(int id)
{
 var db = new MyDataContext();
 return View("Index", db.Categories.Single(x => x.Id == id));
}

In the above example when a database exception (System.Data.DataException) occurs during the execution of the Index action, MVC will display the DatabaseError view.

HandleError Attribute at Controller Level

Simply put this attribute to the controller to tell MVC how to react when an exception occurs with in the action methods of this controller.

[HandleError(ExceptionType = typeof(System.Data.DataException), View = "DatabaseError")]
public class HomeController : Controller
{
 /* Controller Actions with HandleError applied to them */
}

In the above example when a database exception (System.Data.DataException) occurs during the execution of the controler's any action methos, MVC will display the DatabaseError view.

HandleError Attribute at Global Level

You can also apply the HandleError Attribute for the entire application by registering it as a global error handler. For registering a global error handler, open the FilterConfig.cs file with in App_Start folder to find the RegisterGlobalFilters method.

By default, ASP.NET MVC template has already registered a global HandleErrorAttribute to the GlobalFilterCollection for your application. Here you can also add your own custom filter to the global filter collection as well.

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
 filters.Add(new HandleErrorAttribute
 {
 ExceptionType = typeof(System.Data.DataException),
 View = "DatabaseError"
 }); 

 filters.Add(new HandleErrorAttribute()); //by default added
}

Note

Always remember, by default, global filters are executed in their registered order. so register error filters for specific exception types before any other.

You can also set the order of execution of these filter by giving number values to each. The above registered filters can be re-written as:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
 filters.Add(new HandleErrorAttribute(),2); //by default added
 filters.Add(new HandleErrorAttribute
 {
 ExceptionType = typeof(System.Data.DataException),
 View = "DatabaseError"
 },1);
}

Now, all the filters will be executed in the number sequence.

Limitation of HandleError Attribute

1. It has no support to log the exceptions since it suppressed the error once it is handled.
2. It only catch 500 Http error and doesn't catch other HTTP errors like 404,401 etc.
3. It doesn't catch the errors that occur outside the controllers like errors occurs in model.
4. It returns error view even if error occurred in AJAX calls that is not useful in the client-side. It would be great to return a piece of JSON in AJAX exceptions .

Extending HandleError Filter for logging and handling more errors

You can also make your custom error filter by extending HandleError filter. Let's see how to extend this filter to log the exceptions and return a JSON object for AJAX calls.

public class CustomHandleErrorAttribute : HandleErrorAttribute
{
 public override void OnException(ExceptionContext filterContext)
 {
 if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
 {
 return;
 }

 if (new HttpException(null, filterContext.Exception).GetHttpCode() != 500)
 {
 return;
 }

 if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
 {
 return;
 }

 // if the request is AJAX return JSON else view.
 if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
 {
 filterContext.Result = new JsonResult
 {
 JsonRequestBehavior = JsonRequestBehavior.AllowGet,
 Data = new
 {
 error = true,
 message = filterContext.Exception.Message
 }
 };
 }
 else
 {
 var controllerName = (string)filterContext.RouteData.Values["controller"];
 var actionName = (string)filterContext.RouteData.Values["action"];
 var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);

 filterContext.Result = new ViewResult
 {
 ViewName = View,
 MasterName = Master,
 ViewData = new ViewDataDictionary(model),
 TempData = filterContext.Controller.TempData
 };
 }

 // log the error by using your own method
 LogError(filterContext.Exception.Message, filterContext.Exception);

 filterContext.ExceptionHandled = true;
 filterContext.HttpContext.Response.Clear();
 filterContext.HttpContext.Response.StatusCode = 500;

 filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
 }
}

 



About HostForLIFE.eu

HostForLIFE.eu is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2016 Hosting, ASP.NET Core 2.2.1 Hosting, ASP.NET MVC 6 Hosting and SQL 2017 Hosting.


Tag cloud

Sign in