从一个手写的.NET Core
项目开始,了解 .Net Core
的基础,再了解一些必须了解的基本理论,如依赖注入
、控制反转
等,这样才能最直接的了解 .Net Core
与原来 .Net Framwork
有什么区别。
因为.Net Core是跨平台的,每一个ASP.NET Core
应用程序,从本质上来说,都是一个独立的控制台应用,并不需要依托IIS来运行,这也是它能实现跨平台的一个基础。
Program.cs
类的作用是创建并运行WebHost实例,调用Startup类,代码如下:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
});
}
CreateHostBuilder(args)
创建一个 IHostBuilder 对象,大概理解就是一个编译器
Startup
类是 ASP.NET Core
应用程序启动默认调用的类,该类是在 Program.cs
中配置:
Startup
默认有两个重要的方法ConfigureServices 和 Configure
ConfigureServices
配置应用所需服务,在该方法中可以注册应用所需要的功能或服务,并通过依赖关系注入 (DI) 或 ApplicationServices 在整个应用中使用服务Configure
配置应用请求处理管道,如MVC,路由等在 Configure
方法配置应用服务之前,由主机调用,对于需要大量设置的功能,IServiceCollection 上有 Add{Service} 扩展方法。 例如,AddDbContext、AddDefaultIdentity、AddEntityFrameworkStores 和 AddRazorPages
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity(
options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores();
services.AddRazorPages();
}
//单例注入,创建的对象在所有的地方所有的请求会话创建的都是相同
services.AddSingleton();
//瞬时注入,每次都创建一个新的对象
services.AddTransient();
//作用域注入,创建的对象在同一个请求会话时是相同的
services.AddScoped();
Configure
方法用于指定应用响应 HTTP 请求的方式。 可通过将中间件组件添加到 IApplicationBuilder 实例来配置请求管道。 Configure 方法可使用 IApplicationBuilder,但未在服务容器中注册。 托管创建 IApplicationBuilder 并将其直接传递到 Configure。
app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain;charset=utf-8;"; //解决中文乱码
await context.Response.WriteAsync("Use1之前");
await next(); //调用下一个中间件
await context.Response.WriteAsync("Use1之后");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Use2之前");
await next(); //调用下一个中间件
await context.Response.WriteAsync("Use2之后");
});
app.Run(async (context) =>
{
//获取当前进程名
await context.Response.WriteAsync( System.Diagnostics.Process.GetCurrentProcess().ProcessName);
});
请求过来后,以链式的方式执行: Use1之前 --> next --> Use2之前 --> next --> Run --> Use2之后 --> Use1之后
中间件 | 描述 | 使用 | 顺序 |
---|---|---|---|
身份验证 | 提供身份验证支持 | app.UseAuthentication() | 在需要 HttpContext.User 之前。 OAuth 回叫的终端。 |
授权 | 提供身份验证支持 | app.UseAuthorization() | 紧接在身份验证中间件之后。 |
略 | 略 | 略 | 略 |
应用为不同的环境(例如,StartupDevelopment)单独定义 Startup 类时,相应的 Startup 类会在运行时被选中。 优先考虑名称后缀与当前环境相匹配的类。
参考代码:
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
_env = env;
}
public IConfiguration Configuration { get; }
private readonly IWebHostEnvironment _env;
public void ConfigureServices(IServiceCollection services)
{
if (_env.IsDevelopment())
{
Console.WriteLine(_env.EnvironmentName);
}
else if (_env.IsStaging())
{
Console.WriteLine(_env.EnvironmentName);
}
else
{
Console.WriteLine("Not dev or staging");
}
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app)
{
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
实际开发中,我们通过appsettings.{Environment}.json文件,如appsetting.json,appsetting.Devlopment.json等不同名称文件来配置不同环境配置,
ASP.NET Core
支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术。
依赖项是指另一个对象所依赖的对象。
新建接口,实现WriteMessage方法
public interface IMyDependency
{
void WriteMessage(string message);
}
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
在Startup类中ConfigureServices方法注入依赖
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped();
services.AddRazorPages();
}
当实际需要调用WriteMessage方法执行时,通过构造函数,注入IMyDependency接口
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
通过使用 DI 模式,表示控制器:
当下次需要修改WriteMessage方法时
public class MyDependency2 : IMyDependency
{
private readonly ILogger _logger;
public MyDependency2(ILogger logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
修改Startup类中ConfigureServices方法注入
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped();
services.AddRazorPages();
}
这样就不需要修改业务代码
应用程序中的依赖关系方向应该是抽象的方向,而不是实现详细信息的方向。 大部分应用程序都是这样编写的:编译时依赖关系顺着运行时执行的方向流动,从而生成一个直接依赖项关系图。 也就是说,如果类 A 调用类 B 的方法,类 B 调用 C 类的方法,则在编译时,类 A 将取决于类 B,而 B 类又取决于类 C,如图 4-1 所示。
应用依赖关系反转原则后,A 可以调用 B 实现的抽象上的方法,让 A 可以在运行时调用 B,而 B 又在编译时依赖于 A 控制的接口(因此,典型的编译时依赖项发生反转)。 运行时,程序执行的流程保持不变,但接口引入意味着可以轻松插入这些接口的不同实现。
依赖项反转是生成松散耦合应用程序的关键一环,因为可以将实现详细信息编写为依赖并实现更高级别的抽象,而不是相反。 因此,生成的应用程序的可测试性、模块化程度以及可维护性更高。 遵循依赖关系反转原则可实现依赖关系注入。
简单理解,就是我们通过调用接口,然后通过反射,找到实现了接口的方法,执行。
EF Core
是一个 .NET Standard 2.0
库。 因此,EF Core
需要支持运行 .NET Standard 2.0
的实现。 其他 .NET Standard 2.0
库也可引用 EF Core
。
要将 EF Core 添加到应用程序,请安装适用于要使用的数据库提供程序的 NuGet 包。
数据库系统 | 包 |
---|---|
SQL Server 和 SQL Azure | Microsoft.EntityFrameworkCore.SqlServer |
SQLite | Microsoft.EntityFrameworkCore.Sqlite |
PostgreSQL | Npgsql.EntityFrameworkCore.PostgreSQL* |
MySQL | Pomelo.EntityFrameworkCore.MySql* |
EF Core 内存中数据库** | Microsoft.EntityFrameworkCore.InMemory |
注入依赖
public void ConfigureServices(IServiceCollection services)
{
// 配置数据库上下文,支持N个数据库
// services.AddDatabaseAccessor(options =>
// {
// 配置默认数据库
// options.AddDbPool();
// });
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}
新建继承数据库上下文类
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace EFGetStarted
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions options)
: base(options)
{
}
public DbSet Students { get; set; }
public DbSet Enrollments { get; set; }
public DbSet Courses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity().ToTable("Course");
modelBuilder.Entity().ToTable("Enrollment");
modelBuilder.Entity().ToTable("Student");
}
}
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 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; }
}
}
实际代码CURD
public class StudentModel : PageModel
{
private readonly SchoolContext _context;
private readonly ILogger _logger;
public StudentModel(ContosoUniversity.Data.SchoolContext context,
ILogger logger) //依赖注入
{
_context = context;
_logger = logger;
}
/// 查询
public async Task OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
// Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);
Student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
}
/// 更新
public async Task OnPostAsync(int id)
{
var studentToUpdate = await _context.Students.FindAsync(id);
if (studentToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
[Required] //验证必填
[Column("FirstName")] //对应列名
[Display(Name = "First Name")] //显示名称
[StringLength(50)] //最大长度50
public string FirstMidName { get; set; }
[DataType(DataType.Date)] //数据格式为时间
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] //格式化时间格式
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
EF Core
在查询数据时,
EF Core migrations add
命令用于创建数据库的代码,迁移代码位于 MigrationsInitialCreate
类的 Up 方法创建与数据模型实体集对应的数据库表。 Down 方法删除这些表,如下例所示:
每次执行命令都会生成一个新的类名,类名前缀为时间戳,迁移会在 Migrations/SchoolContextModelSnapshot.cs 中创建当前数据模型的快照 。 添加迁移时,EF 会通过将当前数据模型与快照文件进行对比来确定已更改的内容。所以不能直接删除_.cs 文件来删除迁移,需要通过remove
命令来,重置快照,重新生成迁移脚本。
执行迁移命令后,会把对应的类型插入到数据库的__EFMigrationsHistory
表,代表已经执行,如果回滚,它会删除掉对应行记录