asp. net core mvc implements pseudo static function

  • 2021-09-12 00:55:02
  • OfStack

In a large website system, In order to improve the system access performance, it is often released into static pages, such as product details pages and news details pages of shopping malls. After the information is released, the frequency of change will not be very high. If it is processed by dynamic output, it will definitely cause great waste of resources to the server. However, we can't make static pages independently for these contents, so we can use pseudo-static methods to deal with them in the system. As for what is pseudo-static, we can use Baidu. Here we will introduce the way to realize pseudo-static in asp. net core mvc.

In the framework of mvc, view represents the view, and the result of its execution is the final output to the client browser, including html, css, js and so on. If we want to achieve static, we need to save the result of view execution as a static file and save it to a specified location, such as disk, distributed cache, etc. The saved content can be read directly after the next visit, instead of executing business logic once again. What should asp. net core mvc do to achieve this function? The answer is to use filters. In mvc framework, there are many types of filters. Here we want to use action filters. Action filters provide two time points: before and after the action is executed. Before the action is executed, we can judge whether a static page has been generated. If it has been generated, we can directly read the file content and output it, and the subsequent logic will be skipped. If there is no production, continue to go down, capture the results at this stage after the action is executed, and then save the static content generated by the results.

Then we will come to the specific implementation code. First, we define a filter type, and we become StaticFileHandlerFilterAttribute. This class is derived from ActionFilterAttribute provided in the framework. StaticFileHandlerFilterAttribute rewrites two methods provided by the base class: OnActionExecuted (after the action is executed) and OnActionExecuting (before the action is executed). The specific code is as follows:


[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class StaticFileHandlerFilterAttribute : ActionFilterAttribute
{
   public override void OnActionExecuted(ActionExecutedContext context){}
   public override void OnActionExecuting(ActionExecutingContext context){}
}


In OnActionExecuting, it is necessary to judge whether static content has been generated. If direct output content has been generated, the logic implementation is as follows:


// According to 1 Set the rules to generate the name of the static file, here is in accordance with the area+"-"+controller+"-"+action+key Rule generation 
string controllerName = context.RouteData.Values["controller"].ToString().ToLower();
string actionName = context.RouteData.Values["action"].ToString().ToLower();
string area = context.RouteData.Values["area"].ToString().ToLower();
// Here's Key Default equals to id Of course, we can configure different Key Name 
string id = context.RouteData.Values.ContainsKey(Key) ? context.RouteData.Values[Key].ToString() : "";
if (string.IsNullOrEmpty(id) && context.HttpContext.Request.Query.ContainsKey(Key))
{
  id = context.HttpContext.Request.Query[Key];
}
string filePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", area, controllerName + "-" + actionName + (string.IsNullOrEmpty(id) ? "" : ("-" + id)) + ".html");
// Determine whether the file exists 
if (File.Exists(filePath))
{
    // If it exists, read the file directly 
  using (FileStream fs = File.Open(filePath, FileMode.Open))
  {
    using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))
    {
               // Pass contentresult Returns the contents of the file 
       ContentResult contentresult = new ContentResult();
       contentresult.Content = sr.ReadToEnd();
       contentresult.ContentType = "text/html";
       context.Result = contentresult;
    }
  }
}

In OnActionExecuted, we need the result action result, judge whether the action result type is 1 ViewResult, if this result is executed by code, get the result output, and generate static page according to the above 1 rules, the specific implementation is as follows


// Get results 
IActionResult actionResult = context.Result;
 // Judge whether the result is 1 A ViewResult
    if (actionResult is ViewResult)
    {
      ViewResult viewResult = actionResult as ViewResult;
      // The following code performs this ViewResult , and put the result of html Content placement 1 A StringBuiler Object 
      var services = context.HttpContext.RequestServices;
      var executor = services.GetRequiredService<ViewResultExecutor>();
      var option = services.GetRequiredService<IOptions<MvcViewOptions>>();
      var result = executor.FindView(context, viewResult);
      result.EnsureSuccessful(originalLocations: null);
      var view = result.View;
      StringBuilder builder = new StringBuilder();
 
      using (var writer = new StringWriter(builder))
      {
        var viewContext = new ViewContext(
          context,
          view,
          viewResult.ViewData,
          viewResult.TempData,
          writer,
          option.Value.HtmlHelperOptions);
 
        view.RenderAsync(viewContext).GetAwaiter().GetResult();
        // This sentence 1 Be sure to call, otherwise the content will be empty 
        writer.Flush();
      }
      // Generate static file names according to rules 
      string area = context.RouteData.Values["area"].ToString().ToLower();
      string controllerName = context.RouteData.Values["controller"].ToString().ToLower();
      string actionName = context.RouteData.Values["action"].ToString().ToLower();
      string id = context.RouteData.Values.ContainsKey(Key) ? context.RouteData.Values[Key].ToString() : "";
      if (string.IsNullOrEmpty(id) && context.HttpContext.Request.Query.ContainsKey(Key))
      {
        id = context.HttpContext.Request.Query[Key];
      }
      string devicedir = Path.Combine(AppContext.BaseDirectory, "wwwroot", area);
      if (!Directory.Exists(devicedir))
      {
        Directory.CreateDirectory(devicedir);
      }
 
      // Write to a file 
      string filePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", area, controllerName + "-" + actionName + (string.IsNullOrEmpty(id) ? "" : ("-" + id)) + ".html");
      using (FileStream fs = File.Open(filePath, FileMode.Create))
      {
        using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
        {
          sw.Write(builder.ToString());
        }
      }
      // Output the current result 
      ContentResult contentresult = new ContentResult();
      contentresult.Content = builder.ToString();
      contentresult.ContentType = "text/html";
      context.Result = contentresult;
    }

For the Key mentioned above, we directly add the corresponding attributes


public string Key
{
  get;set;
}

So we can use this filter by adding the [StaticFileHandlerFilter] feature to the controller or controller method, or by using [StaticFileHandlerFilter (Key = "set value") if you want to configure a different Key]

Static has been realized, we also need to consider the update, if the background to update an article, we have to update the static page, there are many schemes: 1 is in the background to update the content, the corresponding static page can be deleted synchronously. We introduce another one here, regular update, is to let static pages have a fixed validity period, after this validity period automatically updated. To realize this logic, we need to get the creation time of static page in OnActionExecuting method, and then compare it with the current time to judge whether it has expired. If it has not expired, we will output the content directly, and if it has expired, we will continue to execute the following logic. The specific code is as follows:


// Get File Information Object 
FileInfo fileInfo=new FileInfo(filePath);
// If the settlement time interval is less than or equal to two minutes, it will be output directly. Of course, the rules here can be changed 
TimeSpan ts = DateTime.Now - fileInfo.CreationTime;
if(ts.TotalMinutes<=2)
{
  using (FileStream fs = File.Open(filePath, FileMode.Open))
  {
    using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))
    {
      ContentResult contentresult = new ContentResult();
      contentresult.Content = sr.ReadToEnd();
      contentresult.ContentType = "text/html";
      context.Result = contentresult;
    }
  }
}

At this point, pseudo-static is realized. The current processing method can only improve the access performance to a certain extent, but it may be far from enough for large portal systems. In the way described above, other functional extensions can be made, such as publishing static pages to CDN, publishing to a separate content server, and so on. No matter what way, the realization idea is the same.


Related articles: