Asp-Net-Core Study Notes: 2. MVC Views, Models, Persistence, Files, Error Handling, Logging
TagHelper Getting Started Advantages: Automatically generated based on parameters, no need to write hyperlinks by hand, similar to the url command in Django templates. Add TagHelper in ViewImport @addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers For example, using the link TagHelper a class=btn btn-outline-primary asp-controller=st
目录30 个章节
TagHelper
Getting Started
Advantages: Automatically generated based on parameters, no need to write hyperlinks by hand, similar to the url command in Django templates.
Add TagHelper in ViewImport
@addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers
For example, using the link TagHelper
<a class="btn btn-outline-primary"
asp-controller="student" asp-action="get"
asp-route-id="@student.Id">
查看
</a>
Cache-busting TagHelper
<img src="~/images/banner.jpg" alt="Alternate Text" asp-append-version="true" />
Environment TagHelper
Use local CSS files in the development environment, and CDN CSS files in non-development environments.
Note: integrity is used for integrity checking, to ensure the completeness and security of files provided by the 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>
To prevent the page from failing to display when the CDN fails to load, you can add fallback-related attributes. The first is the file to load on failure, and the second skips integrity checking for that file.
asp-fallback-href="~/lib/twitter-bootstrap/css/bootstrap.css"
asp-suppress-fallback-integrity="true"
Form Tag Helper
I'll just paste some layout code, with the class styles removed, keeping only the most basic code.
It really is quite convenient, and it holds its own against templates like Django and 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.GetEnumSelectList<ClassNameEnum>()"></select>
<button type="submit">提交</button>
</form>
Model Binding
The order in which data from the HTTP request is bound to the corresponding parameters on the controller method:
- Form Values (POST form data)
- Route Values (values in the route)
- Query String (GET query string)
Model Validation
1. Configure the model
First add validation attributes to the Model, for example:
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; }
}
Common model validation methods
- Required
- Range: specify the allowed minimum and maximum values
- MinLength
- MaxLength
- Compare: compare two properties, such as password and confirm password
- RegularExpression: regular expression matching
2. Add validation code in the controller
Use ModelState.IsValid to validate whether the model properties are correct
[HttpPost]
public IActionResult Create(Student student)
{
if (ModelState.IsValid)
{
var newStudent = _studentRepository.Add(student);
return RedirectToAction("details", new { id = newStudent.Id });
}
return View();
}
3. Use TagHelper to display error messages on the page
Example as follows:
<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>
Getting Started with EF Core
First, implement DbContext
public class AppDbContext:DbContext
{
// 将应用程序的配置传递给DbContext
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
// 对要使用到的每个实体都添加 DbSet<TEntity> 属性
// 通过DbSet属性来进行增删改查操作
// 对DbSet采用Linq查询的时候,EFCore自动将其转换为SQL语句
public DbSet<Student> Students { get; set; }
}
Register the DbContext connection pool
services.AddDbContextPool<AppDbContext>(options => options.UseSqlServer(_configuration.GetConnectionString("StudentDBConnection")));
The local SQL Server database configuration is in appserttings.json:
"ConnectionStrings": {
"StudentDBConnection": "server=(localdb)\\MSSQLLocalDB;database=StudentDB;Trusted_Connection=true"
}
Implement the repository
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 IEnumerable<Student> 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;
}
}
Common EF Core commands
In the NuGet console, these are case-insensitive
- Get-Help about_enti: show help; the full name of
about_entiis very long, so you can just write the beginning - Add-Migration: add a migration
- Update-Database: update the database
If using the dotnet CLI, please install it first: dotnet tool install --global dotnet-ef
- dotnet ef migrations add initial: add a migration
- dotnet ef database update: update the database
For more operations, you can refer to the official documentation: https://docs.microsoft.com/en-us/ef/core/cli/dotnet
Add seed data
Override the OnModelCreating method of DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Student>().HasData(
new Student { Id = 1, Name = "小米" },
);
}
To keep the DbContext code from getting too messy, you can also use an extension method:
public static class ModelBuilderExtensions
{
public static void InsertSeedData(this ModelBuilder mBuilder)
{
mBuilder.Entity<Student>().HasData(
new Student { Id = 1, Name = "小米" },
);
}
}
Domain model and database schema
- Use migrations to keep the domain model and database schema in sync
- Use
add-migrationto add a migration - Use
remove-migrationto delete the most recent record - Use
update-database migration-nameto roll back to any migration
File Upload
Define the ViewModel
Use the IFormFile type for the field to be uploaded
public class StudentCreateViewModel
{
public int Id { get; set; }
// 省略无关代码...
[Display(Name = "图片")]
public IFormFile Photo { get; set; }
}
Write the view
Modify the cshtml view file and change the model binding:
@model StudentCreateViewModel
Add a form field for file upload
<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>
To show the file name after a file is selected, you also need to write some JS:
$(document).ready(function () {
$('.custom-file-input').on('change', function () {
var fileName = $(this).val().split('\\').pop();
$(this).next('.custom-file-label').html(fileName);
});
});
Write the controller
Inject HostingEnvironment via the constructor
public StudentController(IStudentRepository studentRepository, HostingEnvironment hostingEnvironment)
{
_studentRepository = studentRepository;
_hostingEnvironment = hostingEnvironment;
}
Logic for handling file upload and saving
[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();
}
Multiple File Upload
Pretty much the same as with a single file
ViewModel
Add a List<IFormFile> typed field
[Display(Name = "图库")]
public List<IFormFile> Gallery { get; set; }
Modify the View
<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 code:
$('#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);
}
});
Modify the Controller Code
// 处理多文件上传
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));
}
}
Error Handling
Add an Error Handling Page
Configure the middleware in Startup.cs:
app.UseStatusCodePagesWithReExecute("/error/{0}");
It's recommended to use UseStatusCodePagesWithReExecute rather than UseStatusCodePagesWithRedirects. The former executes the error jump URL within the pipeline, while the latter redirects to that URL, causing the HTTP error status code to become the 200 status code of the newly executed page.
Then write the error controller:
public class ErrorController : Controller
{
[Route("Error/{statusCode}")]
public IActionResult Index(int statusCode)
{
var statusCodeResult = HttpContext.Features.
Get<IStatusCodeReExecuteFeature>();
var viewModel = new ErrorViewModel
{
Path = statusCodeResult.OriginalPath,
QueryString = statusCodeResult.
OriginalQueryString,
};
switch (statusCode)
{
case 404:
viewModel.Message = "页面未找到";
break;
}
return View("Error", viewModel);
}
}
Oh right, I also defined a ViewModel:
public class ErrorViewModel
{
public int Code { get; set; }
public string Message { get; set; }
public string Path { get; set; }
public string QueryString { get; set; }
}
I won't paste the view code; it just displays these error messages from the ViewModel~
Set Up Global Exception Redirect
Add the middleware
app.UseExceptionHandler("/exception");
Write the handler controller. Here you need to add the AllowAnonymous attribute, allowing users to access this exception page even when not logged in, ensuring the exception page can be displayed no matter what.
[AllowAnonymous]
[Route("exception")]
public IActionResult ExceptionHandler()
{
var exception = HttpContext.Features.
Get<IExceptionHandlerPathFeature>();
var viewModel = new ExceptionViewModel
{
Path = exception.Path,
Message = exception.Error.Message,
StackTrace = exception.Error.StackTrace,
};
return View("Exception", viewModel);
}
Also, the ViewModel is defined as follows:
public class ExceptionViewModel
{
public string Path { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
}
Logging
ASP.NET Core comes with a built-in logging system, which is already registered in the service container by default. You can use it just by injecting it in the controller's constructor, for example:
public class ErrorController : Controller
{
private ILogger<ErrorController> _logger;
public ErrorController(ILogger<ErrorController> logger)
{
this._logger = logger;
}
}
By default, logs are only written to the console or debug output. However, to achieve more functionality, such as writing to a file or pushing to a log server, we need to use a third-party logging component. Here I'm using NLog.
First, you need to install the NLog.Web.AspNetCore package.
Then add the NLog service in Program.cs:
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();
}).UseStartup<Startup>();
For the part about keeping the official default logging providers, you need to look at the ASP.NET Core source code. This article uses version 2.2, which you can view on GitHub at the following address:
https://github.com/dotnet/aspnetcore/blob/v2.2.8/src/DefaultBuilder/src/WebHost.cs
Then, to use NLog, you need to create a configuration file. Create NLog.config in the project root directory:
For documentation on the configuration file, you can refer to: 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>
After that, you can use the logging functionality normally in the program. For example:
var viewModel = new StatusCodeViewModel
{
Code = statusCode,
Path = statusCodeResult.OriginalPath,
QueryString = statusCodeResult.OriginalQueryString,
};
_logger.LogWarning(viewModel.ToString());
You can also configure log levels and namespace filtering in appsettings.json, which has the same effect as configuring them in NLog.conf. For example:
"Logging": {
"LogLevel": {
"Default": "Warning",
"StudyManagement.Controllers.ErrorController":
"Warning"
}
},
Welcome to Connect
Programming Lab focuses on exploring popular new internet technologies and team agile development practices. Reply with linux, flutter, c#, netcore, android, kotlin, java, python, etc. in the backend of the WeChat official account "Programming Lab" to get related technical articles and materials. If you have any questions, you can also leave a message in the official account backend~

- Cnblogs: https://www.cnblogs.com/deali/
- Coding livestream: https://live.bilibili.com/11883038
- Zhihu: https://www.zhihu.com/people/dealiaxy
_EOF_
Asp-Net-Core Study Notes: 2. MVC Views, Models, Persistence, Files, Error Handling, Logging
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
Asp-Net-Core学习笔记:3.使用SignalR实时通信框架开发聊天室
SignalR牛刀小试 在MVP杨老师的博客里看到这么个东西,我还以为是NetCore3才推出的新玩意,原来是已经有很多年的历史了,那看来还是比较成熟的一个技术了。 简介 SignalR是一个.NET Core/.NET Framework的开源实时框架,SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作为底层传输方式。 Signal
2022-02-16 · Asp-Net-Core学习笔记 · 约 1 分钟
Asp-Net-Core学习笔记:身份认证入门
前言 过年前我又来更新了~ 我就说了最近不是在偷懒吧,其实这段时间还是有积累一些东西的,不过还没去整理…… 所以只能发以前没写完的一些笔记出来 就当做是温习一下啦 PS:之前说的红包封面我还没搞,得抓紧时间了 最近在准备搞一个我之前做的开源项目代码合集来做一期分享 两种常见的认证方式 先来看看两种常见的认证方式:基于token的认证和传统的session认证的区别。 session认证 我们知道,
2022-02-16 · Asp-Net-Core学习笔记 · 约 1 分钟
Asp-Net-Core学习笔记:gRPC快速入门
gRPC 使用起来非常的丝滑,目前来说也没遇到什么坑,可以非常平滑地与已有项目集成,如果有类似的场景,强烈推荐尝试一下 gRPC !gRPC 的功能很多,本文仅介绍了最基本的使用,更多的请阅读文档,详细有了本文的基础铺垫,读者再阅读文档深入使用时,会比较轻松上手。
2023-07-11 · Asp-Net-Core学习笔记 · 约 1 分钟
Asp-Net-Core学习笔记:单元测试和集成测试
我在使用 AspNetCore 的这段时间内,看了很多开源项目和博客,发现各种 .Net 体系的新技术很多人都有关注和使用,但却很少有人关注测试。 测试是软件生命周期中的一个非常重要的阶段,对于保证软件的可靠性具有极其重要的意义。在应用程序的开发过程中,为了确保它的功能与预期一致,必须对其进行测试。这样做不仅能够确保功能正确执行,同时能够帮助开发人员尽早地发现并改正系统中所存在的缺陷(Bug),
2023-07-17 · Asp-Net-Core学习笔记 · 约 1 分钟
讨论
共 0 条加载评论中…