原文链接:https://blog.csdn.net/jh_negit/article/details/130867719
1.项目
1.1创建
-
打开VS,选择新建项目,选择Asp.Net Core 空或Web应用,点击下一步;
-
配置项目名称、路径等信息,下一步;
-
选择框架版本,将身份验证类型设为无,取消配置HTTPS(H),创建
1.2启动
1.2.1VS启动
1.2.2控制台启动
-
打开项目路径bin->debug->net6.0(具体.net版本)文件夹,在地址栏中输入cmd;
-
打开控制台窗口,输入命令;
命令有两个:- 默认端口:
dotnet AspNetCore6.PracticalDemo.dll
- 指定端口:
dotnet AspNetCore6.PracticalDemo.dll --urls="http://*:5177"; dotnet AspNetCore6.PracticalDemo.dll --urls=http://localhost:5177--port=5177
- 默认端口:
- 说明:
AspNetCore6.PracticalDemo.dll:项目生成的dll文件;
5177:访问的端口号
1.3发布
1.3.1IIS
-
右键项目->发布->选择文件夹;
-
打卡IIS->网站->添加网站,输入指定内容,点击确定;其中物理路径为项目发布路径;
-
※注意事项:
发布后如果无法访问需要先安装对应的SDK和Hosting Bundle,安装后在控制面板会有对应的Service安装程序,IIS中的模块会新增AspNetCoreModuleV2模块。
2.MVC
2.2视图
2.2.1布局视图
类似与模板,可包含其他视图;
2.2.1.1使用
- 新建布局视图
<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=device-width" /><title>@ViewBag.Title</title>
</head>
<body><div>@RenderBody()</div>
</body>
</html>
- 删除子页面中包含之外的内容;
- 标记子页面所使用的布局页;
@{Layout = "~/Views/Shared/_Layout.cshtml";ViewBag.Title = "学生详细信息";
}
<div>姓名:@Model.Student.Name
</div>
<div>邮箱:@Model.Student.Emial
</div>
<div>班级名称:@Model.Student.ClassName
</div>
2.2.1.2Section使用
- 在布局页定义RenderSection;
@RenderSection("Scripts",false)
- 在子页面定义具体的引用。
@section Scripts{ <script src="~/js/MyScript.js"></script>
}
@section 后的Scripts必须与@RenderSection中的参数Scripts命名一致
2.2.2视图开始
视图开始的意义在于将子页面中的公共部分抽离到开始视图中,如子页面引用布局视图的代码,开始视图遵循就近原则,子页面会优先加载离自己最近的开始视图。
@{Layout = "_Layout";
}
2.2.3视图导入
视图导入用于导入子页面公共命名空间以及其他指令,视图导入遵循就近原则,子页面会优先加载离自己最近的开始视图。
@using StudentManagement.Models
@using StudentManagement.ViewModels
2.3路由
MVC中路由有两种:常规路由和属性路由;
- 常规路由
默认使用app.UseMvcWithDefaultRoute()添加路由规则:Home/Index/1;
可以手动配置为自己所需要的路由规则:
app.UseMvc(route => {route.MapRoute("Default", "{Controller=Home}/{Action=Index}/{id?}");
});
Core3.1及之后版本:
app.UseRouting();
app.UseEndpoints(routes =>{routes.MapControllerRoute("default",pattern: "{controller=Home}/{action=Index}/{id?}");});
- 属性路由
2.4文件上传
- 在视图页添加上传文件控件;
@model StudentCreateViewModel
<form enctype="multipart/form-data" asp-controller="home" asp-action="create" method="post" class="mt-3"><div class="form-group row mt-3"><label asp-for="Photo" class="col-sm-2 col-form-label"></label><div class="col-sm-10"><div class="custom-file"><input asp-for="Photo" class="custom-control custom-file-input" /></div></div></div><div class="form-group row"><div class="col-sm-10"><button type="submit" class="btn btn-primary">创建</button></div></div>
</form>
- 在控制器中处理将图片文件保存到指定路径;
public IActionResult Create(StudentCreateViewModel model){if (ModelState.IsValid){string uniqueFileName = null;if (model.Photo != null){// wwwroot\images路径string uploadFile = Path.Combine(_hostingEnvironment.WebRootPath, "images");//生成图片唯一值uniqueFileName = Guid.NewGuid() + "_" + model.Photo.FileName;//新文件全路径string newFileName = Path.Combine(uploadFile, uniqueFileName);//将图片复制到新文件中using(FileStream stream=new FileStream(newFileName, FileMode.Create)){model.Photo.CopyTo(stream);}Student newStudent = new Student{Name = model.Name,Email = model.Email,ClassName = model.ClassName,PhotoPath = uniqueFileName};_studentRepository.Add(newStudent);return RedirectToAction("Details", new { id = newStudent.Id });}}return View();}
- 如果上传图片后,进程退出则按以下步骤设置即可;
a) 打开工具-选项-项目和解决方案
b) 选择web项目,将浏览器窗口关闭时停止调试程序,在调试停止时关闭浏览器取消选中;
3 中间件
3.1 静态文件
app.UseStaticFiles();
3.2 默认文件
- 使用默认文件
app.UseDefaultFiles();
- 修改默认文件
DefaultFilesOptions defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear();
defaultFilesOptions.DefaultFileNames.Add("MyPage.html");
app.UseDefaultFiles(defaultFilesOptions);
3.3 404找不到页面
3.3.1 添加中间件
当找不到路由时,加载默认错误页面
app. UseStatusCodePages();
3.3.2重定向
- UseStatusCodePagesWithRedirects
找不到路由时重定向到指定的路由,该中间件会覆盖原始请求路径;
app. UseStatusCodePagesWithRedirects ("/Error/{0}");
- UseStatusCodePagesWithReExecute
找不到路由时重定向到指定的路由,该中间件不会覆盖原始请求路径,建议使用该中间件;
app. UseStatusCodePagesWithReExecute ("/Error/{0}");
3.3.3 添加Error控制器及方法
public class ErrorController: Controller
{
[Route("/Error/{statusCode}")]public IActionResult HttpStatusCodeHandler(int statusCode){switch (statusCode){case 404:ViewBag.ErrorMessage = "抱歉,你访问的页面不存在";break;}return View("NotFound");
}
}
3.3.4添加404页
3.4错误页面
当发生异常时会跳转到指定error视图中。
- 添加UseExceptionHandler中间件
app.UseExceptionHandler("/Error");
- 添加Error控制器及方法
public class ErrorController: Controller
{
[AllowAnonymous]//允许匿名访问(即不需要登录也可以访问)[Route("Error")]
public IActionResult Error()
{
var exceptionHandle= HttpContext.Features.Get<IExceptionHandlerPathFeature>();
ViewBag.ExceptionPath = exceptionHandle.Path;//异常路径
ViewBag.ErrorMessage= exceptionHandle.Error.Message;// 异常信息
ViewBag.ErrorStackTrace= exceptionHandle.Error.StackTrace;// 异常堆栈return View();
}
}
3.添加具体Error页面
注入服务
- 中文乱码
builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
- Session
builder.Services.AddSession();
工具
客户端工具LibMan
轻量级的客户端管理工具,可以从CND下载客户端和框架;
注:必须在VS2017版本15.8及以上可以使用。
安装Bootstrap
- 右键wwwroot文件夹,选择新建-客户端;
- 打开添加客户端窗口,输入twitter-bootstrap,安装;
- 等待下载完成即可。
TagHelper
引入
在视图中引入TagHelper:
@addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers
使用
- asp-controller:控制器名
- asp-action:方法名
- asp-route-id:参数
- asp-append-version
属性设置为true时,会为图片标记一个特有的hash值,作用是当本机的值变化时才从服务器中重新读取图片。 - asp-for
<label asp-for="Name"> </label>
默认会生成Id=Name,Name=Name,该Name为Model中的具体属性;
- asp-items
asp-items="Html.GetEnumSelectList<ClassNameEnum>()"
用于生成ClassNameEnum类型的的选择项;
- asp-validation-summary
用于模型验证,值分别为All、ModelOnly和None;
注解
HttpGet和HttpPost
该注解用于请求时设置请求方法为Get或者Post方式,当一个控制器中有两个同名方法,但请求方式不同时则用该注解,默认为HttpGet。
[HttpGet]
public IActionResult Create()
{return View();
}
[HttpPost]
public RedirectToActionResult Create(Student student)
{_studentRepository.Add(student);return RedirectToAction("Details",new { id = student.Id });
}
Required
用于模型验证。
依赖注入
注入实例
Singleton
services.AddSingleton<IStudentRepository, SQLStudentRepository>();
Scoped
services.AddScoped<IStudentRepository, SQLStudentRepository>();
Transient
services.AddTransient<IStudentRepository, SQLStudentRepository>();
热更新
当cshtml页更改后实时更新网站中的内容而不用重新启动项目;
services.AddMvc().AddRazorOptions(options => options.AllowRecompilingViewsOnFileChange = true);
EF Core
SqlServer
使用
-
右键项目,选择Nuget管理,搜索Microsoft.EntityFrameworkCore.SqlServer;
-
选择指定的版本,安装;
-
新建Context类继承DbContext;
public class AppDbContext:DbContext{public AppDbContext(DbContextOptions<AppDbContext> options):base(options){}public DbSet<Student> Students { get; set; }//模型对应表文件
}
对于该类中使用的每个实体都需要添加DbSet 属性,将此属性查询和保存TEntity的实例
4. 连接SqlServer
//注入DbContext
services.AddDbContextPool<AppDbContext>(options => options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True")
) ;
Trusted_Connection=True表示使用Windows认证。
连接字符串建议存放在appsettings.json或其他配置文件中,使用如下
services.AddDbContextPool<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("StudentDBConnection"))) ;
该服务必须在builder.Build()之前注册,否则会报错
在appsetting.json文件中添加以下连接字符串
"ConnectionStrings": {"StudentDBConnection": "Server=(localdb)\\mssqllocaldb;Database=StudentDB;Trusted_Connection=True"//连接本地数据库BlogDBConnection": "Data Source=DESKTOP-EK117L1\\MYSQLSERVER;Database=MyBlog;uid=sa;pwd=123456;Encrypt=True;Trusted_Connection=True;TrustServerCertificate=True;MultipleActiveResultSets=true"}
数据库迁移
-
get-help about_entityframeworkcore
使用该命令获取命令帮助。 -
Add-Migration
使用该命令添加数据库迁移(默认执行最新的迁移文件),输入Name(迁移名称,自定义) ,命令执行成功后会在项目中生成快照。 -
update-database
更新到数据库; -
删除已更新至数据库的内容
添加种子数据
- 重写DbContext中的OnModelCreating方法;
protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<Student>().HasData(new Student{Id = 1,Name = "孙悟空",ClassName = ClassNameEnum.FirstGrade,Email = "wukong@163.com"});}
- 在Nugget控制台中执行add-Migration seedStudentsTabls命令,seedStudentsTabls为自定义名称,用于表示当前迁移的作用名称
- 在Nugget控制台中执行update-database命令,更新数据库数据。
数据操作
新增
public Student Add(Student student){_dbContext.Students.Add(student);_dbContext.SaveChanges();return student;}
删除
public Student Delete(int id){var student=_dbContext.Students.Find(id);if (student != null)_dbContext.Students.Remove(student);return student;}
修改
public Student Update(Student uStudent){var stu = _dbContext.Students.Attach(uStudent); ;stu.State = Microsoft.EntityFrameworkCore.EntityState.Modified;_dbContext.SaveChanges();return uStudent;}
查询
public IEnumerable<Student> GetAllStudents(){return _dbContext.Students;}
日志
ILogger
使用步骤
- 在需要使用日志的地方注入ILogger;
在Error控制器中注入ILogger接口;
public class ErrorController: Controller{private ILogger<ErrorController> _logger;public ErrorController(ILogger<ErrorController> logger){_logger = logger;}
}
- 写日志;
在指定方法中写入日志
[Route("Error")]
public IActionResult Error()
{
_logger.LogError("发生了一个异常,请检查!");
return View();
}
Nlog
使用步骤
-
打开NuGet扩展工具,搜索NLog.Web.AspNetCore,选择需要的版本点击安装;
-
新建nlog.config文件;
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><!-- 要写入的目标内容 --><targets><!-- 将日志写入文件的具体位置 --><target name="allfile" xsi:type="File"fileName="D:\Cache\StudentManagement\Logs\nlog-all-${shortdate}.log"/></targets><!-- 将日志程序名称映射到目标的规则 --><rules><!--记录所有日志,包括Microsoft级别--><logger name="*" minlevel="Information" writeTo="allfile" /></rules>
</nlog>
- 在Program.cs中注册NLog;
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>WebHost.CreateDefaultBuilder(args).ConfigureLogging((hostingContext, logging) =>{
//覆盖原有的日志记录启用NLoglogging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));logging.AddConsole();logging.AddDebug();logging.AddEventSourceLogger();//启动NLog作为记录日志的程序之一logging.AddNLog();}).UseStartup<Startup>();
- 注入ILogger接口
public class ErrorController: Controller{private ILogger<ErrorController> _logger;public ErrorController(ILogger<ErrorController> logger){_logger = logger;}
}
- 写日志
_logger.LogInformation("这是NLogger信息日志");
AOP-Filter
共包含六种:权限验证、资源缓存、方法前后的记录、结果生成前后扩展、响应结果的补充、异常处理
权限验证
角色授权
- 配置鉴权和授权
builder.Services.AddAuthentication(option =>
{option.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;//Cookie方式option.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;option.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;option.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,option=> {//如果未找到用户Cookie,鉴权失败,跳转至指定Action中option.LoginPath = "/Account/Login";//如果用户登录成功,但是角色没有指定权限则跳转至该Action中option.AccessDeniedPath = "/Error/NoAuthority";
});
- 添加鉴权和授权中间件
app.UseAuthentication();//鉴权
app.UseAuthorization();//授权
如果路由配置app.UseRouting()和app.UseEndpoints(...)同时存在,则该中间件必须放在app.UseRouting()和app.UseEndpoints(...)之间,否则会报错
- 标记授权的Action
//Authorize:表示用户必须登录
//Roles= "Admin":标记用户角色必须为Admin时才可以访问
[Authorize(AuthenticationSchemes= CookieAuthenticationDefaults.AuthenticationScheme, Roles= "Admin")]
public IActionResult Index(){return View();}
- 鉴权不通过跳转的指定Action
[HttpPost]public async Task<IActionResult> Login(User model){if (ModelState.IsValid){if (model.Name == "chongzi" && model.Password == "1"){//以下Claim可以写入任意数据var claims = new List<Claim>()//用于鉴别当前用户相关信息{new Claim("Userid","1"),new Claim(ClaimTypes.Role,"Admin"),//标记该用户角色为Adminnew Claim(ClaimTypes.Name,$"{model.Name}-来自于Cookies'"),//标记该用户名new Claim(ClaimTypes.Email,$"18829206496@163.com"),//标记该用户邮箱new Claim("password",model.Password),new Claim("Account","Administrator"),new Claim("role","admin")};ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, new AuthenticationProperties{ExpiresUtc = DateTime.UtcNow.AddMinutes(1)//过期时间为1分钟}).Wait();var user = HttpContext.User;return base.Redirect("/Home/Index");}}return View();}
- 如果登录成功,但是角色不匹配则跳转至AccessDeniedPath 指定的路径
注意事项
- 当同时标记多个Authorize时,若有多个角色Role,则用户必须同时拥有指定角色,才可以正常访问;
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme, Roles = "Admin")]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme, Roles = "Teacher")]
- 当只标记一个Authorize时,用户只需满足其中某一个角色都可以正常访问。
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme, Roles = "Admin,Teacher")]
策略授权
- 添加策略
builder.Services.AddAuthorization(option =>
{//定义名为MyPolicy的策略option.AddPolicy("MyPolicy", policy =>{policy.RequireRole("Admin,User,Teacher");//验证角色必须包含Admin或User或Teacherpolicy.RequireUserName("chongzi1");//验证角色名字必须为chognzipolicy.RequireAssertion(handler =>//可添加不同逻辑{return handler.User.HasClaim(c => c.Type == ClaimTypes.Role);//是否存在角色});});
});
- 在Action中标注
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme,Policy = "MyPolicy")]
public IActionResult Index44()
{return new JsonResult("/Home/Index4 View");
}
数据库或其他第三方授权
- 添加服务接口
public interface IUserService
{bool Validata(string id, string qq);
}
- 定义类实现服务接口
public class UserService : IUserService{public bool Validata(string id, string qq){//各种业务逻辑验证,如数据库、QQ、微信或其他第三方return true;}}
- 定义Requirement类并实现IAuthorizationRequirement接口
public class QQEmailRequirement: IAuthorizationRequirement
{
}
- 定义Handler类实现AuthorizationHandler
public class QQHandler : AuthorizationHandler<QQEmailRequirement>
{private IUserService _UserService;public QQHandler(IUserService userService){_UserService = userService;}protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, QQEmailRequirement requirement){string id = context.User.Claims.First(c => c.Type == "Userid").Value;string qq = context.User.Claims.First(c => c.Type == "QQ").Value;if(_UserService.Validata(id, qq)){context.Succeed(requirement); }return Task.CompletedTask;}
}
- 注入需要的依赖
builder.Services.AddTransient<IUserService, UserService>();
builder.Services.AddTransient<IAuthorizationHandler, QQHandler>();
- 添加策略授权
builder.Services.AddAuthorization(option =>
{option.AddPolicy("MyPolicy", policy =>{policy.AddRequirements(new QQEmailRequirement());});
});
资源缓存
主要用于做缓存服务。
IResourceFilter
流程
- 自定义ResourceFilte类继承Attribute并实现IResourceFilter接口
public class MyResourceFilterAttribute : Attribute, IResourceFilter
{//用于存储缓存内容private static Dictionary<string, object> _DicCache = new Dictionary<string, object>();//在访问资源之前调用public void OnResourceExecuting(ResourceExecutingContext context){string key = context.HttpContext.Request.Path;//当前请求路径if(_DicCache.ContainsKey(key) ){ //如果执行到此处,则直接返回到调用的地方,不会往后执行context.Result =(IActionResult)_DicCache[key];}Console.WriteLine("Exceuted MyResourceFilterAttribute.OnResourceExecuting");}//在访问资源之后调用public void OnResourceExecuted(ResourceExecutedContext context){string key = context.HttpContext.Request.Path;//当前请求路径_DicCache[key]= context.Result;Console.WriteLine("Exceuted MyResourceFilterAttribute.OnResourceExecuted");}}
- 在指定Action中标注该特性
public class ResourceController: Controller{public ResourceController(){Console.WriteLine("ResourceController被构造了");}[MyResourceFilter]public IActionResult Index(){Console.WriteLine("Exceuted ResourceController.Index");return new JsonResult(new[] { "你好呀", "虫子不吃鸟" });}}
执行顺序
- MyResourceFilterAttribute.OnResourceExecuting();
- ResourceController构造方法;
- 指定Action:Index;
- MyResourceFilterAttribute.OnResourceExecuted();
IAsynResourceFilter
异步缓存
流程
- 自定义ResourceFilte类继承Attribute并实现IAsyncResourceFilter接口
public class MyAsyncResourceFilterAttribute : Attribute, IAsyncResourceFilter{//用于存储缓存内容private static Dictionary<string, object> _DicCache=new Dictionary<string, object>();public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next){Console.WriteLine("Exceuted MyAsyncResourceFilterAttribute.OnResourceExecutionAsync Before");string key = context.HttpContext.Request.Path;//当前请求路径if (_DicCache.ContainsKey(key)){//如果执行到此处,则直接返回到调用的地方,不会往后执行context.Result = (IActionResult)_DicCache[key];}else{ResourceExecutedContext resource = await next.Invoke();_DicCache[key] = resource.Result;Console.WriteLine("Exceuted MyAsyncResourceFilterAttribute.OnResourceExecutionAsync After");}}}
- 在指定Action中标注该特性
public class ResourceController : Controller{public ResourceController(){Console.WriteLine("ResourceController被构造了");}[MyAsyncResourceFilter]public IActionResult Index(){Console.WriteLine("Exceuted ResourceController.Index");return new JsonResult(new[] { "你好呀", "虫子不吃鸟" });}}
执行顺序
- await next.Invoke()执行之前
- ResourceController构造方法;
- 指定Action:Index;
- await next.Invoke()执行之后
方法前后的记录
结果生成前后扩展
响应结果的补充
异常处理
反向代理
Ninginx
使用步骤
-
下载
Ninginx下载地址:http://nginx.org/en/download.html - 修改端口号
-
默认端口号为80,将其修改为其他值,打开下载路径\conf\nginx.conf文件:
在Server节点中修改listen的值即可 -
转发
- 单个转发
在server-location节点中添加 :proxy_pass http://localhost:9002;
- 多个转发
- 添加服务列表,在http节点之中,server节点之前添加以下内容
net6WebApi:服务列表名称,自定义upstream net6WebApi{ server localhost:9002 weight:1;server localhost:9003;server localhost:9004;}
weight:设置该服务器的请求权重,默认为轮询策略 - 在server-location节点中添加:
proxy_pass http://net6WebApi;
- 添加服务列表,在http节点之中,server节点之前添加以下内容
- 单个转发
-
启动
在nginx.exe所在目录中运行cmd,输入start nginx即可启动
指令
- 启动
start nginx
- 热加载
nginx -s reload
- 停止
nginx -s quit