目录html
学习Net Core的我的笔记,记录前端
建议看微软官方文档,看不懂的查一下 教程:ASP.NET Core 入门web
Startup类中的ConfigureServices方法是用于服务注册IOCsql
ConfigureServices这个方法是用于服务注册的,服务就是IOC里面的类数据库
IOC容器内的服务有三种生命周期json
咱们有一个类和一个接口,接口的实现类,这里不写,注册以下c#
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IStudentService,StudentService>(); }
写了一个单例的服务,已经注入了,调用后续再写windows
下图很经典,用户的请求须要通过管道,管道内部能够写中间件,若是你什么都不写,那么请求返回的就是固定的,你加了中间件,就会有变化,好比权限验证,身份验证,MVC处理等中间件后端
Startup类里面的Configure方法就是管道的方法,能够在里面写中间件api
app.Run就是运行的一个中间件,如今咱们写一个新的中间件
app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); app.Run(async (context) => { await context.Response.WriteAsync("许嵩!"); });
运行,能够发现,仍是Hello World,根本没有许嵩,由于中间件根本没往下执行,能够这样设置
app.Use(async (context,next) => { await context.Response.WriteAsync("Hello World!"); await next(); }); app.Run(async (context) => { await context.Response.WriteAsync("许嵩!"); });
加了一个next,而后执行await next(); 这样就会执行按照顺序的下一个中间件
public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILogger<Startup> logger) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Use(async (context,next) => { logger.LogInformation("管道1开启"); await context.Response.WriteAsync("Hello World!"); await next(); logger.LogInformation("管道1结束"); }); app.Run(async (context) => { logger.LogInformation("管道2开启"); await context.Response.WriteAsync("许嵩!"); logger.LogInformation("管道2结束"); }); }
加了一个日志ILogger,这样再运行,注意此次运行不选择IIS了,咱们选择Kestrel服务器,就是你的解决方案同名的那个,运行,能够查看日志
直接新建一个控制器Controller,你会发现,Controller没有引入,没法使用.
.net Core和.net Framework不同,.net MVC写MVC直接就能够, .net Core须要注册一下,恰好使用了上面的IOC服务注册,仍是在Startup类中的ConfigureServices写:
services.AddMvc();
管道调用的时候能够加一个路由
app.UseMvc(route => { route.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
Controllers,Models,Views三个文件夹的建立
新建HomeController,添加Index视图
public class HomeController : Controller { public IActionResult Index() { return View(); } }
@{ ViewData["Title"] = "Index"; } <h1>个人第一个.Net Core Web项目</h1>
有三种官方给的环境,分别是
Development(开发)、Staging (分阶段)和 Production(生产)
更改环境变量,点击项目,右键属性,选择调试,更改,如图
在Properties下的launchSettings.json能够更改不一样环境的参数配置
{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:13335", "sslPort": 44347 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "StudyNetCore": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
Startup能够判断环境
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } if (env.IsProduction()) { System.Console.WriteLine("..."); } if (env.IsStaging()) { System.Console.WriteLine("..."); } if (env.IsEnvironment("自定义环境")) { System.Console.WriteLine("..."); }
在ConfigureServices类中注入
services.AddHttpsRedirection(option=> { option.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect; option.HttpsPort = 5001; });
在Configure方法里面使用HTTPS管道,注意HTTPS管道必须在MVC管道以前,不然没意义了就
app.UseHttpsRedirection();
改为这样就能够,加了一个launchUrl,初始值为http://localhost:5000/Home/Index
这样
{ "profiles": { "StudyNetCore": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000/Home/Index", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
真是一波三折~我开始就遇到报错了
这个报错是看看你的项目能不能编译成功,若是有报错请解决报错
这个错误不知道是啥,可是能够找出来是MVC项目的错,因此我把MVC项目的obj文件夹下面的全删了,而后从新编译一次就能够了
这个报错的缘由是,须要把默认项目改为MVC的,才能够执行 Add-Migration InitialCreate,我也不知道为啥😠
首先须要新建三个项目,一个是主项目,我建的是MVC
一个是Model类库,一个是操做EF Core的迁移类库
如图,我建了三个,DB是来放EF Core迁移文件的,DomainModels是放领域模型的,下面的MVC是业务
我为了测试,在DomainModels下新建了一个Blog
using System; using System.Collections.Generic; namespace DomainModels { public class Blog { public int BlogId { get; set; } public string Url { get; set; } public int Rating { get; set; } public List<Post> Posts { get; set; } } }
注意,必定要有主键,默认有Id或者XXId的都是主键
在DB里面使用NuGet引用EF Core,以下
新建DBContext的继承类,个人是这样的
using DomainModels; using Microsoft.EntityFrameworkCore; using System; namespace DB { public class MyContext:DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Server=.;Database=EFCore;Trusted_Connection=True;"); //若是是带用户名密码的这样写 optionsBuilder.UseSqlServer("server=192.168.3.8;uid=sa;pwd=123;database=VaeDB;"); } public DbSet<Blog> Blogs { get; set; } } }
开始迁移,在视图->其余窗口->包管理控制台
首先输入: Add-Migration InitialCreate
后面的是名称,随意写,我写的InitialCreate
而后执行迁移文件:Update-Database
等待一会,你就会发现本地的数据库里面已经有了EFCore数据库和Blogs数据表了
在MVC项目里面新建了一个Controller
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DB; using DomainModels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace StudyNetCore.Controllers { public class EFCoreController : Controller { public IActionResult Index() { return View(); } public void Add() { using (var context = new MyContext()) { var blog = new Blog { Url = "http://sample.com", Rating=98 }; context.Blogs.Add(blog); context.SaveChanges(); } } public void Remove() { using (var context = new MyContext()) { var blog = context.Blogs.Single(b => b.BlogId == 2); context.Blogs.Remove(blog); context.SaveChanges(); } } public void Update() { using (var context = new MyContext()) { var blog = context.Blogs.Single(b => b.BlogId == 1); blog.Url = "http://www.vae.com"; context.SaveChanges(); } } public void Select() { using (var context = new MyContext()) { var blogs = context.Blogs.ToList(); Console.WriteLine(blogs); } } } }
运行项目,输入对应的url,成功操做了数据
上面的数据库配置文件是写在MyContext里面的,这样不合适,因此我写在了json文件里
找到appsettings.json,在里面加上
"ConnectionStrings": { "DefaultConnection": "Server=.;Database=EFCore;Trusted_Connection=True;" }
在Startup里面的ConfigureServices方法里面写
public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; set; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddSingleton<IStudentService,StudentService>(); services.AddMvc();
public class MyContext : DbContext { public MyContext(DbContextOptions<MyContext> options) : base(options) { } public DbSet<Blog> Blogs { get; set; } }
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DB; using DomainModels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace StudyNetCore.Controllers { public class EFCoreController : Controller { private readonly MyContext _myContext; public EFCoreController(MyContext myContext) { _myContext = myContext; } public IActionResult Index() { return View(); } public void Add() { var blog = new Blog { Url = "http://sample.com", Rating = 98 }; _myContext.Blogs.Add(blog); _myContext.SaveChanges(); } public void Remove() { var blog = _myContext.Blogs.Single(b => b.BlogId == 2); _myContext.Blogs.Remove(blog); _myContext.SaveChanges(); } public void Update() { var blog = _myContext.Blogs.Single(b => b.BlogId == 1); blog.Url = "http://www.vae.com"; _myContext.SaveChanges(); } public IActionResult Select() { var list = _myContext.Blogs.ToList(); Console.WriteLine(list); return View(); } } }
和数据库表字段对应的就是EntityModel,和视图对应的就是ViewModel
我新建一个EntityModel,以下
public class Student { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDay { get; set; } }
能够看到,学生有两个名字,还有出生年月日,可是我页面上只想看到一个名字和年龄,这就须要处理一下了,ViewModel以下
public class HomeIndexViewModel { public string Name { get; set; } public int Age { get; set; } }
转化以下:
private readonly StudentService _studentService=new StudentService(); public IActionResult Index() { List<Student> studentList = _studentService.getStudentList(); var vms = studentList.Select(x => new HomeIndexViewModel { Name = x.FirstName + x.LastName, Age = DateTime.Now.Subtract(x.BirthDay).Days / 365 }); return View(vms); }
视图使用以下:
@model IEnumerable<StudyNetCore.ViewModels.HomeIndexViewModel> @{ ViewData["Title"] = "Index"; } <h1>个人第一个.Net Core Web项目</h1> <h1>Student的ViewModel数据展现</h1> <ul> @foreach (var item in Model) { <li>@item.Name - @item.Age</li> } </ul>
@model是指令,只是为了让@Model有智能提示
结果以下:
前端HTML输入Model传给后台我知道,待补充
待补充
而后页面重复刷新会形成post的重复提交,解决办法就是post提交一次以后就当即重定向,以下
return RedirectToAction(nameof(Detail));
一个输入的表单,例如我输入用户的姓名,年龄,手机号,邮箱,密码等等,这个对应的Model须要作数据验证
可能有人会问,前端我验证不就得了,前端数据填写不合法的时候就报错,后端的Model还验证什么呢?我之前也是这么想的
直到我知道了PostMan.....
有不少方法能够绕过你的前端验证的,若是你没加后端验证,有人直接传入非法数据就不安全了
因此,数据验证.先后端都须要作,双重保险
相似这样
public class Student { [Required] public int Id { get; set; } [StringLength(20)] public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)] public DateTime BirthDay { get; set; } }
下面列了一些,更多的用到再更新
[Required]//必须数据
[StringLenght(100)]//最大长度100
[Range(0,999)]//取值范围是0-999
[DateType(DataType.Date)]//要求此数据必为日期类型
[CreaitCard]//信用卡
[Phone]//电话号码
[EmailAddress]//邮箱地址
[DataType(DataType.Password)] //密码
[Url]//必须是url连接
[Compare]//比较数据是否相同
例子
public class Movie { public int ID { get; set; } [StringLength(60, MinimumLength = 3)] [Required] public string Title { get; set; } [Display(Name = "Release Date")] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [Range(1, 100)] [DataType(DataType.Currency)] [Column(TypeName = "decimal(18, 2)")] public decimal Price { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")] [Required] [StringLength(30)] public string Genre { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")] [StringLength(5)] [Required] public string Rating { get; set; } }
Required
和 MinimumLength
特性表示属性必须有值;但用户可输入空格来知足此验证。RegularExpression
特性用于限制可输入的字符。 在上述代码中,即“Genre”(分类):
RegularExpression
“Rating”(分级):
Range
特性将值限制在指定范围内。StringLength
特性使你可以设置字符串属性的最大长度,以及可选的最小长度。这个是母版页,有两个地方须要说明
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @RenderBody() </div> </body> </html>
第一个@ViewBag.Title 子页面能够这样写,这样就标题映射过去了
@{ ViewBag.Title="Index"; }
下面的@RenderBody()就是你子页面展示的地方
还能够写一个母版页节点,让子页面去加载,例如
@RenderSection("Footer",required:false)
这样子页面就可使用
@section Footer{ <h3>你们好,我是脚</h3> }
required:false是否是每一个页面必须的,若是不写,每一个子页面都得加载Footer节点
这个是每一个页面加载以前都必须先加载的页面,一般直接放在Views的文件夹下面,这样就能够对全部的页面起做用了.若是把_ViewStart.cshtml放在了Home文件夹下面,那么仅仅对Home文件夹下的页面起做用
例如,咱们能够把每一个页面都有的东西放在_ViewStart.cshtml里面
@{ Layout = "~/Views/Shared/_Layout.cshtml"; } <h1>都得先加载我</h1>
有时候咱们会在视图里面用到本身写的类,这个时候咱们一般是直接写全引用,或者在页面上写@using
可是,每个页面都写重复的@using这样就很差了,能够统一的在_ViewImports.cshtml里写@using
这样每一个页面直接写类的名称就能够了,好比
@using StudyNetCore.Models;
一般放在Views文件夹下
这个是分部视图,多个页面都用到的HTML能够放到这里面,一般放在Share文件夹下
@model IEnumerable<HomeIndexViewModel> <h1>我是分部视图</h1> <ul> @foreach (var item in Model) { <li>@item.Name</li> <li>@item.Age</li> } </ul>
调用的时候也很简单,输入名字传入Model就能够了
@Html.Partial("_PartialView",Model) <partial name="_PartialView" for="HomeIndexViewModel" />
这两种方式均可以,可是推荐使用TagHelper方式
缺点,分部视图PartialView的缺点就是,这个Model必须是调用者传过来的,不能本身去查询加载数据,下面的ViewComponents就很好的解决了这个问题
暂时不写,感受略麻烦,感受可使用ViewBag代替
两个主要的类先了解一下
UserManager
SignInManager
在_ViewImports.cshtml里面加上
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
而后页面就能够直接调用,如
<a asp-controller="Home" asp-action="data">仍是点我,TagHelper类型的</a>
为何使用TagHelper?由于改变路由以后这些会自动映射
若是返回的是字符类型,那么可使用 $ 符号进行拼接,仍是不错的挺好用,
HtmlEncoder.Default.Encode这个东西是防止js注入的,可是我尝试中文也显示的被编码过了,我不知道怎么不让中文被编码
public string Index(string name, int age=17) { return HtmlEncoder.Default.Encode($"Hello {name},you age is {age}"); }
Model和数据库的字段都是一一对应的,可是我有一个这样的名字,以下
public DateTime ReleaseDate { get; set; }
ReleaseDate这个名字显然不合适,加一个空格就好多了,能够这样写
[Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; }
前端显示就使用@Html.DisplayNameFor,以下
<th> @Html.DisplayNameFor(model => model.ReleaseDate) </th>
这样一来显示的就是有空格的名字了
这样讲一下@Html.DisplayNameFor就是现实名字,@Html.DisplayFor就是显示数据
我后台传入的是一个List,以下
public IActionResult Test() { return View(_context.Movie.ToList()); }
前端接受使用model,而后处理的时候可使用以下
@model IEnumerable<DomainModels.Movie> @{ ViewData["Title"] = "Test"; } <h1>我就是Movie下面的一个Test页面</h1> <div> <ul> @foreach (var item in Model) { <li> 名字是: @Html.DisplayNameFor(model => model.Title) | 内容是: @Html.DisplayFor(modelItem => item.Title) 类别是: @Html.DisplayNameFor(model => model.Genre) | 内容是: @Html.DisplayFor(modelItem => item.Genre) 价格是: @Html.DisplayNameFor(model => model.Price) | 内容是: @Html.DisplayFor(modelItem => item.Price) </li> } </ul> </div>
讲一下标题可使用model => model.Title
内容的话必须使用modelItem => item.Title,其中前面的modelItem 随意写,后面的 item.Title是必须使用的
这个WebAPI和MVC有什么区别呢?其实他俩乍一看很像,都是控制器加Action的模式
可是我以为最大的区别就在于,MVC是有视图的,这个框架包含了不少东西
WebAPI呢根本就没有视图这个东西,因此内容比较纯净,就是单纯的操做数据
很明显的区别就是一个继承的是Controller,一个继承的是ApiController
这个实在没什么讲的,直接新建便可,使用EF Core来操做数据库,贴几个代码看看
using System.Collections.Generic; using System.Linq; using DB; using DomainModels; using Microsoft.AspNetCore.Mvc; namespace WebAPI.Controllers { [Route("api/[controller]")] [ApiController] public class TodoItemController : ControllerBase { private readonly MyContext _context; public TodoItemController(MyContext context) { _context = context; } // GET: api/TodoItem [HttpGet] public IEnumerable<TodoItem> Get() { return _context.TodoItem.ToList(); } // GET: api/TodoItem/5 [HttpGet("{id}", Name = "Get")] public TodoItem Get(int id) { return _context.TodoItem.FirstOrDefault(m => m.Id == id); } // POST: api/TodoItem [HttpPost] public void Post(TodoItem todoItem) { _context.TodoItem.Add(todoItem); _context.SaveChanges(); } [HttpPut("{id}")] public IActionResult Put(int id, TodoItem todoItem) { if (id != todoItem.Id) { return BadRequest(); } _context.TodoItem.Update(todoItem); _context.SaveChanges(); return Ok("ok"); } // DELETE: api/ApiWithActions/5 [HttpDelete("{id}")] public IActionResult Delete(int id) { TodoItem todoItem = _context.TodoItem.Find(id); if (todoItem == null) { return NotFound(); } _context.TodoItem.Remove(todoItem); _context.SaveChanges(); return Ok("ok"); } } }
MVC能够直接输入控制器测试,反正有视图能够看,可是WebAPI这样的须要使用PostMan进行测试
我暂时是直接运行着项目测试的,稍后再讲部署的问题
这个稍微讲一下,参数是json格式的,因此必须选择json,默认是Text格式的,不改为json就会失败
还有一个地方就是禁止Postman的SSL证书,不然也会失败
禁用SSL证书以下
这个真的是太好用了,把你的项目设置为启动项目,而后 工具>NuGet包管理器>程序包管理器控制台
在程序包管理器控制台输入如下
Scaffold-DbContext "Server=192.168.111.111;Database=VaeDB;uid=sa;pwd=123456789;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
这个很简单能够理解,你的数据库链接字符串,最后是导出的位置是Models,也就是你这个启动项目的Models文件夹下
运行一下,Models就直接生成了
//防止CSRF攻击 services.AddAntiforgery(options => { //使用cookiebuilder属性设置cookie属性。 options.FormFieldName = "AntiforgeryKey_Censtry"; options.HeaderName = "X-CSRF-TOKEN-CENSTRY"; options.SuppressXFrameOptionsHeader = false; });
headers: { "X-CSRF-TOKEN-yilezhu": $("input[name='AntiforgeryKey_yilezhu']").val() }
点击你的项目发布,而后你在发布的文件夹内能够看到一个项目名.dll,直接右键打开控制台输入
dotnet Web.dll --urls=http://localhost:8099
固然也能够不指定访问端口号,不写 --urls后面的便可
首先要知道Swagger是什么,学了有什么用
就是一个自动生成API文档的框架
你编写了一个API的后端程序,须要给其余人调用,总得写个文档吧,否则别人怎么知道有哪些API接口,分别是干吗的
你固然能够选择本身一个一个的写,也能够选择使用Swagger自动生成而后喝杯茶
使用Nuget引入Swagger的包,就是这个:Swashbuckle.AspNetCore
Startup里面有两个地方须要配置,首先是须要注入一下服务,而后在中间件那里启用一下
首先,服务注入,在ConfigureServices方法添加Swagger
//Swagger services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { //只列举了几个,还有不少没写,详细可查官方文档 Title = "My API", Version = "v1", Description="个人API的说明文档" }); });
而后在中间件的方法Configure中,启用Swagger
app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
运行你的Web API项目,而后路由输入swagger,便可
以上简简单单的导入包,注入服务,启用中间件就能够看到简单的文档了,如图
一目了然,接口,还能够测试接口,像postman同样
上面简单的实现了Swagger,如今来进阶一下,增长几个功能
先解决第一个问题,运行项目以后首页直接就是Swagger,这个要在launchSettings.json里面设定,在Properties下找到launchSettings.json,修改launchUrl
有两个地方,一个是profiles下的launchUrl,还有一个是项目下的launchUrl
"launchUrl": "swagger",
第二个问题,就是API注释,找到你的项目,右键属性,来到生成这里,勾选下面的输出XML文档文件,我这里是项目的根目录,位置本身随便选,而后选定以后从新编译一下项目便可生成xml文件,此时,你会发现出现了很是多的警告,能够在上面的错误和警告那里加一个 ;1591 这样就不会有那么多警告了
在你的API接口上加上注释,如
/// <summary> /// TodoItem根据Id获取 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet("{id}", Name = "Get")] public TodoItem Get(int id) { return _context.TodoItem.FirstOrDefault(m => m.Id == id); }
这样运行项目就能够有注释了,如图
虽然新建了一个xml,可是里面什么咱们都没加,你如今能够点进去看看,发现xml里面多了好多内容,就是咱们刚才写的接口的注释
若是某些接口不但愿别人看到的话,能够这样写
/// <summary> /// TodoItem获取全部 /// </summary> /// <returns></returns> [HttpGet] [ApiExplorerSettings(IgnoreApi = true)] public IEnumerable<TodoItem> Get() { return _context.TodoItem.ToList(); }
加一个[ApiExplorerSettings(IgnoreApi = true)]就能够了
第三个问题权限