一、Core

  1,防止过度发布

  2,Main

  3,Startup

  4,添加过滤器

  5,依赖注入

  6,中间件

  7,静态文件

  8,路由

  9,环境

  10,配置和选项

  11,日志

  12,使用Sesstion

  13,使用po文件配置本地化

  14,在 ASP.NET 管道中运行 OWIN 中间件

  15,WebSockets

  16,使用内存缓存

二、EF

  1,Include和ThenInclude

  2,通过依赖关系注入注册上下文

  3,种子数据

  4,级联删除

  5,组合PK

  6,使用原始sql

三、Razor页面

四、MVC

  1,模型绑定

  2,视图

  3,标记帮助程序

  4,内置标记帮助程序

  5,分部视图

  6,视图组件

  7,上传文件

  8,筛选器

  9,绑定与压缩

五、Model

六、配置

一、Core

1,防止过度发布

①TryUpdateModelAsync

        public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var emptyStudent = new Student();
if (await TryUpdateModelAsync<Student>(emptyStudent, "student", s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index"); }
return null;
}

code

        [BindProperty]
public Student Student { get; set; }
public async Task<IActionResult> OnPostAsync(int? id)
{
if (!ModelState.IsValid)
{
return Page();
} var studentToUpdate =await _context.Students.FindAsync(id);
if (await TryUpdateModelAsync<Student>(studentToUpdate, "student", s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}

code2

仅更新TryUpdateModelAsync列出的值

在上述示例中:

  • 第二个自变量 ("student", // Prefix) 是用于查找值的前缀。 该自变量不区分大小写。
  • 已发布的表单值通过模型绑定转换为 Student 模型中的类型。

②通过属性名称匹配

using System;

namespace ContosoUniversity.Models
{
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
}

StudentVM

[BindProperty]
public StudentVM StudentVM { get; set; } public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
} var entry = _context.Add(new Student());
entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

code

2,Main

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting; namespace aspnetcoreapp
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
} public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}

Main

UseHttpSys:用于在 HTTP.sys 中托管应用

UseContentRoot:用于指定根内容目录

Build 和 Run 方法生成 IWebHost 对象,该对象托管应用并开始侦听 HTTP 请求

UseStartup:指定Startup 类

3,Startup

ConfigureServices 定义应用所使用的服务(如 ASP.NET Core MVC、Entity Framework Core 和Identity)(可选择)

Configure 定义请求管道的中间件(必须)

ConfigureServices在 Configure之前执行

UseMvc 扩展方法将路由中间件添加到请求管道,并将 MVC 配置为默认设置。

4,添加过滤器

①定义Middleware

using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using WebApplication1.Models; namespace WebApplication1
{
public class RequestSetOptionsMiddleware
{
private readonly RequestDelegate _next;
private IOptions<AppOptions> _injectedOptions; public RequestSetOptionsMiddleware(
RequestDelegate next, IOptions<AppOptions> injectedOptions)
{
_next = next;
_injectedOptions = injectedOptions;
} public async Task Invoke(HttpContext httpContext)
{
Console.WriteLine("RequestSetOptionsMiddleware.Invoke"); var option = httpContext.Request.Query["option"]; if (!string.IsNullOrWhiteSpace(option))
{
_injectedOptions.Value.Option = WebUtility.HtmlEncode(option);
} await _next(httpContext);
}
}
}

RequestSetOptionsMiddleware

②实现IStartupFilter

using Microsoft.AspNetCore.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; namespace WebApplication1
{
public class RequestSetOptionsStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
builder.UseMiddleware<RequestSetOptionsMiddleware>();
next(builder);
};
}
}
}

RequestSetOptionsStartupFilter

③在 ConfigureServices 的服务容器中注册 IStartupFilter

        // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IStartupFilter, RequestSetOptionsStartupFilter>();
services.AddMvc();
}

Code

5,依赖注入

①注册案例

public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase()
); // Add framework services.
services.AddMvc(); // Register application services.
services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
}

6,中间件

①中间件排序

Configure方法中定义的中间的顺序决定请求的顺序,响应的顺序则相反

②Use、Run 和 Map

Run:不会调用next方法。后面的管道将不会执行

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ app.Run(async (context)=>{
await context.Response.WriteAsync("Map Test 1");
}); if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} //使用wwwroot
app.UseStaticFiles(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}); }

Run

Use:如果中间件不调用next方法,会使管道短路。

Map:如果请求路径以给定路径开头,则执行分支。

public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
} private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
} public void Configure(IApplicationBuilder app)
{
app.Map("/map1", HandleMapTest1); app.Map("/map2", HandleMapTest2); app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

Map

public class Startup
{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
} public void Configure(IApplicationBuilder app)
{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch); app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

MapWhen 基于给定谓词的结果创建请求管道分支

app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a"
//...
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b"
//...
});
});

Map 支持嵌套

app.Map("/level1/level2", HandleMultiSeg);

Map 还可同时匹配多个段

③自定义Middleware

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks; namespace IocDemo
{
public class CustomMiddleware
{
private readonly RequestDelegate _next; public CustomMiddleware(RequestDelegate next)
{
_next=next;
}
public Task InvokeAsync(HttpContext context)
{
this._next.Invoke(context);
context.Response.WriteAsync("CustomMiddleware");
return Task.CompletedTask;
}
}
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseCustomer(this IApplicationBuilder budiler)
{
return budiler.UseMiddleware<CustomMiddleware>();
} } }

CustomMiddleware

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; namespace IocDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} //使用自定义middleware
app.UseCustomer(); //使用wwwroot
app.UseStaticFiles(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}); }
}
}

Startup

7,静态文件

①提供 Web 根目录外的文件

请求 http://<server_address>/StaticFiles/images/banner1.svg 提供 banner1.svg 文件

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(); // For the wwwroot folder app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
}

②设置 HTTP 响应标头

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// Requires the following import:
// using Microsoft.AspNetCore.Http;
ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=600");
}
});
}

③静态文件授权方法

新建个静态文件根文件夹

[Authorize]
public IActionResult BannerImage()
{
var file = Path.Combine(Directory.GetCurrentDirectory(),
"MyStaticFiles", "images", "banner1.svg"); return PhysicalFile(file, "image/svg+xml");
}

④默认提供文档

public void Configure(IApplicationBuilder app)
{
app.UseDefaultFiles();
app.UseStaticFiles();
}

要提供默认文件,必须在 UseStaticFiles 前调用 UseDefaultFiles。 UseDefaultFiles 实际上用于重写 URL,不提供文件。 通过 UseStaticFiles 启用静态文件中间件来提供文件。

public void Configure(IApplicationBuilder app)
{
// Serve my app-specific default file, if present.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
}

将默认文件名更改为 mydefault.html

8,路由

routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");

典型路由

routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id:int}");

路由约束

①尾随句点  .  是可选: files/{filename}.{ext?}

②*全方位参数: blog/{*slug}

将匹配以 /blog 开头且其后带有任何值(将分配给 slug 路由值)的 URI

9,环境

ASP.NET Core 在应用程序启动时读取环境变量 ASPNETCORE_ENVIRONMENT(如果未设置 ASPNETCORE_ENVIRONMENT,将默认为 Production

ASPNETCORE_ENVIRONMENT值:

  • Development(开发)
  • Production(生产)
  • Staging(暂存)

10,配置和选项

nuget Microsoft.Extensions.Configuration

①读取json文件

nuget Microsoft.Extensions.Configuration.Json

using System;
using System.IO;
// Requires NuGet package
// Microsoft.Extensions.Configuration.Json
using Microsoft.Extensions.Configuration; public class Program
{
public static IConfiguration Configuration { get; set; } public static void Main(string[] args = null)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json"); Configuration = builder.Build(); Console.WriteLine($"option1 = {Configuration["Option1"]}");
Console.WriteLine($"option2 = {Configuration["option2"]}");
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Console.WriteLine(); Console.WriteLine("Wizards:");
Console.Write($"{Configuration["wizards::Name"]}, ");
Console.WriteLine($"age {Configuration["wizards::Age"]}");
Console.Write($"{Configuration["wizards::Name"]}, ");
Console.WriteLine($"age {Configuration["wizards::Age"]}");
Console.WriteLine(); Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
{
"option1": "value1_from_json",
"option2": , "subsection": {
"suboption1": "subvalue1_from_json"
},
"wizards": [
{
"Name": "Gandalf",
"Age": ""
},
{
"Name": "Harry",
"Age": ""
}
]
}

json文件内容

节点由冒号  :  分隔: Configuration["subsection:suboption1"]

获取数组值: Configuration["wizards:0:Name"]

②读取xml文件

nuget Microsoft.Extensions.Configuration.Xml

using System;
using Microsoft.Extensions.Configuration;
using System.IO; namespace ConsoleDemo
{
class Program
{
static void Main(string[] args)
{
var builder= new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddXmlFile("test.xml");
var configuration=builder.Build();
Console.WriteLine(configuration["wizard:Harry:age"]);
}
}
}
<wizards>
<wizard name="Gandalf">
<age>1000</age>
</wizard>
<wizard name="Harry">
<age>17</age>
</wizard>
</wizards>

xml文件内容

③json绑定到对象里面

nuget Microsoft.Extensions.Configuration.Binder

using System;
using Microsoft.Extensions.Configuration;
using System.IO;
using System.Collections.Generic; namespace ConsoleDemo
{
class Program
{
static void Main(string[] args)
{
var dic=new Dictionary<string,string>();
dic.Add("Person:Name","hunter");
dic.Add("Person:Age","10"); var builder=new ConfigurationBuilder()
.AddInMemoryCollection(dic);
IConfiguration configuration=builder.Build(); var person=new Person();
configuration.GetSection("Person").Bind(person);
Console.WriteLine(person.Name);
Console.WriteLine(person.Age); }
} public class Person{
public string Name{get;set;}
public int Age{get;set;}
}
}

④在razor视图获取mvc视图中使用configuration

@page
@model IndexModel @using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration <!DOCTYPE html>
<html lang="en">
<head>
<title>Index Page</title>
</head>
<body>
<h1>Access configuration in a Razor Pages page</h1>
<p>Configuration[&quot;key&quot;]: @Configuration["key"]</p>
</body>
</html>

在 Razor 页面页中

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration <!DOCTYPE html>
<html lang="en">
<head>
<title>Index View</title>
</head>
<body>
<h1>Access configuration in an MVC view</h1>
<p>Configuration[&quot;key&quot;]: @Configuration["key"]</p>
</body>
</html>

在 MVC 视图中

④注入option配置

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"Option":{
"option1":"value1_from_json",
"option2":-1
} }

appsettings.json

  public class MyOption{
public string option1{get;set;}
public int option2{get;set;}
}

自定义MyOption类

 services.Configure<MyOption>(Configuration.GetSection("Option"));

在ConfigureServices注册

        private  IOptions<MyOption> _option;
public BlogController(IOptions<MyOption> option)
{
_option=option;
_option.Value.option1+_option.Value.option2
}

构造注入并使用

11,日志

①创建日志

nuget Microsoft.Extensions.Logging

public class TodoController : Controller
{
private readonly ITodoRepository _todoRepository;
private readonly ILogger _logger; public TodoController(ITodoRepository todoRepository,
ILogger<TodoController> logger)
{
_todoRepository = todoRepository;
_logger = logger;
}

要创建日志,请先从依赖关系注入容器获取 ILogger 对象

public IActionResult GetById(string id)
{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
var item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);
}

然后在该记录器对象上调用日志记录方法

②事件ID

LoggingEvents自己定义例如:

public class LoggingEvents
{
public const int GenerateItems = ;
public const int ListItems = ;
public const int GetItem = ;
public const int InsertItem = ;
public const int UpdateItem = ;
public const int DeleteItem = ; public const int GetItemNotFound = ;
public const int UpdateItemNotFound = ;
}

LoggingEvents

public IActionResult GetById(string id)
{
_logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
var item = _todoRepository.Find(id);
if (item == null)
{
_logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
return NotFound();
}
return new ObjectResult(item);
}

使用案例

12,使用Sesstion

using Microsoft.AspNetCore.Http

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; namespace MvcDemo
{
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
HostingEnvironment=env;
} public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment{get;} // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var physicalProvider= HostingEnvironment.ContentRootFileProvider;
services.AddSingleton<IFileProvider>(physicalProvider); services.AddSession(option=>{
option.IdleTimeout=TimeSpan.FromSeconds();
option.Cookie.HttpOnly=true;//指示客户端脚本是否可以访问cookie。
}); services.AddMvc();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} app.UseSession();//要在UseMvc之前调用 app.UseStaticFiles(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

Startup

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcDemo.Models;
using Microsoft.Extensions.FileProviders;
using Microsoft.AspNetCore.Http; namespace MvcDemo.Controllers
{
public class SesstionController : Controller
{ public string Index(string str)
{
HttpContext.Session.SetString("str",str); return "sesstion已经设置";
} public string GetSession()
{
var val= HttpContext.Session.GetString("str");
return val;
} }
}

使用

13,使用po文件配置本地化

nuget OrchardCore.Localization.Core

①配置Startup

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; namespace MvcDemo
{
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
HostingEnvironment=env;
} public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment{get;} // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var physicalProvider= HostingEnvironment.ContentRootFileProvider;
services.AddSingleton<IFileProvider>(physicalProvider); services.AddSession(option=>{
option.IdleTimeout=TimeSpan.FromSeconds();
option.Cookie.HttpOnly=true;//指示客户端脚本是否可以访问cookie。
}); services.AddLocalization(option=>{ });
services.AddMvc()
.AddViewLocalization();
services.AddPortableObjectLocalization(options=>{
options.ResourcesPath="Localization";
});
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
new CultureInfo("zh-CHS")
}; options.DefaultRequestCulture = new RequestCulture("zh-CHS");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
}); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} app.UseSession();//要在UseMvc之前调用 app.UseStaticFiles(); app.UseRequestLocalization(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

配置Startup

②使应用内容可本地化

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcDemo.Models;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using System.Globalization;
using Microsoft.AspNetCore.Localization; namespace MvcDemo.Controllers
{
public class LocalizationController : Controller
{ private readonly IStringLocalizer<LocalizationController> _localization; public LocalizationController(IStringLocalizer<LocalizationController> localization)
{
_localization=localization;
} public string Index()
{
return _localization["Localization"];
} public string Get()
{
return _localization["Test{0}",];
} }
}

LocalizationController

③视图本地化

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
@{
ViewData["Title"] = "Home Page";
}
<p>@Localizer["Localization"]</p>
<p>@Localizer["Test{0}",]</p>

视图

④DataAnnotations本地化

⑤po文件

msgid "Localization"
msgstr "Localization" msgid "Home"
msgstr "Home" msgid "Test{0}"
msgstr "Test{0}"

en.po

msgid "Localization"
msgstr "本地化" msgid "Home"
msgstr "主页" msgid "Test{0}"
msgstr "测试{0}"

zh-CHS.po

msgid:键

msgstr:值

14,在 ASP.NET 管道中运行 OWIN 中间件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Owin;
using System.Text;
using System.IO;
using System.Globalization; namespace AspNetCoreOwin
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} app.UseOwin(pipeline=>{
pipeline(next=>OwinHello);
}); app.UseStaticFiles(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
} public Task OwinHello(IDictionary<string,object> environment)
{
string responseText="hello owin";
byte[] responseBytes=Encoding.UTF8.GetBytes(responseText);
var responseStream=(Stream)environment["owin.ResponseBody"];
var responseHeaders=(IDictionary<string,string[]>)environment["owin.ResponseHeaders"];
responseHeaders["Content-Length"]=new string[]{responseBytes.Length.ToString(CultureInfo.InvariantCulture)};
responseHeaders["Content-Type"]=new string[] {"text/plain"};
return responseStream.WriteAsync(responseBytes,,responseBytes.Length);
}
}
}

Startup

案例下载:https://pan.baidu.com/s/1FLfBuqsMuKnv7wSF3BSW4w

15,WebSockets

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.WebSockets;
using System.Net.WebSockets;
using Microsoft.AspNetCore.Http;
using System.Threading;
using System.Collections.Concurrent; namespace WebSocketsDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} app.UseDefaultFiles();
app.UseStaticFiles(); var webSocketOption=new WebSocketOptions()
{
KeepAliveInterval=TimeSpan.FromSeconds(),//向客户端发送“ping”帧的频率,以确保代理保持连接处于打开状态
ReceiveBufferSize=*//用于接收数据的缓冲区的大小
};
app.UseWebSockets(webSocketOption); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}); app.Use(async (context,next)=>{
if(context.Request.Path=="/ws")
{
if(context.WebSockets.IsWebSocketRequest)//判断是否是websocket请求
{
//将TCP连接升级到WebSocket连接,并提供websocket对象
WebSocket webSocket=await context.WebSockets.AcceptWebSocketAsync();
WebSockets.TryAdd(webSocket,"");
await Echo(context,webSocket);
}
else
{
context.Response.StatusCode=;
}
}
else
{
await next();
}
});
} private async Task Echo(HttpContext context,WebSocket websocket)
{
var buffer=new byte[*];
WebSocketReceiveResult result=await websocket.ReceiveAsync(new ArraySegment<byte>(buffer),CancellationToken.None);
while(!result.CloseStatus.HasValue)
{
//广播
foreach(var ws in WebSockets)
{
if(!ws.Key.CloseStatus.HasValue)
{
await ws.Key.SendAsync(new ArraySegment<byte>(buffer,,buffer.Length),result.MessageType,result.EndOfMessage,CancellationToken.None);
}
else
{
string value;
WebSockets.TryRemove(ws.Key,out value);
}
//await ws.SendAsync(new ArraySegment<byte>(buffer,0,buffer.Length),result.MessageType,result.EndOfMessage,CancellationToken.None);
} result=await websocket.ReceiveAsync(new ArraySegment<byte>(buffer),CancellationToken.None);
}
await websocket.CloseAsync(result.CloseStatus.Value,result.CloseStatusDescription,CancellationToken.None);
}
//可以开一个线程去检测WebSocket是否掉线,掉线则从字典中删除
private static ConcurrentDictionary<WebSocket,string> WebSockets=new ConcurrentDictionary<WebSocket,string>();
}
}

Startup

<!doctype html>
<html lang="en">
<head>
<title>Title</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="lib/jquery/dist/jquery.min.js"></script>
</head>
<body> <input type="text" id="message"/><input type="button" id="send" value="发送"/> <div id="context"></div> <!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body> <script> var ws;
if("WebSocket" in window)
{
ws=new WebSocket("ws://localhost:5000/ws");
ws.onopen=function(){
alert("开始")
}
ws.onmessage=function(res){
$("#context").append(res.data+"<br/>")
}
ws.onclose=function(){
alert("退出")
}
}
else
{
alert("不支持websocket");
} $(function(){ $("#send").click(function(){
ws.send($("#message").val())
})
}) </script>
</html>

index.html

案例下载:https://pan.baidu.com/s/1C5CLrtD6Mr66oiM7sfEHaw

16,使用内存缓存

nuget Microsoft.Extensions.Caching.Memory

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Caching; namespace CacheDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
}); services.AddMemoryCache();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

Startup

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using CacheDemo.Models;
using Microsoft.Extensions.Caching.Memory; namespace CacheDemo.Controllers
{
public class HomeController : Controller
{ private IMemoryCache _cache; public HomeController(IMemoryCache cache)
{
_cache=cache;
} public IActionResult Index()
{
DateTime date;
if(_cache.TryGetValue("date",out date))
{
ViewData["date"]=date;
}
else
{
ViewData["date"]="未设置缓存";
}
return View();
} public IActionResult Set()
{
_cache.Set<DateTime>("date",DateTime.Now);
return RedirectToAction("Index");
} }
}

HomeController

案例下载:https://pan.baidu.com/s/1DEpF-_HLlEQFWswPyb-WkA

二、EF

1,Include和ThenInclude

使上下文加载 Student.Enrollments 导航属性,并在每个注册中加载 Enrollment.Course 导航属性

    public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; } public ICollection<Enrollment> Enrollments { get; set; }
} public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; } public Course Course { get; set; }
public Student Student { get; set; }
}
public class Course
{
//主键
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; } public ICollection<Enrollment> Enrollments { get; set; }
}

实体

2,通过依赖关系注入注册上下文

public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddMvc();
}

配置连接字符串

{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;ConnectRetryCount=0;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

appsettings.json

ConnectRetryCount=0 来防止 SQLClient 挂起

3,种子数据

using ContosoUniversity.Models;
using System;
using System.Linq; namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated(); // Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
} var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges(); var courses = new Course[]
{
new Course{CourseID=,Title="Chemistry",Credits=},
new Course{CourseID=,Title="Microeconomics",Credits=},
new Course{CourseID=,Title="Macroeconomics",Credits=},
new Course{CourseID=,Title="Calculus",Credits=},
new Course{CourseID=,Title="Trigonometry",Credits=},
new Course{CourseID=,Title="Composition",Credits=},
new Course{CourseID=,Title="Literature",Credits=}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges(); var enrollments = new Enrollment[]
{
new Enrollment{StudentID=,CourseID=,Grade=Grade.A},
new Enrollment{StudentID=,CourseID=,Grade=Grade.C},
new Enrollment{StudentID=,CourseID=,Grade=Grade.B},
new Enrollment{StudentID=,CourseID=,Grade=Grade.B},
new Enrollment{StudentID=,CourseID=,Grade=Grade.F},
new Enrollment{StudentID=,CourseID=,Grade=Grade.F},
new Enrollment{StudentID=,CourseID=},
new Enrollment{StudentID=,CourseID=},
new Enrollment{StudentID=,CourseID=,Grade=Grade.F},
new Enrollment{StudentID=,CourseID=,Grade=Grade.C},
new Enrollment{StudentID=,CourseID=},
new Enrollment{StudentID=,CourseID=,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}

DbInitializer

// Unused usings removed
using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Data; namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args); using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
} host.Run();
} public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}

Program.cs

第一次运行该应用时,会使用测试数据创建并填充数据库。 更新数据模型时:

  • 删除数据库。
  • 更新 seed 方法。
  • 运行该应用,并创建新的种子数据库。

4,级联删除 

modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)

Code

5,组合PK

    public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}

Model

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}

SchoolContext

 6,使用原始sql

①调用返回实体的查询

public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
} string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.SingleOrDefaultAsync(); if (department == null)
{
return NotFound();
} return View(department);
}

FromSql

②调用返回其他类型的查询

public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync(); if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(), StudentCount = reader.GetInt32() };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}

Code

③调用更新查询

[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}

Code

三、Razor页面

1,路由

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>
<a href="/Students/Edit?studentID=6">Edit</a>

2,命令搭建基架

参考文档:https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-rp/intro#add-scaffold-tooling

3,SelectList

using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq; namespace ContosoUniversity.Pages.Courses
{
public class DepartmentNamePageModel : PageModel
{
public SelectList DepartmentNameSL { get; set; } public void PopulateDepartmentsDropDownList(SchoolContext _context,
object selectedDepartment = null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name // Sort by name.
select d; DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
"DepartmentID", "Name", selectedDepartment);
}
}
} <div class="form-group">
<label asp-for="Course.Department" class="control-label"></label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>

Code

四、MVC

1,模型绑定 

①常用的验证属性

  • [CreditCard]:验证属性是否具有信用卡格式。

  • [Compare]:验证某个模型中的两个属性是否匹配。

  • [EmailAddress]:验证属性是否具有电子邮件格式。

  • [Phone]:验证属性是否具有电话格式。

  • [Range]:验证属性值是否落在给定范围内。

  • [RegularExpression]:验证数据是否与指定的正则表达式匹配。

  • [Required]:将属性设置为必需属性。

  • [StringLength]:验证字符串属性是否最多具有给定的最大长度。

  • [Url]:验证属性是否具有 URL 格式。

2,视图

①Razor语法

<p>Last week this time: @(DateTime.Now - TimeSpan.FromDays(7))</p>

计算 @() 括号中的所有内容,并将其呈现到输出中

@{
var joe = new Person("Joe", 33);
} <p>Age@(joe.Age)</p>

使用显式表达式将文本与表达式结果串联起来

@Html.Raw("<span>Hello World</span>")

输出不进行编码,但呈现为 HTML 标记

@for (var i = 0; i < people.Length; i++)
{
var person = people[i];
<text>Name: @person.Name</text>
}

带分隔符的显式转换

@for (var i = 0; i < people.Length; i++)
{
var person = people[i];
@:Name: @person.Name
}

使用 @ 的显式行转换

@if (value % 2 == 0)
{
<p>The value was even.</p>
}
else if (value >= 1337)
{
<p>The value is large.</p>
}
else
{
<p>The value is odd and small.</p>
}

@if、else if、else

@switch (value)
{
case 1:
<p>The value is 1!</p>
break;
case 1337:
<p>Your number is 1337!</p>
break;
default:
<p>Your number wasn't 1 or 1337.</p>
break;
}

@switch

@for (var i = 0; i < people.Length; i++)
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
}

@for

@foreach (var person in people)
{
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
}

@foreach

@{ var i = 0; }
@while (i < people.Length)
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p> i++;
}

@while

@{ var i = 0; }
@do
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p> i++;
} while (i < people.Length);

@do while

@using (Html.BeginForm())
{
<div>
email:
<input type="email" id="Email" value="">
<button>Register</button>
</div>
}

复合语句 @using

@try
{
throw new InvalidOperationException("You did something invalid.");
}
catch (Exception ex)
{
<p>The exception message: @ex.Message</p>
}
finally
{
<p>The finally statement.</p>
}

@try、catch、finally

②@inherits 指令对视图继承的类提供完全控制

using Microsoft.AspNetCore.Mvc.Razor;

public abstract class CustomRazorPage<TModel> : RazorPage<TModel>
{
public string CustomText { get; } = "Gardyloo! - A Scottish warning yelled from a window before dumping a slop bucket on the street below.";
}

自定义 Razor 页面类型

@inherits CustomRazorPage<TModel>

<div>Custom text: @CustomText</div>

CustomText 显示在视图中

<div>Custom text: Gardyloo! - A Scottish warning yelled from a window before dumping a slop bucket on the street below.</div>

呈现

@functions 指令允许 Razor 页面将 C# 代码块添加到视图中

@functions {
public string GetHello()
{
return "Hello";
}
} <div>From method: @GetHello()</div>

视图

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor; public class _Views_Home_Test_cshtml : RazorPage<dynamic>
{
// Functions placed between here
public string GetHello()
{
return "Hello";
}
// And here.
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
WriteLiteral("\r\n<div>From method: ");
Write(GetHello());
WriteLiteral("</div>\r\n");
}
#pragma warning restore 1998

生成的 Razor C# 类

④_ViewImports.cshtml导入共享指令

支持的指令:

  • @addTagHelper

  • @removeTagHelper

  • @tagHelperPrefix

  • @using

  • @model

  • @inherits

  • @inject

针对 ASP.NET Core MVC 应用的 _ViewImports.cshtml 文件通常放置在 Views 文件夹中

3,标记帮助程序

①@addTagHelper

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper MvcDemo2.TagHelpers.EmailTagHelper,MvcDemo2
@addTagHelper之后第一个参数:需要加载的标记帮助类(*表示所有)
第二个参数:标记帮助类所在的程序集

②简单的将 <email>hunter</email> 变成 <a>hunter</a>

using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;
namespace MvcDemo2.TagHelpers
{
//EmailTagHelper 的根名称是 email,因此 <email> 标记将作为目标名称
public class EmailTagHelper:TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName="a";//用<a>标签替换<email>
} }
}

①添加EmailTagHelper

@using MvcDemo2
@using MvcDemo2.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper MvcDemo2.TagHelpers.EmailTagHelper,MvcDemo2

②修改_ViewImports.cshtml

这时,在页面上的 <email>hunter</email> 会变成<a>hunter</a>

③设置自结束标签 <email/>

    [HtmlTargetElement("email",TagStructure=TagStructure.WithoutEndTag)]
public class EmailTagHelper:TagHelper

HtmlTargetElement

如果存在不是自结束标签,就会报错

④将 <email mail-to="hunter"></email> 变成 <a href="hunter@qq.com">hunter@qq.com</a>

using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;
namespace MvcDemo2.TagHelpers
{
//EmailTagHelper 的根名称是 email,因此 <email> 标记将作为目标名称
public class EmailTagHelper:TagHelper
{
public const string EmailDomain="qq.com";
//可以通过<email mail-to =“...”/>传递
//标记帮助程序采用 Pascal 大小写格式的类和属性名将转换为各自相应的小写短横线格式
public string MailTo{get;set;}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName="a";//用<a>标签替换<email>
var address=MailTo+"@"+EmailDomain; //给标签添加属性
output.Attributes.SetAttribute("href",address);
//设置<email></email>里面的内容
output.Content.SetContent(address);
} }
}

EmailTagHelper

⑤将 <email>hunter</email> 变成<a href="hunter@qq.com">hunter@qq.com</a>

using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;
namespace MvcDemo2.TagHelpers
{
//EmailTagHelper 的根名称是 email,因此 <email> 标记将作为目标名称
public class EmailTagHelper:TagHelper
{
public const string EmailDomain="qq.com";
//可以通过<email mail-to =“...”/>传递 public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName="a";//用<a>标签替换<email> var content=await output.GetChildContentAsync();//获取标签里的内容
var tagter=content.GetContent()+"@"+EmailDomain; //给标签添加属性
output.Attributes.SetAttribute("href",tagter);
//设置<email></email>里面的内容
output.Content.SetContent(tagter);
} }
}

EmailTagHelper

⑥将页面中的 <bold>bbbb</bold> 替换为 <strong>bbbb</strong>

using Microsoft.AspNetCore.Razor.TagHelpers;
namespace MvcDemo2.TagHelpers
{
public class BoldTagHelper:TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.RemoveAll("bold");
output.PreContent.SetHtmlContent("<strong>");
output.PostContent.SetHtmlContent("</strong>");
} }
}

BoldTagHelper

③将model传入标记帮助程序

using System;
namespace MvcDemo2.Models
{
public class WebsiteContext
{
public Version Version{get;set;}
public int CopyrightYear{get;set;}
public bool Approved{get;set;}
public int TagsToShow{get;set;}
}
}

Model

using Microsoft.AspNetCore.Razor.TagHelpers;
using MvcDemo2.Models;
namespace MvcDemo2.TagHelpers
{ //使用<website-information />
public class WebsiteInformationTagHelper:TagHelper
{
public WebsiteContext Info{get;set;} public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName="section";
output.Content.SetHtmlContent(
$@"<ul><li><strong>版本:</strong> {Info.Version}</li>
<li><strong>版权 年:</strong> {Info.CopyrightYear}</li>
<li><strong>是否批准:</strong> {Info.Approved}</li>
<li><strong>显示的标签:</strong> {Info.TagsToShow}</li></ul>"
);
} }
}

WebsiteInformationTagHelper

<website-information info="new WebsiteContext(){
Version=new Version(,),
CopyrightYear=,
Approved=true,
TagsToShow=
}"></website-information>

Html

⑥.NET 类型和生成的 HTML 类型

.NET 类型 输入类型
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
Int type=”number”
Single、Double type=”number”

⑦数据注解和生成的 HTML 类型

特性 输入类型
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”

⑧验证标记帮助程序

<span asp-validation-for="Email"></span>

针对具有 asp-validation-summary 属性的 <div> 元素

asp-validation-summary 显示的验证消息
ValidationSummary.All 属性和模型级别
ValidationSummary.ModelOnly 模型
ValidationSummary.None

实例:

@model RegisterViewModel
@{
ViewData["Title"] = "Home Page";
} <a bold>aaa</a>
<bold>bbbb</bold>
<!--Razor 知道 info 属性是一个类,而不是字符串,并且你想要编写 C# 代码。 编写任何非字符串标记帮助程序属性时,都不应使用 @@ 字符。--> <website-information info="new WebsiteContext(){
Version=new Version(,),
CopyrightYear=,
Approved=true,
TagsToShow=
}"></website-information> <form asp-action="Register" method="POST" role="form">
<legend>注册</legend> <div asp-validation-summary="ModelOnly"></div> <div class="form-group">
<input asp-for="Email" class="form-control" placeholder="Input field">
<span asp-validation-for="Email"></span>
</div> <div class="form-group">
<input asp-for="Password" class="form-control" placeholder="Input field">
<span asp-validation-for="Password"></span>
</div> <button type="submit" class="btn btn-primary">Submit</button>
</form>

Index.cshtml

        [HttpPost]
public IActionResult Register(RegisterViewModel model)
{
return View("Index");
}

Controller

⑨选择标记帮助程序

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering; namespace MvcDemo2.Models
{
public class CountryViewModel
{
public string Country{get;set;}
public List<SelectListItem> Countries {get;}=new List<SelectListItem>(){
new SelectListItem(){Value="MX",Text="墨西哥"},
new SelectListItem(){Value="CA",Text="加拿大"},
new SelectListItem(){Value="US",Text="美国"}
};
}
}

CountryViewModel

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcDemo2.Models; namespace MvcDemo2.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
var country=new CountryViewModel();
country.Country="US";
return View(country);
} [HttpPost]
public IActionResult Index(CountryViewModel model)
{
return View(model);
} }
}

HomeController

@model CountryViewModel

<form method="POST" asp-action="Index">

    <select asp-for="Country" asp-items="Model.Countries"></select>

    <input type="submit" value="提交" />
</form>

Index

⑩枚举绑定

public enum CountryEnum
{
[Display(Name = "墨西哥合众国")]
Mexico,
[Display(Name = "美国")]
USA,
Canada,
France,
Germany,
Spain
}

定义枚举

<select asp-for="Country" asp-items="Html.GetEnumSelectList<CountryEnum>()"></select>

⑩选项分组

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering; namespace MvcDemo2.Models
{
public class CountryViewModel
{
public string Country{get;set;} public List<SelectListItem> Countries {get;}=new List<SelectListItem>(){
new SelectListItem(){Value="MX",Text="墨西哥",Group=new SelectListGroup(){Name="分组一"}},
new SelectListItem(){Value="CA",Text="加拿大",Group=new SelectListGroup(){Name="分组二"}},
new SelectListItem(){Value="US",Text="美国",Group=new SelectListGroup(){Name="分组三"}}
}; } }

CountryViewModel

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcDemo2.Models; namespace MvcDemo2.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
var country=new CountryViewModel();
country.Country="US";
return View(country);
} [HttpPost]
public IActionResult Index(CountryViewModel model)
{
return View(model);
} }
}

HomeController

<select asp-for="Country" asp-items="Model.Countries"></select>

⑩多重选择

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering; namespace MvcDemo2.Models
{
public class CountryViewModel
{
public IEnumerable<string> Countrys{get;set;} public List<SelectListItem> Countries {get;}=new List<SelectListItem>(){
new SelectListItem(){Value="MX",Text="墨西哥"},
new SelectListItem(){Value="CA",Text="加拿大"},
new SelectListItem(){Value="US",Text="美国"}
}; } }

CountryViewModel

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcDemo2.Models; namespace MvcDemo2.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
var country=new CountryViewModel();
country.Countrys=new []{"US","MX"};
return View(country);
} [HttpPost]
public IActionResult Index(CountryViewModel model)
{
return View(model);
} }
}

HomeController

@model CountryViewModel

<form method="POST" asp-action="Index">

    <select asp-for="Countrys" asp-items="Model.Countries"></select>

    <input type="submit" value="提交" />
</form>

index

⑩无选定内容

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering; namespace MvcDemo2.Models
{
public class CountryViewModel
{
public string Country{get;set;} public List<SelectListItem> Countries {get;}=new List<SelectListItem>(){
new SelectListItem(){Value="MX",Text="墨西哥"},
new SelectListItem(){Value="CA",Text="加拿大"},
new SelectListItem(){Value="US",Text="美国"}
}; } }

CountryViewModel

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcDemo2.Models; namespace MvcDemo2.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
var country=new CountryViewModel();
return View(country);
} [HttpPost]
public IActionResult Index(CountryViewModel model)
{
return View(model);
} }
}

HomeController

@model CountryViewModel

<form method="POST" asp-action="Index">

    <select asp-for="Country" asp-items="Model.Countries">
<option value="">--none--</option>
</select> <input type="submit" value="提交" />
</form>

index

4,内置标记帮助程序

①asp-all-route-data

@{
var parms = new Dictionary<string, string>
{
{ "speakerId", "" },
{ "currentYear", "true" }
};
} <a asp-route="speakerevalscurrent"
asp-all-route-data="parms">Speaker Evaluations</a>

试图

<a href="/Speaker/EvaluationsCurrent?speakerId=11&currentYear=true">Speaker Evaluations</a>

前面的代码生成以下 HTML:

[Route("/Speaker/EvaluationsCurrent",
Name = "speakerevalscurrent")]
public IActionResult Evaluations(
int speakerId,
bool currentYear) => View();

Controller

②asp-fragment

可定义要追加到 URL 的 URL 片段

<a asp-controller="Speaker"
asp-action="Evaluations"
asp-fragment="SpeakerEvaluations">Speaker Evaluations</a>

试图

<a href="/Speaker/Evaluations#SpeakerEvaluations">Speaker Evaluations</a>

生成的 HTML:

③asp-area

设置相应路由的区域名称

<a asp-area="Blogs"
asp-controller="Home"
asp-action="AboutBlog">About Blog</a>

试图

<a href="/Blogs/Home/AboutBlog">About Blog</a>

生成的 HTML:

④asp-protocol

在URL 中指定协议(比如 https

<a asp-protocol="https"
asp-controller="Home"
asp-action="About">About</a>

试图

<a href="https://localhost/Home/About">About</a>

生成的 HTML:

⑤asp-host

在 URL 中指定主机名

<a asp-protocol="https"
asp-host="microsoft.com"
asp-controller="Home"
asp-action="About">About</a>

试图

<a href="https://microsoft.com/Home/About">About</a>

生成的 HTML:

⑥缓存标记帮助程序

<cache enabled="true">@DateTime.Now</cache>

属性expires-after:设置过期时间

<cache expires-after="@TimeSpan.FromSeconds(120)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

属性expires-sliding:设置多久未被访问过期设置

<cache expires-sliding="@TimeSpan.FromSeconds(60)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

⑦环境标记帮助程序

<environment include="Development">Development</environment>
<environment include="Staging">Staging</environment>
<environment include="Production">Production</environment>

不同的环境显示不同的标签

⑧图像标记帮助程序

<img src="~/images/1.jpg"  asp-append-version="true"/>

asp-append-version:追加版本号

5,分部视图

@await Html.PartialAsync("ViewName")
@await Html.PartialAsync("ViewName.cshtml")
@await Html.PartialAsync("~/Views/Folder/ViewName.cshtml")
@await Html.PartialAsync("/Views/Folder/ViewName.cshtml")
@await Html.PartialAsync("../Account/LoginPartial.cshtml")
@model string
姓名:@Model

分布页面Person.cshtml

@await Html.PartialAsync("Person","Hunter")

调用

6,视图组件

①添加 ViewComponent 类

在根目录新建一个ViewComponents文件夹,建ViewComponent类放在此文件夹中

using MvcDemo2.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq; namespace MvcDemo2.ViewComponents
{
public class ProductListViewComponent:ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(bool ischecked,string name)
{
var list=new List<Product>(){
new Product(){IsChecked=true,Name="iphone x",Price=},
new Product(){IsChecked=false,Name="iphone 8p",Price=},
};
var data= list.Where(m=>m.IsChecked==ischecked&&m.Name.Contains(name));
return View(data);
} }
}

ProductListViewComponent

②创建视图组件 Razor 视图

创建 Views/Shared/Components 文件夹。 此文件夹 必须 命名为 Components

@model IEnumerable<Product>

@foreach(var item in Model)
{
<p>@item.Name</p>
}

Default.cshtml

@await Component.InvokeAsync("ProductList",new {ischecked=true,name="iphone"})

视图页面调用

7,上传文件

①使用模型绑定上传小文件

using System.Collections.Generic;
using Microsoft.AspNetCore.Http; namespace MvcDemo2.Models
{
public class FileUpViewModel
{
public IEnumerable<IFormFile> files {get;set;}
public string name{get;set;}
}
}

FileUpViewModel

        public IActionResult FileUp(FileUpViewModel model)
{
return View();
}

Controller

<form asp-action="FileUp" enctype="multipart/form-data" method="POST" role="form">
<legend>提交</legend> <input type="file" name="files" class="form-control" multiple/>
<input type="text" name="name" class="form-control"/> <button type="submit" class="btn btn-primary">Submit</button>
</form>

View

8,筛选器

每种筛选器类型都在筛选器管道中的不同阶段执行。

  • 授权筛选器最先运行,用于确定是否已针对当前请求为当前用户授权。 如果请求未获授权,它们可以让管道短路。

  • 资源筛选器是授权后最先处理请求的筛选器。 它们可以在筛选器管道的其余阶段运行之前以及管道的其余阶段完成之后运行代码。 出于性能方面的考虑,可以使用它们来实现缓存或以其他方式让筛选器管道短路。它们在模型绑定之前运行,所以可以影响模型绑定。

  • 操作筛选器可以在调用单个操作方法之前和之后立即运行代码。 它们可用于处理传入某个操作的参数以及从该操作返回的结果。

  • 异常筛选器用于在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。

  • 结果筛选器可以在执行单个操作结果之前和之后立即运行代码。 仅当操作方法成功执行时,它们才会运行。对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。

下图展示了这些筛选器类型在筛选器管道中的交互方式:

①筛选器通过不同的接口定义支持同步和异步实现

using FiltersSample.Helper;
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters
{
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
} public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
}
}
}

同步实现

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters
{
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// 在行动执行之前做一些事情
var resultContext = await next();
// 在执行操作后执行某些操作; resultContext.Result将被设置
}
}
}

异步实现

②添加为全局筛选器

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
"Result filter added to MvcOptions.Filters")); // an instance
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
}); services.AddScoped<AddHeaderFilterWithDi>();
}

ConfigureServices

③取消和设置短路

通过设置提供给筛选器方法的 context 参数上的 Result 属性,可以在筛选器管道的任意位置设置短路。 例如,以下资源筛选器将阻止执行管道的其余阶段

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters
{
public class ShortCircuitingResourceFilterAttribute : Attribute,
IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult()
{
Content = "Resource unavailable - header should not be set"
};
} public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}

ShortCircuitingResourceFilterAttribute

9,绑定与压缩

[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]

bundleconfig.json

outputFileName:要输出的捆绑文件名称。可以包含中的相对路径bundleconfig.json文件(必填)
inputFiles:要将捆绑在一起的文件的数组。 这些是配置文件的相对路径(可选)
minify:输出类型缩减选项
sourceMap:指示是否生成捆绑的文件的源映射的标志

①需要引用: <DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />

②执行命令:  dotnet bundle 会合并并压缩inputFiles里的文件到outputFileName

五、Model

1,数据注解

①DisplayFormat

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

②Column

 [Column("FirstName")]
public string FirstMidName { get; set; }

实体属性表字段映射

2,DatabaseGenerated标记主键

实体

六、配置

asp.net core2.0学习笔记的更多相关文章

  1. ASP.NET MVC2.0学习笔记:路由设置

    Route设置 在 <Professional in ASP.NET MVC2.0>一书的第四章,主要讲述了Route的简单设置.格式化设置.约束设置.区域路由.匹配文件.路由调试以及对R ...

  2. asp.net MVC2.0学习笔记

    asp.net;与mvc都是不可替代的:只是多一种选择:(解决了许多asp.net的许多缺点) model:充血模型.领域模型:很大程度的封装: 控制器:处理用户的交互,处理业务逻辑的调用,指定具体的 ...

  3. 一起学ASP.NET Core 2.0学习笔记(二): ef core2.0 及mysql provider 、Fluent API相关配置及迁移

    不得不说微软的技术迭代还是很快的,上了微软的船就得跟着她走下去,前文一起学ASP.NET Core 2.0学习笔记(一): CentOS下 .net core2 sdk nginx.superviso ...

  4. Asp.Net Core WebApi学习笔记(四)-- Middleware

    Asp.Net Core WebApi学习笔记(四)-- Middleware 本文记录了Asp.Net管道模型和Asp.Net Core的Middleware模型的对比,并在上一篇的基础上增加Mid ...

  5. ASP.NET Core 2 学习笔记(十三)Swagger

    Swagger也算是行之有年的API文件生成器,只要在API上使用C#的<summary />文件注解标签,就可以产生精美的线上文件,并且对RESTful API有良好的支持.不仅支持生成 ...

  6. ASP.NET Core 2 学习笔记(十二)REST-Like API

    Restful几乎已算是API设计的标准,通过HTTP Method区分新增(Create).查询(Read).修改(Update)和删除(Delete),简称CRUD四种数据存取方式,简约又直接的风 ...

  7. ASP.NET Core 2 学习笔记(十)视图

    ASP.NET Core MVC中的Views是负责网页显示,将数据一并渲染至UI包含HTML.CSS等.并能痛过Razor语法在*.cshtml中写渲染画面的程序逻辑.本篇将介绍ASP.NET Co ...

  8. sql server 关于表中只增标识问题 C# 实现自动化打开和关闭可执行文件(或 关闭停止与系统交互的可执行文件) ajaxfileupload插件上传图片功能,用MVC和aspx做后台各写了一个案例 将小写阿拉伯数字转换成大写的汉字, C# WinForm 中英文实现, 国际化实现的简单方法 ASP.NET Core 2 学习笔记(六)ASP.NET Core 2 学习笔记(三)

    sql server 关于表中只增标识问题   由于我们系统时间用的过长,数据量大,设计是采用自增ID 我们插入数据的时候把ID也写进去,我们可以采用 关闭和开启自增标识 没有关闭的时候 ,提示一下错 ...

  9. DirectX 总结和DirectX 9.0 学习笔记

    转自:http://www.cnblogs.com/graphics/archive/2009/11/25/1583682.html DirectX 总结 DDS DirectXDraw Surfac ...

随机推荐

  1. Android的网络通信机制

    1. Socket接口 不常用 2.HttpURLConnection接口 3. HttpClient接口 http://blog.csdn.net/ccc20134/article/details/ ...

  2. FPN-Feature Pyramid Networks for Object Detection

    FPN-Feature Pyramid Networks for Object Detection 标签(空格分隔): 深度学习 目标检测 这次学习的论文是FPN,是关于解决多尺度问题的一篇论文.记录 ...

  3. Mysql 插入中文错误:Incorrect string value: '\xE7\xA8\x8B\xE5\xBA\x8F...' for column 'course' at row 1

    create table my_user (    id tinyint(4) not null auto_increment,    account varchar(255) default nul ...

  4. FPN 学习笔记

    通常,利用网络对物体进行检测时,浅层网络分辨率高,学到的是图片的细节特征,深层网络,分辨率低,学到的更多的是语义特征. 1).通常的CNN使用如下图中显示的网络,使用最后一层特征图进行预测 例如VGG ...

  5. 关于形如--error LNK2005: xxx 已经在 msvcrtd.lib ( MSVCR90D.dll ) 中定义--的问题分析解决

    转自:http://hi.baidu.com/qinfengxiaoyue/item/ff262ccfb53b4c2ba0b50a89 引自:http://blog.csdn.net/sptoor/a ...

  6. python 内置函数总结(大部分)

    python 内置函数大讲堂 python全栈开发,内置函数 1. 内置函数 python的内置函数截止到python版本3.6.2,现在python一共为我们提供了68个内置函数.它们就是pytho ...

  7. springboot系列十四、自定义实现starter

    一.starter的作用 当我们实现了一个组建,希望尽可能降低它的介入成本,一般的组建写好了,只要添加spring扫描路径加载spring就能发挥作用.有个更简单的方式扫描路径都不用加,直接引入jar ...

  8. dubbo系列三、架构介绍及各模块关系

    一.整体设计 图例说明: 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口. 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代 ...

  9. sqlserver 无法获得数据库独占权

    ALTER DATABASE trqxs_cs SET OFFLINE WITH ROLLBACK IMMEDIATE

  10. 读SRE Google运维解密有感(一)

    前言 这几天打算利用碎片时间读了一下"SRE Google运维解密"这本书,目前读了前几章,感觉收获颇多,结合自己的工作经历和书中的要点,写一些感悟和思考 SRE 有关SRE我就不 ...