Explain the common extension points of asp.net MVC: filter and model binding

Time:2021-10-11

1、 Filter

Each request in asp.net MVC will be assigned to a specific action (hereinafter referred to as “method”) under the corresponding controller (hereinafter referred to as “controller”) for processing. Normally, it is OK to write code directly in the method. However, if you want to process some logic before or after the method is executed, you need to use a filter here.

There are three commonly used filters: authorize (authorization filter), handleerror (exception filter) and actionfilter (user-defined filter). The corresponding classes are: authorizeattribute, handleerrorattribute and actionfilterattribute. Inherit these classes and rewrite the methods to achieve different functions.

1. Authorize authorization filter

As the name suggests, the authorization filter is used for authorization. The authorization filter is executed before the method is executed. It is used to restrict whether requests can enter this method. Create a new method:


public JsonResult AuthorizeFilterTest()
{
 return Json(new ReturnModel_Common { msg = "hello world!" });
}

Direct access results:

Now let’s assume that the authorizefiltertest method is a background method, and the user must have a valid token to access. The normal practice is to receive and verify the token in the authorizefiltertest method. However, once there are many methods, it is obviously impractical to write verification code in each method. At this time, the authorization filter is used:

public class TokenValidateAttribute : AuthorizeAttribute
  {
    /// <summary>
    ///Logical processing of authorization verification. If true is returned, authorization is passed, and if false, the opposite is true
    /// </summary>
    /// <param name="httpContext"></param>
    /// <returns></returns>
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
      string token = httpContext.Request["token"];
      if (string.IsNullOrEmpty(token))
      {
        return false;
      }
      else
      {
        return true;
      }
    }
  }

A new class inheriting the authorizeattribute is created, and the authorizecore method is rewritten. This pseudo code realizes that if the token has a value, it returns true, and if it does not, it returns false. It is marked on the method that can be accessed only after authorization:


[TokenValidate]
public JsonResult AuthorizeFilterTest()
{
  return Json(new ReturnModel_Common { msg = "hello world!" })
}

After tokenvalidate is marked, the authorizecore method is executed before the authorizefiltertest. If the authorizecore returns true, the authorization successfully executes the code in the authorizefiltertest, otherwise the authorization fails. Do not pass token:

Pass token:

When the authorization fails to pass the token, the default unauthorized page of MVC is entered. Improvements are made here: no matter whether the authorization is successful or failed, ensure that the return value format is consistent to facilitate front-end processing. At this time, override the handleunauthorizedrequest method in the authorizeattribute class:

/// <summary>
///Authorization failure handling
/// </summary>
/// <param name="filterContext"></param>
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
  base.HandleUnauthorizedRequest(filterContext);

  var json = new JsonResult();
  json.Data = new ReturnModel_Common
  {
    success = false,
    code = ReturnCode_ Interface.token expired or error,
    msg = "token expired or error"
  };
  json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
  filterContext.Result = json;
}

effect:

Actual combat: the most widely used authorization filter is the permission management system. After the user logs in successfully, the server outputs an encrypted token, which will be brought with subsequent requests. The server unties the token in the authorizecore method to get the user ID, and checks whether the database has the permission to request the current interface according to the user ID. if yes, it returns true, otherwise it returns false. Compared with successful login, the advantage of this method for authorization to cookies and sessions is that one interface is used by both PC and app.

2. Handleerror exception filter

The exception filter handles code exceptions and is executed when the system code throws errors. MVC has implemented the exception filter by default and registered with the app_ Filterconfig.cs in the start Directory:


filters.Add(new HandleErrorAttribute());

This takes effect in the whole system. Any interface or page that reports an error will execute the MVC default exception handling and return to a default error reporting page: views / shared / error (this page can be seen only when the program reports an error to the server. If the local debugging permission is high, you can still see the specific error reporting information)

@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="width=device-width" />
  < title > error < / Title >
</head>
<body>
  <hgroup>
    <h1>Wrong</ h1>
    <h2>An error occurred while processing your request</ h2>
  </hgroup>
</body>
</html>

The default exception filter obviously cannot meet the use requirements. Rewrite the exception filter to meet the actual needs of the project:

1) Error reporting can record the controller and method where the error code is located, as well as the request parameters and time when the error is reported;

2) Return JSON in a specific format to facilitate front-end processing. Because most of the current system are Ajax requests, which report errors and return to the MVC default error page, the front end is difficult to handle

Create a new class logexceptionattribute, inherit handleerrorattribute, and override the internal onexception method:

public override void OnException(ExceptionContext filterContext)
 {
   if (!filterContext.ExceptionHandled)
   {
     string controllerName = (string)filterContext.RouteData.Values["controller"];
     string actionName = (string)filterContext.RouteData.Values["action"];
     string param = Common.GetPostParas();
     string ip = HttpContext.Current.Request.UserHostAddress;
     LogManager.GetLogger("LogExceptionAttribute").Error("Location:{0}/{1} Param:{2}UserIP:{3} Exception:{4}", controllerName, actionName, param, ip, filterContext.Exception.Message);

     filterContext.Result = new JsonResult
     {
       Data = new ReturnModel_ Common {success = false, code = returncode_interface. Error thrown by the server, MSG = filtercontext. Exception. Message},
       JsonRequestBehavior = JsonRequestBehavior.AllowGet
     };
   }
   if (filterContext.Result is JsonResult)
     filterContext.ExceptionHandled = true;// If the returned result is jsonresult, the setting exception has been handled
   else
     base.OnException(filterContext);// Execute the logic of the base class handleerrorattribute and turn to the error page
 }

The exception filter is not marked on the method like the authorization filter, and directly to the app_ Register in filterconfig.cs under the start directory so that all interfaces can take effect:


filters.Add(new LogExceptionAttribute());

NLog is used as a logging tool in the exception filter. Nuget installation command:


Install-Package NLog
Install-Package NLog.Config

Compared with log4net, NLog configuration is simple, just a few lines of code. Nlog.config:


<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <targets>
  <target xsi:type="File" name="f" fileName="${basedir}/log/${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" />
  <target xsi:type="File" name="f2" fileName="D:\log\MVCExtension${shortdate}.log" layout="${uppercase:${level}} ${longdate} ${message}" />
 </targets>
 <rules>
  <logger name="*" minlevel="Debug" writeTo="f2" />
 </rules>
</nlog>

If an error is reported, the log is recorded in the mvcextension directory under the log directory of disk D. one log directory for each project is convenient for management. After all configurations are completed, see the following code:


public JsonResult HandleErrorFilterTest()
{
  int i = int.Parse("abc");
  return Json(new ReturnModel_Data { data = i });
}

If the string is forcibly converted to int type, an error must be reported, and the page response is:

At the same time, the log also records:

3. Actionfilter user defined filter

Custom filters are more flexible and can be accurately injected into pre request, request and post request. Inherit the abstract class actionfilterattribute and override the methods in it:


public class SystemLogAttribute : ActionFilterAttribute
{
  public string Operate { get; set; }

  public override void OnActionExecuted(ActionExecutedContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuted");
    base.OnActionExecuted(filterContext);
  }

  public override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnActionExecuting");
    base.OnActionExecuting(filterContext);
  }

  public override void OnResultExecuted(ResultExecutedContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuted");
    base.OnResultExecuted(filterContext);
  }

  public override void OnResultExecuting(ResultExecutingContext filterContext)
  {
    filterContext.HttpContext.Response.Write("<br/>" + Operate + ":OnResultExecuting");
    base.OnResultExecuting(filterContext);
  }
}

This filter is suitable for system operation logging:

[systemlog (operate = "add user")]
public string CustomerFilterTest()
{
  Response. Write ("< br / > action in progress...);
  Return "< br / > end of action execution";
}

Look at the results:

The execution order of the four methods is onactionexecuting – > onactionexecuted – > onresultexecuting – > onresultexecuted, which controls the whole request process very accurately.

In practice, the process of logging is as follows: write an operation log in the onactionexecuting method to the database, save the primary key of the record in the global variable, and indicate that the request is over in the onresultexecuted method. At this time, you naturally know whether the user’s operation is successful, and update the success field of the operation log according to the primary key.

Two, model binding (ModelBinder)

Let’s look at a common method:


public ActionResult Index(Student student)
{
  return View();
}

The parameter accepted by this method is a student object. If the parameters passed from the front end remain the same as the properties in the student object, they will be bound to this object automatically. There is no need to bind the new student object in the method and bind the properties one by one. The binding process is completed by the defaultmodelbinder in MVC, which also inherits the imodelbinder interface, Now use the imodelbinder interface and defaultmodelbinder to achieve more flexible model binding.

Scenario 1: an encrypted string token is passed from the front end. Some fields in the token need to be used in the method, so you have to receive the string, decrypt the string and convert it into an object in the method. It’s OK for such a method. If there are too many duplicate codes, even if the general method is extracted, you still need to call the general method in the method, Is there any way to encapsulate this object directly in parameters?

Model bound objects:

public class TokenModel
{
  /// <summary>
  ///Primary key
  /// </summary>
  public int Id { get; set; }

  /// <summary>
  ///Name
  /// </summary>
  public string Name { set; get; }

  /// <summary>
  ///Introduction
  /// </summary>
  public string Description { get; set; }

}

Create a tokenbinder that inherits the imodelbinder interface and implements the bindmodel method:


public class TokenBinder : IModelBinder
{
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    var token = controllerContext.HttpContext.Request["token"];
    if (!string.IsNullOrEmpty(token))
    {
      string[] array = token.Split(':');
      if (array.Length == 3)
      {
        return new TokenModel() { Id = int.Parse(array[0]), Name = array[1], Description = array[2] };
      }
      else
      {
        return new TokenModel() { Id = 0 };
      }
    }
    else
    {
      return new TokenModel() { Id = 0 };
    }
  }
}

This method receives a token parameter, parses and encapsulates the token parameter. The code part is completed. You need to go to the application_ Register in the start method:


ModelBinders.Binders.Add(typeof(TokenModel), new TokenBinder());

Now simulate this interface:


public JsonResult TokenBinderTest(TokenModel tokenModel)
{
  var output = "Id:" + tokenModel.Id + ",Name:" + tokenModel.Name + ",Description:" + tokenModel.Description;
  return Json(new ReturnModel_Common { msg = output });
}

Call next:

It can be seen that “1: Wang Jie: oppoic. Cnblogs. Com” has been bound to the tokenmodel object. However, if a more complex model is bound to imodelbinder, there is nothing that imodelbinder can do.

Scenario 2: remove the first space of an attribute of the object


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

  public string Name { get; set; }

  public string Class { get; set; }
}

If there are spaces in the name attribute passed from the front end, how to remove them? More flexible control with defaultmodelbinder

public class TrimModelBinder : DefaultModelBinder
{
  protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
  {
    var obj = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    If (obj is string & & propertydescriptor. Attributes [typeof (trimattribute)]! = null) // judge whether it is of string type and marked with [trim]
    {
      return (obj as string).Trim();
    }
    return obj;
  }
}

Label the entities that need to format the first attribute:


[ModelBinder(typeof(TrimModelBinder))]
public class Student
{
  public int Id { get; set; }

  [Trim]
  public string Name { get; set; }

  public string Class { get; set; }
}

Well, test:

public JsonResult TrimBinderTest(Student student)
{
  if (string.IsNullOrEmpty(student.Name) || string.IsNullOrEmpty(student.Class))
  {
    Return JSON (New returnmodel_common {MSG = "parameter not found"});
  }
  else
  {
    Return JSON (New returnmodel_common {MSG = "Name:" + student. Name + ", length:" + student. Name. Length + "class:" + student. Class + ", length:" + student. Class. Length});
  }
}

It can be seen that the name length marked with trim attribute is the length excluding spaces: 7, while the length of class attribute not marked is 6.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support developpaer.