StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的请求处理与响应的任务。它们之间的差异在于对“错误”的界定上,对于ExceptionHandlerMiddleware中间件来说,它所谓的错误就是抛出异常,但是对于StatusCodePagesMiddleware中间件来说,则将介于400~599之间的响应状态码视为错误。如下面的代码片段所示,StatusCodePagesMiddleware中间件也采用“标准”的定义方式,针对它的配置选项通过一个对应的对象以Options模式的形式提供给它。 [本文已经同步到《ASP.NET Core框架揭秘》之中]
1: public class StatusCodePagesMiddleware
2: {
3: public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options);
4: public Task Invoke(HttpContext context);
5: }
除了针对错误的界定,StatusCodePagesMiddleware和ExceptionHandlerMiddleware这两个中间件对于错误处理器的表达也不相同。我们知道ExceptionHandlerMiddleware中间件使用的错误处理器实际上就是一个类型为RequestDelegate的委托对象,但是错误处理器之于StatusCodePagesMiddleware中间件来说则是一个类型为Func<StatusCodeContext, Task>的委托对象。如下面的代码片段所示,为StatusCodePagesMiddleware中间件提供配置选项的StatusCodePagesOptions对象的唯一目的就是提供这个作为错误处理器的委托对象。
1: public class StatusCodePagesOptions
2: {
3: public Func<StatusCodeContext, Task> HandleAsync { get; set; }
4: }
我们知道一个RequestDelegate对象相当于一个类型为Func<HttpContext, Task>类型的委托对象,而一个StatusCodeContext对象实际上也是对一个HttpContext对象的封装,所以StatusCodePagesMiddleware中间件和ExceptionHandlerMiddleware中间件所使采用的错误处理器并没有本质上的不同。如下面的代码片段所示,除了从StatusCodeContext对象中获取代表当前请求上下文的HttpContext对象之外,我们还可以通过其Next属性得到一个RequestDelegate对象,它代表由后续中间件组成的请求处理管道。至于另一个属性Options,很明显它返回我们在创建StatusCodePagesMiddleware中间件所指定的StatusCodePagesOptions对象。
1: public class StatusCodeContext
2: {
3: public HttpContext HttpContext { get; }
4: public RequestDelegate Next { get; }
5: public StatusCodePagesOptions Options { get; }
6:
7: public StatusCodeContext(HttpContext context, StatusCodePagesOptions options, RequestDelegate next);
8: }
一、针对响应状态码的错误处理
由于采用了针对响应状态码的错误处理策略,所以实现在StatusCodePagesMiddleware中间件中的所有错误处理操作只会发生在当前响应状态码在400~599之间的情况,如下所示的代码片段体现了这一点。从下面给出的代码片段可以看出,StatusCodePagesMiddleware中间件在决定是否执行错误处理操作时除了会查看当前响应状态码之外,还会查看响应内容以及媒体类型,如果已经包含了响应内容或者设置了媒体类型,该中间件将不会执行任何操作。
1: public class StatusCodePagesMiddleware
2: {
3: private RequestDelegate _next;
4: private StatusCodePagesOptions _options;
5:
6: public StatusCodePagesMiddleware(RequestDelegate next,
7: IOptions<StatusCodePagesOptions> options)
8: {
9: _next = next;
10: _options = options.Value;
11: }
12:
13: public async Task Invoke(HttpContext context)
14: {
15: await _next(context);
16: var response = context.Response;
17: if ((response.StatusCode >= 400 && response.StatusCode <= 599) &&!response.ContentLength.HasValue && string.IsNullOrEmpty(response.ContentType))
18: {
19: await _options.HandleAsync(new StatusCodeContext(context, _options, _next));
20: }
21: }
22: }
StatusCodePagesMiddleware中间件针对错误的处理非常简单,它只需要从StatusCodePagesOptions对象中提取出作为错误处理器的这个Func<StatusCodeContext, Task>对象,然后创建一个StatusCodeContext对象作为输入参数调用这个委托对象即可。
二、阻止异常处理
如果当前响应已经被写入了内容,或者响应的媒体类型已经被预先设置,那么StatusCodePagesMiddleware中间件将不会再执行任何的错误处理操作。这种情况实际上代表由后续中间件构成的管道可能需要自行控制当前的响应,所以StatusCodePagesMiddleware中间件不应该再做任何的干预。从这个意义上来讲,StatusCodePagesMiddleware中间件仅仅是作为一种后备的错误处理机制而已。
更进一步来将,如果后续的某个中间件返回了一个状态码在400~599之间的响应,并且这个响应只有报头集合没有主体(媒体类型自然也不会设置),那么按照我们在上面给出的错误处理逻辑,StatusCodePagesMiddleware中间件还是会按照自己的策略来处理并响应请求。为了解决这种情况下,我们必须赋予后续中间件一个能够阻止StatusCodePagesMiddleware中间件进行错误处理的能力。
阻止StatusCodePagesMiddleware中间件进行错误处理的机制是借助于一个名为StatusCodePagesFeature的特性来实现的。StatusCodePagesFeature对应如下这个IStatusCodePagesFeature接口,它具有唯一的布尔类型的属性成员Enabled。默认使用的StatusCodePagesFeature类型实现了这个接口,默认情况下这个开关是开启的。
1: public interface IStatusCodePagesFeature
2: {
3: bool Enabled { get; set; }
4: }
5:
6: public class StatusCodePagesFeature : IStatusCodePagesFeature
7: {
8: public bool Enabled { get; set; } = true ;
9: }
StatusCodePagesMiddleware中间件在将请求交付给后续管道之前,它会创建一个StatusCodePagesFeature特性对象并将其添加到当前HttpContext之中。当最终决定是否执行错误处理操作的时候,它还会通过这个特性检验是否某个后续的中间件不希望自己“画蛇添足”地进行不必要的错误处理,如下的代码片段很好的体现了这一点。
1: public class StatusCodePagesMiddleware
2: {
3: …
4: public async Task Invoke(HttpContext context)
5: {
6: StatusCodePagesFeature feature = new StatusCodePagesFeature();
7: context.Features.Set<IStatusCodePagesFeature>(feature);
8:
9: await _next(context);
10: var response = context.Response;
11: if ((response.StatusCode >= 400 && response.StatusCode <= 599) && !response.ContentLength.HasValue &&string.IsNullOrEmpty(response.ContentType) &&
12: feature.Enabled)
13: {
14: await _options.HandleAsync(new StatusCodeContext(context, _options, _next));
15: }
16: }
17: }
我们通过一个简单的实例来演示如果利用这个StatusCodePagesFeature特性来屏蔽StatusCodePagesMiddleware中间件。在下面这个应用中,我们将针对请求的处理定义在Invoke方法中,该方法会返回一个状态码为“401 Unauthorized”的响应。我们通过随机数让这个方法会在50%的情况下利用StatusCodePagesFeature特性来阻止StatusCodePagesMiddleware中间件自身对错误的处理。我们通过调用扩展方法UseStatusCodePages注册的StatusCodePagesMiddleware中间件会直接响应一个内容为“Error occurred!”的字符串。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .Configure(app => app
8: .UseStatusCodePages(async context => await context.HttpContext.Response.WriteAsync("Error occurred!"))
9: .Run(Invoke))
10: .Build()
11: .Run();
12: }
13:
14: private static Random _random = new Random();
15: private static Task Invoke(HttpContext context)
16: {
17: context.Response.StatusCode = 401;
18:
19: if (_random.Next() % 2 == 0)
20: {
21: context.Features.Get<IStatusCodePagesFeature>().Enabled = false;
22: }
23: return Task.CompletedTask;
24: }
25: }
对于针对该应用的请求来说,我们会得到如下两种不同的响应。没有主体内容响应是通过Invoke方法产生的,这种情况下发生在StatusCodePagesMiddleware中间件通过StatusCodePagesFeature特性被屏蔽的时候。具有主体内容的响应则来源于StatusCodePagesMiddleware中间件。
1: HTTP/1.1 401 Unauthorized
2: Date: Sun, 18 Dec 2016 01:59:37 GMT
3: Server: Kestrel
4: Content-Length: 15
5:
6: Error occurred!
7:
8:
9: HTTP/1.1 401 Unauthorized
10: Date: Sun, 18 Dec 2016 01:59:38 GMT
11: Content-Length: 0
12: Server: Kestrel
三、注册StatusCodePagesMiddleware中间件
我们在大部分情况下都会调用ApplicationBuilder相应的扩展方法来注册StatusCodePagesMiddleware中间件。对于StatusCodePagesMiddleware中间件的注册来说,除了我们已经很熟悉的UseStatusCodePages方之外,还具有额外一些扩展方法供我们选择。
UseStatusCodePages
我们可以调用如下三个UseStatusCodePages方法重载来注册StatusCodePagesMiddleware中间件。不论我们调用那个重载,系统最终都会根据提供的StatusCodePagesOptions对象调用构造函数来创建这个中间件对象,而且这个StatusCodePagesOptions必须具有一个作为错误处理器的Func<StatusCodeContext, Task>对象。如果没有指定任何参数,StatusCodePagesOptions对象需要以Options模式的形式注册为服务。
1: public static class StatusCodePagesExtensions
2: {
3: public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app)
4: {
5: return app.UseMiddleware<StatusCodePagesMiddleware>();
6: }
7:
8: public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, StatusCodePagesOptions options)
9: {
10: return app.UseMiddleware<StatusCodePagesMiddleware>(Options.Create(options));
11: }
12:
13: public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, Func<StatusCodeContext, Task> handler)
14: {
15: return app.UseStatusCodePages(new StatusCodePagesOptions
16: {
17: HandleAsync = handler
18: });
19: }
20: }
由于StatusCodePagesMiddleware中间件最终的目的还是将定制的错误信息响应给客户端,所以我们可以在注册该中间件的时候直接指定响应的内容和媒体类型,这样的注册方式可以通过调用如下这个UseStatusCodePages方法来完成。从如下所示的代码片段我们不难看出,我们通过bodyFormat方法指定的实际上是一个模板,它可以包含一个表示响应状态的占位符(“{0}”)。
1: public static class StatusCodePagesExtensions
2: {
3: public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, string contentType, string bodyFormat)
4: {
5: return app.UseStatusCodePages(context =>
6: {
7: var body = string.Format(CultureInfo.InvariantCulture, bodyFormat, context.HttpContext.Response.StatusCode);
8: context.HttpContext.Response.ContentType = contentType;
9: return context.HttpContext.Response.WriteAsync(body);
10: });
11: }
12: }
UseStatusCodePagesWithRedirects
如果我们调用UseStatusCodePagesWithRedirects方法,可以让注册的StatusCodePagesMiddleware中间件向指定的路径发送一个客户端重定向。从如下所示的实现代码可以看出,这个作为参数locationFormat的重定向地址也是一个模板,它可以包含一个表示响应状态的占位符(“{0}”)。我们可以指定一个完整的地址,也可以指定一个相对于PathBase的相对路径,后者需要包含表示基地址的“~/”前缀。
1: public static class StatusCodePagesExtensions
2: {
3: public static IApplicationBuilder UseStatusCodePagesWithRedirects(this IApplicationBuilder app, string locationFormat)
4: {
5: if (locationFormat.StartsWith("~"))
6: {
7: locationFormat = locationFormat.Substring(1);
8: return app.UseStatusCodePages(context =>
9: {
10: var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode);
11: context.HttpContext.Response.Redirect(context.HttpContext.Request.PathBase + location);
12: return Task.CompletedTask;
13: });
14: }
15: else
16: {
17: return app.UseStatusCodePages(context =>
18: {
19: var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode);
20: context.HttpContext.Response.Redirect(location);
21: return Task.CompletedTask;
22: });
23: }
24: }
25: }
我们通过一个简单的应用来演示针对客户端重定向的错误页面呈现方式。我们在如下这个应用中注册了一个路由模板为“error/{statuscode}”的路由,路由参数“statuscode”自然代表响应的状态码。在作为路由处理器的HandleError方法中,我们会直接响应一个包含响应状态码的字符串。我们调用UseStatusCodePagesWithRedirects方法注册StatusCodePagesMiddleware中间件的时候将重定义路径设置为“error/{0}”。
1: public class Program
2: {
3: private static Random _random = new Random();
4: public static void Main()
5: {
6: new WebHostBuilder()
7: .UseKestrel()
8: .ConfigureServices(svcs => svcs.AddRouting())
9: .Configure(app => app
10: .UseStatusCodePagesWithRedirects("~/error/{0}")
11: .UseRouter(builder=>builder.MapRoute("error/{statuscode}", HandleError))
12: .Run(context=>Task.Run(()=>context.Response.StatusCode = _random.Next(400,599))))
13: .Build()
14: .Run();
15: }
16:
17: private async static Task HandleError(HttpContext context)
18: {
19: var statusCode = context.GetRouteData().Values["statuscode"];
20: await context.Response.WriteAsync($"Error occurred ({statusCode})");
21: }
22: }
针对该应用的请求总是会得到一个状态码在400~599之间的响应, StatusCodePagesMiddleware在此情况下会向我们指定的路径(“~/error/{statuscode}”)发送一个客户端重定向。由于重定向请求的路径与注册的路由相匹配,所以作为路由处理器的HandleError方法会响应如图11所示的这个错误页面。
UseStatusCodePagesWithReExecute
除了采用客户端重定向的方式来呈现错误页面之外,我们还可以调用UseStatusCodePagesWithReExecute方法注册StatusCodePagesMiddleware中间件并让它采用服务端重定向的方式来处理错误请求。如下面的代码片段所示,当我们调用这个方法的时候不仅可以指定重定向的路径,还可以指定指定查询字符串。这里作为重定向地址的参数pathFormat依旧是一个路径模板,它可以包含一个表示响应状态的占位符(“{0}”)。
1: public static class StatusCodePagesExtensions
2: {
3: public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app, string pathFormat, string queryFormat = null);
4: }
现在我们对上面演示的这个实例略作修改来演示采服务端重定向呈现出来的错误页面。如下面的代码片段所示,我们仅仅将针对UseStatusCodePagesWithRedirects方法的调用替换成针对UseStatusCodePagesWithReExecute方法的调用而已。
1: public class Program
2: {
3: private static Random _random = new Random();
4: public static void Main()
5: {
6: new WebHostBuilder()
7: .UseKestrel()
8: .ConfigureServices(svcs => svcs.AddRouting())
9: .Configure(app => app
10: .UseStatusCodePagesWithReExecute("/error/{0}")
11: .UseRouter(builder=>builder.MapRoute("error/{statuscode}", HandleError))
12: .Run(context=>Task.Run(()=>context.Response.StatusCode = _random.Next(400,599))))
13: .Build()
14: .Run();
15: }
16:
17: private async static Task HandleError(HttpContext context)
18: {
19: var statusCode = context.GetRouteData().Values["statuscode"];
20: await context.Response.WriteAsync($"Error occurred ({statusCode})");
21: }
22: }
对于前面演示的实例,由于错误页面是通过客户端重定向的方式呈现出来的,所以浏览器地址栏显示的是重定向地址。我们在选择这个实例中采用了服务端重定向,虽然显示的页面内容并没有不同,但是地址栏上的地址是不会发生改变的
之所以被命名为UseStatusCodePagesWithReExecute,是因为通过这方法注册的StatusCodePagesMiddleware中间件进行错误处理的时候,它仅仅是提供的重定向路径和查询字符串应用到当前HttpContext,然后递交给后续管道重新执行。UseStatusCodePagesWithReExecute方法中注册StatusCodePagesMiddleware中间件的实现总体上可以由如下所示的代码片段来体现。
1: public static class StatusCodePagesExtensions
2: {
3: public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app,string pathFormat,string queryFormat = null)
4: {
5: return app.UseStatusCodePages(async context =>
6: {
7: var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode));
8: var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode);
9: context.HttpContext.Request.Path = newPath;
10: context.HttpContext.Request.QueryString = newQueryString;
11: await context.Next(context.HttpContext);
12: });
13: }
14: }
与ExceptionHandlerMiddleware中间价类似,StatusCodePagesMiddleware中间件在处理请求的过程中会改变当前请求上下文的状态,具体体现在将指定的请求路径和查询字符串重新应用到当前请求上下文中。为了不影响前置中间件对请求的正常处理,StatusCodePagesMiddleware中间件在完成自身处理流程之后必须将当前请求上下文恢复到原始的状态。StatusCodePagesMiddleware中间件依旧是采用一个特性来保存原始的路径和查询字符串。这个特性对应的接口为具有如下定义的IStatusCodeReExecuteFeature,令人费解的是该接口仅仅包含两个针对路径的属性,并没有我们希望的用于携带原始查询上下文的属性,但是默认实现类型StatusCodeReExecuteFeature包含了这个属性。
1: public interface IStatusCodeReExecuteFeature
2: {
3: string OriginalPath { get; set; }
4: string OriginalPathBase { get; set; }
5: }
6:
7: public class StatusCodeReExecuteFeature : IStatusCodeReExecuteFeature
8: {
9: public string OriginalPath { get; set; }
10: public string OriginalPathBase { get; set; }
11: public string OriginalQueryString { get; set; }
12: }
当StatusCodePagesMiddleware中间件在处理异常请求的过程中,在将指定的重定向路径和查询字符串应用到当前请求上下文上之前,它会根据原始的上下文创建一个StatusCodeReExecuteFeature特性对象并将其添加到当前HttpContext之上。当整个请求处理过程结束之后,StatusCodePagesMiddleware中间件还会负责将这个特性从当前HttpContext中移除,并恢复原始的请求路径和查询字符串。如下所示的代码片段体现了UseStatusCodePagesWithReExecute方法的真实逻辑。
1: public static class StatusCodePagesExtensions
2: {
3: public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app,string pathFormat,string queryFormat = null)
4: {
5: return app.UseStatusCodePages(async context =>
6: {
7: var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode));
8: var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode);
9: var newQueryString = queryFormat == null ? QueryString.Empty : new QueryString(formatedQueryString);
10:
11: var originalPath = context.HttpContext.Request.Path;
12: var originalQueryString = context.HttpContext.Request.QueryString;
13:
14: context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(new StatusCodeReExecuteFeature()
15: {
16: OriginalPathBase = context.HttpContext.Request.PathBase.Value,
17: OriginalPath = originalPath.Value,
18: OriginalQueryString = originalQueryString.HasValue ? originalQueryString.Value : null,
19: });
20:
21: context.HttpContext.Request.Path = newPath;
22: context.HttpContext.Request.QueryString = newQueryString;
23: try
24: {
25: await context.Next(context.HttpContext);
26: }
27: finally
28: {
29: context.HttpContext.Request.QueryString = originalQueryString;
30: context.HttpContext.Request.Path = originalPath;
31: context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(null);
32: }
33: });
34: }
35: }
ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件
ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件
ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件
StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面的更多相关文章
- ASP.NET Core应用的错误处理[4]:StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
StatusCodePagesMiddleware中间件与ExceptionHandlerMiddleware中间件比较类似,它们都是在后续请求处理过程中“出错”的情况下利用一个错误处理器来完成最终的 ...
- ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止.出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得 ...
- ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”
DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作,而ExceptionHandlerMi ...
- ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”
ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面” DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求 ...
- ASP.NET Core错误处理中间件[1]: 呈现错误信息
NuGet包"Microsoft.AspNetCore.Diagnostics"中提供了几个与异常处理相关的中间件.当ASP.NET Core应用在处理请求过程中出现错误时,我们可 ...
- ASP.NET Core 6框架揭秘实例演示[32]:错误页面的集中呈现方式
由于ASP.NET是一个同时处理多个请求的Web应用框架,所以在处理某个请求过程中出现异常并不会导致整个应用的中止.出于安全方面的考量,为了避免敏感信息外泄,客户端在默认情况下并不会得到详细的出错信息 ...
- ASP.NET网站中设置404自定义错误页面
在用ASP.NET WebForm开发一个网站时,需要自定义404错误页面. 做法是这样的 在网站根目录下建立了一个404.html的错误页面,然后在Global.asax文件中,加入如下代码: &l ...
- 新西兰程序员 ASP.NET网站中设置404自定义错误页面
新西兰程序员 ASP.NET网站中设置404自定义错误页面 在用ASP.NET WebForm开发一个网站时,需要自定义404错误页面. 做法是这样的 在网站根目录下建立了一个404.html的错误页 ...
- ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求
我们通过<以Web的形式发布静态文件>和<条件请求与区间请求>中的实例演示,以及上面针对条件请求和区间请求的介绍,从提供的功能和特性的角度对这个名为StaticFileMidd ...
随机推荐
- linux命令学习笔记(44):top命令
top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管 理器.下面详细介绍它的使用方法.top是一个动态显示过程,即可以通过用户按键来不断刷 ...
- 1107 Social Clusters (30)(30 分)
When register on a social network, you are always asked to specify your hobbies in order to find som ...
- scala & spark实战
java.lang.Long is not a valid external type for schema of string java.lang.RuntimeException: Error ...
- Set_ML
参考资料:斯坦福(http://cs231n.github.io/linear-classify/:http://cs231n.stanford.edu/slides/2017/) Mastering ...
- IHE-PIX 备注
IHE给出了各个Actor之间如何通讯的建议: 1. 应用程序通讯时必须用MLLP包装或者解析. 2. 客户端建立连接后,服务器端必须用此连接进行应答.客户端可以继续用此连接启 ...
- POJ2553( 有向图缩点)
The Bottom of a Graph Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 9779 Accepted: ...
- POJ1797(dijkstra求最短最长边)
Heavy Transportation Time Limit: 3000MS Memory Limit: 30000K Total Submissions: 26442 Accepted: ...
- AR/VR-AR:AR
ylbtech-AR/VR-AR:AR 增强现实技术(Augmented Reality,简称 AR),是一种实时地计算摄影机影像的位置及角度并加上相应图像.视频.3D模型的技术,这种技术的目标是在屏 ...
- 03_通过OpenHelper获取SqliteDatabase对象
MyOpenHelper openHelper = new MyOpenHelper(this); 类似于java的File file = new File();只是声明这个东西,但是文件还并没有真正 ...
- string行读入&&文件输入
普通读入的时候会以空格作为分隔符 直接用cin>>s读入,此时可以直接处理文件尾的情况 text代码: #include <iostream>#include <cstd ...