Part II- Configuration of Features

In the previous post, we saw the step-by-step procedure for creating a new project with ASP.NET Core 2.0 and Angular 5. To make the optimal use of the MVC features, you need to perform some configurations on the following features:

  • Session
  • Exception handling and action logging
  • Action filter
  • Reading appsettings.json

Session

You need to enable session in the Startup.cs for using the session features. To enable session and session state, the following code should be replaced:

Session and session state enabling code screenshot

Replace the above code with the following:

public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc().AddSessionStateTempDataProvider();
   services.AddSession();
}

In ASP.NET Core 2.0 session, value is stored as byte array. You can store any value inside the session. Use the below code for setting and getting byte arrays to session.

HttpContext.Session.Set("key", bytearray);
HttpContext.Session.TryGetValue("key", out bytearray);

You can convert values to byte array using the following code:

byte[] ObjectToByteArray(object obj)
{
      if (obj == null)
      {
                  return null;
      }

      BinaryFormatter bf = new BinaryFormatter();
      using (MemoryStream ms = new MemoryStream())
      {
            bf.Serialize(ms, obj);
            return ms.ToArray();
       }
}

You can retrieve values from byte array using the following code:

private Object ByteArrayToObject(byte[] arrBytes)
{
   MemoryStream memStream = new MemoryStream();
   BinaryFormatter binForm = new BinaryFormatter();
   memStream.Write(arrBytes, 0, arrBytes.Length);
   memStream.Seek(0, SeekOrigin.Begin);
   Object obj = (Object)binForm.Deserialize(memStream);
   return obj;
}

Exception handling and action logging

Using the middleware pipeline, you can intercept all actions in ASP.NET Core. For that you need to create a Middleware class and configure it in the application globally.

First you need to create the class ActionMiddleware.cs

public class ActionMiddleware
{
   private readonly RequestDelegate _next;
   public ActionMiddleware(RequestDelegate next)
   {
       next = next;
   }

   public async Task Invoke(HttpContext context)
   {
       try
        {
                //Code for logging all action requests. context.Request will get the request
                await _next(context);
        }
        catch (Exception ex)
        {
           string path = context.Request.Path.Value;
           //Code for logging errors.

            context.Response.ContentType = "text/plain";
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            if (ex is ApplicationException)
            {
                    await context.Response.WriteAsync(ex.Message);
             }
        }
    }
}

Above class can be used for logging requests and handling exceptions. You need to configure the above class in the Configure section in Startup.cs. Add the below line of code in Configure section in Startup.cs

app.UseMiddleware<ActionMiddleware>();

Action Filter

First, you need to create two class inheriting Attribute and IActionFilter.

public class ValidateSession : Attribute, IActionFilter
{
   public void OnActionExecuting(ActionExecutingContext context)
    {
      if (context.ActionDescriptor.FilterDescriptors.ToList().Where(x => x.Filter.GetType() == typeof(SkipSessionAttribute)).Count() == 0)
      {
         var redWeb = new RedirectResult(@"~/Account/RedirectSessionOut");
         var redApi = new RedirectResult(@"~/Account/SessionOutAPI");

         if (!/*session checking*/)
        {
         context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;

         if (context.HttpContext.Request.Path.StartsWithSegments("/api"))
          {
                context.Result = redApi;
          }
          else
          {
                context.Result = redWeb;
          }
        }

        return;
     }
   }
   public void OnActionExecuted(ActionExecutedContext context)
   {
   }
}


public class SkipSessionAttribute : Attribute, IActionFilter
{   
   public void OnActionExecuting(ActionExecutingContext context)
   {
   }

   public void OnActionExecuted(ActionExecutedContext context)
   {
   }
}

You can register the action filter in ConfigureServices section in Startup.cs.

services.AddMvc().AddSessionStateTempDataProvider();

Replace the above line in ConfigureServices section in Startup.cs with below code:

services.AddMvc(options =>
      {
           options.Filters.Add(new ValidateSession());
      }).AddSessionStateTempDataProvider();

In the action filter, you can check for session expire/ authentication. If the verification fails, the page is redirected to Account/RedirectSessionOut in the case of web requests, and Account/SessionOutAPI in the case of API calls.

You need to return HTTP 401 error from Account/SessionOutAPI in case of web API calls. Inside the client app, during the error check for HTTP 401 error, you need to figure out if the error is thrown because of session out or any unauthenticated request that has occurred.

The second class SkipSessionAttribute can be used as an attribute to action, where the session checking needs to be skipped.

   SkipSession]
   public IActionResult Index()
   {       
        return View();
   }

Reading appsettings.json

In ASP.NET, there is AppSettings section in web.config file for storing key value pairs. In ASP.NET Core 2.0 there is no web.config file. Instead, there is the appsettings.json file where you can store values.

The advantage of appsettings.json is that you can store settings as multiple sections. For instance, if you need to store some system settings and SMTP settings, you can add two sections: Settings and SmtpConfig to appsettings.json.

 

"Settings": {
    "Instance": "dev",
    "Timeout": 120,
    "Name": "Hello"
  },
"Smtp": {
    "Server": "servername",
    "Host": "hostname",
    "Username": "username",
    "Password": "password"
 }

Each block is called sections. Now you need to create two classes which contains properties same as these sections.

public class Settings
{
    public string Instance { get; set; }
    public int Timeout { get; set; }
    public string Name { get; set; }
}

public class SmtpConfig
{

    public string Server { get; set; }
    public string Host { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
}

Inside the ConfigureServices (IServiceCollection services), you need to configure these configuration sections using dependency injection:

services.Configure<Domain.Settings>(Configuration.GetSection("Settings"));
services.Configure<Domain.SmtpConfig>(Configuration.GetSection("Smtp"));

In the above two sections, you can define settings which are common across the system settings file. You need to make this setting available throughout the actions. For this, define it in the ActionMiddleWare class that you have created for logging and exception handling.

Declare the settings class in ActionMiddleWare.cs.

private readonly Settings settings;

In the constructor, assign setting to the declared variable:

public ActionMiddleware(RequestDelegate next, IOptions<Domain.Settings> options)
{
    _next = next;
    this.settings = options.Value;
}

In the Invoke function, add a net item to HttpContext for settings.

public async Task Invoke(HttpContext context)
{
      try
       {

       //Code for actionlog context.Request  -- get request and paramenters
            context.Items.Add("settings", this.settings);
            await _next(context);
        }
      catch (Exception ex)
       {
      string path = context.Request.Path.Value;

      //Code for logging error here.

      context.Response.ContentType = "text/plain";
      context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

      if (ex is ApplicationException)
         {
                await context.Response.WriteAsync(ex.Message);
         }
     }
 }

In any action, you can get the settings by the below code:

Settings settings = (Settings)(HttpContext.Items["settings"]);

Also there is another way of accessing appsettings.json. You have configured the SmtpConfig section in ConfigureServices using dependency injection. You can create a constructor for controller to receive this settings.

private readonly IOptions<Settings> _settings;

public HomeController(IOptions<Settings> settings)
{
    _settings = settings;
}

Using these two methods you can access AppSettings.json.

Hope my posts will help you. Try out these steps and let us know your thoughts in the comments section.

Posted by Gokul Suresh

I am Gokul Suresh, Senior Software Engineer at Zerone Consulting. Passion, combined with a get-it-done spirit drives me to research and learn new technologies. I have experience in designing, implementing and adapting technically sophisticated online web applications using Microsoft ASP.NET, MVC, Web API, Angular JS and SQL.

Leave a Reply