ASP.NETCore使用Swagger

什么是 MiniProfiler

MiniProfiler 是一款针对 .NET、Ruby、Go 与 Node.js 的性能分析的轻量级程序。可以对一个页面本身,及该页面通过 直接引用、Ajax、Iframe 形式访问的其它页面进行监控,监控内容包括数据库内容,并可以显示数据库访问的 SQL(支持 EF、EF Core 等 )。并且以很友好的方式展现在页面上。

简单使用

步骤

  1. nuget 安装对应包

    1
    Install-Package MiniProfiler.AspNetCore.Mvc
  2. Startup.ConfigureServices 中注册

    1
    2
    3
    4
    5
    //注册MiniProfiler
    services.AddMiniProfiler(options =>
    //配置MiniProfiler的路由基础路径,按照当前配置,你可以使用"/profiler/results"来访问分析报告
    options.RouteBasePath = "/profiler"
    );
  3. Startup.Configure 方法中,启用 MiniProfiler 服务

    1
    2
    //启用MiniProfiler服务
    app.UseMiniProfiler();
  4. 此时启动项目,随意测试请求 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
  5. 更加具体的使用可以参考官方文档:https://miniprofiler.com/dotnet/

AddMiniProfiler的一些选项说明

来源:https://miniprofiler.com/dotnet/AspDotNetCore

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
public void ConfigureServices(IServiceCollection services)
{
// ...existing configuration...

// Note .AddMiniProfiler() returns a IMiniProfilerBuilder for easy intellisense
services.AddMiniProfiler(options =>
{
// All of this is optional. You can simply call .AddMiniProfiler() for all defaults

// (Optional) Path to use for profiler URLs, default is /mini-profiler-resources
options.RouteBasePath = "/profiler";

// (Optional) Control storage
// (default is 30 minutes in MemoryCacheStorage)
// Note: MiniProfiler will not work if a SizeLimit is set on MemoryCache!
// See: https://github.com/MiniProfiler/dotnet/issues/501 for details
(options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(60);

// (Optional) Control which SQL formatter to use, InlineFormatter is the default
options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();

// (Optional) To control authorization, you can use the Func<HttpRequest, bool> options:
// (default is everyone can access profilers)
options.ResultsAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;
options.ResultsListAuthorize = request => MyGetUserFunction(request).CanSeeMiniProfiler;
// Or, there are async versions available:
options.ResultsAuthorizeAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfiler;
options.ResultsAuthorizeListAsync = async request => (await MyGetUserFunctionAsync(request)).CanSeeMiniProfilerLists;

// (Optional) To control which requests are profiled, use the Func<HttpRequest, bool> option:
// (default is everything should be profiled)
options.ShouldProfile = request => MyShouldThisBeProfiledFunction(request);

// (Optional) Profiles are stored under a user ID, function to get it:
// (default is null, since above methods don't use it by default)
options.UserIdProvider = request => MyGetUserIdFunction(request);

// (Optional) Swap out the entire profiler provider, if you want
// (default handles async and works fine for almost all applications)
options.ProfilerProvider = new MyProfilerProvider();

// (Optional) You can disable "Connection Open()", "Connection Close()" (and async variant) tracking.
// (defaults to true, and connection opening/closing is tracked)
options.TrackConnectionOpenClose = true;

// (Optional) Use something other than the "light" color scheme.
// (defaults to "light")
options.ColorScheme = StackExchange.Profiling.ColorScheme.Auto;

// Optionally change the number of decimal places shown for millisecond timings.
// (defaults to 2)
options.PopupDecimalPlaces = 1;

// The below are newer options, available in .NET Core 3.0 and above:

// (Optional) You can disable MVC filter profiling
// (defaults to true, and filters are profiled)
options.EnableMvcFilterProfiling = true;
// ...or only save filters that take over a certain millisecond duration (including their children)
// (defaults to null, and all filters are profiled)
// options.MvcFilterMinimumSaveMs = 1.0m;

// (Optional) You can disable MVC view profiling
// (defaults to true, and views are profiled)
options.EnableMvcViewProfiling = true;
// ...or only save views that take over a certain millisecond duration (including their children)
// (defaults to null, and all views are profiled)
// options.MvcViewMinimumSaveMs = 1.0m;

// (Optional) listen to any errors that occur within MiniProfiler itself
// options.OnInternalError = e => MyExceptionLogger(e);

// (Optional - not recommended) You can enable a heavy debug mode with stacks and tooltips when using memory storage
// It has a lot of overhead vs. normal profiling and should only be used with that in mind
// (defaults to false, debug/heavy mode is off)
//options.EnableDebugMode = true;
});
}

结合Swagger

如何添加 Swagger 支持这里就不说了,默认已经弄好。

步骤

  1. 创建 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 -->
    <!DOCTYPE html>
    <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>
  2. 修改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
    6
    ArgumentNullException: 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");

    这样改了项目名就可以动态获得,当然还是有其他问题,不过一般也够用了

  3. 获取MiniProfiler相关Script片段,这个很重要,网上很多教程就直接给一个<script>代码给人使用,但是版本不匹配的话会出问题

    在其中一个Controller里添加该方法

    1
    2
    3
    4
    5
    6
    [HttpGet("GetMiniProfilerScript")]
    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>
  4. 把上面获取到的script片段加入到index.html文件的最前面

  5. 重新启动项目,随意访问一个接口,然后就能看到页面左上方有一个浮动窗显示xxx ms,点击该面板就能看到熟悉的MiniProfiler分析界面

再次强调

  1. 网上的一些教程的 index.html 就是乱搞的,我试过几个都有问题,如果我的也有问题,只能自己再找找

  2. MiniProfiler 相关 Script 片段这个最好不要随意使用别人给的,版本号很难刚好与自己所需版本匹配,还是使用上面提供的获取办法自己获取最好

该部分内容参考文章:WebAPI性能监控-MiniProfiler与Swagger集成