IApplicationBuilder可以通过委托方式注册中间件,委托的入参也是委托,这就可以将这些委托注册成一个链,如上图所示;最终会调用Builder方法返回一个委托,这个委托就是把所有的中间件串起来后合并成的一个委托方法,Builder的委托入参是HttpContext(实际上所有的委托都是对HttpContext进行处理);
RequestDelegate是处理整个请求的委托。
中间件的执行顺序和注册顺序是相关的
//注册委托方式,注册自己逻辑
// 对所有请求路径
app.Use(async (context, next) =>
{
await next();
await context.Response.WriteAsync("Hello2");
});
// 对特定路径指定中间件,对/abc路径进行中间件注册处理
app.Map("/abc", abcBuilder =>
{
// Use表示注册一个完整的中间件,将next也注册进去
abcBuilder.Use(async (context, next) =>
{
await next();
await context.Response.WriteAsync("abcHello");
});
});
// Map复杂判断,判断当前请求是否符合某种条件
app.MapWhen(context =>
{
return context.Request.Query.Keys.Contains("abc");
}
, builder =>
{
// 使用Run表示这里就是中间件的执行末端,不再执行后续中间件
builder.Run(async context =>
{
await context.Response.WriteAsync("new abc");
});
});
应用程序一旦开始向Response进行write时,后续的中间件就不能再操作Head,否则会报错
可以通过context.Resopnse.HasStarted方法判断head是否已经被操作
public class MyMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger logger;
public MyMiddleware(RequestDelegate next,ILogger logger)
{
this.next = next;
this.logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
using (logger.BeginScope("TraceIndentifier:{TraceIdentifier}",context.TraceIdentifier))
{
logger.LogDebug("Start");
await next(context);
logger.LogDebug("End");
}
}
}
public static class MyBuilderExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware();
}
}
// 中间件使用
app.UseMyMiddleware();
// startup中的Configure
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();// 开发环境下的异常页面,生产环境下是需要被关闭,页面如下图所示
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoggingSerilog v1"));
}
// startup中的Configure
app.UseExceptionHandler("/error");
// 控制器
public class ErrprController : Controller
{
[Route("/error")]
public IActionResult Index()
{
// 获取上下文中的异常
var exceptionHandlerPathFeature = HttpContext.Features.Get();
var ex = exceptionHandlerPathFeature?.Error;
var knowException = ex as IKnowException;
if(knowException == null)
{
var logger = HttpContext.RequestServices.GetService>();
logger.LogError(ex,ex.Message);
knowException = KnowException.Unknow;
}
else
{
knowException = KnowException.FromKnowException(knowException);
}
return View(knowException);
}
}
// 定义接口
public interface IKnowException
{
public string Message {get;}
public int ErrorCode {get;}
public object[] ErrorData {get;}
}
// 定义实现
public class KnowException : IKnowException
{
public string Message {get;private set;}
public int ErrorCode {get;private set;}
public object[] ErrorData {get;private set;}
public readonly static IKnowException Uknow = new KnowException{Message = "未知错误",ErrorCode = 9999};
public static IKnowException FromKnowException(IKnowException exception)
{
return new KnowException{Message = exception.Message,ErrorCode = exception.ErrorCode,ErrorData = exception.ErrorData};
}
}
// 需要定义一个错误页面 index.html,输出错误Message和ErrorCode
// startup中的Configure
app.UseExceptionHandler(errApp =>
{
errApp.Run(async context =>
{
var exceptionHandlerPathFeature = HttpContext.Features.Get();
var ex = exceptionHandlerPathFeature?.Error;
var knowException = ex as IKnowException;
if(knowException == null)
{
var logger = HttpContext.RequestServices.GetService>();
logger.LogError(exceptionHandlerPathFeature.Error,exceptionHandlerPathFeature.Error.Message);
knowException = KnowException.Unknow;
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
else
{
knowException = KnowException.FromKnowException(knowException);
context.Response.StatusCode = StatusCodes.Status200OK;
}
var jsonOptions = context.RequestServices.GetService>();
context.Response.ContextType = "application/json";charset=utf-8";
await context.Response.WriteAsync(System.Text.Json.JsonSerializer(knowException,jsonOptions.Value));
});
});
异常过滤器是作用在整个MVC框架体系之下,在MVC整个声明周期中发生作用,也就是说它只能工作早MVC Web Api的请求周期里面
// 自定义异常过滤器
public class MyException : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
IKnowException knowException = context.Exception as IKnowException;
if(knowException == null)
{
var loger = context.HttpContext.RequestServices.GetService>();
logger.LogError(context.Exception,context.Exception.Message);
knowException = KnowException.UnKnow;
context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
else
{
knowException = KnowException.FromKnowException(knowException);
context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
}
context.Result = new JsonResult(knowException)
{
ContextType = "application/json:charset=utf-8"
}
}
}
// startup注册
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(mvcoption =>
{
mvcOptions.Filters.Add();
}).AddJsonOptions(jsonOptions => {
jsonOptions.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscapt
});
}
public class MyExceptionFilterAttriburte : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
IKnowException knowException = context.Exception as IKnowException;
if(knowException == null)
{
var logger = context.HttpContext.RequestServices.GetServices>();
logger.LogError(context.Exception,context.Exception.Message);
knowException = KnowException.UnKnow;
context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
else
{
knowException = KnowException.FromKnowException(knowException);
context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
}
context.Result = new JsonResult(knowException)
{
ContextType = "application/json:charset=utf-8"
}
}
}
// 使用方式
在Controller控制器上方标注[MyExceptionFilter]
或者在 startup中ConfigureServices注册
services.AddMvc(mvcoption =>
{
mvcOptions.Filters.Add();
});
// startup的Configure方法中
app.UseDefaultFiles();// 设置默认访问根目录文件index.html
app.UseStaticFiles();// 将www.root目录映射出去
如果需要浏览文件目录,需要如下配置
// startup中的ConfigureServices中配置
services.AddDirectoryBrowser();
// startup的Configure方法中
app.UseDirectoryBrowser();
app.UseStaticFiles();
// startup的Configure方法中
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
// 将程序中名为file文件目录注入
RequestPath = "/files",// 设置文件指定访问路径,将文件目录映射为指定的Url地址
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"file"))
});
实际生产中会遇到将非api请求重定向到指定目录,采用如下配置
// startup的Configure方法中
app.MapWhen(context =>
{
return !context.Request.Path.Value.StartWith("/api");
},appBuilder =>
{
var option = new RewriteOptions();
option.AddRewrite(".*","/index.html",true);
appBuilder.UseRewriter(option);
appBuilder.UseStaticFiles();
});
// 映射指定目录文件- 物理文件
IFileProvider provider1 = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory);
// 获取文件目录下内容
var contents = provider1.GetDirectoryContents("/");
// 输出文件信息
foreach (var item in contents)
{
var stream = item.CreateReadStream();// 获取文件流
Console.WriteLine(item.Name);
}
// 嵌入式文件
IFileProvider fileProvider2 = new EmbeddedFileProvider(typeof(Program).Assembly);
var html = fileProvider2.GetFileInfo("file.html");// file.html文件属性设置为嵌入的资源
// 组合文件提供程序
IFileProvider fileProvider3 = new CompositeFileProvider(provider1,fileProvider2);
var contexts3 = fileProvider3.GetDirectoryContents("/");