学编程,来源栈;先学习,再交钱
当前系列: ASP.NET 修改讲义

复习:性能:平衡 / 善意欺骗 / 缓存 / 线程和异步 / 队列 / 压缩合并 / 高并发大流量 

Cache对象

MVC中可以HttpContext直接获取缓存对象,可以把它当做一个“全局”容器,里面以键(string)值(object)对的形式存储着缓存数据。

可调用其索引器读写缓存数据,其过程非常类似于session/HttpContext.Current.Items

public ActionResult Single(int id)
{
    string key = "article";   
    object cachedModel = HttpContext.Cache[key];
    if (cachedModel == null)
    {
        HttpContext.Cache[key] = service.Get(id);
    }//else nothing
    return View((SingleModel)HttpContext.Cache[key]);

常见面试题:区别

  • Cache:作用于整个application,所有的请求都可以读取
  • Session:作用于(基于cookie等识别的)单个用户,A用户无法获取B用户存储的信息
  • HttpContext.Current.Items:作用于当前HTTP请求,即使同一个用户,也无法读取上一个请求中存储的数据

配置缓存

实际上,上述索引器内部只是简单的调用了两个方法:

  • Get(key)方法:获取缓存数据
  • Insert(key, value)(或者Add())方法:添加缓存数据

而Insert()(或者Add())方法还有很多重载,可以定义更多的缓存配置:

public object Add(
    string key, object value, //键值对
    CacheDependency dependencies, //主要适用于文件,当文件发生变动时,删除当前缓存
    DateTime absoluteExpiration,
    TimeSpan slidingExpiration,
    CacheItemPriority priority, //优先级
    CacheItemRemovedCallback onRemoveCallback);//当缓存被删除时调用){

过期时间(Expire)

其实,实际开发中更常用的是设置缓存区数据的过期时间。一旦超过过期时间,就将该缓存数据予以清理。

ASP.NET中可以设置:

  1. 永不过期(NeverRemoved):默认值
  2. 绝对(Absolute)过期时间:即缓存只在某一绝对时间(比如2020年2月11日20:50)以前有效
  3. 滑动(Slide)过期时间:其实就是一段时间,比如30秒,它从缓存数据最后一次被访问起算,30秒后过期;但是,在这30秒内,一旦该数据又被访问,那么过期时间将会被重新计算,变成当前时间加30秒……(和session失效机制非常类似)

在MVC中,绝对/滑动过期时间不能同时设立,否则会报异常(演示:略):

  • 设置Absolute时Slide参数只能为TimeSpan.Zero
    DateTime.Now.AddSeconds(30), TimeSpan.Zero
  • 设置Slide时Absolute参数只能为DateTime.MaxValue
    DateTime.MaxValue, new TimeSpan(0, 0, 5),

@想一想@:为什么要设计一个滑动过期呢?

滑动过期基于这样一种假设:越是被频繁请求的数据,以后就越有可能被再次请求(类似于“犹太定律”,事实上也确实如此,^_^)。利用滑动过期,就能自动的筛选出最被频繁访问的数据,提高命中率,最大限度的压榨性能。

优先级(Priority)

和Session类似,虽然我们设置了缓存的过期时间,但并不能保证在此期间缓存一定存在——在某些情况下,有效期内的缓存也会被MVC自动清除。

这时候,ASP.NET会清除那些优先级低的,保留优先级高的。

我们可以在插入缓存数据的时候指定其优先级(枚举):

public enum CacheItemPriority

依赖(Dependency)

默认的cache数据会一直保存,这样,如果真实/源数据发生改变,缓存数据就会变得“不正确”。

演示:更改数据库中数据,改变页面呈现……

所以,ASP.NET提供了缓存依赖机制,即:为缓存数据设置一个“依赖”,如果依赖发生变化,让缓存失效,以便能获取正确数据。

我们常用的是

SqlCacheDependency

即:一旦数据库数据发生变动,就让缓存失效。

因为localDb不支持从数据库发送notification(通知)到.NET运行时,所以我们采用的是poll机制:由ASP.NET定时的轮循检查数据库变更。

所以需要在数据库上设置一个表,专门的记录其最后更改时间……

首先,使用aspnet_regsql.exe命令配置数据库:(复习:数据库中存放session

  1. 打开VS的命名行工具:Developer Command Prompt
  2. 在弹出的黑窗口中输入命令行:
    aspnet_regsql.exe -S (localdb)\MSSQLLocalDB -E -d 17bang -ed
    启动数据库的缓存依赖,其中:
    • -S 后跟数据库服务器对象名 (localdb)\MSSQLLocalDB
    • -d 后跟数据库名 17bang

    检查数据库会发现多了一个表:AspNet_SqlCacheTablesForChangeNotification

  3. 再输入命令:
    aspnet_regsql.exe -S (localdb)\MSSQLLocalDB -E -d 17bang -t Articles -et
    指定数据库上的表(缓存依赖),其中:
    • -t 后跟表名Users
    检查数据库发现表AspNet_SqlCacheTablesForChangeNotification里

然后,在web.config的system.web中配置poll时间等

  <system.web>
    <caching>
      <sqlCacheDependency enabled="true" pollTime="1000">
        <databases>
          <add name="17bang" connectionStringName="17bang" pollTime="1000" />
        </databases>
      </sqlCacheDependency>
    </caching>
  </system.web>

其中pollTime的单位是毫秒。connectionStringName

最后,可以在Insert时添加SqlCacheDependency实例对象做参数:

    HttpContext.Cache.Insert(cacheKey, model,
        //17bang数据库名,Users是表名
        new SqlCacheDependency("17bang", "Articles"));
演示:改变数据库中的数据……

CacheItemRemoved(/Update)Callback

常见面试题:Add() vs Insert()

  • 当存入的数据键值和缓存中已有的数据键值重复时,Insert()使用新数据覆盖旧数据,Add()不予处理(what?)
    演示:同一个key两次I插入
  • Insert()的回调函数参数是CacheItemUpdateCallback,Add()的是CacheItemRemovedCallback
  • Insert()有重载,Add()没有

很明显,Insert()更好用。

ASP.NET还为我们提供了一个delegate参数选项

  • Add()和Insert()都有:
    public delegate void CacheItemRemovedCallback(
        string key, object value, CacheItemRemovedReason reason);
  • Insert()独有:
    public delegate void CacheItemUpdateCallback(string key, CacheItemUpdateReason reason, ……);
    

可以设定当cache数据过期/被删除时可调用的方法。我们来演示一下:

    (k, v, r) => {Trace.WriteLine(
        $"cache with key:({k}) and value:({v}) is deleted, reason:({r})"); }
演示:output窗口输出……


OutputCache

使用上文所述的API很灵活,但:

  • 只是缓存UI层获取数据,
  • 而且稍显累赘

所以MVC推出了OutputCache,可以:

  • 直接缓存生成的Html数据
  • 可以声明方式实现
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class OutputCacheAttribute : ActionFilterAttribute, IExceptionFilter

有意思:OutputCache实际上继承自ActionFilterAttribute!

可以看出,OutputCache可适用于Controller(但一般不会)和Action,包括ChildAction。

常用设置

  • Duration:缓存时间,单位秒。这里只能是absolute的,不能slide
  • Location:可设置为已枚举值,确定缓存数据的存放位置,一般是Server(服务器端)、Client(客户端)和Any(任意位置,默认)。注意:设置客户端仅意味着ASP.NET(通过Response Header)给客户端一个请求缓存的指示,不能保证浏览器一定按指示予以缓存。
  • VaryByParam:指示是否根据url参数设置不同的副本,可以填写的值为:
    *
    为所有不同的url参数(名称/个数/值)缓存不同的副本,
    空字符串或者None
    忽略url参数的差异,所有不同url参数都共用同一个缓存副本
    分号(;)分隔的参数名,如:id;name
    为指定的url参数缓存不同的副本,忽略未指定的url参数差异
    但是注意:routeData 不是 Params

断点演示:Action中的断点不会被击中,MVC会直接将之前缓存的HTML文件/片段返回给客户端……

Cache Profile

可以在web.config的system.web/caching下配置:
<outputCacheSettings>
  <outputCacheProfiles>
    <add name="ArticleSingle" duration="100" varyByParam="name;age"  />
  </outputCacheProfiles>
</outputCacheSettings>

然后,在OutputCache中引用:

[OutputCache(CacheProfile = "ArticleSingle")]
public ActionResult Single(int id)

这样,能实现和

[OutputCache(Duration = 100)]
一样的效果。

@想一想@:为什么还要额外提供这个在web.config中进行配置的CacheProfile选项?

  • 重用:多个Controller/Action可以使用一样的配置
  • 调优:no profile no improvement

此外,还可以直接禁用OutputCache(一般是因为调试):

<outputCache enableOutputCache="false"></outputCache>

MVCDonutCache

很多时候,我们希望能缓存一个页面,但是其中的某一个部分例外(如:LogonStatus),怎么办呢?

MVC暂时未能提供内置的支持(演示),我们需要(通过NuGet)引入第三方插件MVCDonutCache 

它的实现非常简单:

  1. 将父Action的OutputCache换成DonutOutputCache
        [DonutOutputCache(Duration = 5)]  //需要添加using DevTrends.MvcDonutCaching;
  2. 在@Html.ChildAction()末尾添加一个bool值参数true,如:
        @Html.Action("_Inviter", "Shared", true)

演示:父Action页面和ChildAction页面都显示DateTime.Now

建议:总是使用MVCDonutCache



bundle

MVC内置了js/css文件压缩机制。

实现

以.css文件为例,我们先添加3个.css文件:

里面有注释、空格和一个.b1/.b2/.b3/.bat类定义等。

RegisterBundles

然后在BundleConfig.RegisterBundles()中进行配置:

    bundles.Add(new StyleBundle("~/b").Include(
        "~/Content/b1.css",
        "~/Content/b1.css"
        ));

意思是用“~/b”包含(Include)两css文件:

  • ~/Content/b1.css
  • ~/Content/b1.css

如果是.js文件就用ScriptBundle替换StyleBundle。

Render

接下来就可以在View中使用:

    @Styles.Render("~/b")

F5运行演示:上述代码转化成:

<link href="/Content/b1.css" rel="stylesheet">
<link href="/Content/b2.css" rel="stylesheet">
如果是.js文件就使用:@Scripts.Render()。

debug="false"

上述b1.css和b2.css文件还没有被压缩和合并:这是因为还是在调试状态下运行。

在web.config中system.web节点下修改:

<compilation debug="false" targetFramework="4.6.1"/>

然后Ctrl+F5 release状态下运行,生成的HTML标签变成:

<link href="/b?v=eTZCJLM-wumVB4Lk6hPRwROI38FiegKqZ8_EsrepW9c1" rel="stylesheet">

演示:/b的内容为:b1.css和b2.css文件的压缩合并后内容:

body{}.b1{}body{}.b2{}body{}

参数v

注意href="/b?v=eTZCJLM-wumVB4Lk6hPRwROI38FiegKqZ8_EsrepW9c1"中v的值是MVC自动生成的。

MVC会监视着“~/b” bundle中的css文件,如果他们的内容发生变化,v值也会相应的变化(反之不会变化),保证浏览器不会使用缓存着的“/b”内容,而是重新获取变化后的最新内容。

演示:

通配符:*和{version}

Include()中除了可以逐一列举,还可以使用通配符:

  • *:表示任意多个任意字符,但只能放在末尾,后面不能再有任何字符。比如:b*就表所有以b开头的css文件,包括:b1.css/b2.css/b3.css/bat.css……
        bundles.Add(new StyleBundle("~/b").Include(
            "~/Content/b*"
            ));
  • {version}:表示版本号,比如 7.1.0就可以被匹配,但version后面还可以跟文件后缀名,比如:
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));
    注意:不要同一文件的不同版本放置在相同位置,这样会把所有版本都拉入进来。

RazorPages中没有bundle功能,个人估计是因为bundle会删除所有注释,包括文件头部的版权声明,这可能是违反版权法律规定的。


Ajax

ASP.NET可以为Ajax请求返回两种格式的数据:

  • HTML:一般是partial view(演示:页头通知),基本上没啥可说的,除了:
    if (Request.IsAjaxRequest())    //判断是否是Ajax请求
  • JSON:需要在controller中调用

Json()

方法。返回一个(仍然是ActionResult子类的)JsonResult对象。

Json()方法有参数:

  • data:object类型,*必须,Json方法会将其自动转换成JSON格式的数据返回给前端
  • JsonRequestBehavior:默认为DenyGet,即(因为安全原因)不会响应GET请求
  • contentType
  • contentEncoding

演示:点赞/踩

发起请求

使用了“困难模式”,在赞和踩的父元素上绑定事件:

<div yz-article-appraise>
    <a data-direction="up">赞 <span>95</span> </a>
    <a data-direction="down">踩 <span>3</span> </a>
</div>
$('[yz-article-appraise]').click(function (event) {

@想一想:Ajax请求的url应该怎么写,需要告诉后端哪些内容?

url: "/Article/_Appraise?articleId=@ViewContext.RouteData.Values["id"]&direction=" + $(event.target).data("direction"),

#体会#:JavaScript代码和Razor的C#代码混用,^_^

@想一想@:为什么这里不用this而是event.target,如果使用this的话位置应该放在哪里?

Action响应

ArticleController下声明:

public ActionResult _Appraise(int articleId, Direction direction)

不要忘了利用Model绑定,使用Action参数接收前端数据。

Direction是一个枚举,声明在global中:

public enum Direction
{
    Up,
    Down

然后Action方法中该做啥做啥,比如:

int amount = service.Appraise(articleId, direction);

返回的amount是当前赞/踩的数量,传入Json()中。演示:因为发起的是GET请求报错

  1. 修改请求类型
    method: "POST",
  2. 允许响应GET请求
    return Json(amount, JsonRequestBehavior.AllowGet);

如果前端确实不需要数据返回的话,Action方法可以返回void的(但建议):

public void _Appraise(

处理data

赞/踩数量标签仍然用属性定位:

<span yz-article-appraise-amount >95</span>

@想一想@:为什么?

为重用整理过后的代码:

let $target = $(event.target),
    direction = $target.data("direction");
success: function (data) {
    $target.find('[yz-article-appraise-amount]').text(data);
},

[Remote]:远程验证

需要using System.Web.Mvc;

  1. 首先在需要验证的属性上添加[Remote]特性,指定action/controller等
    [Remote("IsNameDuplicated", "Register", ErrorMessage = "* 用户名重复", HttpMethod = "GET")]
    public string UserName { get; set; }
  2. 然后按照上述action和controller,添加一个Action:
    //必须返回一个JsonResult
    public JsonResult IsNameDuplicated(string UserName)//UserName就是需要验证的属性名称
    {
        return Json(UserName != "zyfei",  //true:通过验证 JsonRequestBehavior.AllowGet);
    }
  3. 生成的HTML代码里面就会包含相关信息:
    <input data-val="true" data-val-remote="* 用户名重复" data-val-remote-additionalfields="*.UserName" data-val-remote-type="GET" 
    data-val-remote-url="/Register/IsNameDuplicated" id="UserName" name="UserName" type="text" value="">
  4. 当用户输入用户名后,焦点移出输入框,Ajax请求会自动发起:


功能实战:文章评论

课前已准备:

  • Comment的Entity定义:
    1. 抽象出Content作为Article和Comment共同的基类,有virtual Publish()方法
    2. 有对Article的引用Refer和ReferId
  • DbFactory:准备好n条数据
  • 相关的Service和Repository

Ajax之前

还是PerViewPerModel,要重用列表和发布的Model。

评论列表

不再将Comments视为SingleModel的一部分:

@foreach (var item in Model.Comments)

而是抽象出:

@Html.Action("_List", "Comment", new {articleId = articleId})
其中,articleId由ViewData而来:
int articleId = Convert.ToInt32(ViewContext.RouteData.Values["id"]);

评论发布

UI层使用:
@Html.Partial("~/Views/Comment/_Publish.cshtml", 
        new _PublishModel { ReferId = articleId})
以便独立出Comment._PublishModel:
public class _PublishModel
{
    [Required(ErrorMessage = " *必填")]
    public string Body { get; set; }
    public int ReferId { get; set; }
PartialView中,保证form表单提交后到达:
@using (Html.BeginForm("_Publish", "Comment"))
ReferId通过hidden input传递,且有防CSFR机制:
@Html.TextAreaFor(m => m.Body)
@Html.ValidationMessageFor(m=>m.Body)
@Html.HiddenFor(m => m.ReferId)
@Html.AntiForgeryToken()
public class CommentController : BaseController
{
    [NeedLogOn]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult _Publish(_PublishModel model)
且完成提交后重定向到之前页面:
int CommentId = service.Publish(model);
return RedirectToAction("Single", new { id = model.ReferId });

Ajax实现

尽量利用MVC已有的功能,在不改变现有代码的基础上进行改造。

前端

$('form').submit(function (event) {
    event.preventDefault();
    let $form = $(this);
    $.ajax({
        url: $form.attr('action'),
        data: $form.serialize(),
        method: "post",

@想一想@:为什么要在form.submit(),而不是在button.click()事件中处理?(更保险,没有遗漏,保全Model验证……

PartialView重构

一般来说,MVC中但凡复杂一点的数据,我们就直接返回现成的HTML内容。

所以引入_ListItem.cshtml,从_List.cshtml中复制粘贴并修改:

@model ViewModels.Comment._ItemModel
<div data-id="@Model.Id">
    <small>
        作者: <a href="/User/@Model.Author.Id">@Model.Author.UserName</a>
    </small>
    <div>
        @Html.Raw(Model.Body)

然后通过service获取model填充:

public ActionResult _Publish(_PublishModel model)
{
    int commentId = service.Publish(model);
    if (Request.IsAjaxRequest())
    {
        return PartialView("_ListItem", service.GetItem(commentId));

不要忘了,之前的评论列表,也使用PartialView重构:

@model List<ViewModels.Comment._ItemModel>
@foreach (var item in Model)
{
    @Html.Partial("_ListItem", item)

未登录提示

演示:未登录用户发布评论,插入重定向页面内容

这是因为NeedLogon没有区分Ajax请求,导致发生了页面跳转,并将跳转后页面内容作为Ajax响应返回。

所以首先需要区分是否为Ajax请求,如果是Ajax请求:

if (filterContext.HttpContext.Request.IsAjaxRequest())

一般要两个要求:

  1. 给前端一个清晰的信号:出问题了!所以我们设置Response.StatusCode
  2. 不能继续执行后面的代码了,所以设置Result,并且给予一定的提示
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.Result = new JsonResult
{
    Data = "该请求需要登录才能响应……" 

F12演示:

  • 网络响应:403错误
  • 被$.ajax()的error捕获,且能获取其responseText

缓存清理

评论列表可以被缓存;然后可能就有“更严格的”需求,当有新评论被发布时,更新缓存数据!

这就需要我们通过编程的方式实现。

首先,缓存列表数据:

string key = getCacheKey(articleId);
if (HttpContext.Cache.Get(key) == null)
{
    HttpContext.Cache.Insert(key, service.GetOf(articleId));
}//else nothing

注意这个key值,带有articleId,被封装成方法:

private string getCacheKey(int articleId)
{
    return $"commentsof-{articleId}";
这就是我们后面删除缓存时需要用到的:
public ActionResult _Publish(_PublishModel model)
{
    string key = getCacheKey(model.ReferId);
    HttpContext.Cache.Remove(key);


异步Action

要使用异步,只需要将Action方法改为异步方法复习,如下:

        public async Task<ActionResult> Index()
        {
            //……await之前其他代码:准备message/client等
            await client.SendMailAsync(mail);
            //……await之后其他代码
            return View();
        }

复习:async为什么能提升性能

实战:Email验证

复习:后台流程

简化成注册时填入email。

课前准备

  • entity中为User添加OwnedEntity Email:Address/ValidToken/ValidTime,建库建表准备好
    [ComplexType]
    public class Email
  • User.Register()方法中调用Email.MakeValidCode():(注意null检查等)
    ValidCode = new Random().Next(9999).ToString("0000");
  • Register页面已有输入Email的文本框,及对应的ViewModel
  • 注意automapper从string到Email对象的映射:
    cfg.CreateMap<VM.Register.IndexModel, User>()
        .ForMember(u => u.Email, 
            opt => opt.MapFrom(i => new Email { Addresss = i.Email }))

同步发送

发件人是系统邮箱,收件人是User.Email.Address。

关键是要生成一个链接,这就需要用到HtmlTemplate:

Body = EmailTemplate.Valid( 
    ConfigurationManager.AppSettings["domain"],
    Id, Email.ValidCode),
public static string Valid(string domain, int userId, string code)
{
    return $@"点击<a href='{domain}/Email/Valid/{userId}?code={code}'>{domain}/Email/Valid/{userId}?code={code}</a>完成验证";
}
注意
  1. 链接中需要用到User.Id,所以上述代码要写在User.Register()中;
  2. 链接必须是完整的,即包含协议域名(如https://17bang.ren),但该域名在开发时(localhost)和发布时(17bang.ren)是不一样的。所以最好的方案是把它写在web.config里,方便切换:
    <add key="domain" value="http://localhost:62679"/>

复习:email的发送

异步蔓延

将email的发送方法改为:

await client.SendMailAsync(mail);

同时修改方法的定义:

public async Task Register()

不能是void,因为它还要被继续异步调用

public async Task<int> Save(IndexModel model)
{
    await user.Register();

直到生成异步Action:

public async Task<ActionResult> Index(IndexModel model)
{
    int id = await userService.Save(model);

演示:

  • 发送email的用户名和密码错误,抛出异常(ASP.NET已经处理好了异步Action的异常抛出问题)
  • email的收件人无效,不会抛出异常


作业

  1. 利用缓存优化项目性能。但注意:
    1. 文章发布之后要能及时更新相应的列表页
    2. 同一个页面,不同的当前用户会有些许差别,比如文章作者访问他自己的文章单页,就会有“修改”链接。这种差异在应用了缓存之后如何体现?
  2. 使用async发送Email,完成密码重置功能
  3. 使用Ajax完成以下功能:
    1. 异步加载呈现:侧边栏关键字
    2. 当前用户有未读消息时铃铛闪烁,没有时不闪烁。注意性能优化【难】:如果铃铛已经闪烁,除非打开过我的消息页面,否则就不用再轮询查询……
    3. 用户上传头像之后,直接显示头像内容
    4. 完善点赞和踩:
      • 自己不能给自己点赞/踩
      • 一篇文章一个用户只能点一次赞或踩
      • 赞和踩都会消耗帮帮豆
      • 防止CSRF攻击【难】
      评论也可以点赞和踩
    5. 完善评论发布功能
      • 防注入防CSRF
      • 生成消息给文章作者
      • 文章作者和评论作者都获得积分奖励
    6. 文章发布页可以添加一个新的分类(通过获得JSON数据实现)
    7. 实现文章分类(有标题和说明)的添加/删除/修改功能,注意:
      1. 用户只能操作自己的分类
      2. @想一想@:要不要对用户输入进行HTML编码?
      3. 删除某分类之后,以前属于这个分类的文章怎么办?
    8. 注册时通过用户名查找到邀请人
    9. 可以引用/回复其他人的评论回复评论【难】,注意:
      • 在页面呈现时确定楼层数,并将楼层数传递到前端
      • 回复评论时显示的是楼层数,但传递到后台的、表明其引用的,还是Id
      • @想一想@:能否和发布评论重用?
    10. 调整文章顺序:
      • 在拖拽结束后发起Ajax请求
      • 后台【难】:为文章建立双向链表,每个文章都有Prev和Next属性,调整文章顺序的实质是链表节点的插入……
    11. 首页即时聊天【难】:
      每隔若干秒查询有无新的聊天记录。怎么才算“新”?所以要记录
  4. 功能索引页sample.17bang.ren其他未实现之功能,比如:
    • 帮帮币交易
    • 广告(文章&侧边栏)
    • 掉落帮帮币







分别使用AjaxForm和原生JQuery的post()实现求助的应答

  • 将所有非JQuery相关的js文件压缩合并成yz.js
  • 将所有非Bootstrap相关的cs文件压缩合并成yz.cs


  • 将求助/文章/意见建议列表页进行output cache,注意:
    1. 上述列表页面都有分页和过滤筛选参数
    2. 在缓存页上能即时的反映用户的登录情况
    3. 保证发布新的求助/文章/意见建议后跳转到列表首页,能看到新发布的内容
  1. 使用cache API缓存求助/文章/意见建议页的评论,策略为sliding,每新发布一条评论,就删除之前的评论集合
  2. 思考“一起帮”那些页面内容可以缓存,为什么?找出三个以上的例子,按不同的缓存策略,分别使用:
    1. 编程API
    2. DonutOutputCache
    缓存之


键值策略

Cache是全局的:所以要避免不同的。。。使用了相同的键值

演示:

建议使用{Controller}-{Action}-{Parameters}的形式构建cache key值,以免重复冲突。通常我们可以抽象出一个方法:

    public static string GetCacheKey(
        string controller, string action, params object[] parameters)
    {
        string key = $"{controller}-{action}-";
        return key + string.Join("_", parameters);
    }

在Action中调用:参数id表示文章编号,int类型;参数all表示是否显示全部内容,bool类型

    string key = Keys.GetCacheKey(nameof(ArticleController), nameof(Single), id, all);


为什么呢?

我们知道:IIS本身是多线程(复习)的,且一个线程对应(处理)一个Request请求,请求处理完毕发送response到客户端,over,线程回收。

如果我们直接引入异步方法,就很有可能:

  • 响应request的同步方法已经执行完毕,将response返回给客户端,而异步方法还没有执行完毕
  • 等异步方法执行完毕,已经无法影响response了
这……ʅ(‾◡◝)ʃ




在MVC3以及之前,还需要Controller继承AsyncController

    public class RegisterController : AsyncController



学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 ASP.NET 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码