什么是 MiniProfiler
MiniProfiler 是一款针对 .NET、Ruby、Go 与 Node.js 的性能分析的轻量级程序。可以对一个页面本身,及该页面通过 直接引用、Ajax、Iframe 形式访问的其它页面进行监控,监控内容包括数据库内容,并可以显示数据库访问的 SQL(支持 EF、EF Core 等 )。并且以很友好的方式展现在页面上。
简单使用
步骤
nuget 安装对应包
1
Install-Package MiniProfiler.AspNetCore.Mvc
到
Startup.ConfigureServices
中注册1
2
3
4
5//注册MiniProfiler
services.AddMiniProfiler(options =>
//配置MiniProfiler的路由基础路径,按照当前配置,你可以使用"/profiler/results"来访问分析报告
options.RouteBasePath = "/profiler"
);在
Startup.Configure
方法中,启用 MiniProfiler 服务1
2//启用MiniProfiler服务
app.UseMiniProfiler();此时启动项目,随意测试请求 Api,然后我们就能通过 “/profiler/results” 地址访问到 MiniProfiler 提供的分析报告页面,可以看到类似表格
duration (ms) with children (ms) from start (ms) http://localhost:5000/WeatherForecast 48.1 72.4 +0.6 MiniProfiler Init 2.8 7.9 +3.7 Authorize 1.1 1.1 +5.0 Get Profiler IDs 3.3 3.3 +7.1 Set Headers 0.7 0.7 +10.5 Action: UseMiniProfiler.Controllers.WeatherForecastController.Get 4.6 16.4 +52.7 Action Filter (Execing): UnsupportedContentTypeFilter 0.7 0.7 +54.6 Action Filter (Execing): ModelStateInvalidFilter 0.0 0.0 +55.4 Controller Action: UseMiniProfiler.Controllers.WeatherForecastController.Get 2.4 2.4 +55.9 Action Filter (Execed): ModelStateInvalidFilter 0.0 0.0 +58.4 Action Filter (Execed): UnsupportedContentTypeFilter 0.0 0.0 +58.5 Result Filter (Execing): ClientErrorResultFilter 0.0 0.0 +58.8 Object: IEnumerable`1 8.7 8.7 +60.1 Result Filter (Execed): ClientErrorResultFilter 0.0 0.0 +68.9 更加具体的使用可以参考官方文档:https://miniprofiler.com/dotnet/
AddMiniProfiler的一些选项说明
来源:https://miniprofiler.com/dotnet/AspDotNetCore
1 | public void ConfigureServices(IServiceCollection services) |
结合Swagger
如何添加 Swagger 支持这里就不说了,默认已经弄好。
步骤
创建 index.html 放在项目根目录,该文件会作为 Swagger 自定义首页,并在 VS 里右键该文件设置为 ”嵌入的资源“。
html 内容如下,这个无效的话可以再去官方 github 项目文件里找找,我网上找了几个都有问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117<!-- HTML for static distribution bundle build -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>%(DocumentTitle)</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
</style>
%(HeadContent)
</head>
<body>
<div id="swagger-ui"></div>
<!-- Workaround for https://github.com/swagger-api/swagger-editor/issues/1371 -->
<script>
if (window.navigator.userAgent.indexOf("Edge") > -1) {
console.log("Removing native Edge fetch in favor of swagger-ui's polyfill")
window.fetch = undefined;
}
</script>
<script src="./swagger-ui-bundle.js"></script>
<script src="./swagger-ui-standalone-preset.js"></script>
<script>
/* Source: https://gist.github.com/lamberta/3768814
* Parse a string function definition and return a function object. Does not use eval.
* @param {string} str
* @return {function}
*
* Example:
* var f = function (x, y) { return x * y; };
* var g = parseFunction(f.toString());
* g(33, 3); //=> 99
*/
function parseFunction(str) {
if (!str) return void (0);
var fn_body_idx = str.indexOf('{'),
fn_body = str.substring(fn_body_idx + 1, str.lastIndexOf('}')),
fn_declare = str.substring(0, fn_body_idx),
fn_params = fn_declare.substring(fn_declare.indexOf('(') + 1, fn_declare.lastIndexOf(')')),
args = fn_params.split(',');
args.push(fn_body);
function Fn() {
return Function.apply(this, args);
}
Fn.prototype = Function.prototype;
return new Fn();
}
window.onload = function () {
var configObject = JSON.parse('%(ConfigObject)');
var oauthConfigObject = JSON.parse('%(OAuthConfigObject)');
// Workaround for https://github.com/swagger-api/swagger-ui/issues/5945
configObject.urls.forEach(function (item) {
if (item.url.startsWith("http") || item.url.startsWith("/")) return;
item.url = window.location.href.replace("index.html", item.url).split('#')[0];
});
// If validatorUrl is not explicitly provided, disable the feature by setting to null
if (!configObject.hasOwnProperty("validatorUrl"))
configObject.validatorUrl = null
// If oauth2RedirectUrl isn't specified, use the built-in default
if (!configObject.hasOwnProperty("oauth2RedirectUrl"))
configObject.oauth2RedirectUrl = (new URL("oauth2-redirect.html", window.location.href)).href;
// Apply mandatory parameters
configObject.dom_id = "#swagger-ui";
configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset];
configObject.layout = "StandaloneLayout";
// Parse and add interceptor functions
var interceptors = JSON.parse('%(Interceptors)');
if (interceptors.RequestInterceptorFunction)
configObject.requestInterceptor = parseFunction(interceptors.RequestInterceptorFunction);
if (interceptors.ResponseInterceptorFunction)
configObject.responseInterceptor = parseFunction(interceptors.ResponseInterceptorFunction);
// Begin Swagger UI call region
const ui = SwaggerUIBundle(configObject);
ui.initOAuth(oauthConfigObject);
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>修改
Startup.Configure
的 UseSwaggerUI 中间件的配置1
2
3
4
5
6
7
8
9
10//启用中间件服务对swagger-ui,指定Swagger JSON终结点
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.RoutePrefix = string.Empty;
//新增下面
c.IndexStream = () => GetType().GetTypeInfo()
.Assembly.GetManifestResourceStream("<your-project-namespace>.index.html");
});<your-project-namespace>
需改为自己的假如
<your-project-namespace>
这里填错了,会报下面错误1
2
3
4
5
6ArgumentNullException: Value cannot be null. (Parameter 'stream')
System.IO.StreamReader..ctor(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)
Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.RespondWithIndexHtml(HttpResponse response)
Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)所以我后来改了写法
1
2
3//获得当前程序集的名字
string projectName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream($"{projectName}.index.html");这样改了项目名就可以动态获得,当然还是有其他问题,不过一般也够用了
获取MiniProfiler相关Script片段,这个很重要,网上很多教程就直接给一个<script>代码给人使用,但是版本不匹配的话会出问题
在其中一个Controller里添加该方法
1
2
3
4
5
6[ ]
public IActionResult GetMiniProfilerScript()
{
var html = MiniProfiler.Current.RenderIncludes(HttpContext);
return Ok(html.Value);
}然后运行项目访问该方法,获得其内容,类似下面,实际就是一段<script>
1
<script async="async" id="mini-profiler" src="/profiler/includes.min.js?v=4.2.22+4563a9e1ab" data-version="4.2.22+4563a9e1ab" data-path="/profiler/" data-current-id="82827eb7-0fbf-46ac-a21b-d8547627b53b" data-ids="9871c780-0c60-4b17-a5ba-0b4b848c6ef9,82827eb7-0fbf-46ac-a21b-d8547627b53b" data-position="Left"" data-scheme="Light" data-authorized="true" data-max-traces="15" data-toggle-shortcut="Alt+P" data-trivial-milliseconds="2.0" data-ignored-duplicate-execute-types="Open,OpenAsync,Close,CloseAsync"></script>
把上面获取到的script片段加入到index.html文件的最前面
重新启动项目,随意访问一个接口,然后就能看到页面左上方有一个浮动窗显示
xxx ms
,点击该面板就能看到熟悉的MiniProfiler分析界面
再次强调
网上的一些教程的 index.html 就是乱搞的,我试过几个都有问题,如果我的也有问题,只能自己再找找
MiniProfiler 相关 Script 片段这个最好不要随意使用别人给的,版本号很难刚好与自己所需版本匹配,还是使用上面提供的获取办法自己获取最好
该部分内容参考文章:WebAPI性能监控-MiniProfiler与Swagger集成