@[TOC](JXMaker .NET CORE3.1系列教程(一)——开发环境与创建Web项目)
JXMaker .NET CORE3.1系列教程(一)——开发环境搭建与创建的一个Web项目
开发环境
教程中使用的开发环境为Win2010 +VS 2019
安装VS2019
- 下载VS2019
打开VS2019官方网站
https://visualstudio.microsoft.com/zh-hans/vs/
下载在线安装包(安装过程需要联网,必须保证网络质量),我下载的是企业版(Enterprise )
- 安装
双击打开安装程序,等待下载完成后弹出如下窗口。如果有条件可以全部安装,否则可以按需勾选,本教程只需勾选左上角第一个“asp.net and web development”即可,然后点击安装(install)继续安装直到完成即可
- 打开并激活
从开始菜单打开VS2019(如下图),
选择“继续但无需代码”
点击帮助——注册产品
输入注册码即可注册
分享一个激活码:
Visual Studio 2019企业版 Enterprise 激活码:BF8Y8-GN2QH-T84XB-QVY3B-RC4DF
创建Web项目
打开VS2019,在弹出窗口点击“创建新项目”
在弹出窗口选择语言为C#
C#和.NET CORE的关系,可以查看这篇文章:https://www.cnblogs.com/sumuncle/p/9262764.html
再选择“ASP.NET CORE Web应用程序”,点击“下一步”按钮
完善相应的项目信息点击“创建”即可进入下一步
选择.netcore版本为 ASP.NET Core 3.1 ,取消勾选“https支持”,选择空模板,点击“创建”按钮
了解项目属性
-
查看项目属性
在项目上点击右键——“属性”即可查看相应项目属性,可以查看属性
-
修改目标框架
如果刚才上一步选择错误可以在如下图所示位置修改框架(或者需要引用别人的项目但双方框架版本不一致可以在此升级/降级)
-
设置默认图标、清单
在这里可以设置程序的默认图标
当然,也可以选择“编辑项目文件”,在里面直接添加节点进行配置。
项目的基本结构
一个空Web项目包含了
- Program.cs
Program.cs控制程序的启动,该类中的Main()方法是程序的入口 - Startup.cs
Startup.cs类用于设置程序的初始配置 - appsettings.json
该Json是程序的配置文件,允许用户动态修改的配置可以写在该文件中
Program.cs解析
- Main方法是程序的入口
程序运行时首先执行的是Program类中的Main方法
我们来看Main方法的代码
public static void Main(string[] args) //该方法是一个静态方法,参数args可以在通过控制台启动它时传入
//常见的args参数使用方法:如程序在开发环境和生产环境(实际使用的环境)需要读取不同的配置文件,我们就可以
//规定args的第一个参数如果为"dev"则读取开发环境使用的配置appsettings.dev.json 如果为"prod" 则使用正式的
//appsettings.json配置文件。
{
CreateHostBuilder(args).Build().Run();//创建一个服务器(.net core 内置一个服务器Kestrel)并运行,
//CreateHostBuilder方法用于创建该服务器(底下定义的,放回类型为IHostBuilder),Build()方法则是创建(获得
//实例),Run()方法为启动这个服务器,开始监听。
}
- Kestrel服务器的配置与启动
前文我们提到了.net core中内置了一个Kestrel服务器,该服务器是一个基于libuv的跨平台ASP.NET Core web服务器,libuv是一个跨平台的异步I/O库。ASP.NET Core模板项目使用Kestrel作为默认的web服务器。Main方法所执行的代码就是创建并启动该服务器。它调用了一个CreateHostBuilder方法,该方法需要返回一个IHostBuilder类型的对象(IHostBuilder是一个接口,不是实际的类型,为了方便理解我们就暂且这么说),因此系统帮我们在Program类中又生成了一个默认的方法,代码如下:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
这种写法(箭头函数)等价于:
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)//创建一个服务器Builder对象
.ConfigureWebHostDefaults(webBuilder =>//ConfigureWebHostDefault方法对Web服务器进行默认设置
//该函数的参数是一个委托(Action<T>是一个可以传入参数,没有返回值的委托),函数可以接收一个
//IWebHostBuilder类型的参数webBuilder,通过调用该对象的相关方法可以实现对配置的修改
{
webBuilder.UseStartup<Startup>();//使用名为"Startup"的类作为Startup(配置),意味着我们可以将更多的
//配置写在Startup.cs文件中
});
}
Startup.cs解析
- Startup只是一个单纯的类,并没有继承其他类或实现其他接口,通过约定(我感觉是通过反射调用这个类),该类具有ConfigureServices方法和Configure方法。
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
}
-
IOC 与 ConfigureServices方法
该方法用于将服务注入到容器中(依赖注入(DI)和反转控制(IOC)),依赖注入按我个人理解就是一个大的工厂模式,在web服务器启动之前将后面需要一直调用的服务(类的实例、配置值对象)加载到程序的底层,一旦需要使用哪个服务直接找底层获取,由底层根据需求创建这个服务的实例并注入给顶层,不用再自己写一遍获取、实例化的函数。
这样做的好处比如我们系统现有设计使用SQLServer,客户需要临时切换到mongodb,此时我们无需修改代码,只需要另外写一套mongodb 只要进行一次配置,顶层下次需要时就会得到mongodb 的操作类(服务),而所有数据库类都是使用相同的接口,因此传入的参数、返回的参数均一致,业务层编写时就不用具体去关注使用哪个数据库,只要调用通过注入得到的服务中的相关方法即可。- IoC容器及其操作
ConfigureServices函数的参数 IServiceCollection services对象就是对IoC容器进行操作(服务注册)的对象,可以通过它在容器中对服务进行注册。 - 注入服务的生命周期
.netcore中,注入服务的生命周期分为三种 Transient、Scoped、Singleton
Transient(瞬时的)
每次请求时都会创建的瞬时生命周期服务。这个生命周期最适合轻量级,无状态的服务。
(每次都进行实例化)
Scoped(作用域的)
在同作用域,服务每个请求只创建一次。
(从请求开始就创建,请求结束就销毁)
Singleton(唯一的)
全局只创建一次,第一次被请求的时候被创建,然后就一直使用这一个.
(相当于单例) - IoC容器及其操作
使用语法如下(在ConfigureServices中使用):
//ITestService为接口 TestService为实现这个接口的类(下同)
services.AddTransient<ITestService, TestService>();
services.AddScoped<ITestService2, TestService2>();
services.AddSingleton<ITestService3, TestService3>();
例1:
比如我们现在需要使用mvc模式,我们可以在ConfigureServices中添加如下代码:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();//需要使用controllers和views(不含RazorPages)
//services.AddRazorPages();//要使用RazorPages需要再添加这个
//services.AddControllers();//只需要控制器,不需要视图使用这个(只写api使用)
//services.AddMvc();//MVC全部功能,比较大,3.1版本如果没用到其他功能建议不要用
}
例2:
系统中需要一个时钟处理的操作类Clock,里面包含一个输出当前时区的方法GetTimeZone(),该方法返回值为string,输出当前所使用的时间模式/时区(如“China(+8)”或"UTC")。当系统在中国运行时使用中国时区的模式,当系统在国外运行时可以选择使用UTC时间(在Startup中修改一下配置即可)。
我们先在项目中创建两个目录IServices和Services,Services目录主要时存放服务的类,IServices用来存放服务的接口。
我们在Iservices创建一个IClock接口,代码如下:
public interface IClock
{
/// <summary>
/// 获取时区
/// </summary>
/// <returns>返回时区说明字符串,如中国为“China(+8)”,UTC为“UTC”</returns>
public string GetTimeZone();
}
之后在Services目录创建两个类,分别是ChinaClock和UTCClock,他们的代码分别如下:
首先要添加引用(两个类都要):
using WebApplication1.IServices;
public class ChinaClock : IClock
{
public string GetTimeZone()
{
return "China(+8)";
}
}
public class UTCClock:IClock
{
public string GetTimeZone()
{
return "UTC";
}
}
接下来我们添加依赖注入,在Startup.cs的ConfigureServices方法中,加入以下代码:
services.AddSingleton<IClock,ChinaClock>();//在中国使用则使用此代码
//services.AddSingleton<IClock, UTCClock>();//在非中国地区使用改为此代码
程序运行时需要调用时钟操作类则底层会根据情况返回不同的时钟类的实例供顶层使用,顶层就不用再去区分相应时区。具体如何调用我们在后面创建控制器部分再讲。
- Configuere方法
Configuere对Asp.Net Code中HTTP请求的管道(Pipeline)进行配置(响应HTTP/HTTPS请求的整个过程)
在HTTP请求管道中对http请求进行处理的服务就称为中间件,如MVC就是一个中间件。在进入MVC之前可能需要进行身份认证(Auth),因此这也是一个中间件,Auth中间件一般放在MVC之前。又比如对静态文件请求进行响应的具体过程,称为Static Files中间件。
借用一张图:
- 代码解析
重要的中间件
错误页面(DeveloperExceptionPage)中间件
DeveloperExceptionPage中间件可以将未被处理的异常在页面中展示,如需使用,则在Startup.cs文件Configure方法中添加如下代码:
app.UseDeveloperExceptionPage();
一般地,这种操作存在安全漏洞,容易被别人根据错误堆栈猜测出工程中使用的技术、数据库等信息,因此我们需要定义他只在开发环境中使用,到生产环境中则不使用此中间件。
Configure函数有一个IWebHostEnvironment类型的参数 env,该参数在运行时会被注入一个IWebHostEnvironment实例,通过该实例的IsDevelopment()方法可以用于判断当前开发过程中的环境变量“ASPNETCORE_ENVIRONMENT”的值,当该值为“Development”则为开发环境,为“Production”则是在生产环境中。
此时,Configure方法中代码如下:
if (env.IsDevelopment())//当前是否是开发模式
{
app.UseDeveloperExceptionPage();//该中间件实现如果异常未被处理则使用一个页面展示异常信息
}
该环境变量可以在项目属性中配置,配置修改方法如下:
静态文件中间件
在Startup.cs文件Configure方法中添加如下代码
app.UseStaticFiles();
即可引入静态文件中间件,该中间件默认对项目目录下"wwwroot"目录里的文件/文件夹提供服务,比如wwwroot目录下有index.html文件,则在浏览器输入http://项目运行地址/index.html 就能访问。
如果需要修改这些默认信息,可以按如下方式:
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider("d:/www/"),//物理路径地址(绝对路径),注意斜杠方向
RequestPath="/static"//可以指定子目录 如"static"则需要输入http://项目运行地址/static/index.html 才能访问到d:/www/index.html
});
端点中间件、路由中间件
- 端点
端点就是请求的URL结尾部分(从Path部分开始),这部分会被中间件进行处理,如下图:
- 端点中间件
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context => //对端点为“/”的请求进行响应
{
await context.Response.WriteAsync("Hello World!");//输出helloworld
});
});
- 路由+端点实现路由功能
我们对端点中间件的配置进行修改,强调:必须先上路由中间件再上端点中间件(先后顺序),因为路由中间件可以获取端点,再将端点传送给端点中间件进行处理。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())//当前是否是开发模式
{
app.UseDeveloperExceptionPage();//该中间件实现如果异常未被处理则使用一个页面展示异常信息
}
app.UseRouting();//路由中间件,低版本该中间件集成在MVC中间件中。3.0以后独立出来
//必须先引入路由中间件,端点中间件才能对路由中间件处理后得到的端点进行配置,否则会报错
app.UseEndpoints(endpoints => //端点中间件 对端点进行配置处理
{
endpoints.MapControllerRoute(name:"default",pattern: "{Controller=Home}/{Action=Index}/{id?}");//通过端点中间件实现路由配置 如端点与规则(pattern)相匹配,则推送给相应控制器的对应action
});
}
Configure整体代码:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())//当前是否是开发模式
{
app.UseDeveloperExceptionPage();//该中间件实现如果异常未被处理则使用一个页面展示异常信息
}
app.UseStaticFiles();//他不需要知道路由信息,所以可以放在路由前面
app.UseRouting();//路由中间件,低版本该中间件集成在MVC中间件中。3.0以后独立出来
//必须先引入路由中间件,端点中间件才能对路由中间件处理后得到的端点进行配置,否则会报错
app.UseEndpoints(endpoints => //端点中间件 对端点进行配置处理
{
endpoints.MapControllerRoute(name:"default",pattern: "{Controller=Home}/{Action=Index}/{id?}");//通过端点中间件实现路由配置 如端点与规则(pattern)相匹配,则推送给相应控制器的对应action
});
}
创建控制器(Controller)
Controller(控制器),是MVC模式中是用来处理传入浏览器的请求,从Model里面取数据,然后指定返回浏览器响应的视图模板的Class类。
控制器类一般放在项目的Controllers目录中,按照约定,控制器类都以Controller结尾。
接下来我们来创建一个名为Home的控制器。先在项目里创建一个Controllers目录,并在里面创建一个类 名为HomeController的类(HomeController.cs文件),如下:
HomeController类中首先要添加 MVC的引用
using Microsoft.AspNetCore.Mvc;
控制器类继承Microsoft.AspNetCore.Mvc下的Controller类
public class HomeController:Controller
{
}
假如我们在该控制器中需要调用我们前面写的时钟处理帮助类实例中的相关方法,就需要使用依赖注入,.net core默认带有一个轻量级依赖注入框架,我们只需要为该类写一个构造函数,构造函数中的参数会被自动注入
public class HomeController:Controller
{
private readonly IClock _clockServices;
public HomeController(IClock clock)//构造函数,运行时会自动注入IClock的实例
{
_clockServices = clock;//将注入的实例的引用(内存地址)赋值给_clockServices变量,action中需要引用该实例的直接使用_clockServices变量即可。
}
}
创建Action
Action是控制器(Controller)类中提供的方法,在.netcore中该方法返回类型统一为IActionResult(也可以沿用.net framework mvc4的ContentResult、JsonResult等,在core中他们都实现IActionResult接口,使用该接口即可),参数会根据用户请求自动注入。
比如我们在Home控制器中创建一个名为Index的action,我们只需要在HomeController类中添加一个返回类型为IActionResult的函数,函数名为Index,代码如下:
public ContentResult Index()
{
return Content(_clockServices.GetTimeZone(),"text/html",Encoding.UTF8);
}
点击运行,即可查看到如下效果:
修改Startup.cs内容,将IClock类型的服务注入改为 UTCClock,则浏览器将会输出"UTC"。