Asp.Net Core学习笔记:(二)视图、模型、持久化、文件、错误处理、日志

作者:神秘网友 发布时间:2021-02-23 13:55:56

Asp.Net Core学习笔记:(二)视图、模型、持久化、文件、错误处理、日志

TagHelper

入门

优点:根据参数自动生成,不需要手写超链接,类似Django模板里面的url命令。

在ViewImport中添加TagHelper

@addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers

比如,链接TagHelper使用

a class="btn btn-outline-primary" 
   asp-controller="student" asp-action="get" 
   asp-route-id="@student.Id"
    查看
/a

缓存破坏的TagHelper

img src="~/images/banner.jpg" asp-append-version="true" /

环境 TagHelper

在开发环境中使用本地css文件,在非开发环境下使用的是CDN的css文件。

注:integrity是用来做完整性检查的,保证CDN提供文件的完整和安全。

environment include="Development"
    link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" /
/environment

environment exclude="Development"
    link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"
/environment

为了防止CDN加载失败页面无法显示,可以加上fallback相关属性,第一个是失败时加载的文件,第二个是不检查这个文件的完整性

asp-fallback-href="~/lib/twitter-bootstrap/css/bootstrap.css"
asp-suppress-fallback-integrity="true"

表单 Tag Helper

直接贴上一个布局的代码,把class样式都去掉了,保留最基本代码。

确实是很方便的,和Django、jinja2之类的模板比完全不输。

@model Student

form asp-controller="student" asp-action="create"
    label asp-for="Name"/label
    input asp-for="Name" /

    label asp-for="Email"/label
    input asp-for="Email" /

    label asp-for="ClassName"/label
    select asp-for="ClassName" asp-items="Html.GetEnumSelectListClassNameEnum()"/select

    button type="submit"提交/button
/form

模型绑定

将Http请求中的数据绑定到控制器方法上对应参数的顺序:

  • Form Values (Post表单数据)
  • Route Values (路由中的值)
  • Query String (Get的查询字符串)

模型验证

1.设置模型

首先在Model中加入验证属性,如:

public class Student
{
    public int Id { get; set; }

    [Display(Name = "姓名"), MaxLength(4, ErrorMessage = "名字长度不能超过四个字")]
    [Required(ErrorMessage = "请输入名字!")]
    public string Name { get; set; }

    [Display(Name = "班级")]
    public ClassNameEnum ClassName { get; set; }

    [Required(ErrorMessage = "请输入邮箱!")]
    [Display(Name = "邮箱")]
    [RegularExpression(@"^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$", ErrorMessage = "邮箱格式不正确")]
    public string Email { get; set; }
}
常用的模型验证方法
  • Required
  • Range:指定允许的最小值和最大值
  • MinLength
  • MaxLength
  • Compare:比较两个属性,比如密码和确认密码
  • RegularExpression:正则匹配

2.在控制器中加入验证代码

使用ModelState.IsValid来验证模型属性是否正确

[HttpPost]
public IActionResult Create(Student student)
{
    if (ModelState.IsValid)
    {
        var newStudent = _studentRepository.Add(student);
        return RedirectToAction("details", new { id = newStudent.Id });
    }

    return View();
}

3.使用TagHelper在网页上显示错误信息

例子如下:

div class="text-danger" asp-validation-summary="All"/div

div class="form-group row"
label asp-for="Name" class="col-sm-2 col-form-label"/label
div class="col-sm-10"
input asp-for="Name" class="form-control" /
span class="text-danger" asp-validation-for="Name"/span
/div
/div

EF Core入门

首先实现DbContext

public class AppDbContext:DbContext
{
    // 将应用程序的配置传递给DbContext
    public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { }

    // 对要使用到的每个实体都添加 DbSetTEntity 属性
    // 通过DbSet属性来进行增删改查操作
    // 对DbSet采用Linq查询的时候,EFCore自动将其转换为SQL语句
    public DbSetStudent Students { get; set; }
}

注册DbContext连接池

services.AddDbContextPoolAppDbContext(options = options.UseSqlServer(_configuration.GetConnectionString("StudentDBConnection")));

其中,本地SqlServer数据库的配置,在appserttings.json中:

"ConnectionStrings": {
    "StudentDBConnection": "server=(localdb)\\MSSQLLocalDB;database=StudentDB;Trusted_Connection=true"
}

实现仓储

public class SqlStudentRepository : IStudentRepository
{
    private readonly AppDbContext _context;

    public SqlStudentRepository(AppDbContext context)
    {
        _context = context;
    }
    public Student Add(Student student)
    {
        _context.Students.Add(student);
        _context.SaveChanges();
        return student;
    }
    public Student Delete(int id)
    {
        var student = _context.Students.Find(id);
        if (student != null)
        {
            _context.Students.Remove(student);
            _context.SaveChanges();
        }
        return student;
    }
    public IEnumerableStudent GetAll() = _context.Students;
    public Student GetById(int id) = _context.Students.Find(id);
    public Student Update(Student updatedStudent)
    {
        var student = _context.Students.Attach(updatedStudent);
        student.State = EntityState.Modified;
        _context.SaveChanges();
        return updatedStudent;
    }
}

EF Core 常用命令

在nuget控制台中,不区分大小写

  • Get-Help about_enti:显示帮助,about_enti全名很长可以只写前面的
  • Add-Migration:添加迁移记录
  • Update-Database:更新数据库

添加种子数据

重写DbContextOnModelCreating方法

protected override void OnModelCreating(ModelBuilder modelBuilder) { 
    modelBuilder.EntityStudent().HasData(
        new Student { Id = 1, Name = "小米" },
    );
}

为了避免DbContext代码太乱,也可以使用扩展方法的方式:

public static class ModelBuilderExtensions
{
    public static void InsertSeedData(this ModelBuilder mBuilder)
    {
        mBuilder.EntityStudent().HasData(
            new Student { Id = 1, Name = "小米" },
        );
    }
}

领域模型与数据库架构

  • 使用迁移功能同步领域模型和数据库架构
  • 使用 add-migration 添加迁移记录
  • 使用 remove-migration 删除最近一条记录
  • 使用 update-database 迁移记录名称 可以回滚至任意一次迁移

文件上传

定义ViewModel

要上传的字段采用 IFormFile 类型

public class StudentCreateViewModel
{
    public int Id { get; set; }
	// 省略无关代码...
    [Display(Name = "图片")]
    public IFormFile Photo { get; set; }
}

编写视图

修改cshtml视图文件,修改模型绑定:

@model StudentCreateViewModel

加入上传文件的表单项

div class="form-group row"
    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="form-control custom-file-input" /
        label class="custom-file-label"请选择图片/label
        /div
    /div
/div

为了选择文件后能显示出文件名还要编写js:

$(document).ready(function () {
    $('.custom-file-input').on('change', function () {
        var fileName = $(this).val().split('\\').pop();
        $(this).next('.custom-file-label').html(fileName);
    });
});

编写控制器

通过构造函数注入 HostingEnvironment

public StudentController(IStudentRepository studentRepository, HostingEnvironment hostingEnvironment)
{
    _studentRepository = studentRepository;
    _hostingEnvironment = hostingEnvironment;
}

处理文件上传和保存的逻辑

[HttpPost]
public IActionResult Create(StudentCreateViewModel model)
{
    if (ModelState.IsValid)
    {
        var uniqueFileName = "";
        if (model.Photo != null)
        {
            var uploadDir = Path.Combine(
                _hostingEnvironment.WebRootPath, 
                "uploads", "images");
            uniqueFileName = Guid.NewGuid().ToString() + 
                "_" + model.Photo.FileName;
            model.Photo.CopyTo(new FileStream(
                Path.Combine(uploadDir, uniqueFileName), 
                FileMode.Create));
        }
        var student = new Student
        {
            Name = model.Name,
            Email = model.Email,
            ClassName = model.ClassName,
            PhotoPath = uniqueFileName,
        };

        var newStudent = _studentRepository.Add(student);
        return RedirectToAction("details", 
                                new { id = newStudent.Id });
    }

    return View();
}

多文件上传

和单文件差不多

ViewModel

增加 ListIFormFile 类型字段

[Display(Name = "图库")]
public ListIFormFile Gallery { get; set; }

修改视图

div class="form-group row"
    label asp-for="Gallery" class="col-sm-2 col-form-label"/label
    div class="col-sm-10"
        div class="custom-file"
            input asp-for="Gallery" multiple id="gallery-input" class="form-control custom-file-input" /
            label id="gallery-label" class="custom-file-label"请选择图片 可以一次选择多张/label
        /div
    /div
/div

js代码:

$('#gallery-input').on('change', function () {
    var label = $(this).next('#gallery-label');
    var files = $(this)[0].files;
    if (files.length  1) {
        label.html(`你已经选择了${files.length}个文件`);
    } else if (files.length == 1) {
        label.html(files[0].name);
    }
});

修改控制器代码

// 处理多文件上传
if (model.Gallery != null  model.Gallery.Count  0)
{
    foreach(var photo in model.Gallery)
    {
        uniqueFileName = Guid.NewGuid().ToString() + "_" 
            + photo.FileName;
        photo.CopyTo(new FileStream(Path.Combine(uploadDir, 
        uniqueFileName),FileMode.Create));
    }
}

错误处理

添加错误处理页面

Startup.cs 中设置中间件:

app.UseStatusCodePagesWithReExecute("/error/{0}");

推荐用 UseStatusCodePagesWithReExecute 而不是 UseStatusCodePagesWithRedirects,前者在管道内执行执行错误跳转url,后者会重定向到该url,导致http错误状态码变成新页面的正常执行的200码了。

接着编写错误控制器:

public class ErrorController : Controller
{
    [Route("Error/{statusCode}")]
    public IActionResult Index(int statusCode)
    {
        var statusCodeResult = HttpContext.Features.
            GetIStatusCodeReExecuteFeature();
        var viewModel = new ErrorViewModel
        {
            Path = statusCodeResult.OriginalPath,
            QueryString = statusCodeResult.
                OriginalQueryString,
        };
        switch (statusCode)
        {
            case 404:
                viewModel.Message = "页面未找到";
                break;
        }
        return View("Error", viewModel);
    }
}

对了,我还定义了ViewModel:

public class ErrorViewModel
{
    public int Code { get; set; }
    public string Message { get; set; }
    public string Path { get; set; }
    public string QueryString { get; set; }
}

视图代码就不贴了,无非就是显示ViewModel里的这些错误信息~

设置全局异常跳转

添加中间件

app.UseExceptionHandler("/exception");

编写处理用的控制器,这里需要添加AllowAnonymous注解,允许用户在未登录的时候访问到这个异常页面,保证无论如何可以显示出异常页面。

[AllowAnonymous]
[Route("exception")]
public IActionResult ExceptionHandler()
{
    var exception = HttpContext.Features.
        GetIExceptionHandlerPathFeature();
    var viewModel = new ExceptionViewModel
    {
        Path = exception.Path,
        Message = exception.Error.Message,
        StackTrace = exception.Error.StackTrace,
    };
    return View("Exception", viewModel);
}

另外,ViewModel定义如下:

public class ExceptionViewModel
{
    public string Path { get; set; }
    public string Message { get; set; }
    public string StackTrace { get; set; }
}

日志记录

AspNetCore里面自带了一套日志系统,默认已经注册到了服务容器里了,只要在控制器的构造函数里注入就可以使用了,比如:

public class ErrorController : Controller
{
    private ILoggerErrorController _logger;

    public ErrorController(ILoggerErrorController logger)
    {
        this._logger = logger;
    }
}

默认的日志只会记录到控制台或者调试输出,不过我们为了实现更多功能,比如记录到文件或者推送到日志服务器,我们需要使用第三方的日志组件。这里我用的是NLog。

首先要安装NLog.Web.AspNetCore这个包。

之后在Program.cs里引入nlog服务:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =
    WebHost.CreateDefaultBuilder(args)
    .ConfigureLogging((hostingContext, logging) =
     {
         // 保留官方的代码中的默认日志程序
 logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
         logging.AddConsole();
         logging.AddDebug();
         logging.AddEventSourceLogger();
         // 引入 nlog
         logging.AddNLog();
      }).UseStartupStartup();

保留官方默认日志程序那里,要看AspNetCore的源代码,本文用的是2.2版本,在github看,地址如下:

https://github.com/dotnet/aspnetcore/blob/v2.2.8/src/DefaultBuilder/src/WebHost.cs

然后,为了使用nlog,需要创建一个配置文件,在项目根目录创建 NLog.config

关于配置文件的说明可以参考:https://github.com/NLog/NLog/wiki

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"
      autoReload="true"
      throwConfigExceptions="true"
  targets
    target name="f1" xsi:type="File" fileName="Logs\nlog-all-${shortdate}.log"/
    target name="n1" xsi:type="Network" address="tcp://localhost:4001"/
    target name="c1" xsi:type="Console" encoding="utf-8"
            error="true"
            detectConsoleAvailable="true" /
    target name="c2" xsi:type="ColoredConsole" encoding="utf-8"
          useDefaultRowHighlightingRules="true"
          errorStream="true"
          enableAnsiOutput="true"
          detectConsoleAvailable="true"
          DetectOutputRedirected="true"
    /target
  /targets
  rules
    logger name="*" maxLevel="Debug" writeTo="c2" /
    logger name="*" minLevel="Info" writeTo="f1" /
  /rules
/nlog

之后在程序中就可以正常使用日志功能了。比如:

var viewModel = new StatusCodeViewModel
{
    Code = statusCode,
    Path = statusCodeResult.OriginalPath,
    QueryString = statusCodeResult.OriginalQueryString,
};
_logger.LogWarning(viewModel.ToString());

还有可以在appsettings.json里面配置日志等级和命名空间过滤,跟在NLog.conf里面配置效果是一样的。例如:

"Logging": {
    "LogLevel": {
        "Default": "Warning",
        "StudyManagement.Controllers.ErrorController": 
        "Warning"
    }
},

欢迎交流

程序设计实验室专注于互联网热门新技术探索与团队敏捷开发实践,在公众号「程序设计实验室」后台回复 linux、flutter、c#、netcore、android、kotlin、java、python 等可获取相关技术文章和资料,同时有任何问题都可以在公众号后台留言~

  • 博客园:https://www.cnblogs.com/deali/
  • 打代码直播间:https://live.bilibili.com/11883038
  • 知乎:https://www.zhihu.com/people/dealiaxy

Asp.Net Core学习笔记:(二)视图、模型、持久化、文件、错误处理、日志 相关文章

  1. Nodejs 学习笔记和实例【 一篇全覆盖:写后端 api 接口完全够了】

    本文pdf下载 Nodejs学习笔记 Node.js官方API文档 一、基础知识 1. 命令行窗口 dir命令:列出当前目录下所有的文件夹名字; md命令:创建一个文件夹; rd命令:删除一个文件夹; path环境变量的作用:当我们在命令行窗口中使用命令打开一个文件或者调用一个程

  2. .net core的程序发布到iis上步骤和端口修改方式

    .net core的项目publish到服务器上如何进行呢。根据以往.net framework的经验。 右键项目,点publish发布到文件系统上。 然后把文件都拷贝到服务器上的一个目录。 由于是.netcore项目。 第一步: 以.net core 3.1为例子,在服务器上保证安装好 .net core SDK

  3. .net5 core webapi进阶之一:System.Text.Json的用法详解(下篇)JSON序列化和反序列化

    本篇接着介绍 JSON 的序列化和反序列化。 一、要完成的功能如下: 1 . 序列化:实例化 Company 和 User 对象,并序列化成一个JSON字符串,对应终结点为 JsonDemo6( ) 。 2 . 反序列化:读取配置文件appcom.json中的信息,并反序列化成 Company 和 User 对象

  4. RAZOR显示表格数据

    asp.net mvc4.0 razor页面获取数据库表记录显示在页面上,页面前段显示字段名必须区分大小写,否则不认识字段名,提示错误信息 1.control类 publicActionResultListUser(){//TestDataContextdb=newTestDataContext();//IEnumerabletbUsermodel=fromuindb.tbU

  5. Redis学习笔记(一)安装

    一、Redis简介 Redis是完全开源的,遵守BSD协议,是一个高性能的key-value数据库。 Redis 与其他 key - value 缓存产品有以下三个特点: 1)Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 2)Redis不仅仅支持简

  6. C++Primer plus学习记录第一日.1

    最近在深入一些炫目的源码,发现了大量的CPP的身影,各种C函数穿插其中,我发现我的CPP的学习之路必须开始了。从C转过来的我感觉到这个写法还是比较亲切的。但是特性也好多,我选择C++Primer plus这本经典的书籍进行入门的学习~ 编译的环境选择微软的Visual

  7. [学习笔记] SAM——后缀自动机

    [学习笔记] SAM——后缀自动机 零.前言 ? 真是给我整的有够难受的,这个SAM,也不算搞懂了。只是粗浅的理解了一下,且在这里试图将它写下来。 ?上面是这个笔记的初稿,现在做了一些题,感觉自己不说懂完了,但是还是有一点点点点东西的。/cy 一.概念 1.自动

  8. LeetCode 41. 缺失的第一个正数

    新手学习中,有任何错误或者更好地方法、思路欢迎指教! #Array 6 题目难度: 困难 题目描述: 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。进阶:你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗示例 1:输

  9. 基于android的app开发!Android之内存泄漏调试学习与总结,深度好文

    一,鸿蒙核心内容掌握程度 看看下面这些鸿蒙知识点你掌握了多少: 基础环境和开发工具 开发工具安装 运行开发工具完成基础配置DevEco Studio 运行第一个hello world 运行第一个页面 通过代码创建页面 Feature Ability 编程实现页面跳转 市面上的鸿蒙教程大多

  10. 安卓入门开发教程!作为移动开发程序员应该怎样去规划自己的学习路线含泪整理面经

    前言 今年的寒来得格外慢,眼看年关将近,开年就入春了,但西北季风似乎没有往年的无情。 天气和互联网行业的双重寒冷险些让我翻不过身。 那时的我正处在一个尴尬的境地,工作两年,压力不大,朝九晚五,做着一些在刚入职就一直在做的增删改查。 曾经也找过

每天更新java,php,javaScript,go,python,nodejs,vue,android,mysql等相关技术教程,教程由网友分享而来,欢迎大家分享IT技术教程到本站,帮助自己同时也帮助他人!

Copyright 2020, All Rights Reserved. Powered by 跳墙网(www.tqwba.com)|网站地图|关键词