English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Implementing Fake Static Function in asp.net core mvc

      In large-scale website systems, in order to improve the system access performance, some content that does not change frequently is often published as static pages, such as product detail pages of the mall, news detail pages, and so on. Once these pieces of information are published, the frequency of change will not be very high. If we still use the dynamic output method to process it, it will definitely cause a lot of resource waste on the server. But we can't make static pages for all these contents independently. Therefore, we can process them in a pseudo-static way in the system. What is pseudo-static? You can search Baidu for more information. Here, we will introduce the way to implement pseudo-static in ASP.NET Core MVC.

  In the MVC framework, the view represents the view, and its execution result is the final content output to the client browser, including html, css, js, and so on. If we want to implement staticization, we need to save the result of the view execution into a static file and store it in a specified location, such as disk, distributed cache, etc. Next time, when accessed, the saved content can be directly read without executing the business logic again. So how should we achieve this function in ASP.NET Core MVC? The answer is to use a filter. In the MVC framework, various types of filters are provided. Here, we use the action filter, which provides two timing points: before the action is executed and after the action is executed. We can first judge whether a static page has been generated before the action is executed. If it has been generated, we can directly read the file content and output it, and skip the subsequent logic. If not, we continue to proceed and capture the result at this stage after the action is executed, and then save the generated static content.

  Let's implement the code specifically. First, we define a filter type, which we call StaticFileHandlerFilterAttribute. This class inherits from the ActionFilterAttribute provided by the framework. StaticFileHandlerFilterAttribute overrides 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, we need to determine whether the static content has been generated, if it has been generated, output the content directly, the logic implementation is as follows:

//Generate the name of the static file according to certain rules, here it is according to 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, the Key defaults to id, of course, we can configure different Key names
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))
    {
       //Return the file content through contentresult
       ContentResult contentresult = new ContentResult();
       contentresult.Content = sr.ReadToEnd();
       contentresult.ContentType = "text/html";
       context.Result = contentresult;
    }
  }
}

  In OnActionExecuted, we need the result action result, determine whether the action result type is a ViewResult, if it is, execute the result through code, get the result output, generate a static page according to the same rules, the specific implementation is as follows         

//Get the result
IActionResult actionResult = context.Result;
 //Determine whether the result is a ViewResult
    if (actionResult is ViewResult)
    {
      ViewResult viewResult = actionResult as ViewResult;
      //The following code is to execute this ViewResult and put the resulting html content into 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 call must be made, otherwise the content will be empty
        writer.Flush();
      }
      //Generate static file name according to the 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 the 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;
    }

  The Key mentioned above, we directly add the corresponding property

public string Key
{
  get;set;
}

  This way we can use this filter, the method of use: add the [StaticFileHandlerFilter] attribute to the controller or controller method, if you want to configure a different Key, you can use [StaticFileHandlerFilter(Key = "Set the value")]

  Staticization has been implemented, we still need to consider updates. If the background updates an article, we need to update the static page as well. There are many solutions: one is to delete the corresponding static page synchronously when the content is updated in the background. Here we introduce another method, scheduled update, which is to give the static page a certain validity period, and automatically update after the expiration of this period. To implement this logic, we need to get the creation time of the static page in the OnActionExecuting method, then compare it with the current time, judge whether it has expired, if not expired, output the content directly, if expired, continue executing the logic behind. The specific code is as follows:

//Get the file information object
FileInfo fileInfo = new FileInfo(filePath);
//Settlement time interval, if less than or equal to two minutes, 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;
    }
  }
}

  The pseudo-static has been implemented. The current method can only improve the access performance to a certain extent, but for large-scale portal systems, it may be far from enough. According to the method introduced above, other functional extensions can be carried out, such as publishing the generated static pages to CDN, or to a separate content server, etc. No matter what method is used, the implementation approach is the same.

That's all for this article. I hope it will be helpful to everyone's learning and that everyone will support the Yelling Tutorial more.

Statement: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (when sending an email, please replace # with @) to report violations, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.

You May Also Like