[
  {
    "path": ".gitattributes",
    "content": "*.doc  diff=astextplain\n*.DOC\tdiff=astextplain\n*.docx\tdiff=astextplain\n*.DOCX\tdiff=astextplain\n*.dot\tdiff=astextplain\n*.DOT\tdiff=astextplain\n*.pdf\tdiff=astextplain\n*.PDF\tdiff=astextplain\n*.rtf\tdiff=astextplain\n*.RTF\tdiff=astextplain\n\n*.jpg  \tbinary\n*.png \tbinary\n*.gif \tbinary\n\n*.cs text=auto diff=csharp \n*.vb text=auto\n*.resx text=auto\n*.c text=auto\n*.cpp text=auto\n*.cxx text=auto\n*.h text=auto\n*.hxx text=auto\n*.py text=auto\n*.rb text=auto\n*.java text=auto\n*.html text=auto\n*.htm text=auto\n*.css text=auto\n*.scss text=auto\n*.sass text=auto\n*.less text=auto\n*.js text=auto\n*.lisp text=auto\n*.clj text=auto\n*.sql text=auto\n*.php text=auto\n*.lua text=auto\n*.m text=auto\n*.asm text=auto\n*.erl text=auto\n*.fs text=auto\n*.fsx text=auto\n*.hs text=auto\n\n*.csproj text=auto\n*.vbproj text=auto\n*.fsproj text=auto\n*.dbproj text=auto\n*.sln text=auto eol=crlf\n\n*.sh eol=lf\n"
  },
  {
    "path": ".gitignore",
    "content": "[Oo]bj/\n[Bb]in/\nTestResults/\n.nuget/\n.build/\n.testPublish/\npublish/\n*.sln.ide/\n_ReSharper.*/\npackages/\nshared/\nartifacts/\nPublishProfiles/\n.vs/\nbower_components/\nnode_modules/\ndebugSettings.json\nproject.lock.json\n*.user\n*.suo\n*.cache\n*.docstates\n_ReSharper.*\nnuget.exe\n*net45.csproj\n*net451.csproj\n*k10.csproj\n*.psess\n*.vsp\n*.pidb\n*.userprefs\n*DS_Store\n*.ncrunchsolution\n*.*sdf\n*.ipch\n.settings\n*.sln.ide\nnode_modules\n*launchSettings.json\n*.orig\n"
  },
  {
    "path": "AspNetCoreDiagnosticScenarios.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio 15\r\nVisualStudioVersion = 15.0.28010.2036\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Scenarios\", \"Scenarios\\Scenarios.csproj\", \"{D05E3242-E5ED-493F-B75D-48A494C6CE4B}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|Any CPU = Debug|Any CPU\r\n\t\tRelease|Any CPU = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{D05E3242-E5ED-493F-B75D-48A494C6CE4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{D05E3242-E5ED-493F-B75D-48A494C6CE4B}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{D05E3242-E5ED-493F-B75D-48A494C6CE4B}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{D05E3242-E5ED-493F-B75D-48A494C6CE4B}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {F02E06C8-1856-4E32-A48C-3C827878CD3A}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "AspNetCoreGuidance.md",
    "content": "# Table of contents\n - [ASP.NET Core Guidance](#aspnet-core-guidance)\n   - [Avoid using synchronous Read/Write overloads on HttpRequest.Body and HttpResponse.Body](#avoid-using-synchronous-readwrite-overloads-on-httprequestbody-and-httpresponsebody)\n   - [Prefer using HttpRequest.ReadFormAsync() over HttpRequest.Form](#prefer-using-httprequestreadformasync-over-httprequestform)\n   - [Avoid reading large request bodies or response bodies into memory](#avoid-reading-large-request-bodies-or-response-bodies-into-memory)\n   - [Use buffered and synchronous reads and writes as an alternative to asynchronous reading and writing](#use-buffered-and-synchronous-reads-and-writes-as-an-alternative-to-asynchronous-reading-and-writing)\n   - [Do not store IHttpContextAccessor.HttpContext in a field](#do-not-store-ihttpcontextaccessorhttpcontext-in-a-field)\n   - [Do not access the HttpContext from multiple threads in parallel. It is not thread safe.](#do-not-access-the-httpcontext-from-multiple-threads-in-parallel-it-is-not-thread-safe)\n   - [Do not use the HttpContext after the request is complete](#do-not-use-the-httpcontext-after-the-request-is-complete)\n   - [Do not capture the HttpContext in background threads](#do-not-capture-the-httpcontext-in-background-threads)\n   - [Do not capture services injected into the controllers on background threads](#do-not-capture-services-injected-into-the-controllers-on-background-threads)\n   - [Avoid adding headers after the HttpResponse has started](#avoid-adding-headers-after-the-httpresponse-has-started)\n\n# ASP.NET Core Guidance\n\nASP.NET Core is a cross-platform, high-performance, open-source framework for building modern, cloud-based, Internet-connected applications. This guide captures some of the common pitfalls and practices when writing scalable server applications.\n\n## Avoid using synchronous Read/Write overloads on HttpRequest.Body and HttpResponse.Body\n\nAll IO in ASP.NET Core is asynchronous. Servers implement the `Stream` interface which has both synchronous and asynchronous overloads. The asynchronous ones should be preferred to avoid blocking thread pool threads (this could lead to thread pool starvation).\n\n❌ **BAD** This example uses the `StreamReader.ReadToEnd` and as a result blocks the current thread to wait for the result. This is an example of [sync over async](AsyncGuidance.md#avoid-using-taskresult-and-taskwait).\n\n```C#\npublic class MyController : Controller\n{\n    [HttpGet(\"/pokemon\")]\n    public ActionResult<PokemonData> Get()\n    {\n        // This synchronously reads the entire http request body into memory.\n        // If the client is slowly uploading, we're doing sync over async because Kestrel does *NOT* support synchronous reads.\n        var json = new StreamReader(Request.Body).ReadToEnd();\n\n        return JsonConvert.DeserializeObject<PokemonData>(json);\n    }\n}\n```\n\n:white_check_mark: **GOOD** This example uses `StreamReader.ReadToEndAsync` and as a result, does not block the thread while reading.\n\n```C#\npublic class MyController : Controller\n{\n    [HttpGet(\"/pokemon\")]\n    public async Task<ActionResult<PokemonData>> Get()\n    {\n        // This asynchronously reads the entire http request body into memory.\n        var json = await new StreamReader(Request.Body).ReadToEndAsync();\n\n        return JsonConvert.DeserializeObject<PokemonData>(json);\n    }\n}\n```\n\n:bulb:**NOTE: If the request is large it could lead to out of memory problems which can result in a Denial Of Service. See [this](#avoid-reading-large-request-bodies-or-response-bodies-into-memory) for more information.**\n\n## Prefer using HttpRequest.ReadFormAsync() over HttpRequest.Form\n\nYou should always prefer `HttpRequest.ReadFormAsync()` over `HttpRequest.Form`. The only time it is safe to use `HttpRequest.Form` is the form has already been read by a call to `HttpRequest.ReadFormAsync()` and the cached form value is being read using `HttpRequest.Form`. \n\n❌ **BAD** This example uses HttpRequest.Form uses [sync over async](AsyncGuidance.md#avoid-using-taskresult-and-taskwait) under the covers and can lead to thread pool starvation (in some cases).\n\n```C#\npublic class MyController : Controller\n{\n    [HttpPost(\"/form-body\")]\n    public IActionResult Post()\n    {\n        var form = HttpRequest.Form;\n        \n        Process(form[\"id\"], form[\"name\"]);\n\n        return Accepted();\n    }\n}\n```\n\n:white_check_mark: **GOOD** This example uses `HttpRequest.ReadFormAsync()` to read the form body asynchronously.\n\n```C#\npublic class MyController : Controller\n{\n    [HttpPost(\"/form-body\")]\n    public async Task<IActionResult> Post()\n    {\n        var form = await HttpRequest.ReadFormAsync();\n        \n        Process(form[\"id\"], form[\"name\"]);\n\n        return Accepted();\n    }\n}\n```\n\n## Avoid reading large request bodies or response bodies into memory\n\nIn .NET any single object allocation greater than 85KB ends up in the large object heap ([LOH](https://blogs.msdn.microsoft.com/maoni/2006/04/19/large-object-heap/)). Large objects are expensive in 2 ways:\n\n- The allocation cost is high because the memory for a newly allocated large object has to be cleared (the CLR guarantees that memory for all newly allocated objects is cleared)\n- LOH is collected with the rest of the heap (it requires a \"full garbage collection\" or Gen2 collection)\n\nThis [blog post](https://adamsitnik.com/Array-Pool/#the-problem) describes the problem succinctly:\n\n> When a large object is allocated, it’s marked as Gen 2 object. Not Gen 0 as for small objects. The consequences are that if you run out of memory in LOH, GC cleans up whole managed heap, not only LOH. So it cleans up Gen 0, Gen 1 and Gen 2 including LOH. This is called full garbage collection and is the most time-consuming garbage collection. For many applications, it can be acceptable. But definitely not for high-performance web servers, where few big memory buffers are needed to handle an average web request (read from a socket, decompress, decode JSON & more).\n\nNaively storing a large request or response body into a single `byte[]` or `string` may result in quickly running out of space in the LOH and may cause performance issues for your application because of full GCs running. \n\n## Use buffered and synchronous reads and writes as an alternative to asynchronous reading and writing\n\nWhen using a serializer/de-serializer that only supports synchronous reads and writes (like JSON.NET) then prefer buffering the data into memory before passing data into the serializer/de-serializer.\n\n:bulb:**NOTE: If the request is large it could lead to out of memory problems which can result in a Denial Of Service. See [this](#avoid-reading-large-request-bodies-or-response-bodies-into-memory) for more information.**\n\n## Do not store IHttpContextAccessor.HttpContext in a field\n\nThe `IHttpContextAccessor.HttpContext` will return the `HttpContext` of the active request when accessed from the request thread. It should not be stored in a field or variable.\n\n❌ **BAD** This example stores the `HttpContext` in a field then attempts to use it later.\n\n```C#\npublic class MyType\n{\n    private readonly HttpContext _context;\n    public MyType(IHttpContextAccessor accessor)\n    {\n        _context = accessor.HttpContext;\n    }\n    \n    public void CheckAdmin()\n    {\n        if (!_context.User.IsInRole(\"admin\"))\n        {\n            throw new UnauthorizedAccessException(\"The current user isn't an admin\");\n        }\n    }\n}\n```\n\nThe above logic will likely capture a null or bogus `HttpContext` in the constructor for later use.\n\n:white_check_mark: **GOOD** This example stores the `IHttpContextAccessor` itself in a field and uses the `HttpContext` field at the correct time (checking for null).\n\n```C#\npublic class MyType\n{\n    private readonly IHttpContextAccessor _accessor;\n    public MyType(IHttpContextAccessor accessor)\n    {\n        _accessor = accessor;\n    }\n    \n    public void CheckAdmin()\n    {\n        var context = _accessor.HttpContext;\n        if (context != null && !context.User.IsInRole(\"admin\"))\n        {\n            throw new UnauthorizedAccessException(\"The current user isn't an admin\");\n        }\n    }\n}\n```\n\n## Do not access the HttpContext from multiple threads in parallel. It is not thread safe.\n\nThe `HttpContext` is *NOT* threadsafe. Accessing it from multiple threads in parallel can cause corruption resulting in undefined behavior (hangs, crashes, data corruption).\n\n❌ **BAD** This example makes 3 parallel requests and logs the incoming request path before and after the outgoing http request. This accesses the request path from multiple threads potentially in parallel.\n\n```C#\npublic class AsyncController : Controller\n{\n    [HttpGet(\"/search\")]\n    public async Task<SearchResults> Get(string query)\n    {\n        var query1 = SearchAsync(SearchEngine.Google, query);\n        var query2 = SearchAsync(SearchEngine.Bing, query);\n        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);\n\n        await Task.WhenAll(query1, query2, query3);\n        \n        var results1 = await query1;\n        var results2 = await query2;\n        var results3 = await query3;\n\n        return SearchResults.Combine(results1, results2, results3);\n    }\n\n    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)\n    {\n        var searchResults = SearchResults.Empty;\n        try\n        {\n            _logger.LogInformation(\"Starting search query from {path}.\", HttpContext.Request.Path);\n            searchResults = await _searchService.SearchAsync(engine, query);\n            _logger.LogInformation(\"Finishing search query from {path}.\", HttpContext.Request.Path);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Failed query from {path}\", HttpContext.Request.Path);\n        }\n\n        return searchResults;\n    }\n}\n```\n\n:white_check_mark: **GOOD** This example copies all data from the incoming request before making the 3 parallel requests.\n\n```C#\npublic class AsyncController : Controller\n{\n    [HttpGet(\"/search\")]\n    public async Task<SearchResults> Get(string query)\n    {\n        string path = HttpContext.Request.Path;\n        var query1 = SearchAsync(SearchEngine.Google, query, path);\n        var query2 = SearchAsync(SearchEngine.Bing, query, path);\n        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);\n\n        await Task.WhenAll(query1, query2, query3);\n        \n        var results1 = await query1;\n        var results2 = await query2;\n        var results3 = await query3;\n\n        return SearchResults.Combine(results1, results2, results3);\n    }\n\n    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query, string path)\n    {\n        var searchResults = SearchResults.Empty;\n        try\n        {\n            _logger.LogInformation(\"Starting search query from {path}.\", path);\n            searchResults = await _searchService.SearchAsync(engine, query);\n            _logger.LogInformation(\"Finishing search query from {path}.\", path);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Failed query from {path}\", path);\n        }\n\n        return searchResults;\n    }\n}\n```\n\n## Do not use the HttpContext after the request is complete\n\nThe `HttpContext` is only valid as long as there is an active http request in flight. The entire ASP.NET Core pipeline is an asynchronous chain of delegates that executes every request. When the `Task` returned from this chain completes, the `HttpContext` is recycled. \n\n❌ **BAD** This example uses async void (which is a **ALWAYS** bad in ASP.NET Core applications) and as a result, accesses the `HttpResponse` after the http request is complete. It will crash the process as a result.\n\n```C#\npublic class AsyncVoidController : Controller\n{\n    [HttpGet(\"/async\")]\n    public async void Get()\n    {\n        await Task.Delay(1000);\n\n        // THIS will crash the process since we're writing after the response has completed on a background thread\n        await Response.WriteAsync(\"Hello World\");\n    }\n}\n```\n\n:white_check_mark: **GOOD** This example returns a `Task` to the framework so the http request doesn't complete until the entire action completes.\n\n```C#\npublic class AsyncController : Controller\n{\n    [HttpGet(\"/async\")]\n    public async Task Get()\n    {\n        await Task.Delay(1000);\n        \n        await Response.WriteAsync(\"Hello World\");\n    }\n}\n```\n\n## Do not capture the HttpContext in background threads\n\n❌ **BAD** This example shows a closure is capturing the `HttpContext` from the Controller property. This is bad because this work item could run\noutside of the request scope and as a result, could lead to reading a bogus `HttpContext`.\n\n```C#\n[HttpGet(\"/fire-and-forget-1\")]\npublic IActionResult FireAndForget1()\n{\n    _ = Task.Run(() =>\n    {\n        await Task.Delay(1000);\n\n        // This closure is capturing the context from the Controller property. This is bad because this work item could run\n        // outside of the http request leading to reading of bogus data.\n        var path = HttpContext.Request.Path;\n        Log(path);\n    });\n\n    return Accepted();\n}\n```\n\n\n:white_check_mark: **GOOD** This example copies the data required in the background task during the request explicitly and does not reference\nanything from the controller itself.\n\n```C#\n[HttpGet(\"/fire-and-forget-3\")]\npublic IActionResult FireAndForget3()\n{\n    string path = HttpContext.Request.Path;\n    _ = Task.Run(async () =>\n    {\n        await Task.Delay(1000);\n\n        // This captures just the path\n        Log(path);\n    });\n\n    return Accepted();\n}\n```\n\n## Do not capture services injected into the controllers on background threads\n\n❌ **BAD** This example shows a closure is capturing the `DbContext` from the Controller action parameter. This is bad because this work item could run\noutside of the request scope and the `PokemonDbContext` is scoped to the request. As a result, this will end up with an `ObjectDisposedException`.\n\n```C#\n[HttpGet(\"/fire-and-forget-1\")]\npublic IActionResult FireAndForget1([FromServices]PokemonDbContext context)\n{\n    _ = Task.Run(() =>\n    {\n        await Task.Delay(1000);\n\n        // This closure is capturing the context from the Controller action parameter. This is bad because this work item could run\n        // outside of the request scope and the PokemonDbContext is scoped to the request. As a result, this throws an ObjectDisposedException\n        context.Pokemon.Add(new Pokemon());\n        await context.SaveChangesAsync();\n    });\n\n    return Accepted();\n}\n```\n\n:white_check_mark: **GOOD** This example injects an `IServiceScopeFactory` and creates a new dependency injection scope in the background thread and does not reference\nanything from the controller itself.\n\n```C#\n[HttpGet(\"/fire-and-forget-3\")]\npublic IActionResult FireAndForget3([FromServices]IServiceScopeFactory serviceScopeFactory)\n{\n    // This version of fire and forget adds some exception handling. We're also no longer capturing the PokemonDbContext from the incoming request.\n    // Instead, we're injecting an IServiceScopeFactory (which is a singleton) in order to create a scope in the background work item.\n    _ = Task.Run(async () =>\n    {\n        await Task.Delay(1000);\n\n        // Create a scope for the lifetime of the background operation and resolve services from it\n        using (var scope = serviceScopeFactory.CreateScope())\n        {\n            // This will resolve a PokemonDbContext from the correct scope and the operation will succeed\n            var context = scope.ServiceProvider.GetRequiredService<PokemonDbContext>();\n\n            context.Pokemon.Add(new Pokemon());\n            await context.SaveChangesAsync();\n        }\n    });\n\n    return Accepted();\n}\n```\n\n## Avoid adding headers after the HttpResponse has started\n\nASP.NET Core does not buffer the http response body. This means that the very first time the response is written, the headers are sent along with that chunk of the body to the client. When this happens, it's no longer possible to change response headers.\n\n❌ **BAD** This logic tries to add response headers after the response has already started.\n\n```C#\napp.Use(async (next, context) =>\n{\n    await context.Response.WriteAsync(\"Hello \");\n    \n    await next();\n    \n    // This may fail if next() already wrote to the response\n    context.Response.Headers[\"test\"] = \"value\";    \n});\n```\n\n:white_check_mark: **GOOD** This example checks if the http response has started before writing to the body.\n\n```C#\napp.Use(async (next, context) =>\n{\n    await context.Response.WriteAsync(\"Hello \");\n    \n    await next();\n    \n    // Check if the response has already started before adding header and writing\n    if (!context.Response.HasStarted)\n    {\n        context.Response.Headers[\"test\"] = \"value\";\n    }\n});\n```\n\n:white_check_mark: **GOOD** This example uses `HttpResponse.OnStarting` to set the headers before the response headers are flushed to the client.\n\nIt allows you to register a callback that will be invoked just before response headers are written to the client. It gives you the ability to append or override headers just in time, without requiring knowledge of the next middleware in the pipeline.\n\n```C#\napp.Use(async (next, context) =>\n{\n    // Wire up the callback that will fire just before the response headers are sent to the client.\n    context.Response.OnStarting(() => \n    {       \n        context.Response.Headers[\"someheader\"] = \"somevalue\"; \n        return Task.CompletedTask;\n    });\n    \n    await next();\n});\n```\n"
  },
  {
    "path": "AsyncGuidance.md",
    "content": "# Table of contents\n - [Asynchronous Programming](#asynchronous-programming)\n   - [Asynchrony is viral](#asynchrony-is-viral)\n   - [Async void](#async-void)\n   - [Prefer Task.FromResult over Task.Run for pre-computed or trivially computed data](#prefer-taskfromresult-over-taskrun-for-pre-computed-or-trivially-computed-data)\n   - [Avoid using Task.Run for long-running work that blocks the thread](#avoid-using-taskrun-for-long-running-work-that-blocks-the-thread)\n   - [Avoid using Task.Result and Task.Wait](#avoid-using-taskresult-and-taskwait)\n   - [Prefer await over ContinueWith](#prefer-await-over-continuewith)\n   - [Always create TaskCompletionSource\\<T\\> with TaskCreationOptions.RunContinuationsAsynchronously](#always-create-taskcompletionsourcet-with-taskcreationoptionsruncontinuationsasynchronously)\n   - [Always dispose CancellationTokenSource(s) used for timeouts](#always-dispose-cancellationtokensources-used-for-timeouts)\n   - [Always flow CancellationToken(s) to APIs that take a CancellationToken](#always-flow-cancellationtokens-to-apis-that-take-a-cancellationtoken)\n   - [Cancelling uncancellable operations](#cancelling-uncancellable-operations)\n   - [Always call FlushAsync on StreamWriter(s) or Stream(s) before calling Dispose](#always-call-flushasync-on-streamwriters-or-streams-before-calling-dispose)\n   - [Prefer async/await over directly returning Task](#prefer-asyncawait-over-directly-returning-task)\n   - [AsyncLocal\\<T\\>](#asynclocalt)\n   - [ConfigureAwait](#configureawait)\n   - [Scenarios](#scenarios)\n   - [Timer callbacks](#timer-callbacks)\n   - [Implicit async void delegates](#implicit-async-void-delegates)\n   - [ConcurrentDictionary.GetOrAdd](#concurrentdictionarygetoradd)\n   - [Constructors](#constructors)\n   - [WindowsIdentity.RunImpersonated](#windowsidentityrunimpersonated)\n \n# Asynchronous Programming\n\nAsynchronous programming has been around for several years on the .NET platform but has historically been very difficult to do well. Since the introduction of async/await\nin C# 5 asynchronous programming has become mainstream. Modern frameworks (like ASP.NET Core) are fully asynchronous and it's very hard to avoid the async keyword when writing\nweb services. As a result, there's been lots of confusion on the best practices for async and how to use it properly. This section will try to lay out some guidance with examples of bad and good patterns of how to write asynchronous code.\n\n## Asynchrony is viral \n\nOnce you go async, all of your callers **SHOULD** be async, since efforts to be async amount to nothing unless the entire call stack is async. In many cases, being partially asynchronous can be worse than being entirely synchronous. Therefore it is best to go all in, and make everything async at once.\n\n❌ **BAD** This example uses the `Task.Result` and as a result blocks the current thread to wait for the result. This is an example of [sync over async](#avoid-using-taskresult-and-taskwait).\n\n```C#\npublic int DoSomethingAsync()\n{\n    var result = CallDependencyAsync().Result;\n    return result + 1;\n}\n```\n\n:white_check_mark: **GOOD** This example uses the await keyword to get the result from `CallDependencyAsync`.\n\n```C#\npublic async Task<int> DoSomethingAsync()\n{\n    var result = await CallDependencyAsync();\n    return result + 1;\n}\n```\n\n## Async void\n\nThe use of async void in ASP.NET Core applications is **ALWAYS** bad. Avoid it, never do it. Typically, it's used when developers are trying to implement fire-and-forget patterns triggered by a controller action. Async void methods will crash the process if an exception is thrown. We'll look at more of the patterns that cause developers to do this in ASP.NET Core applications but here's a simple example:\n\n❌ **BAD** Async void methods can't be tracked and therefore unhandled exceptions can result in application crashes.\n\n```C#\npublic class MyController : Controller\n{\n    [HttpPost(\"/start\")]\n    public IActionResult Post()\n    {\n        BackgroundOperationAsync();\n        return Accepted();\n    }\n    \n    public async void BackgroundOperationAsync()\n    {\n        var result = await CallDependencyAsync();\n        DoSomething(result);\n    }\n}\n```\n\n:white_check_mark: **GOOD** `Task`-returning methods are better since unhandled exceptions trigger the [`TaskScheduler.UnobservedTaskException`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler.unobservedtaskexception?view=netframework-4.7.2).\n\n```C#\npublic class MyController : Controller\n{\n    [HttpPost(\"/start\")]\n    public IActionResult Post()\n    {\n        Task.Run(BackgroundOperationAsync);\n        return Accepted();\n    }\n    \n    public async Task BackgroundOperationAsync()\n    {\n        var result = await CallDependencyAsync();\n        DoSomething(result);\n    }\n}\n```\n\n## Prefer `Task.FromResult` over `Task.Run` for pre-computed or trivially computed data\n\nFor pre-computed results, there's no need to call `Task.Run`, which will end up queuing a work item to the thread pool that will immediately complete with the pre-computed value. Instead, use `Task.FromResult`, to create a task wrapping already computed data.\n\n❌ **BAD** This example wastes a thread-pool thread to return a trivially computed value.\n\n```C#\npublic class MyLibrary\n{\n   public Task<int> AddAsync(int a, int b)\n   {\n       return Task.Run(() => a + b);\n   }\n}\n```\n\n:white_check_mark: **GOOD** This example uses `Task.FromResult` to return the trivially computed value. It does not use any extra threads as a result.\n\n```C#\npublic class MyLibrary\n{\n   public Task<int> AddAsync(int a, int b)\n   {\n       return Task.FromResult(a + b);\n   }\n}\n```\n\n:bulb:**NOTE: Using `Task.FromResult` will result in a `Task` allocation. Using `ValueTask<T>` can completely remove that allocation.**\n\n:white_check_mark: **GOOD** This example uses a `ValueTask<int>` to return the trivially computed value. It does not use any extra threads as a result. It also does not allocate an object on the managed heap.\n\n```C#\npublic class MyLibrary\n{\n   public ValueTask<int> AddAsync(int a, int b)\n   {\n       return new ValueTask<int>(a + b);\n   }\n}\n```\n\n## Avoid using Task.Run for long-running work that blocks the thread\n\nLong-running work in this context refers to a thread that's running for the lifetime of the application doing background work (like processing queue items, or sleeping and waking up to process some data). `Task.Run` will queue a work item to the thread pool. The assumption is that that work will finish quickly (or quickly enough to allow reusing that thread within some reasonable timeframe). Stealing a thread-pool thread for long-running work is bad since it takes that thread away from other work that could be done (timer callbacks, task continuations, etc). Instead, spawn a new thread manually to do long-running blocking work.\n\n:bulb: **NOTE: The thread pool grows if you block threads but it's bad practice to do so.**\n\n:bulb: **NOTE:`Task.Factory.StartNew` has an option `TaskCreationOptions.LongRunning` that under the covers creates a new thread and returns a Task that represents the execution. Using this properly requires several non-obvious parameters to be passed in to get the right behavior on all platforms.**\n\n:bulb: **NOTE: Don't use `TaskCreationOptions.LongRunning` with async code as this will create a new thread which will be destroyed after first `await`.**\n\n\n❌ **BAD** This example steals a thread-pool thread forever, to execute queued work on a `BlockingCollection<T>`.\n\n```C#\npublic class QueueProcessor\n{\n    private readonly BlockingCollection<Message> _messageQueue = new BlockingCollection<Message>();\n    \n    public void StartProcessing()\n    {\n        Task.Run(ProcessQueue);\n    }\n    \n    public void Enqueue(Message message)\n    {\n        _messageQueue.Add(message);\n    }\n    \n    private void ProcessQueue()\n    {\n        foreach (var item in _messageQueue.GetConsumingEnumerable())\n        {\n             ProcessItem(item);\n        }\n    }\n    \n    private void ProcessItem(Message message) { }\n}\n```\n\n:white_check_mark: **GOOD** This example uses a dedicated thread to process the message queue instead of a thread-pool thread.\n\n```C#\npublic class QueueProcessor\n{\n    private readonly BlockingCollection<Message> _messageQueue = new BlockingCollection<Message>();\n    \n    public void StartProcessing()\n    {\n        var thread = new Thread(ProcessQueue) \n        {\n            // This is important as it allows the process to exit while this thread is running\n            IsBackground = true\n        };\n        thread.Start();\n    }\n    \n    public void Enqueue(Message message)\n    {\n        _messageQueue.Add(message);\n    }\n    \n    private void ProcessQueue()\n    {\n        foreach (var item in _messageQueue.GetConsumingEnumerable())\n        {\n             ProcessItem(item);\n        }\n    }\n    \n    private void ProcessItem(Message message) { }\n}\n```\n\n:white_check_mark: **GOOD** This example utilizes a `TaskFactory` with `TaskCreationOptions.LongRunning` to process the message queue instead of creating a thread manually.\n\n```C#\npublic class QueueProcessor\n{\n    private readonly BlockingCollection<Message> _messageQueue = new BlockingCollection<Message>();\n\n    public Task StartProcessing() => Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);\n\n    public void Enqueue(Message message)\n    {\n        _messageQueue.Add(message);\n    }\n\n    private void ProcessQueue()\n    {\n        foreach (var item in _messageQueue.GetConsumingEnumerable())\n        {\n            ProcessItem(item);\n        }\n    }\n\n    private void ProcessItem(Message message) { }\n}\n```\n\nUtilizing `TaskCreationOptions.LongRunning` introduces several advantages in comparison with manual thread creation:\n\n- It can be easily combined with `await` and TPL APIs, such as `Task.WhenAll`, amongst others.\n- It provides a superior exception-handling mechanism. For instance, in the event of an unhandled exception in a manually created thread, the application will crash (unless handled via `AppDomain.CurrentDomain.UnhandledException`), but with `.LongRunning`, it will be wrapped into a `Task` as an `AggregateException`.\n\n:bulb: **NOTE: The `TaskCreationOptions.LongRunning` option is essentially a recommendation to the `TaskScheduler`, which may interpret it differently in custom `TaskScheduler` applications or runtimes, or future updates to the .NET runtime libraries. If your primary goal is to spawn a new dedicated thread, then you might consider using the manual thread creation approach discussed previously.**\n\n\n## Avoid using `Task.Result` and `Task.Wait`\n\nThere are very few ways to use `Task.Result` and `Task.Wait` correctly so the general advice is to completely avoid using them in your code. \n\n### :warning: Sync over `async`\n\nUsing `Task.Result` or `Task.Wait` to block waiting on an asynchronous operation to complete is *MUCH* worse than calling a truly synchronous API to block. This phenomenon is dubbed \"Sync over async\". Here is what happens at a very high level:\n\n- An asynchronous operation is kicked off.\n- The calling thread is blocked waiting for that operation to complete.\n- When the asynchronous operation completes, it unblocks the code waiting on that operation. This takes place on another thread.\n\nThe result is that we need to use 2 threads instead of 1 to complete synchronous operations. This usually leads to [thread-pool starvation](https://blogs.msdn.microsoft.com/vancem/2018/10/16/diagnosing-net-core-threadpool-starvation-with-perfview-why-my-service-is-not-saturating-all-cores-or-seems-to-stall/) and results in service outages.\n\n### :warning: Deadlocks\n\nThe `SynchronizationContext` is an abstraction that gives application models a chance to control where asynchronous continuations run. ASP.NET (non-core), WPF, and Windows Forms each have an implementation that will result in a deadlock if Task.Wait or Task.Result is used on the main thread. This behavior has led to a bunch of \"clever\" code snippets that show the \"right\" way to block waiting for a Task. The truth is, there's no good way to block waiting for a Task to complete.\n\n:bulb:**NOTE: ASP.NET Core does not have a `SynchronizationContext` and is not prone to the deadlock problem.**\n\n❌ **BAD** The below are all examples that are, in one way or another, trying to avoid the deadlock situation but still succumb to \"sync over async\" problems.\n\n```C#\npublic string DoOperationBlocking()\n{\n    // Bad - Blocking the thread that enters.\n    // DoAsyncOperation will be scheduled on the default task scheduler, and remove the risk of deadlocking.\n    // In the case of an exception, this method will throw an AggregateException wrapping the original exception.\n    return Task.Run(() => DoAsyncOperation()).Result;\n}\n\npublic string DoOperationBlocking2()\n{\n    // Bad - Blocking the thread that enters.\n    // DoAsyncOperation will be scheduled on the default task scheduler, and remove the risk of deadlocking.\n    // In the case of an exception, this method will throw the exception without wrapping it in an AggregateException.\n    return Task.Run(() => DoAsyncOperation()).GetAwaiter().GetResult();\n}\n\npublic string DoOperationBlocking3()\n{\n    // Bad - Blocking the thread that enters, and blocking the threadpool thread inside.\n    // In the case of an exception, this method will throw an AggregateException containing another AggregateException, containing the original exception.\n    return Task.Run(() => DoAsyncOperation().Result).Result;\n}\n\npublic string DoOperationBlocking4()\n{\n    // Bad - Blocking the thread that enters, and blocking the threadpool thread inside.\n    return Task.Run(() => DoAsyncOperation().GetAwaiter().GetResult()).GetAwaiter().GetResult();\n}\n\npublic string DoOperationBlocking5()\n{\n    // Bad - Blocking the thread that enters.\n    // Bad - No effort has been made to prevent a present SynchonizationContext from becoming deadlocked.\n    // In the case of an exception, this method will throw an AggregateException wrapping the original exception.\n    return DoAsyncOperation().Result;\n}\n\npublic string DoOperationBlocking6()\n{\n    // Bad - Blocking the thread that enters.\n    // Bad - No effort has been made to prevent a present SynchonizationContext from becoming deadlocked.\n    return DoAsyncOperation().GetAwaiter().GetResult();\n}\n\npublic string DoOperationBlocking7()\n{\n    // Bad - Blocking the thread that enters.\n    // Bad - No effort has been made to prevent a present SynchonizationContext from becoming deadlocked.\n    var task = DoAsyncOperation();\n    task.Wait();\n    return task.GetAwaiter().GetResult();\n}\n```\n\n## Prefer `await` over `ContinueWith`\n\n`Task` existed before the async/await keywords were introduced and as such provided ways to execute continuations without relying on the language. Although these methods are still valid to use, we generally recommend that you prefer `async`/`await` to using `ContinueWith`. `ContinueWith` also does not capture the `SynchronizationContext` and as a result is actually semantically different to `async`/`await`.\n\n❌ **BAD** The example uses `ContinueWith` instead of `async`\n\n```C#\npublic Task<int> DoSomethingAsync()\n{\n    return CallDependencyAsync().ContinueWith(task =>\n    {\n        return task.Result + 1;\n    });\n}\n```\n\n:white_check_mark: **GOOD** This example uses the `await` keyword to get the result from `CallDependencyAsync`.\n\n```C#\npublic async Task<int> DoSomethingAsync()\n{\n    var result = await CallDependencyAsync();\n    return result + 1;\n}\n```\n\n## Always create `TaskCompletionSource<T>` with `TaskCreationOptions.RunContinuationsAsynchronously`\n\n`TaskCompletionSource<T>` is an important building block for libraries trying to adapt things that are not inherently awaitable to be awaitable via a `Task`. It is also commonly used to build higher-level operations (such as batching and other combinators) on top of existing asynchronous APIs. By default, `Task` continuations will run *inline* on the same thread that calls Try/Set(Result/Exception/Canceled). As a library author, this means having to understand that calling code can resume directly on your thread. This is extremely dangerous and can result in deadlocks, thread-pool starvation, corruption of state (if code runs unexpectedly) and more. \n\nAlways use `TaskCreationOptions.RunContinuationsAsynchronously` when creating the `TaskCompletionSource<T>`. This will dispatch the continuation onto the thread pool instead of executing it inline.\n\n❌ **BAD** This example does not use `TaskCreationOptions.RunContinuationsAsynchronously` when creating the `TaskCompletionSource<T>`.\n\n```C#\npublic Task<int> DoSomethingAsync()\n{\n    var tcs = new TaskCompletionSource<int>();\n    \n    var operation = new LegacyAsyncOperation();\n    operation.Completed += result =>\n    {\n        // Code awaiting on this task will resume on this thread!\n        tcs.SetResult(result);\n    };\n    \n    return tcs.Task;\n}\n```\n\n:white_check_mark: **GOOD** This example uses `TaskCreationOptions.RunContinuationsAsynchronously` when creating the `TaskCompletionSource<T>`.\n\n```C#\npublic Task<int> DoSomethingAsync()\n{\n    var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);\n    \n    var operation = new LegacyAsyncOperation();\n    operation.Completed += result =>\n    {\n        // Code awaiting on this task will resume on a different thread-pool thread\n        tcs.SetResult(result);\n    };\n    \n    return tcs.Task;\n}\n```\n\n:bulb:**NOTE: There are 2 enums that look alike. [`TaskCreationOptions.RunContinuationsAsynchronously`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions?view=netcore-2.0#System_Threading_Tasks_TaskCreationOptions_RunContinuationsAsynchronously) and [`TaskContinuationOptions.RunContinuationsAsynchronously`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions?view=netcore-2.0). Be careful not to confuse their usage.** \n\n## Always dispose `CancellationTokenSource`(s) used for timeouts\n\n`CancellationTokenSource` objects that are used for timeouts (are created with timers or use the `CancelAfter` method), can put pressure on the timer queue if not disposed.\n\n❌ **BAD** This example does not dispose of the `CancellationTokenSource` and as a result, the timer stays in the queue for 10 seconds after each request is made.\n\n```C#\npublic async Task<Stream> HttpClientAsyncWithCancellationBad()\n{\n    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));\n\n    using (var client = _httpClientFactory.CreateClient())\n    {\n        var response = await client.GetAsync(\"http://backend/api/1\", cts.Token);\n        return await response.Content.ReadAsStreamAsync();\n    }\n}\n```\n\n:white_check_mark: **GOOD** This example disposes of the `CancellationTokenSource` and properly removes the timer from the queue.\n\n```C#\npublic async Task<Stream> HttpClientAsyncWithCancellationGood()\n{\n    using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))\n    {\n        using (var client = _httpClientFactory.CreateClient())\n        {\n            var response = await client.GetAsync(\"http://backend/api/1\", cts.Token);\n            return await response.Content.ReadAsStreamAsync();\n        }\n    }\n}\n```\n\n## Always flow `CancellationToken`(s) to APIs that take a `CancellationToken`\n\nCancellation is cooperative in .NET. Everything in the call chain has to be explicitly passed the `CancellationToken` in order for it to work well. This means you need to explicitly pass the token into other APIs that take a token if you want cancellation to be most effective.\n\n❌ **BAD** This example neglects to pass the `CancellationToken` to `Stream.ReadAsync` making the operation effectively not cancellable.\n\n```C#\npublic async Task<string> DoAsyncThing(CancellationToken cancellationToken = default)\n{\n   byte[] buffer = new byte[1024];\n   // We forgot to pass flow cancellationToken to ReadAsync\n   int read = await _stream.ReadAsync(buffer, 0, buffer.Length);\n   return Encoding.UTF8.GetString(buffer, 0, read);\n}\n```\n\n:white_check_mark: **GOOD** This example passes the `CancellationToken` into `Stream.ReadAsync`.\n\n```C#\npublic async Task<string> DoAsyncThing(CancellationToken cancellationToken = default)\n{\n   byte[] buffer = new byte[1024];\n   // This properly flows cancellationToken to ReadAsync\n   int read = await _stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);\n   return Encoding.UTF8.GetString(buffer, 0, read);\n}\n```\n\n## Cancelling uncancellable operations\n\nOne of the coding patterns that appears when doing asynchronous programming is canceling an uncancellable operation. This usually means creating another task that completes when a timeout or `CancellationToken` fires, and then using `Task.WhenAny` to detect a complete or cancelled operation.\n\n### Using CancellationTokens\n\n❌ **BAD** This example uses `Task.Delay(-1, token)` to create a `Task` that completes when the `CancellationToken` fires, but if it doesn't fire, there's no way to dispose of the `CancellationTokenRegistration` created inside of `Task.Delay`. This can lead to a memory leak.\n\n```C#\npublic static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)\n{\n    // There's no way to dispose of the registration\n    var delayTask = Task.Delay(-1, cancellationToken);\n\n    var resultTask = await Task.WhenAny(task, delayTask);\n    if (resultTask == delayTask)\n    {\n        // Operation cancelled\n        throw new OperationCanceledException();\n    }\n\n    return await task;\n}\n```\n\n:white_check_mark: **GOOD** This example disposes of the `CancellationTokenRegistration` when one of the `Task(s)` is complete.\n\n```C#\npublic static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)\n{\n    var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);\n\n    // This disposes the registration as soon as one of the tasks trigger\n    using (cancellationToken.Register(state =>\n    {\n        ((TaskCompletionSource<object>)state).TrySetResult(null);\n    },\n    tcs))\n    {\n        var resultTask = await Task.WhenAny(task, tcs.Task);\n        if (resultTask == tcs.Task)\n        {\n            // Operation cancelled\n            throw new OperationCanceledException(cancellationToken);\n        }\n\n        return await task;\n    }\n}\n```\n\n:white_check_mark: **GOOD** Prefer [`Task.WaitAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync?view=net-6.0) on .NET >= 6;\n\n### Using a timeout\n\n❌ **BAD** This example does not cancel the timer even if the operation successfully completes. This means you could end up with lots of timers, which can flood the timer queue. \n\n```C#\npublic static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)\n{\n    var delayTask = Task.Delay(timeout);\n\n    var resultTask = await Task.WhenAny(task, delayTask);\n    if (resultTask == delayTask)\n    {\n        // Operation cancelled\n        throw new OperationCanceledException();\n    }\n\n    return await task;\n}\n```\n\n:white_check_mark: **GOOD** This example cancels the timer if the operation successfully completes.\n\n```C#\npublic static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)\n{\n    using (var cts = new CancellationTokenSource())\n    {\n        var delayTask = Task.Delay(timeout, cts.Token);\n\n        var resultTask = await Task.WhenAny(task, delayTask);\n        if (resultTask == delayTask)\n        {\n            // Operation cancelled\n            throw new OperationCanceledException();\n        }\n        else\n        {\n            // Cancel the timer task so that it does not fire\n            cts.Cancel();\n        }\n\n        return await task;\n    }\n}\n```\n\n:white_check_mark: **GOOD** Prefer [`Task.WaitAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync?view=net-6.0) on .NET >= 6;\n\n## Always call `FlushAsync` on `StreamWriter`(s) or `Stream`(s) before calling `Dispose`\n\nWhen writing to a `Stream` or `StreamWriter`, even if the asynchronous overloads are used for writing, the underlying data might be buffered. When data is buffered, disposing the `Stream` or `StreamWriter` via the `Dispose` method will synchronously write/flush, which results in blocking the thread and could lead to thread-pool starvation. Either use the asynchronous `DisposeAsync` method (for example via `await using`) or call `FlushAsync` before calling `Dispose`.\n\n:bulb:**NOTE: This is only problematic if the underlying subsystem does IO.**\n\n❌ **BAD** This example ends up blocking the request by writing synchronously to the HTTP-response body.\n\n```C#\napp.Run(async context =>\n{\n    // The implicit Dispose call will synchronously write to the response body\n    using (var streamWriter = new StreamWriter(context.Response.Body))\n    {\n        await streamWriter.WriteAsync(\"Hello World\");\n    }\n});\n```\n\n:white_check_mark: **GOOD** This example asynchronously flushes any buffered data while disposing the `StreamWriter`.\n\n```C#\napp.Run(async context =>\n{\n    // The implicit AsyncDispose call will flush asynchronously\n    await using (var streamWriter = new StreamWriter(context.Response.Body))\n    {\n        await streamWriter.WriteAsync(\"Hello World\");\n    }\n});\n```\n\n:white_check_mark: **GOOD** This example asynchronously flushes any buffered data before disposing the `StreamWriter`.\n\n```C#\napp.Run(async context =>\n{\n    using (var streamWriter = new StreamWriter(context.Response.Body))\n    {\n        await streamWriter.WriteAsync(\"Hello World\");\n\n        // Force an asynchronous flush\n        await streamWriter.FlushAsync();\n    }\n});\n```\n\n## Prefer `async`/`await` over directly returning `Task`\n\nThere are benefits to using the `async`/`await` keyword instead of directly returning the `Task`:\n- Asynchronous and synchronous exceptions are normalized to always be asynchronous.\n- The code is easier to modify (consider adding a `using`, for example).\n- Diagnostics of asynchronous methods are easier (debugging hangs etc).\n- Exceptions thrown will be automatically wrapped in the returned `Task` instead of surprising the caller with an actual exception.\n- Async locals will not leak out of async methods. If you set an async local in a non-async method, it will \"leak\" out of that call.\n\n❌ **BAD** This example directly returns the `Task` to the caller.\n\n```C#\npublic Task<int> DoSomethingAsync()\n{\n    return CallDependencyAsync();\n}\n```\n\n:white_check_mark: **GOOD** This example uses async/await instead of directly returning the Task.\n\n```C#\npublic async Task<int> DoSomethingAsync()\n{\n    return await CallDependencyAsync();\n}\n```\n\n:bulb:**NOTE: There are performance considerations when using an async state machine over directly returning the `Task`. It's always faster to directly return the `Task` since it does less work but you end up changing the behavior and potentially losing some of the benefits of the async state machine.**\n\n## AsyncLocal\\<T\\>\n\nAsync locals are a way to store/retrieve ambient state throughout an application. This can be a *very* useful alternative to flowing explicit state everywhere, especially through call sites that you do not have much control over. While it is powerful, it is also dangerous if used incorrectly. Async locals are attached to the [execution context](https://docs.microsoft.com/en-us/dotnet/api/system.threading.executioncontext) which flows *everywhere implicitly*. Disabling execution context flow requires the use of advanced APIs (typically prefixed with the Unsafe name). As such, there's very little control over what code will attempt to access these values. \n\n### Creating an AsyncLocal\\<T\\>\n\nIf you can avoid async locals, do so by explicitly passing state around or using techniques like inversion of control.\n\nIf you cannot avoid it, it's best to make sure that anything put into an async local is:\n\n1. Not disposable\n2. Immutable/read-only/thread-safe\n\nLet's look at 2 examples:\n\n1. ❌ **BAD** A disposable object stored in an async local\n\n```C#\nusing (var thing = new DisposableThing())\n{\n    // Make the disposable object available ambiently\n    DisposableThing.Current = thing;\n\n    Dispatch();\n\n    // We're about to dispose the object so make sure nobody else captures this instance\n    DisposableThing.Current = null;\n}\n\nvoid Dispatch()\n{\n    // Task.Run will capture the current execution context (which means async locals are captured in the callback)\n    _ = Task.Run(async () =>\n    {\n        // Delay for a second then log\n        await Task.Delay(1000);\n\n        Log();\n    });\n}\n\nvoid Log()\n{\n    try\n    {\n        // Get the current value and make sure it's not null before reading the value\n        var thing = DisposableThing.Current;\n        if (thing is not null)\n        {\n            Console.WriteLine($\"Logging ambient value {thing.Value}\");\n        }\n    }\n    catch (Exception ex)\n    {\n        Console.WriteLine(ex);\n    }\n}\n\nConsole.ReadLine();\n\nclass DisposableThing : IDisposable\n{\n    private static readonly AsyncLocal<DisposableThing?> _current = new();\n\n    private bool _disposed;\n\n    public static DisposableThing? Current\n    {\n        get => _current.Value;\n        set\n        {\n            _current.Value = value;\n        }\n    }\n\n    public int Value\n    {\n        get\n        {\n            if (_disposed) throw new ObjectDisposedException(GetType().FullName);\n            return 1;\n        }\n    }\n\n    public void Dispose()\n    {\n        _disposed = true;\n    }\n}\n```\n\nThis above example will always result in an `ObjectDisposedException` being thrown. Even though the `Log` method defensively checks for null before logging the value, it has a reference to the disposed of `DisposableThing`. Setting the `AsyncLocal<DisposableThing>` to null does not affect the code inside of `Log`, this is because the execution context is copy on write. This means that all future reads `DisposableThing.Current` will be null, but it won't affect any of the previous reads.\n\nWhen we set `DisposableThing.Current = null;` we are making a new execution context, not mutating the one that was captured by `Task.Run`. To get a better understanding of this run the following code:\n\n```C#\nDisposableThing.Current = new DisposableThing();\n\nConsole.WriteLine(\"After setting thing \" + ExecutionContext.Capture().GetHashCode());\n\nDisposableThing.Current = null;\n\nConsole.WriteLine(\"After setting Current to null \" + ExecutionContext.Capture().GetHashCode());\n```\n\nThe hash code of the execution context is different each time we set a new value.\n\n⚠️ It might be tempting to update the logic in `DisposableThing.Current` to mutate the original execution context instead of setting the async local directly ([StrongBox\\<T\\>](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.strongbox-1) is a reference type that stores the underlying T in a mutable field):\n\n```C#\nclass DisposableThing : IDisposable\n{\n    private static readonly AsyncLocal<StrongBox<DisposableThing?>> _current = new();\n\n    private bool _disposed;\n\n    public static DisposableThing? Current\n    {\n        get => _current.Value?.Value;\n        set\n        {\n            var box = _current.Value;\n            if (box is not null)\n            {\n                // Mutate the value in any execution context that was copied\n                box.Value = null;\n            }\n\n            if (value is not null)\n            {\n                _current.Value = new StrongBox<DisposableThing?>(value);\n            }\n        }\n    }\n\n    public int Value\n    {\n        get\n        {\n            if (_disposed) throw new ObjectDisposedException(GetType().FullName);\n            return 1;\n        }\n    }\n\n    public void Dispose()\n    {\n        _disposed = true;\n    }\n}\n```\n\nThis will have the desired effect and will set the value to null in any execution context that references this async local value.\n\n```C#\nDisposableThing.Current = new DisposableThing();\n\nConsole.WriteLine(\"After setting thing \" + ExecutionContext.Capture().GetHashCode());\n\nDisposableThing.Current = null;\n\nConsole.WriteLine(\"After setting Current to null \" + ExecutionContext.Capture().GetHashCode());\n```\n\n⚠️ While this looks attractive, the reference to `DisposableThing.Current` might have still been captured before the value was set to null:\n\n```C#\nvoid Dispatch()\n{\n    // Task.Run will capture the current execution context (which means async locals are captured in the callback)\n    _ = Task.Run(async () =>\n    {\n        // Get the current reference\n        var current = DisposableThing.Current;\n\n        // Delay for a second then log\n        await Task.Delay(1000);\n\n        Log(current);\n    });\n}\n\nvoid Log(DisposableThing thing)\n{\n    try\n    {\n        Console.WriteLine($\"Logging ambient value {thing.Value}\");\n    }\n    catch (Exception ex)\n    {\n        Console.WriteLine(ex);\n    }\n}\n```\n\nThere's a race condition between the capture of the `DisposableThing`, the disposal of `DisposableThing` and setting `DisposableThing.Current` it to null. In the end, the code is unreliable and may fail at random. Don't store disposable objects in async locals.\n\n2. ❌ **BAD** A non-thread-safe object stored in an async local\n\n```C#\nAmbientValues.Current = new Dictionary<int, string>();\n\nParallel.For(0, 10, i =>\n{\n    AmbientValues.Current[i] = \"processing\";\n    LogCurrentValues();\n    AmbientValues.Current[i] = \"done\";\n});\n\n\nvoid LogCurrentValues()\n{\n    foreach (var pair in AmbientValues.Current)\n    {\n        Console.WriteLine(pair);\n    }\n}\n\nclass AmbientValues\n{\n    private static readonly AsyncLocal<Dictionary<int, string>> _current = new();\n\n    public static Dictionary<int, string> Current\n    {\n        get => _current.Value!;\n        set => _current.Value = value;\n    }\n}\n```\n\nThe above example stores a normal `Dictionary<int, string>` in an async local and does some parallel processing on it. While this may be obvious from the above example, async locals allow arbitrary code on arbitrary threads to access the execution context and thus any async locals associated with said context. As a result, it is important to assume that data can be accessed concurrently and should be made thread-safe as a result.\n\n```C#\nclass AmbientValues\n{\n    private static readonly AsyncLocal<ConcurrentDictionary<int, string>> _current = new();\n\n    public static ConcurrentDictionary<int, string> Current\n    {\n        get => _current.Value!;\n        set => _current.Value = value;\n    }\n}\n```\n\n:white_check_mark: **GOOD** The above uses a `ConcurrentDictionary<int, string>` which is thread safe.\n\n### Don't leak your AsyncLocal\\<T\\>\n\nAsync locals flow across awaits automatically and can be captured by any API that explicitly calls `ExecutionContext.Capture`. The latter can lead to memory leaks in certain situations.\n\n#### Common APIs that capture the ExecutionContext\n\nAPIs that run user callbacks usually capture the current execution context in order to preserve async locals between callback registration and execution. Here are examples of some APIs that do this:\n\n- `Timer`\n- `CancellationToken.Register`\n- `new FileSystemWatcher`\n- `SocketAsyncEventArgs`\n- `Task.Run`\n- `ThreadPool.QueueUserWorkItem`\n\n❌ **BAD** Here's an example of an execution context leak that causes memory pressure because of a lifetime mismatch between the API capturing the execution context, and the lifetime of the data stored in the async local.\n\n```C#\nusing System.Collections.Concurrent;\n\n// Singleton cache\nvar cache = new NumberCache(TimeSpan.FromHours(1));\n\nvar executionContext = ExecutionContext.Capture();\n\n// Simulate 10000 concurrent requests\nParallel.For(0, 10000, i =>\n{\n    // Restore the initial ExecutionContext per \"request\"\n    ExecutionContext.Restore(executionContext!);\n\n    ChunkyObject.Current = new ChunkyObject();\n\n    cache.Add(i);\n});\n\nConsole.WriteLine(\"Before GC: \" + BytesAsString(GC.GetGCMemoryInfo().HeapSizeBytes));\nConsole.ReadLine();\n\nGC.Collect();\nGC.WaitForPendingFinalizers();\n\nConsole.WriteLine(\"After GC: \" + BytesAsString(GC.GetGCMemoryInfo().HeapSizeBytes));\nConsole.ReadLine();\n\nstatic string BytesAsString(long bytes)\n{\n    string[] suffix = { \"B\", \"KB\", \"MB\", \"GB\", \"TB\" };\n    int i;\n    double doubleBytes = 0;\n\n    for (i = 0; bytes / 1024 > 0; i++, bytes /= 1024)\n    {\n        doubleBytes = bytes / 1024.0;\n    }\n\n    return string.Format(\"{0:0.00} {1}\", doubleBytes, suffix[i]);\n}\n\npublic class NumberCache\n{\n    private readonly ConcurrentDictionary<int, CancellationTokenSource> _cache = new ConcurrentDictionary<int, CancellationTokenSource>();\n    private TimeSpan _timeSpan;\n\n    public NumberCache(TimeSpan timeSpan)\n    {\n        _timeSpan = timeSpan;\n    }\n\n    public void Add(int key)\n    {\n        var cts = _cache.GetOrAdd(key, _ => new CancellationTokenSource());\n        // Delete entry on expiration\n        cts.Token.Register((_, _) => _cache.TryRemove(key, out _), null);\n\n        // Start count down\n        cts.CancelAfter(_timeSpan);\n    }\n}\n\nclass ChunkyObject\n{\n    private static readonly AsyncLocal<ChunkyObject?> _current = new();\n\n    // Stores lots of data (but it should be gen0)\n    private readonly string _data = new string('A', 1024 * 32);\n\n    public static ChunkyObject? Current\n    {\n        get => _current.Value;\n        set => _current.Value = value;\n    }\n\n    public string Data => _data;\n}\n```\n\nThe above example has a singleton `NumberCache` that stores numbers for an hour. We have a `ChunkyObject` which stores a 32K string in a field, and has an async local so that any code running may access the current `ChunkyObject`. This object should be collected when the `GC` runs, but instead, we're implicitly capturing the `ChunkyObject` in the `NumberCache` via `CancellationToken.Register`. \n\n**Instead of just caching the number and a `CancellationTokenSource`, we're implicitly capturing and storing all async locals attached to the current execution context for an hour!**\n\nTry running the sample locally. Running this on my machine reports numbers like this:\n\n```\nBefore GC: 654.65 MB\nAfter GC: 659.68 MB\n```\n\nHere's a look at the heap with those objects. You can see we have stored 10,000 ChunkyObjects, strings rooted by those chunky objects. The object graph looks like\nCancellationTokenSource -> ExecutionContext -> AsyncLocalValueMap -> ChunkObject -> string.\n\n<img width=\"758\" alt=\"image\" src=\"https://user-images.githubusercontent.com/95136/188351756-967f3d37-b302-49d3-ba04-595433c6949c.png\">\n\n\nWith one small tweak to this code, we can avoid the implicit execution context capture.\n\n:white_check_mark: **GOOD** Use `CancellationToken.UnsafeRegister` to avoid capturing the execution context and any async locals as part of the `NumberCache`:\n\n```C#\npublic class NumberCache\n{\n    private readonly ConcurrentDictionary<int, CancellationTokenSource> _cache = new ConcurrentDictionary<int, CancellationTokenSource>();\n    private TimeSpan _timeSpan;\n\n    public NumberCache(TimeSpan timeSpan)\n    {\n        _timeSpan = timeSpan;\n    }\n\n    public void Add(int key)\n    {\n        var cts = _cache.GetOrAdd(key, _ => new CancellationTokenSource());\n        // Delete entry on expiration\n        cts.Token.UnsafeRegister((_, _) => _cache.TryRemove(key, out _), null);\n\n        // Start count down\n        cts.CancelAfter(_timeSpan);\n    }\n}\n```\n\nThe GC numbers after this change:\n\n```\nBefore GC: 10.32 MB\nAfter GC: 5.10 MB\n```\n\nThe heap looks like we'd expect. There's no execution context capture, so the `ChunkyObject` isn't stored.\n\n<img width=\"752\" alt=\"image\" src=\"https://user-images.githubusercontent.com/95136/188352462-d7d627c6-e4e0-4487-b783-30880cc4916f.png\">\n\n\n:bulb: **NOTE: You have NO control over how APIs decide to store the execution context, but with this understanding, you should be able to minimize memory leaks by clearing the memory using the technique described in [Creating an AsyncLocal\\<T\\>](#creating-an-asynclocalt) section.**\n\n```C#\nusing System.Collections.Concurrent;\n\n// Singleton cache\nvar cache = new NumberCache(TimeSpan.FromHours(1));\n\nvar executionContext = ExecutionContext.Capture();\n\n// Simulate 10000 concurrent requests\nParallel.For(0, 10000, i =>\n{\n    // Restore the initial ExecutionContext per \"request\"\n    ExecutionContext.Restore(executionContext!);\n\n    ChunkyObject.Current = new ChunkyObject();\n\n    cache.Add(i);\n\n    // Null out the chunky object so the GC can release the memory\n    ChunkyObject.Current = default;\n});\n\nclass ChunkyObject\n{\n    private static readonly AsyncLocal<StrongBox<ChunkyObject?>> _current = new();\n\n    // Stores lots of data (but it should be gen0)\n    private readonly string _data = new string('A', 1024 * 32);\n\n    public static ChunkyObject? Current\n    {\n        get => _current.Value?.Value;\n        set\n        {\n            var box = _current.Value;\n            if (box is not null)\n            {\n                // Mutate the value in any execution context that was copied\n                box.Value = null;\n            }\n\n            if (value is not null)\n            {\n                _current.Value = new StrongBox<ChunkyObject?>(value);\n            }\n        }\n    }\n\n    public string Data => _data;\n}\n```\n\nThis technique reduces the heap memory **significantly**:\n\n```\nBefore GC: 7.91 MB\nAfter GC: 5.66 MB\n```\n\nThe execution context is storing `StrongBox<ChunkyObject>` with a null reference to the `ChunkyObject`. This is technically still a \"leak\" but we've reduced the impact significantly. Here's a look at the memory profile showing objects with 10,000 allocations (the number of requests we created). You can see the GC has collected `ChunkObject` instances but there are still 10,000 references to `StrongBox<ChunkyObject>`.\n\n<img width=\"760\" alt=\"image\" src=\"https://user-images.githubusercontent.com/95136/188351308-b174f843-0435-46db-8f31-b4d78c740947.png\">\n\n### Avoid setting AsyncLocal\\<T\\> values outside of async methods\n\nAsync methods have a special behavior for async locals that makes sure values do not propagate outside of the async method.\n\n❌ **BAD** Avoid setting async local values outside of async methods:\n\n```C#\nvar local = new AsyncLocal<int>();\nMethodA();\nConsole.WriteLine(local.Value);\n\nvoid MethodA()\n{\n    local.Value = 1;\n    MethodB();\n    Console.WriteLine(local.Value);\n}\n\nvoid MethodB()\n{\n    local.Value = 2;\n    Console.WriteLine(local.Value);\n}\n```\n\nThe above prints 2, 2, 2. The execution context mutations are being propagated outside of the method. This can lead to extremely confusing behavior and hard-to-track down bugs.\n\n:white_check_mark: **GOOD** Set async locals in async methods:\n\n```C#\nvar local = new AsyncLocal<int>();\nawait MethodA();\nConsole.WriteLine(local.Value);\n\nasync Task MethodA()\n{\n    local.Value = 1;\n    await MethodB();\n    Console.WriteLine(local.Value);\n}\n\nasync Task MethodB()\n{\n    local.Value = 2;\n    Console.WriteLine(local.Value);\n}\n```\n\nThe above will print 2, 1, 0. This is because the async method restores the original execution context on exit.\n\n## ConfigureAwait\n\nTBD\n\n# Scenarios\n\nThe above tries to distill general guidance but doesn't do justice to the kinds of real-world situations that cause code like this to be written in the first place (bad code). This section tries to take concrete examples from real applications and turn them into something simple to help you relate these problems to existing codebases.\n\n## `Timer` callbacks\n\n❌ **BAD** The `Timer` callback is `void`-returning and we have asynchronous work to execute. This example uses `async void` to accomplish it and as a result, can crash the process if an exception occurs.\n\n```C#\npublic class Pinger\n{\n    private readonly Timer _timer;\n    private readonly HttpClient _client;\n    \n    public Pinger(HttpClient client)\n    {\n        _client = client;\n        _timer = new Timer(Heartbeat, null, 1000, 1000);\n    }\n\n    public async void Heartbeat(object state)\n    {\n        await _client.GetAsync(\"http://mybackend/api/ping\");\n    }\n}\n```\n\n❌ **BAD** This attempts to block the `Timer` callback. This may result in thread-pool starvation and is an example of [sync over async](#warning-sync-over-async)\n\n```C#\npublic class Pinger\n{\n    private readonly Timer _timer;\n    private readonly HttpClient _client;\n    \n    public Pinger(HttpClient client)\n    {\n        _client = client;\n        _timer = new Timer(Heartbeat, null, 1000, 1000);\n    }\n\n    public void Heartbeat(object state)\n    {\n        _client.GetAsync(\"http://mybackend/api/ping\").GetAwaiter().GetResult();\n    }\n}\n```\n\n:white_check_mark: **GOOD** This example uses an `async Task`-based method and discards the `Task` in the `Timer` callback. If this method fails, it will not crash the process. Instead, it will fire the [`TaskScheduler.UnobservedTaskException`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler.unobservedtaskexception) event.\n\n```C#\npublic class Pinger\n{\n    private readonly Timer _timer;\n    private readonly HttpClient _client;\n    \n    public Pinger(HttpClient client)\n    {\n        _client = client;\n        _timer = new Timer(Heartbeat, null, 1000, 1000);\n    }\n\n    public void Heartbeat(object state)\n    {\n        // Discard the result\n        _ = DoAsyncPing();\n    }\n\n    private async Task DoAsyncPing()\n    {\n        await _client.GetAsync(\"http://mybackend/api/ping\");\n    }\n}\n```\n\n:white_check_mark: **GOOD** This example uses the new [`PeriodicTimer`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.periodictimer) introduced in .NET 6:\n\n```C#\npublic class Pinger : IDisposable\n{\n    private readonly PeriodicTimer _timer;\n    private readonly HttpClient _client;\n\n    public Pinger(HttpClient client)\n    {\n        _client = client;\n        _timer = new PeriodicTimer(TimeSpan.FromSeconds(1));\n        _ = Task.Run(DoAsyncPings);\n    }\n\n    public void Dispose()\n    {\n        _timer.Dispose();\n    }\n\n    private async Task DoAsyncPings()\n    {\n        while (await _timer.WaitForNextTickAsync())\n        {\n            // TODO: Handle exceptions\n            await _client.GetAsync(\"http://mybackend/api/ping\");\n        }\n    }\n}\n```\n\n## Implicit `async void` delegates\n\nImagine a `BackgroundQueue` with a `FireAndForget` that takes a callback. This method will execute the callback at some time in the future.\n\n❌ **BAD** This will force callers to either block in the callback or use an `async void` delegate.\n\n```C#\npublic class BackgroundQueue\n{\n    public static void FireAndForget(Action action) { }\n}\n```\n\n❌ **BAD** This calling code is creating an `async void` method implicitly. The compiler fully supports this today.\n\n```C#\npublic class Program\n{\n    public void Main(string[] args)\n    {\n        var httpClient = new HttpClient();\n        BackgroundQueue.FireAndForget(async () =>\n        {\n            await httpClient.GetAsync(\"http://pinger/api/1\");\n        });\n        \n        Console.ReadLine();\n    }\n}\n```\n\n:white_check_mark: **GOOD** This BackgroundQueue implementation offers both sync and `async` callback overloads.\n\n```C#\npublic class BackgroundQueue\n{\n    public static void FireAndForget(Action action) { }\n    public static void FireAndForget(Func<Task> action) { }\n}\n```\n\n## `ConcurrentDictionary.GetOrAdd`\n\nIt's pretty common to cache the result of an asynchronous operation and `ConcurrentDictionary` is a good data structure for doing that. `GetOrAdd` is a convenience API for trying to get an item if it's already there or adding it if it isn't. The callback is synchronous so it's tempting to write code that uses `Task.Result` to produce the value of an asynchronous process but that can lead to thread-pool starvation.\n\n❌ **BAD** This may result in thread-pool starvation since we're blocking the request thread if the person data is not cached.\n\n```C#\npublic class PersonController : Controller\n{\n   private AppDbContext _db;\n   \n   // This cache needs expiration\n   private static ConcurrentDictionary<int, Person> _cache = new ConcurrentDictionary<int, Person>();\n   \n   public PersonController(AppDbContext db)\n   {\n      _db = db;\n   }\n   \n   public IActionResult Get(int id)\n   {\n       var person = _cache.GetOrAdd(id, (key) => _db.People.FindAsync(key).Result);\n       return Ok(person);\n   }\n}\n```\n\n:white_check_mark: **GOOD** This implementation won't result in thread-pool starvation since we're storing a task instead of the result itself.\n\n:warning: `ConcurrentDictionary.GetOrAdd`, when accessed concurrently, may run the value-constructing delegate multiple times. This can result in needlessly kicking off the same potentially expensive computation multiple times.\n\n```C#\npublic class PersonController : Controller\n{\n   private AppDbContext _db;\n   \n   // This cache needs expiration\n   private static ConcurrentDictionary<int, Task<Person>> _cache = new ConcurrentDictionary<int, Task<Person>>();\n   \n   public PersonController(AppDbContext db)\n   {\n      _db = db;\n   }\n   \n   public async Task<IActionResult> Get(int id)\n   {\n       var person = await _cache.GetOrAdd(id, (key) => _db.People.FindAsync(key));\n       return Ok(person);\n   }\n}\n```\n\n:white_check_mark: **GOOD** This implementation prevents the delegate from being executed multiple times, by using the `async` lazy pattern: even if construction of the AsyncLazy instance happens multiple times (\"cheap\" operation), the delegate will be called only once.\n\n```C#\npublic class PersonController : Controller\n{\n   private AppDbContext _db;\n   \n   // This cache needs expiration\n   private static ConcurrentDictionary<int, AsyncLazy<Person>> _cache = new ConcurrentDictionary<int, AsyncLazy<Person>>();\n   \n   public PersonController(AppDbContext db)\n   {\n      _db = db;\n   }\n   \n   public async Task<IActionResult> Get(int id)\n   {\n       var person = await _cache.GetOrAdd(id, (key) => new AsyncLazy<Person>(() => _db.People.FindAsync(key))).Value;\n       return Ok(person);\n   }\n   \n   private class AsyncLazy<T> : Lazy<Task<T>>\n   {\n      public AsyncLazy(Func<Task<T>> valueFactory) : base(valueFactory)\n      {\n      }\n   }\n}\n```\n\n## Constructors\n\nConstructors are synchronous. If you need to initialize some logic that may be asynchronous, there are a couple of patterns for dealing with this.\n\nHere's an example of using a client API that needs to connect asynchronously before use.\n\n```C#\npublic interface IRemoteConnectionFactory\n{\n   Task<IRemoteConnection> ConnectAsync();\n}\n\npublic interface IRemoteConnection\n{\n    Task PublishAsync(string channel, string message);\n    Task DisposeAsync();\n}\n```\n\n\n❌ **BAD** This example uses `Task.Result` to get the connection in the constructor. This could lead to thread-pool starvation and deadlocks.\n\n```C#\npublic class Service : IService\n{\n    private readonly IRemoteConnection _connection;\n    \n    public Service(IRemoteConnectionFactory connectionFactory)\n    {\n        _connection = connectionFactory.ConnectAsync().Result;\n    }\n}\n```\n\n:white_check_mark: **GOOD** This implementation uses a static factory pattern in order to allow asynchronous construction:\n\n```C#\npublic class Service : IService\n{\n    private readonly IRemoteConnection _connection;\n\n    private Service(IRemoteConnection connection)\n    {\n        _connection = connection;\n    }\n\n    public static async Task<Service> CreateAsync(IRemoteConnectionFactory connectionFactory)\n    {\n        return new Service(await connectionFactory.ConnectAsync());\n    }\n}\n```\n\n## WindowsIdentity.RunImpersonated\n\nThis API runs the specified action as the impersonated Windows identity. An [asynchronous version of the callback](https://docs.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonatedasync) was introduced in .NET 5.0.\n\n❌ **BAD** This example tries to execute the query asynchronously, and then wait for it outside of the call to `RunImpersonated`. This will throw because the query might be executing outside of the impersonation context.\n\n```C#\npublic async Task<IEnumerable<Product>> GetDataImpersonatedAsync(SafeAccessTokenHandle safeAccessTokenHandle)\n{\n    Task<IEnumerable<Product>> products = null;\n    WindowsIdentity.RunImpersonated(\n        safeAccessTokenHandle,\n        context =>\n        {\n            products = _db.QueryAsync(\"SELECT Name from Products\");\n        });\n    return await products;\n}\n```\n\n❌ **BAD** This example uses `Task.Result` to execute the query synchronously (sync over async). This could lead to thread-pool starvation and deadlocks.\n\n```C#\npublic IEnumerable<Product> GetDataImpersonated(SafeAccessTokenHandle safeAccessTokenHandle)\n{\n    return WindowsIdentity.RunImpersonated(\n        safeAccessTokenHandle,\n        context => _db.QueryAsync(\"SELECT Name from Products\").Result);\n}\n```\n\n:white_check_mark: **GOOD** This example awaits the result of `RunImpersonated` (the delegate is `Func<Task<IEnumerable<Product>>>` in this case). It is the recommended practice in frameworks earlier than .NET 5.0.\n\n```C#\npublic async Task<IEnumerable<Product>> GetDataImpersonatedAsync(SafeAccessTokenHandle safeAccessTokenHandle)\n{\n    return await WindowsIdentity.RunImpersonated(\n        safeAccessTokenHandle, \n        context => _db.QueryAsync(\"SELECT Name from Products\"));\n}\n```\n\n:white_check_mark: **GOOD** This example uses the asynchronous `RunImpersonatedAsync` function and awaits its result. It is available in .NET 5.0 or newer.\n\n```C#\npublic async Task<IEnumerable<Product>> GetDataImpersonatedAsync(SafeAccessTokenHandle safeAccessTokenHandle)\n{\n    return await WindowsIdentity.RunImpersonatedAsync(\n        safeAccessTokenHandle, \n        context => _db.QueryAsync(\"SELECT Name from Products\"));\n}\n```\n"
  },
  {
    "path": "Gotchas.md",
    "content": "\n"
  },
  {
    "path": "Guidance.md",
    "content": "# Common Pitfalls writing scalable services in ASP.NET Core\n\nThis document serves as a guide for writing scalable services in ASP.NET Core. Some of the guidance is general purpose but will be explained through the lens of writing \nweb services. The examples shown here are based on experiences with customer applications and issues found on Github and Stack Overflow.\n\n- [General ASP.NET Core](AspNetCoreGuidance.md)\n- [Asynchronous Programming](AsyncGuidance.md)\n- [.NET API Gotchas](Gotchas.md)\n"
  },
  {
    "path": "HttpClientGuidance.md",
    "content": "# Table of contents\n - [Using HttpClient](#using-httpclient)\n - [Different platform implementations](#different-platform-implementations)\n - [A note about WebClient](#a-note-about-webclient)\n   \n## Using HttpClient\n\n[HttpClient](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0) is the primary API for making outbound HTTP requests in .NET. \n\n## Different Platform Implementations\n\n`HttpClient` is a wrapper API around an `HttpMessageHandler`. The most inner `HttpMessageHandler` is the one that's responsible for making the HTTP request. There are several implementations on various .NET platforms. This document is focused on server applications and will focus on 2-3 main implementations:\n- HttpClientHandler/WebRequestHandler on .NET Framework\n- SocketHttpHandler on .NET Core/5\n- WinHttpHandler on .NET Framework or .NET Core/5 (runs on both but is Windows-specific)\n\n## A note about WebClient\n\nWebClient is considered a legacy .NET API at this point and has been completely superseded by HttpClient. New code should be written with HttpClient.\n\n❌ **BAD** This example uses the legacy WebClient to make a synchronous HTTP request.\n\n```C#\npublic string DoSomethingAsync()\n{\n    var client = new WebClient();\n    return client.DownloadString(\"http://www.google.com\");\n}\n```\n\n:white_check_mark: **GOOD** This example uses an HttpClient to asynchronously make an HTTP request.\n\n```C#\nstatic readonly HttpClient client = new HttpClient();\n\npublic async Task<string> DoSomethingAsync()\n{\n    return await client.GetStringAsync(\"http://www.google.com\");\n}\n```\n"
  },
  {
    "path": "README.md",
    "content": "﻿# ASP.NET Core Diagnostic Scenarios\r\n \r\nThe goal of this repository is to show problematic application patterns for ASP.NET Core applications and a walk-through on how to solve those issues.\r\nIt shall serve as a collection of knowledge from real-life application issues our customers have encountered.\r\n\r\n## Common Pitfalls Writing scalable services in ASP.NET Core\r\n\r\nNext, you can find some guides for writing scalable services in ASP.NET Core. Some of the guidance is general purpose but will be explained through the lens of writing web services. \r\n\r\n- [General ASP.NET Core](AspNetCoreGuidance.md)\r\n- [Asynchronous Programming](AsyncGuidance.md)\r\n\r\n*NOTE:* The examples shown here are based on experiences with customer applications and issues found on Github and Stack Overflow.\r\n\r\n### All Thanks to Our Contributors:\r\n<a href=\"https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/graphs/contributors\">\r\n  <img src=\"https://contrib.rocks/image?repo=davidfowl/AspNetCoreDiagnosticScenarios\" />\r\n</a>\r\n"
  },
  {
    "path": "Scenarios/Controllers/AsyncFactoryController.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Scenarios.Services;\n\nnamespace Scenarios.Controllers\n{\n    public class AsyncFactoryController : Controller\n    {\n        /// <summary>\n        /// This action is problematic because it tries to resolve the remote connection from the DI container\n        /// which requires an asynchronous operation. See Startup.cs for the registration of RemoteConnection.\n        /// </summary>\n        [HttpGet(\"/async-di-1\")]\n        public async Task<IActionResult> PublishAsync([FromServices]RemoteConnection remoteConnection)\n        {\n            await remoteConnection.PublishAsync(\"group\", \"hello\");\n\n            return Accepted();\n        }\n\n        [HttpGet(\"/async-di-2\")]\n        public async Task<IActionResult> PublishAsync([FromServices]RemoteConnectionFactory remoteConnectionFactory)\n        {\n            // This doesn't have the dead lock issue but it makes a new connection every time\n            var connection = await remoteConnectionFactory.ConnectAsync();\n\n            await connection.PublishAsync(\"group\", \"hello\");\n\n            // Dispose the connection we created\n            await connection.DisposeAsync();\n\n            return Accepted();\n        }\n\n        [HttpGet(\"/async-di-3\")]\n        public async Task<IActionResult> PublishAsync([FromServices]LoggingRemoteConnection remoteConnection)\n        {\n            // This doesn't have the dead lock issue but it makes a new connection every time\n            await remoteConnection.PublishAsync(\"group\", \"hello\");\n\n            return Accepted();\n        }\n\n        /// <summary>\n        /// This is the cleanest pattern for dealing with async construction. The implementation of the connection is a bit\n        /// more complicated but consumption looks like the first method that takes RemoteConnection and it is actually safe.\n        /// </summary>\n        [HttpGet(\"/async-di-4\")]\n        public async Task<IActionResult> PublishAsync([FromServices]LazyRemoteConnection remoteConnection)\n        {\n            await remoteConnection.PublishAsync(\"group\", \"hello\");\n\n            return Accepted();\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/AsyncOperationController.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Scenarios.Services;\n\nnamespace Scenarios.Controllers\n{\n    /// <summary>\n    /// This controller shows to various ways people attempt to blocking code over an async API. There is no \n    /// good way to turn asynchronous code into synchronous code. All of these blocking calls can cause thread pool starvation.\n    /// </summary>\n    public class AsyncOperationController : Controller\n    {\n        [HttpGet(\"/async-1\")]\n        public IActionResult BadBlocking1()\n        {\n            var service = new LegacyService();\n\n            var result = service.DoOperationBlocking();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-2\")]\n        public IActionResult BadBlocking2()\n        {\n            var service = new LegacyService();\n\n            var result = service.DoOperationBlocking2();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-3\")]\n        public IActionResult BadBlocking3()\n        {\n            var service = new LegacyService();\n\n            var result = service.DoOperationBlocking3();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-4\")]\n        public IActionResult BadBlocking4()\n        {\n            var service = new LegacyService();\n\n            var result = service.DoOperationBlocking4();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-5\")]\n        public IActionResult BadBlocking5()\n        {\n            var service = new LegacyService();\n\n            var result = service.DoOperationBlocking5();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-6\")]\n        public IActionResult BadBlocking6()\n        {\n            var service = new LegacyService();\n\n            var result = service.DoOperationBlocking6();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-7\")]\n        public IActionResult BadBlocking7()\n        {\n            var service = new LegacyService();\n\n            var result = service.DoOperationBlocking7();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-8\")]\n        public async Task<IActionResult> BadBlocking8()\n        {\n            var service = new LegacyService();\n\n            var result = await service.DoAsyncOverSyncOperation();\n\n            return Ok(result);\n        }\n\n        /// <summary>\n        /// DoSyncOperationWithAsyncReturn has an async API over a synchronous call.\n        /// </summary>\n        [HttpGet(\"/async-9\")]\n        public async Task<IActionResult> GoodBlocking()\n        {\n            var service = new LegacyService();\n\n            var result = await service.DoSyncOperationWithAsyncReturn();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-10\")]\n        public async Task<IActionResult> AsyncCall()\n        {\n            var service = new LegacyService();\n\n            var result = await service.DoAsyncOperation();\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-11\")]\n        public async Task<IActionResult> AsyncCallLegacyBad()\n        {\n            var service = new LegacyService();\n\n            var result = await service.DoAsyncOperationOverLegacyBad(HttpContext.RequestAborted);\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/async-12\")]\n        public async Task<IActionResult> AsyncCallLegacyGood()\n        {\n            var service = new LegacyService();\n\n            var result = await service.DoAsyncOperationOverLegacy(HttpContext.RequestAborted);\n\n            return Ok(result);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/AsyncVoidController.cs",
    "content": "﻿using System;\nusing System.Net.WebSockets;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace Scenarios.Controllers\n{\n    public class AsyncVoidController : Controller\n    {\n        [HttpGet(\"/async-void-1\")]\n        public async void Get()\n        {\n            await Task.Delay(1000);\n\n            // THIS will crash the process since we're writing after the response has completed on a background thread\n            await Response.WriteAsync(\"Hello World\");\n        }\n\n        [HttpGet(\"/async-void-2\")]\n        public async Task BrokenWebSockets()\n        {\n            if (HttpContext.WebSockets.IsWebSocketRequest)\n            {\n                var ws = await HttpContext.WebSockets.AcceptWebSocketAsync();\n\n                // This is broken because we're not holding the request open until the WebSocket is closed!\n                _ = Echo(ws);\n            }\n        }\n\n        private async Task Echo(WebSocket webSocket)\n        {\n            var buffer = new byte[1024 * 4];\n            var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);\n            while (!result.CloseStatus.HasValue)\n            {\n                await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);\n\n                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);\n            }\n            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/BigJsonInputController.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.WebUtilities;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Linq;\nusing Scenarios.Model;\n\nnamespace Scenarios.Controllers\n{\n    public class BigJsonInputController : Controller\n    {\n        [HttpPost(\"/big-json-input-1\")]\n        public IActionResult BigJsonSynchronousInput()\n        {\n            // This synchronously reads the entire http request body into memory and it has several problems:\n            // 1. If the request is large it could lead to out of memory problems which can result in a Denial Of Service.\n            // 2. If the client is slowly uploading, we're doing sync over async because Kestrel does *NOT* support synchronous reads.\n            var json = new StreamReader(Request.Body).ReadToEnd();\n\n            var rootobject = JsonConvert.DeserializeObject<PokemonData>(json);\n\n            return Accepted();\n        }\n\n        /// <summary>\n        /// This uses MVC's built in model binding to create the PokemonData object. This is the most preferred approach as it handles all of the \n        /// correct buffering on your behalf.\n        /// </summary>\n        [HttpPost(\"/big-json-input-2\")]\n        public IActionResult BigJsonInput([FromBody]PokemonData rootobject)\n        {   \n            return Accepted();\n        }\n\n        [HttpPost(\"/big-json-input-3\")]\n        public async Task<IActionResult> BigContentBad()\n        {\n            // This asynchronously reads the entire http request body into memory. It still suffers from the Denial Of Service\n            // issue if the request body is too large but there's no threading issue.\n            var json = await new StreamReader(Request.Body).ReadToEndAsync();\n\n            var rootobject = JsonConvert.DeserializeObject<PokemonData>(json);\n\n            return Accepted();\n        }\n\n        [HttpPost(\"/big-json-input-4\")]\n        public async Task<IActionResult> BigContentNewtonsofJsonManualGood()\n        {\n            var streamReader = new HttpRequestStreamReader(Request.Body, Encoding.UTF8);\n\n            var jsonReader = new JsonTextReader(streamReader);\n            var serializer = new JsonSerializer();\n\n            // This asynchronously reads the entire payload into a JObject then turns it into the real object.\n            var obj = await JToken.ReadFromAsync(jsonReader);\n            var rootobject = obj.ToObject<PokemonData>(serializer);\n\n            return Accepted();\n        }\n\n        [HttpPost(\"/big-json-input-5\")]\n        public async Task<IActionResult> BigContentSystemTextJsonManualGood()\n        {\n            var rootobject = System.Text.Json.JsonSerializer.DeserializeAsync<PokemonData>(Request.Body);\n\n            return Accepted();\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/BigJsonOutputController.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Scenarios.Services;\n\nnamespace Scenarios.Controllers\n{\n    public class BigJsonOutputController : Controller\n    {\n        private readonly PokemonService _pokemonService;\n        public BigJsonOutputController(PokemonService pokemonService)\n        {\n            _pokemonService = pokemonService;\n        }\n\n        [HttpGet(\"/big-json-content-1\")]\n        public async Task<IActionResult> BigContentJsonBad()\n        {\n            var obj = await _pokemonService.GetPokemonBufferdStringAsync();\n\n            return Ok(obj);\n        }\n\n        [HttpGet(\"/big-json-content-2\")]\n        public async Task<IActionResult> BigContentJsonGood()\n        {\n            var obj = await _pokemonService.GetPokemonAsync();\n\n            return Ok(obj);\n        }\n\n        [HttpGet(\"/big-json-content-3\")]\n        public async Task<IActionResult> BigContentJsonManualUnbufferedBad()\n        {\n            var obj = await _pokemonService.GetPokemonManualUnbufferedBadAsync();\n\n            return Ok(obj);\n        }\n\n        [HttpGet(\"/big-json-content-4\")]\n        public async Task<IActionResult> BigContentJsonManualUnbufferedGood()\n        {\n            var obj = await _pokemonService.GetPokemonManualUnbufferedGoodAsync();\n            return Ok(obj);\n        }\n\n        [HttpGet(\"/big-json-content-5\")]\n        public async Task<IActionResult> BigContentJsonManualBuffered()\n        {\n            var obj = await _pokemonService.GetPokemonManualBufferedAsync();\n            return Ok(obj);\n        }\n\n        [HttpGet(\"/big-json-content-6\")]\n        public async Task<IActionResult> BigContentJsonNewJson()\n        {\n            var obj = await _pokemonService.GetPokemonAsyncNewJson();\n            return Ok(obj);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/CancelAsyncOperationController.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Net.Http;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace Scenarios.Controllers\n{\n    public class CancelAsyncOperationController : Controller\n    {\n        private readonly string _url = \"https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json\";\n\n        [HttpGet(\"/cancellation-1\")]\n        public async Task<Stream> HttpClientAsyncWithCancellationBad([FromServices]IHttpClientFactory clientFactory)\n        {\n            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));\n\n            using (var client = clientFactory.CreateClient())\n            {\n                var response = await client.GetAsync(_url, cts.Token);\n                return await response.Content.ReadAsStreamAsync();\n            }\n        }\n\n        [HttpGet(\"/cancellation-2\")]\n        public async Task<Stream> HttpClientAsyncWithCancellationBetter([FromServices]IHttpClientFactory clientFactory)\n        {\n            using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))\n            {\n                using (var client = clientFactory.CreateClient())\n                {\n                    var response = await client.GetAsync(_url, cts.Token);\n                    return await response.Content.ReadAsStreamAsync();\n                }\n            }\n        }\n\n        [HttpGet(\"/cancellation-3\")]\n        public async Task<Stream> HttpClientAsyncWithCancellationBest([FromServices]IHttpClientFactory clientFactory)\n        {\n            // This has the timeout configured in Startup\n            using (var client = clientFactory.CreateClient(\"timeout\"))\n            {\n                return await client.GetStreamAsync(_url);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/CancelLegacyOperationController.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Scenarios.Services;\n\nnamespace Scenarios.Controllers\n{\n    public class CancelLegacyOperationController : Controller\n    {\n        [HttpGet(\"/legacy-cancellation-1\")]\n        public async Task<IActionResult> LegacyCancellationWithTimeoutBad()\n        {\n            var service = new LegacyService();\n            var timeout = TimeSpan.FromSeconds(10);\n\n            var result = await service.DoAsyncOperation().TimeoutAfterBad(timeout);\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/legacy-cancellation-2\")]\n        public async Task<IActionResult> LegacyCancellationWithTimeoutGood()\n        {\n            var service = new LegacyService();\n            var timeout = TimeSpan.FromSeconds(10);\n\n            var result = await service.DoAsyncOperation().TimeoutAfter(timeout);\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/legacy-cancellation-3\")]\n        public async Task<IActionResult> LegacyCancellationWithCancellationBad()\n        {\n            var service = new LegacyService();\n\n            var result = await service.DoAsyncOperation().WithCancellationBad(HttpContext.RequestAborted);\n\n            return Ok(result);\n        }\n\n        [HttpGet(\"/legacy-cancellation-4\")]\n        public async Task<IActionResult> LegacyCancellationWithCancellationGood()\n        {\n            var service = new LegacyService();\n\n            var result = await service.DoAsyncOperation().WithCancellation(HttpContext.RequestAborted);\n\n            return Ok(result);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/EmphemeralOperationController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Scenarios.Services;\n\n// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860\n\nnamespace Scenarios.Controllers\n{\n    public class EmphemeralOperationController : Controller\n    {\n        [HttpGet(\"/timer-1\")]\n        public IActionResult TimerLeak()\n        {\n            var operation = new EphemeralOperation();\n            return Ok();\n        }\n\n        [HttpGet(\"/timer-2\")]\n        public IActionResult TimeLeakFix()\n        {\n            var operation = new EphemeralOperation2();\n            return Ok();\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/FireAndForgetController.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing Scenarios.Model;\n\nnamespace Scenarios.Controllers\n{\n    public class FireAndForgetController : Controller\n    {\n        [HttpGet(\"/fire-and-forget-1\")]\n        public IActionResult FireAndForget1([FromServices]PokemonDbContext context)\n        {\n            // This is an implicit async void method. ThreadPool.QueueUserWorkItem takes an Action, but the compiler allows\n            // async void delegates to be used in its place. This is dangerous because unhandled exceptions will bring down the entire server process.\n            ThreadPool.QueueUserWorkItem(async state =>\n            {\n                await Task.Delay(1000);\n\n                // This closure is capturing the context from the Controller action parameter. This is bad because this work item could run\n                // outside of the request scope and the PokemonDbContext is scoped to the request. As a result, this will crash the process with\n                // and ObjectDisposedException\n                context.Pokemon.Add(new Pokemon());\n                await context.SaveChangesAsync();\n            });\n\n            return Accepted();\n        }\n\n\n        [HttpGet(\"/fire-and-forget-2\")]\n        public IActionResult FireAndForget2([FromServices]PokemonDbContext context)\n        {\n            // This uses Task.Run instead of ThreadPool.QueueUserWorkItem. It's mostly equivalent to the FireAndForget1 but since we're using \n            // async Task instead of async void, unhandled exceptions won't crash the process. They will however trigger the TaskScheduler.UnobservedTaskException\n            // event when exceptions go unhandled.\n            Task.Run(async () =>\n            {\n                await Task.Delay(1000);\n\n                // This closure is capturing the context from the Controller action parameter. This is bad because this work item could run\n                // outside of the request scope and the PokemonDbContext is scoped to the request. As a result, this will throw an unhandled ObjectDisposedException.\n                context.Pokemon.Add(new Pokemon());\n                await context.SaveChangesAsync();\n            });\n\n            return Accepted();\n        }\n\n        [HttpGet(\"/fire-and-forget-3\")]\n\n        public IActionResult FireAndForget3([FromServices]IServiceScopeFactory serviceScopeFactory)\n        {\n            // This version of fire and forget adds some exception handling. We're also no longer capturing the PokemonDbContext from the incoming request.\n            // Instead, we're injecting an IServiceScopeFactory (which is a singleton) in order to create a scope in the background work item.\n            Task.Run(async () =>\n            {\n                await Task.Delay(1000);\n\n                // Create a scope for the lifetime of the background operation and resolve services from it\n                using (var scope = serviceScopeFactory.CreateScope())\n                {\n                    var loggerFactory = scope.ServiceProvider.GetRequiredService<ILoggerFactory>();\n                    var logger = loggerFactory.CreateLogger(\"Background Task\");\n\n                    // THIS IS DANGEROUS! We're capturing the HttpContext from the incoming request in the closure that\n                    // runs the background work item. This will not work because the incoming http request will be over before\n                    // the work item executes.\n                    using (logger.BeginScope(\"Background operation kicked off from {RequestId}\", HttpContext.TraceIdentifier))\n                    {\n                        try\n                        {\n                            // This will a PokemonDbContext from the correct scope and the operation will succeed\n                            var context = scope.ServiceProvider.GetRequiredService<PokemonDbContext>();\n\n                            context.Pokemon.Add(new Pokemon());\n                            await context.SaveChangesAsync();\n                        }\n                        catch (Exception ex)\n                        {\n                            logger.LogError(ex, \"Background task failed.\");\n                        }\n                    }\n                }\n            });\n\n            return Accepted();\n        }\n\n        [HttpGet(\"/fire-and-forget-4\")]\n        public IActionResult FireAndForget4([FromServices]IServiceScopeFactory serviceScopeFactory)\n        {\n            Task.Run(async () =>\n            {\n                await Task.Delay(1000);\n\n                using (var scope = serviceScopeFactory.CreateScope())\n                {\n                    // Instead of capturing the HttpContext from the controller property, we use the IHttpContextAccessor\n                    var accessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();\n                    var loggerFactory = scope.ServiceProvider.GetRequiredService<ILoggerFactory>();\n\n                    var logger = loggerFactory.CreateLogger(\"Background Task\");\n\n                    // THIS IS DANGEROUS! We're trying to use the HttpContext from the incoming request in the closure that\n                    // runs the background work item. This will not work because the incoming http request will be over before\n                    // the work item executes.\n                    using (logger.BeginScope(\"Background operation kicked off from {RequestId}\", accessor.HttpContext.TraceIdentifier))\n                    {\n                        try\n                        {\n\n                            var context = scope.ServiceProvider.GetRequiredService<PokemonDbContext>();\n\n                            context.Pokemon.Add(new Pokemon());\n                            await context.SaveChangesAsync();\n                        }\n                        catch (Exception ex)\n                        {\n                            logger.LogError(ex, \"Background task failed.\");\n                        }\n                    }\n                }\n            });\n\n            return Accepted();\n        }\n\n        [HttpGet(\"/fire-and-forget-5\")]\n        public IActionResult FireAndForget5([FromServices]IServiceScopeFactory serviceScopeFactory)\n        {\n            // Capture the trace identifier first\n            string traceIdenifier = HttpContext.TraceIdentifier;\n\n            Task.Run(async () =>\n            {\n                await Task.Delay(1000);\n\n                using (var scope = serviceScopeFactory.CreateScope())\n                {\n                    var loggerFactory = scope.ServiceProvider.GetRequiredService<ILoggerFactory>();\n\n                    var logger = loggerFactory.CreateLogger(\"Background Task\");\n\n                    // This uses the traceIdenifier captured at the time the request started.\n                    using (logger.BeginScope(\"Background operation kicked off from {RequestId}\", traceIdenifier))\n                    {\n                        try\n                        {\n\n                            var context = scope.ServiceProvider.GetRequiredService<PokemonDbContext>();\n\n                            context.Add(new Pokemon());\n                            await context.SaveChangesAsync();\n                        }\n                        catch (Exception ex)\n                        {\n                            logger.LogError(ex, \"Background task failed.\");\n                        }\n                    }\n                }\n            });\n\n            return Accepted();\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/HighCpuController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace Scenarios.Controllers\n{\n    public class HighCpuController : Controller\n    {\n        [HttpPost(\"/cpu-1\")]\n        public string BurnCpu()\n        {\n            Stopwatch watch = new Stopwatch();\n            watch.Start();\n\n            while (true)\n            {\n                watch.Stop();\n                if (watch.ElapsedMilliseconds > 1000 * 5)\n                    break;\n                watch.Start();\n            }\n            return \"Working\";\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/HttpClientController.cs",
    "content": "﻿using System.IO;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace Scenarios.Controllers\n{\n    public class HttpClientController : Controller\n    {\n        private readonly string _url = \"https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json\";\n\n        [Route(\"/httpclient-1\")]\n        public async Task<string> OutgoingWorse()\n        {\n            var client = new HttpClient();\n            return await client.GetStringAsync(_url);\n        }\n\n        [Route(\"/httpclient-2\")]\n        public async Task<string> OutgoingBad()\n        {\n            using (var client = new HttpClient())\n            {\n                return await client.GetStringAsync(_url);\n            }\n        }\n\n        [Route(\"/httpclient-3\")]\n        public async Task<Stream> OutgoingGood([FromServices]IHttpClientFactory clientFactory)\n        {\n            using (var client = clientFactory.CreateClient())\n            {\n                return await client.GetStreamAsync(_url);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Controllers/MemoryLeakController.cs",
    "content": "﻿using System;\nusing System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\n\nnamespace Scenarios.Controllers\n{\n    public class MemoryLeakController : ControllerBase\n    {\n        private static ConcurrentDictionary<string, string> _cache = new ConcurrentDictionary<string, string>();\n\n        [HttpGet(\"/leak\")]\n        public string Leak1(string id)\n        {\n            return _cache.GetOrAdd(id, _ => new string('c', 1024 * 1024 * 5)).Substring(0, 5);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Infrastructure/TaskExtensions.cs",
    "content": "﻿namespace System.Threading.Tasks\n{\n    public static class TaskExtensions\n    {\n        /// <summary>\n        /// The timer won't be disposed until this token triggers. If it is a long lived token\n        /// that may result in memory leaks and timer queue exhaustion!\n        /// </summary>\n        public static async Task<T> WithCancellationBad<T>(this Task<T> task, CancellationToken cancellationToken)\n        {\n            var delayTask = Task.Delay(-1, cancellationToken);\n\n            var resultTask = await Task.WhenAny(task, delayTask);\n            if (resultTask == delayTask)\n            {\n                // Operation cancelled\n                throw new OperationCanceledException();\n            }\n\n            return await task;\n        }\n\n        /// <summary>\n        /// This properly registers and unregisters the token when one of the operations completes\n        /// </summary>\n        public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)\n        {\n            var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);\n\n            // This disposes the registration as soon as one of the tasks trigger\n            using (cancellationToken.Register(state =>\n            {\n                ((TaskCompletionSource<object>)state).TrySetResult(null);\n            },\n            tcs))\n            {\n                var resultTask = await Task.WhenAny(task, tcs.Task);\n                if (resultTask == tcs.Task)\n                {\n                    // Operation cancelled\n                    throw new OperationCanceledException(cancellationToken);\n                }\n\n                return await task;\n            }\n        }\n\n        /// <summary>\n        /// This method does not cancel the timer even if the operation successfully completes.\n        /// This means you could end up with timer queue flooding!\n        /// </summary>\n        public static async Task<T> TimeoutAfterBad<T>(this Task<T> task, TimeSpan timeout)\n        {\n            var delayTask = Task.Delay(timeout);\n\n            var resultTask = await Task.WhenAny(task, delayTask);\n            if (resultTask == delayTask)\n            {\n                // Operation cancelled\n                throw new OperationCanceledException();\n            }\n\n            return await task;\n        }\n\n        /// <summary>\n        /// This method cancels the timer if the operation successfully completes.\n        /// </summary>\n        public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)\n        {\n            using (var cts = new CancellationTokenSource())\n            {\n                var delayTask = Task.Delay(timeout, cts.Token);\n\n                var resultTask = await Task.WhenAny(task, delayTask);\n                if (resultTask == delayTask)\n                {\n                    // Operation cancelled\n                    throw new OperationCanceledException();\n                }\n                else\n                {\n                    // Cancel the timer task so that it does not fire\n                    cts.Cancel();\n                }\n\n                return await task;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Model/PokemonData.cs",
    "content": "﻿namespace Scenarios.Model\n{\n    public class PokemonData\n    {\n        public Pokemon[] pokemon { get; set; }\n    }\n\n    public class Pokemon\n    {\n        public int id { get; set; }\n        public string num { get; set; }\n        public string name { get; set; }\n        public string img { get; set; }\n        public string[] type { get; set; }\n        public string height { get; set; }\n        public string weight { get; set; }\n        public string candy { get; set; }\n        public int candy_count { get; set; }\n        public string egg { get; set; }\n        public float spawn_chance { get; set; }\n        public float avg_spawns { get; set; }\n        public string spawn_time { get; set; }\n        public float[] multipliers { get; set; }\n        public string[] weaknesses { get; set; }\n        public Next_Evolution[] next_evolution { get; set; }\n        public Prev_Evolution[] prev_evolution { get; set; }\n    }\n\n    public class Next_Evolution\n    {\n        public string num { get; set; }\n        public string name { get; set; }\n    }\n\n    public class Prev_Evolution\n    {\n        public string num { get; set; }\n        public string name { get; set; }\n    }\n}\n"
  },
  {
    "path": "Scenarios/PokemonDbContext.cs",
    "content": "﻿using System.IO;\nusing Microsoft.EntityFrameworkCore;\nusing Newtonsoft.Json;\nusing Scenarios.Model;\n\nnamespace Scenarios\n{\n    public class PokemonDbContext : DbContext\n    {\n        public PokemonDbContext(DbContextOptions<PokemonDbContext> options)\n            : base(options)\n        {\n\n        }\n\n        public DbSet<Pokemon> Pokemon { get; set; }\n\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            PokemonData pokemonData;\n            using (var stream = File.OpenRead(\"pokemon.json\"))\n            using (var streamReader = new StreamReader(stream))\n            using (var reader = new JsonTextReader(streamReader))\n            {\n                var serializer = new JsonSerializer();\n                pokemonData = serializer.Deserialize<PokemonData>(reader);\n            }\n\n            modelBuilder.Entity<Pokemon>()\n                .HasData(pokemonData.pokemon);\n\n            modelBuilder.Entity<Pokemon>()\n                        .Ignore(p => p.next_evolution)\n                        .Ignore(p => p.multipliers)\n                        .Ignore(p => p.prev_evolution)\n                        .Ignore(p => p.weaknesses)\n                        .Ignore(p => p.type);\n\n            base.OnModelCreating(modelBuilder);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Program.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\n\nnamespace Scenarios\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateHostBuilder(args).Build().Run();\n        }\n\n        public static IHostBuilder CreateHostBuilder(string[] args) =>\n            Host.CreateDefaultBuilder(args)\n                .ConfigureLogging(logging =>\n                {\n                    logging.ClearProviders();\n                })\n                .ConfigureWebHostDefaults(builder =>\n                {\n                    builder.UseStartup<Startup>();\n                });\n    }\n}\n"
  },
  {
    "path": "Scenarios/Scenarios.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.1\" />\n    <PackageReference Include=\"Microsoft.AspNet.WebApi.Client\" Version=\"5.2.7\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Scenarios/Services/EphemeralOperation.cs",
    "content": "﻿using System;\nusing System.Threading;\n\nnamespace Scenarios.Services\n{\n    /// <summary>\n    /// Creating this object without disposing it will cause a memory leak. The object graph will look like this\n    /// Timer -> TimerHolder -> TimerQueueTimer ->  EphemeralOperation -> Timer -> ...\n    /// The timer holds onto the state which in turns holds onto the Timer, we have a circular reference.\n    /// The GC will not clean these up even if it is unreferenced. It needs to be explicitly disposed in order to avoid the leak.\n    /// </summary>\n    public class EphemeralOperation : IDisposable\n    {\n        private Timer _timer;\n        private int _ticks;\n\n        public EphemeralOperation()\n        {\n            _timer = new Timer(state =>\n            {\n                _ticks++;\n            },\n            null,\n            1000,\n            1000);\n        }\n\n        public void Dispose()\n        {\n            _timer.Dispose();\n        }\n    }\n\n    /// <summary>\n    /// This fixes the cycle by using a WeakReference to the state object. The object graph now looks like this:\n    /// Timer -> TimerHolder -> TimerQueueTimer -> WeakReference&lt;EphemeralOperation&gt; -> Timer -> ...\n    /// If EphemeralOperation2 falls out of scope, the timer should be released.\n    /// </summary>\n    public class EphemeralOperation2 : IDisposable\n    {\n        private Timer _timer;\n        private int _ticks;\n\n        public EphemeralOperation2()\n        {\n            _timer = new Timer(OnTimerCallback,\n            new WeakReference<EphemeralOperation2>(this),\n            1000,\n            1000);\n        }\n\n        private static void OnTimerCallback(object state)\n        {\n            var thisRef = (WeakReference<EphemeralOperation2>)state;\n            if (thisRef.TryGetTarget(out var op))\n            {\n                op._ticks++;\n            }\n        }\n\n        public void Dispose()\n        {\n            _timer.Dispose();\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Services/LegacyService.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Scenarios.Services\n{\n    /// <summary>\n    /// The <see cref=\"LegacyService\"/> shows to various ways people attempt to blocking code over an async API. There is NO \n    /// good way to turn asynchronous code into synchronous code. All of these blocking calls can cause thread pool starvation.\n    /// </summary>\n    public class LegacyService\n    {\n        public string DoOperationBlocking()\n        {\n            return Task.Run(() => DoAsyncOperation()).Result;\n        }\n\n        public string DoOperationBlocking2()\n        {\n            return Task.Run(() => DoAsyncOperation()).GetAwaiter().GetResult();\n        }\n\n        public string DoOperationBlocking3()\n        {\n            return Task.Run(() => DoAsyncOperation().Result).Result;\n        }\n\n        public string DoOperationBlocking4()\n        {\n            return Task.Run(() => DoAsyncOperation().GetAwaiter().GetResult()).GetAwaiter().GetResult();\n        }\n\n        public string DoOperationBlocking5()\n        {\n            return DoAsyncOperation().Result;\n        }\n\n        public string DoOperationBlocking6()\n        {\n            return DoAsyncOperation().GetAwaiter().GetResult();\n        }\n\n        public string DoOperationBlocking7()\n        {\n            var task = DoAsyncOperation();\n            task.Wait();\n            return task.GetAwaiter().GetResult();\n        }\n\n        /// <summary>\n        /// DoAsyncOperation is a truly async operation. This is the recommended way to do asynchronous calls.\n        /// </summary>\n        /// <returns></returns>\n        public async Task<string> DoAsyncOperation()\n        {\n            var random = new Random();\n\n            // Mimick some asynchrous activity\n            await Task.Delay(random.Next(10) * 1000);\n\n            return Guid.NewGuid().ToString();\n        }\n\n        /// <summary>\n        /// DoAsyncOverSyncOperation is wasteful. It uses a thread pool thread to return an easily computed value.\n        /// The preferred approach is DoSyncOperationWithAsyncReturn.\n        /// </summary>\n        /// <returns></returns>\n        public Task<string> DoAsyncOverSyncOperation()\n        {\n            return Task.Run(() => Guid.NewGuid().ToString());\n        }\n\n        /// <summary>\n        /// This is the recommended way to return a Task for an already computed result. There's no need to use a thread pool thread,\n        /// just to return a Task.\n        /// </summary>\n        /// <returns></returns>\n        public Task<string> DoSyncOperationWithAsyncReturn()\n        {\n            return Task.FromResult(Guid.NewGuid().ToString());\n        }\n\n        /// <summary>\n        /// DoAsyncOperationOverLegacyBad shows an async operation that does not properly clean up references to the canceallation token.\n        /// </summary>\n        public Task<string> DoAsyncOperationOverLegacyBad(CancellationToken cancellationToken)\n        {\n            // The following TaskCompletionSource hasn't been creating with the TaskCreationOptions.RunContinuationsAsynchronously\n            // option. This means that the calling code will resume in the OnCompleted callback. This has a couple of consequences\n            // 1. It will extend the lifetime of these objects since they will be on the stack when user code is resumed.\n            // 2. If the calling code blocks, it could *steal* the thread from the LegacyAsyncOperation.\n            var tcs = new TaskCompletionSource<string>();\n\n            var operation = new LegacyAsyncOperation();\n\n            if (cancellationToken.CanBeCanceled)\n            {\n                // CancellationToken.Register returns a CancellationTokenRegistration that needs to be disposed.\n                // If this isn't disposed, it will stay around in the CancellationTokenSource until the\n                // backing CancellationTokenSource is disposed.\n                cancellationToken.Register(state =>\n                {\n                    ((LegacyAsyncOperation)state).Cancel();\n                },\n                operation);\n            }\n\n            // Not removing the event handler can result in a memory leak\n            // this object is referenced by the callback which itself isn't cleaned up until\n            // the token is disposed or the registration is disposed.\n            operation.Completed += OnCompleted;\n\n            operation.Start();\n\n            return tcs.Task;\n\n            void OnCompleted(string result, bool cancelled)\n            {\n                if (cancelled)\n                {\n                    tcs.TrySetCanceled(cancellationToken);\n                }\n                else\n                {\n                    tcs.TrySetResult(result);\n                }\n            }\n        }\n\n        public Task<string> DoAsyncOperationOverLegacy(CancellationToken cancellationToken)\n        {\n            var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);\n\n            var operation = new LegacyAsyncOperation();\n\n            var registration = default(CancellationTokenRegistration);\n\n            if (cancellationToken.CanBeCanceled)\n            {\n                registration = cancellationToken.Register(state =>\n                {\n                    ((LegacyAsyncOperation)state).Cancel();\n                },\n                operation);\n            }\n\n            operation.Completed += OnCompleted;\n\n            operation.Start();\n\n            return tcs.Task;\n\n            void OnCompleted(string result, bool cancelled)\n            {\n                registration.Dispose();\n\n                operation.Completed -= OnCompleted;\n\n                if (cancelled)\n                {\n                    tcs.TrySetCanceled(cancellationToken);\n                }\n                else\n                {\n                    tcs.TrySetResult(result);\n                }\n            }\n        }\n\n        /// <summary>\n        /// Pretends to be a legacy async operation that doesn't natively support Task\n        /// </summary>\n        private class LegacyAsyncOperation\n        {\n            private Timer _timer;\n\n            public Action<string, bool> Completed;\n\n            private bool _cancelled;\n\n            public void Start()\n            {\n                _timer = new Timer(OnCompleted, null, new Random().Next(10) * 1000, Timeout.Infinite);\n            }\n\n            private void OnCompleted(object state)\n            {\n                var cancelled = _cancelled;\n                _cancelled = false;\n\n                Completed(Guid.NewGuid().ToString(), cancelled);\n\n                _timer.Dispose();\n            }\n\n            public void Cancel()\n            {\n                _cancelled = true;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Services/PokemonService.cs",
    "content": "﻿using System.IO;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Threading.Tasks;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Linq;\nusing Scenarios.Model;\n\nnamespace Scenarios.Services\n{\n    /// <summary>\n    /// This service shows the various ways to make an outgoing HTTP request to get a JSON payload. It shows the various tradeoffs involved in doing this. It\n    /// uses JSON.NET to perform Deserialization. In general there are 3 approaches:\n    /// 1. Buffer the response in memory before handing it to the JSON serializer. This could lead to out of memory exceptions which can lead to a Denial Of Service.\n    /// 2. Stream the response and synchronously read from the stream. This can lead to thread pool starvation.\n    /// 3. Stream the response and asynchronously read from the stream.\n    /// </summary>\n    public class PokemonService\n    {\n        private readonly HttpClient _client;\n        private readonly string _url = \"https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json\";\n\n        public PokemonService(HttpClient client)\n        {\n            _client = client;\n        }\n\n        public async Task<PokemonData> GetPokemonBufferdStringAsync()\n        {\n            // This service returns the entire JSON payload into memory before converting that into a JSON object\n            var json = await _client.GetStringAsync(_url);\n\n            return JsonConvert.DeserializeObject<PokemonData>(json);\n        }\n\n        public async Task<PokemonData> GetPokemonAsync()\n        {\n            var response = await _client.GetAsync(_url);\n            // This is a hack to work around the fact that this JSON api returns text/plain\n            response.Content.Headers.ContentType = new MediaTypeHeaderValue(\"application/json\");\n            // Use the built in methods to read the response as JSON\n            return await response.Content.ReadAsAsync<PokemonData>();\n        }\n\n        public async Task<PokemonData> GetPokemonManualUnbufferedBadAsync()\n        {\n            // Using HttpCompletionOption.ResponseHeadersRead avoids buffering the entire response body into memory\n            using (var response = await _client.GetAsync(_url, HttpCompletionOption.ResponseHeadersRead))\n            {\n                // Get the response stream\n                var responseStream = await response.Content.ReadAsStreamAsync();\n\n                // Create a StreamReader and JsonTextReader over that\n                // This does some buffering but we're not double buffering\n                var textReader = new StreamReader(responseStream);\n                var reader = new JsonTextReader(textReader);\n\n                var serializer = new JsonSerializer();\n\n                // *THIS* is a problem, we're doing synchronous IO here over the Stream. If the back end is slow, this can result\n                // in thread pool starvation.\n                return serializer.Deserialize<PokemonData>(reader);\n            }\n        }\n\n        public async Task<PokemonData> GetPokemonManualUnbufferedGoodAsync()\n        {\n            // Using HttpCompletionOption.ResponseHeadersRead avoids buffering the entire response body into memory\n            using (var response = await _client.GetAsync(_url, HttpCompletionOption.ResponseHeadersRead))\n            {\n                // Get the response stream\n                var responseStream = await response.Content.ReadAsStreamAsync();\n\n                // Create a StreamReader and JsonTextReader over that\n                // This does double buffering...\n                var textReader = new StreamReader(responseStream);\n                var reader = new JsonTextReader(textReader);\n\n                var serializer = new JsonSerializer();\n\n                // This asynchronously reads the JSON object into memory. This does true asynchronous IO. The only downside is that we're\n                // converting the object graph to an intermediate DOM before going to the object directly.\n                var obj = await JToken.ReadFromAsync(reader);\n\n                // Convert the JToken to an object\n                return obj.ToObject<PokemonData>(serializer);\n            }\n        }\n\n        public async Task<PokemonData> GetPokemonManualBufferedAsync()\n        {\n            // This buffers the entire response into memory so that we don't end up doing blocking IO when \n            // de-serializing the JSON. If the payload is *HUGE* this could result in large allocations that lead to a Denial Of Service.\n            using (var response = await _client.GetAsync(_url))\n            {\n                // Get the response stream\n                var responseStream = await response.Content.ReadAsStreamAsync();\n\n                // Create a StreamReader and JsonTextReader over that\n                // This does double buffering...\n                var textReader = new StreamReader(responseStream);\n                var reader = new JsonTextReader(textReader);\n\n                var serializer = new JsonSerializer();\n\n                // Because we're buffering the entire response, we're also avoiding synchronous IO\n                return serializer.Deserialize<PokemonData>(reader);\n            }\n        }\n\n        public async Task<PokemonData> GetPokemonAsyncNewJson()\n        {\n            using var response = await _client.GetAsync(_url, HttpCompletionOption.ResponseHeadersRead);\n\n            // Get the response stream\n            var responseStream = await response.Content.ReadAsStreamAsync();\n\n            return await System.Text.Json.JsonSerializer.DeserializeAsync<PokemonData>(responseStream);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Services/RemoteConnection.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace Scenarios.Services\n{\n    /// <summary>\n    /// This represents a remote connection to something. Unfortunately Dispose is still problematic but that will be fixed with\n    /// IAsyncDisposable. In the mean time, to dispose something on shutdown, we'd need to block in the dispose implementation (but that happens off request threads).\n    /// </summary>\n    public interface IRemoteConnection\n    {\n        Task PublishAsync(string channel, string message);\n        Task DisposeAsync();\n    }\n\n    public class RemoteConnection : IRemoteConnection\n    {\n        public Task PublishAsync(string channel, string message)\n        {\n            return Task.CompletedTask;\n        }\n\n        public Task DisposeAsync() => Task.CompletedTask;\n    }\n\n    public class RemoteConnectionFactory\n    {\n        // Configurtion would be used to read the connection information\n        private readonly IConfiguration _configuration;\n\n        public RemoteConnectionFactory(IConfiguration configuration)\n        {\n            _configuration = configuration;\n        }\n\n        /// <summary>\n        /// This factory method creates a new connection every time after connecting to that remote end point\n        /// </summary>\n        public async Task<RemoteConnection> ConnectAsync()\n        {\n            var random = new Random();\n\n            // Fake delay to a remote connection\n            await Task.Delay(random.Next(10) * 1000);\n\n            return new RemoteConnection();\n        }\n    }\n\n    /// <summary>\n    /// This implementation uses the RemoteConnectionFactory and lazily initializes the connection when operations happen\n    /// </summary>\n    public class LazyRemoteConnection : IRemoteConnection\n    {\n        private readonly AsyncLazy<RemoteConnection> _connectionTask;\n\n        public LazyRemoteConnection(RemoteConnectionFactory remoteConnectionFactory)\n        {\n            _connectionTask = new AsyncLazy<RemoteConnection>(() => remoteConnectionFactory.ConnectAsync());\n        }\n\n        public async Task PublishAsync(string channel, string message)\n        {\n            var connection = await _connectionTask.Value;\n\n            await connection.PublishAsync(channel, message);\n        }\n\n        public async Task DisposeAsync()\n        {\n            // Don't connect just to dispose\n            if (!_connectionTask.IsValueCreated)\n            {\n                return;\n            }\n\n            var connection = await _connectionTask.Value;\n\n            await connection.DisposeAsync();\n        }\n\n        private class AsyncLazy<T> : Lazy<Task<T>>\n        {\n            public AsyncLazy(Func<Task<T>> valueFactory) : base(valueFactory)\n            {\n            }\n        }\n    }\n\n    /// <summary>\n    /// This connection implementation gets an IRemoteConnection and an ILoggerFactory in the constructor.\n    /// It will dead lock the DI resolution process because it will end up waiting on the same lock.\n    /// </summary>\n    public class LoggingRemoteConnection : IRemoteConnection\n    {\n        private readonly IRemoteConnection _remoteConnection;\n        private readonly ILogger _logger;\n        public LoggingRemoteConnection(IRemoteConnection connection, ILogger logger)\n        {\n            _remoteConnection = connection;\n            _logger = logger;\n        }\n\n        public Task DisposeAsync()\n        {\n            _logger.LogInformation(\"Disposing the remote connection\");\n            return _remoteConnection.DisposeAsync();\n        }\n\n        public Task PublishAsync(string channel, string message)\n        {\n            _logger.LogInformation(\"Publishing message={message} to the remote connection on channel {channel}\", message, channel);\n            return _remoteConnection.PublishAsync(channel, message);\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/Startup.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Hosting;\nusing Scenarios.Services;\nusing System.Threading;\n\nnamespace Scenarios\n{\n    public class Startup\n    {\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddHttpClient();\n\n            services.AddHttpClient<PokemonService>();\n\n            services.AddHttpClient(\"timeout\", client =>\n            {\n                client.Timeout = TimeSpan.FromSeconds(10);\n            });\n\n            services.AddHttpContextAccessor();\n\n            services.AddDbContext<PokemonDbContext>(o =>\n            {\n                o.UseInMemoryDatabase(\"MyApplication\");\n            });\n\n            services.AddSingleton(sp =>\n            {\n                // This is *BAD*, do not try to do asynchronous work in a synchronous callback!\n                // This can lead to thread pool starvation.\n                return sp.GetRequiredService<RemoteConnectionFactory>().ConnectAsync().Result;\n            });\n\n            services.AddSingleton(sp =>\n            {\n                // This is *BAD*, do not try to do asynchronous work in a synchronous callback!\n                // This specific implementation can lead to a dead lock\n                return GetLoggingRemoteConnection(sp).Result;\n            });\n\n            services.AddSingleton<LazyRemoteConnection>();\n            services.AddSingleton<RemoteConnectionFactory>();\n\n            services.AddControllers();\n        }\n\n        private async Task<LoggingRemoteConnection> GetLoggingRemoteConnection(IServiceProvider sp)\n        {\n            // As part of service resolution, we hold a lock on the container\n            var connectionFactory = sp.GetRequiredService<RemoteConnectionFactory>();\n            var connection = await connectionFactory.ConnectAsync();\n\n            // We've resumed on a different thread and we're about to trigger another call to GetRequiredService.\n            // This call requires the same lock so it will wait for the existing service resolution to release it\n            // before continuing. \n\n            // This will result in a dead lock because we're running as part of the original service resolution!\n            var logger = sp.GetRequiredService<ILogger<LoggingRemoteConnection>>();\n\n            return new LoggingRemoteConnection(connection, logger);\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, PokemonDbContext context)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n\n            app.UseFileServer();\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllers();\n            });\n\n            // Force database seeding to execute\n            context.Database.EnsureCreated();\n        }\n    }\n}\n"
  },
  {
    "path": "Scenarios/pokemon.json",
    "content": "﻿{\n  \"pokemon\": [\n    {\n      \"id\": 1,\n      \"num\": \"001\",\n      \"name\": \"Bulbasaur\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/001.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"0.71 m\",\n      \"weight\": \"6.9 kg\",\n      \"candy\": \"Bulbasaur Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 0.69,\n      \"avg_spawns\": 69,\n      \"spawn_time\": \"20:00\",\n      \"multipliers\": [ 1.58 ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"002\",\n          \"name\": \"Ivysaur\"\n        },\n        {\n          \"num\": \"003\",\n          \"name\": \"Venusaur\"\n        }\n      ]\n    },\n    {\n      \"id\": 2,\n      \"num\": \"002\",\n      \"name\": \"Ivysaur\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/002.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"13.0 kg\",\n      \"candy\": \"Bulbasaur Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.042,\n      \"avg_spawns\": 4.2,\n      \"spawn_time\": \"07:00\",\n      \"multipliers\": [\n        1.2,\n        1.6\n      ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"001\",\n          \"name\": \"Bulbasaur\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"003\",\n          \"name\": \"Venusaur\"\n        }\n      ]\n    },\n    {\n      \"id\": 3,\n      \"num\": \"003\",\n      \"name\": \"Venusaur\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/003.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"2.01 m\",\n      \"weight\": \"100.0 kg\",\n      \"candy\": \"Bulbasaur Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.017,\n      \"avg_spawns\": 1.7,\n      \"spawn_time\": \"11:30\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"001\",\n          \"name\": \"Bulbasaur\"\n        },\n        {\n          \"num\": \"002\",\n          \"name\": \"Ivysaur\"\n        }\n      ]\n    },\n    {\n      \"id\": 4,\n      \"num\": \"004\",\n      \"name\": \"Charmander\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/004.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"8.5 kg\",\n      \"candy\": \"Charmander Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 0.253,\n      \"avg_spawns\": 25.3,\n      \"spawn_time\": \"08:45\",\n      \"multipliers\": [ 1.65 ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"005\",\n          \"name\": \"Charmeleon\"\n        },\n        {\n          \"num\": \"006\",\n          \"name\": \"Charizard\"\n        }\n      ]\n    },\n    {\n      \"id\": 5,\n      \"num\": \"005\",\n      \"name\": \"Charmeleon\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/005.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"19.0 kg\",\n      \"candy\": \"Charmander Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.012,\n      \"avg_spawns\": 1.2,\n      \"spawn_time\": \"19:00\",\n      \"multipliers\": [ 1.79 ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"004\",\n          \"name\": \"Charmander\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"006\",\n          \"name\": \"Charizard\"\n        }\n      ]\n    },\n    {\n      \"id\": 6,\n      \"num\": \"006\",\n      \"name\": \"Charizard\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/006.png\",\n      \"type\": [\n        \"Fire\",\n        \"Flying\"\n      ],\n      \"height\": \"1.70 m\",\n      \"weight\": \"90.5 kg\",\n      \"candy\": \"Charmander Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0031,\n      \"avg_spawns\": 0.31,\n      \"spawn_time\": \"13:34\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"004\",\n          \"name\": \"Charmander\"\n        },\n        {\n          \"num\": \"005\",\n          \"name\": \"Charmeleon\"\n        }\n      ]\n    },\n    {\n      \"id\": 7,\n      \"num\": \"007\",\n      \"name\": \"Squirtle\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/007.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.51 m\",\n      \"weight\": \"9.0 kg\",\n      \"candy\": \"Squirtle Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 0.58,\n      \"avg_spawns\": 58,\n      \"spawn_time\": \"04:25\",\n      \"multipliers\": [ 2.1 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"008\",\n          \"name\": \"Wartortle\"\n        },\n        {\n          \"num\": \"009\",\n          \"name\": \"Blastoise\"\n        }\n      ]\n    },\n    {\n      \"id\": 8,\n      \"num\": \"008\",\n      \"name\": \"Wartortle\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/008.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"22.5 kg\",\n      \"candy\": \"Squirtle Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.034,\n      \"avg_spawns\": 3.4,\n      \"spawn_time\": \"07:02\",\n      \"multipliers\": [ 1.4 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"007\",\n          \"name\": \"Squirtle\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"009\",\n          \"name\": \"Blastoise\"\n        }\n      ]\n    },\n    {\n      \"id\": 9,\n      \"num\": \"009\",\n      \"name\": \"Blastoise\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/009.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"85.5 kg\",\n      \"candy\": \"Squirtle Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0067,\n      \"avg_spawns\": 0.67,\n      \"spawn_time\": \"00:06\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"007\",\n          \"name\": \"Squirtle\"\n        },\n        {\n          \"num\": \"008\",\n          \"name\": \"Wartortle\"\n        }\n      ]\n    },\n    {\n      \"id\": 10,\n      \"num\": \"010\",\n      \"name\": \"Caterpie\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/010.png\",\n      \"type\": [\n        \"Bug\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"2.9 kg\",\n      \"candy\": \"Caterpie Candy\",\n      \"candy_count\": 12,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 3.032,\n      \"avg_spawns\": 303.2,\n      \"spawn_time\": \"16:35\",\n      \"multipliers\": [ 1.05 ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"011\",\n          \"name\": \"Metapod\"\n        },\n        {\n          \"num\": \"012\",\n          \"name\": \"Butterfree\"\n        }\n      ]\n    },\n    {\n      \"id\": 11,\n      \"num\": \"011\",\n      \"name\": \"Metapod\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/011.png\",\n      \"type\": [\n        \"Bug\"\n      ],\n      \"height\": \"0.71 m\",\n      \"weight\": \"9.9 kg\",\n      \"candy\": \"Caterpie Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.187,\n      \"avg_spawns\": 18.7,\n      \"spawn_time\": \"02:11\",\n      \"multipliers\": [\n        3.55,\n        3.79\n      ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"010\",\n          \"name\": \"Caterpie\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"012\",\n          \"name\": \"Butterfree\"\n        }\n      ]\n    },\n    {\n      \"id\": 12,\n      \"num\": \"012\",\n      \"name\": \"Butterfree\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/012.png\",\n      \"type\": [\n        \"Bug\",\n        \"Flying\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"32.0 kg\",\n      \"candy\": \"Caterpie Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.022,\n      \"avg_spawns\": 2.2,\n      \"spawn_time\": \"05:23\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Electric\",\n        \"Ice\",\n        \"Flying\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"010\",\n          \"name\": \"Caterpie\"\n        },\n        {\n          \"num\": \"011\",\n          \"name\": \"Metapod\"\n        }\n      ]\n    },\n    {\n      \"id\": 13,\n      \"num\": \"013\",\n      \"name\": \"Weedle\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/013.png\",\n      \"type\": [\n        \"Bug\",\n        \"Poison\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"3.2 kg\",\n      \"candy\": \"Weedle Candy\",\n      \"candy_count\": 12,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 7.12,\n      \"avg_spawns\": 712,\n      \"spawn_time\": \"02:21\",\n      \"multipliers\": [\n        1.01,\n        1.09\n      ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Psychic\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"014\",\n          \"name\": \"Kakuna\"\n        },\n        {\n          \"num\": \"015\",\n          \"name\": \"Beedrill\"\n        }\n      ]\n    },\n    {\n      \"id\": 14,\n      \"num\": \"014\",\n      \"name\": \"Kakuna\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/014.png\",\n      \"type\": [\n        \"Bug\",\n        \"Poison\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"10.0 kg\",\n      \"candy\": \"Weedle Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.44,\n      \"avg_spawns\": 44,\n      \"spawn_time\": \"02:30\",\n      \"multipliers\": [\n        3.01,\n        3.41\n      ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Psychic\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"013\",\n          \"name\": \"Weedle\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"015\",\n          \"name\": \"Beedrill\"\n        }\n      ]\n    },\n    {\n      \"id\": 15,\n      \"num\": \"015\",\n      \"name\": \"Beedrill\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/015.png\",\n      \"type\": [\n        \"Bug\",\n        \"Poison\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"29.5 kg\",\n      \"candy\": \"Weedle Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.051,\n      \"avg_spawns\": 5.1,\n      \"spawn_time\": \"04:50\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Psychic\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"013\",\n          \"name\": \"Weedle\"\n        },\n        {\n          \"num\": \"014\",\n          \"name\": \"Kakuna\"\n        }\n      ]\n    },\n    {\n      \"id\": 16,\n      \"num\": \"016\",\n      \"name\": \"Pidgey\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/016.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"1.8 kg\",\n      \"candy\": \"Pidgey Candy\",\n      \"candy_count\": 12,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 15.98,\n      \"avg_spawns\": 1.598,\n      \"spawn_time\": \"01:34\",\n      \"multipliers\": [\n        1.71,\n        1.92\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"017\",\n          \"name\": \"Pidgeotto\"\n        },\n        {\n          \"num\": \"018\",\n          \"name\": \"Pidgeot\"\n        }\n      ]\n    },\n    {\n      \"id\": 17,\n      \"num\": \"017\",\n      \"name\": \"Pidgeotto\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/017.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"30.0 kg\",\n      \"candy\": \"Pidgey Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 1.02,\n      \"avg_spawns\": 102,\n      \"spawn_time\": \"01:30\",\n      \"multipliers\": [ 1.79 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"016\",\n          \"name\": \"Pidgey\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"018\",\n          \"name\": \"Pidgeot\"\n        }\n      ]\n    },\n    {\n      \"id\": 18,\n      \"num\": \"018\",\n      \"name\": \"Pidgeot\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/018.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"39.5 kg\",\n      \"candy\": \"Pidgey Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.13,\n      \"avg_spawns\": 13,\n      \"spawn_time\": \"01:50\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"016\",\n          \"name\": \"Pidgey\"\n        },\n        {\n          \"num\": \"017\",\n          \"name\": \"Pidgeotto\"\n        }\n      ]\n    },\n    {\n      \"id\": 19,\n      \"num\": \"019\",\n      \"name\": \"Rattata\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/019.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"3.5 kg\",\n      \"candy\": \"Rattata Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 13.05,\n      \"avg_spawns\": 1.305,\n      \"spawn_time\": \"01:55\",\n      \"multipliers\": [\n        2.55,\n        2.73\n      ],\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"020\",\n          \"name\": \"Raticate\"\n        }\n      ]\n    },\n    {\n      \"id\": 20,\n      \"num\": \"020\",\n      \"name\": \"Raticate\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/020.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.71 m\",\n      \"weight\": \"18.5 kg\",\n      \"candy\": \"Rattata Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.41,\n      \"avg_spawns\": 41,\n      \"spawn_time\": \"01:56\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"019\",\n          \"name\": \"Rattata\"\n        }\n      ]\n    },\n    {\n      \"id\": 21,\n      \"num\": \"021\",\n      \"name\": \"Spearow\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/021.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"2.0 kg\",\n      \"candy\": \"Spearow Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 4.73,\n      \"avg_spawns\": 473,\n      \"spawn_time\": \"12:25\",\n      \"multipliers\": [\n        2.66,\n        2.68\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"022\",\n          \"name\": \"Fearow\"\n        }\n      ]\n    },\n    {\n      \"id\": 22,\n      \"num\": \"022\",\n      \"name\": \"Fearow\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/022.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"38.0 kg\",\n      \"candy\": \"Spearow Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.15,\n      \"avg_spawns\": 15,\n      \"spawn_time\": \"01:11\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"021\",\n          \"name\": \"Spearow\"\n        }\n      ]\n    },\n    {\n      \"id\": 23,\n      \"num\": \"023\",\n      \"name\": \"Ekans\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/023.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"2.01 m\",\n      \"weight\": \"6.9 kg\",\n      \"candy\": \"Ekans Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 2.27,\n      \"avg_spawns\": 227,\n      \"spawn_time\": \"12:20\",\n      \"multipliers\": [\n        2.21,\n        2.27\n      ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"024\",\n          \"name\": \"Arbok\"\n        }\n      ]\n    },\n    {\n      \"id\": 24,\n      \"num\": \"024\",\n      \"name\": \"Arbok\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/024.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"3.51 m\",\n      \"weight\": \"65.0 kg\",\n      \"candy\": \"Ekans Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.072,\n      \"avg_spawns\": 7.2,\n      \"spawn_time\": \"01:50\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"023\",\n          \"name\": \"Ekans\"\n        }\n      ]\n    },\n    {\n      \"id\": 25,\n      \"num\": \"025\",\n      \"name\": \"Pikachu\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/025.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"6.0 kg\",\n      \"candy\": \"Pikachu Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 0.21,\n      \"avg_spawns\": 21,\n      \"spawn_time\": \"04:00\",\n      \"multipliers\": [ 2.34 ],\n      \"weaknesses\": [\n        \"Ground\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"026\",\n          \"name\": \"Raichu\"\n        }\n      ]\n    },\n    {\n      \"id\": 26,\n      \"num\": \"026\",\n      \"name\": \"Raichu\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/026.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"30.0 kg\",\n      \"candy\": \"Pikachu Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0076,\n      \"avg_spawns\": 0.76,\n      \"spawn_time\": \"23:58\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"025\",\n          \"name\": \"Pikachu\"\n        }\n      ]\n    },\n    {\n      \"id\": 27,\n      \"num\": \"027\",\n      \"name\": \"Sandshrew\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/027.png\",\n      \"type\": [\n        \"Ground\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"12.0 kg\",\n      \"candy\": \"Sandshrew Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.11,\n      \"avg_spawns\": 111,\n      \"spawn_time\": \"01:58\",\n      \"multipliers\": [ 2.45 ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"028\",\n          \"name\": \"Sandslash\"\n        }\n      ]\n    },\n    {\n      \"id\": 28,\n      \"num\": \"028\",\n      \"name\": \"Sandslash\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/028.png\",\n      \"type\": [\n        \"Ground\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"29.5 kg\",\n      \"candy\": \"Sandshrew Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.037,\n      \"avg_spawns\": 3.7,\n      \"spawn_time\": \"12:34\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"027\",\n          \"name\": \"Sandshrew\"\n        }\n      ]\n    },\n    {\n      \"id\": 29,\n      \"num\": \"029\",\n      \"name\": \"Nidoran ♀ (Female)\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/029.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"7.0 kg\",\n      \"candy\": \"Nidoran ♀ (Female) Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.38,\n      \"avg_spawns\": 138,\n      \"spawn_time\": \"01:51\",\n      \"multipliers\": [\n        1.63,\n        2.48\n      ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"030\",\n          \"name\": \"Nidorina\"\n        },\n        {\n          \"num\": \"031\",\n          \"name\": \"Nidoqueen\"\n        }\n      ]\n    },\n    {\n      \"id\": 30,\n      \"num\": \"030\",\n      \"name\": \"Nidorina\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/030.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"20.0 kg\",\n      \"candy\": \"Nidoran ♀ (Female) Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.088,\n      \"avg_spawns\": 8.8,\n      \"spawn_time\": \"07:22\",\n      \"multipliers\": [\n        1.83,\n        2.48\n      ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"029\",\n          \"name\": \"Nidoran(Female)\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"031\",\n          \"name\": \"Nidoqueen\"\n        }\n      ]\n    },\n    {\n      \"id\": 31,\n      \"num\": \"031\",\n      \"name\": \"Nidoqueen\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/031.png\",\n      \"type\": [\n        \"Poison\",\n        \"Ground\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"60.0 kg\",\n      \"candy\": \"Nidoran ♀ (Female) Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.012,\n      \"avg_spawns\": 1.2,\n      \"spawn_time\": \"12:35\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Ice\",\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"029\",\n          \"name\": \"Nidoran(Female)\"\n        },\n        {\n          \"num\": \"030\",\n          \"name\": \"Nidorina\"\n        }\n      ]\n    },\n    {\n      \"id\": 32,\n      \"num\": \"032\",\n      \"name\": \"Nidoran ♂ (Male)\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/032.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"0.51 m\",\n      \"weight\": \"9.0 kg\",\n      \"candy\": \"Nidoran ♂ (Male) Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.31,\n      \"avg_spawns\": 131,\n      \"spawn_time\": \"01:12\",\n      \"multipliers\": [\n        1.64,\n        1.7\n      ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"033\",\n          \"name\": \"Nidorino\"\n        },\n        {\n          \"num\": \"034\",\n          \"name\": \"Nidoking\"\n        }\n      ]\n    },\n    {\n      \"id\": 33,\n      \"num\": \"033\",\n      \"name\": \"Nidorino\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/033.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"0.89 m\",\n      \"weight\": \"19.5 kg\",\n      \"candy\": \"Nidoran ♂ (Male) Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.083,\n      \"avg_spawns\": 8.3,\n      \"spawn_time\": \"09:02\",\n      \"multipliers\": [ 1.83 ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"032\",\n          \"name\": \"Nidoran(Male)\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"034\",\n          \"name\": \"Nidoking\"\n        }\n      ]\n    },\n    {\n      \"id\": 34,\n      \"num\": \"034\",\n      \"name\": \"Nidoking\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/034.png\",\n      \"type\": [\n        \"Poison\",\n        \"Ground\"\n      ],\n      \"height\": \"1.40 m\",\n      \"weight\": \"62.0 kg\",\n      \"candy\": \"Nidoran ♂ (Male) Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.017,\n      \"avg_spawns\": 1.7,\n      \"spawn_time\": \"12:16\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Ice\",\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"032\",\n          \"name\": \"Nidoran(Male)\"\n        },\n        {\n          \"num\": \"033\",\n          \"name\": \"Nidorino\"\n        }\n      ]\n    },\n    {\n      \"id\": 35,\n      \"num\": \"035\",\n      \"name\": \"Clefairy\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/035.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"7.5 kg\",\n      \"candy\": \"Clefairy Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 0.92,\n      \"avg_spawns\": 92,\n      \"spawn_time\": \"03:30\",\n      \"multipliers\": [\n        2.03,\n        2.14\n      ],\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"036\",\n          \"name\": \"Clefable\"\n        }\n      ]\n    },\n    {\n      \"id\": 36,\n      \"num\": \"036\",\n      \"name\": \"Clefable\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/036.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"40.0 kg\",\n      \"candy\": \"Clefairy Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.012,\n      \"avg_spawns\": 1.2,\n      \"spawn_time\": \"03:29\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"035\",\n          \"name\": \"Clefairy\"\n        }\n      ]\n    },\n    {\n      \"id\": 37,\n      \"num\": \"037\",\n      \"name\": \"Vulpix\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/037.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"9.9 kg\",\n      \"candy\": \"Vulpix Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.22,\n      \"avg_spawns\": 22,\n      \"spawn_time\": \"13:43\",\n      \"multipliers\": [\n        2.74,\n        2.81\n      ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"038\",\n          \"name\": \"Ninetales\"\n        }\n      ]\n    },\n    {\n      \"id\": 38,\n      \"num\": \"038\",\n      \"name\": \"Ninetales\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/038.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"19.9 kg\",\n      \"candy\": \"Vulpix Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0077,\n      \"avg_spawns\": 0.77,\n      \"spawn_time\": \"01:32\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"037\",\n          \"name\": \"Vulpix\"\n        }\n      ]\n    },\n    {\n      \"id\": 39,\n      \"num\": \"039\",\n      \"name\": \"Jigglypuff\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/039.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.51 m\",\n      \"weight\": \"5.5 kg\",\n      \"candy\": \"Jigglypuff Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 0.39,\n      \"avg_spawns\": 39,\n      \"spawn_time\": \"08:46\",\n      \"multipliers\": [ 1.85 ],\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"040\",\n          \"name\": \"Wigglytuff\"\n        }\n      ]\n    },\n    {\n      \"id\": 40,\n      \"num\": \"040\",\n      \"name\": \"Wigglytuff\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/040.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"12.0 kg\",\n      \"candy\": \"Jigglypuff Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.018,\n      \"avg_spawns\": 1.8,\n      \"spawn_time\": \"12:28\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"039\",\n          \"name\": \"Jigglypuff\"\n        }\n      ]\n    },\n    {\n      \"id\": 41,\n      \"num\": \"041\",\n      \"name\": \"Zubat\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/041.png\",\n      \"type\": [\n        \"Poison\",\n        \"Flying\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"7.5 kg\",\n      \"candy\": \"Zubat Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 6.52,\n      \"avg_spawns\": 652,\n      \"spawn_time\": \"12:28\",\n      \"multipliers\": [\n        2.6,\n        3.67\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Ice\",\n        \"Psychic\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"042\",\n          \"name\": \"Golbat\"\n        }\n      ]\n    },\n    {\n      \"id\": 42,\n      \"num\": \"042\",\n      \"name\": \"Golbat\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/042.png\",\n      \"type\": [\n        \"Poison\",\n        \"Flying\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"55.0 kg\",\n      \"candy\": \"Zubat Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.42,\n      \"avg_spawns\": 42,\n      \"spawn_time\": \"02:15\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Ice\",\n        \"Psychic\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"041\",\n          \"name\": \"Zubat\"\n        }\n      ]\n    },\n    {\n      \"id\": 43,\n      \"num\": \"043\",\n      \"name\": \"Oddish\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/043.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"0.51 m\",\n      \"weight\": \"5.4 kg\",\n      \"candy\": \"Oddish Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.02,\n      \"avg_spawns\": 102,\n      \"spawn_time\": \"03:58\",\n      \"multipliers\": [ 1.5 ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"044\",\n          \"name\": \"Gloom\"\n        },\n        {\n          \"num\": \"045\",\n          \"name\": \"Vileplume\"\n        }\n      ]\n    },\n    {\n      \"id\": 44,\n      \"num\": \"044\",\n      \"name\": \"Gloom\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/044.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"8.6 kg\",\n      \"candy\": \"Oddish Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.064,\n      \"avg_spawns\": 6.4,\n      \"spawn_time\": \"11:33\",\n      \"multipliers\": [ 1.49 ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"043\",\n          \"name\": \"Oddish\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"045\",\n          \"name\": \"Vileplume\"\n        }\n      ]\n    },\n    {\n      \"id\": 45,\n      \"num\": \"045\",\n      \"name\": \"Vileplume\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/045.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"18.6 kg\",\n      \"candy\": \"Oddish Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0097,\n      \"avg_spawns\": 0.97,\n      \"spawn_time\": \"23:58\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"043\",\n          \"name\": \"Oddish\"\n        },\n        {\n          \"num\": \"044\",\n          \"name\": \"Gloom\"\n        }\n      ]\n    },\n    {\n      \"id\": 46,\n      \"num\": \"046\",\n      \"name\": \"Paras\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/046.png\",\n      \"type\": [\n        \"Bug\",\n        \"Grass\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"5.4 kg\",\n      \"candy\": \"Paras Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 2.36,\n      \"avg_spawns\": 236,\n      \"spawn_time\": \"01:42\",\n      \"multipliers\": [ 2.02 ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Poison\",\n        \"Flying\",\n        \"Bug\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"047\",\n          \"name\": \"Parasect\"\n        }\n      ]\n    },\n    {\n      \"id\": 47,\n      \"num\": \"047\",\n      \"name\": \"Parasect\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/047.png\",\n      \"type\": [\n        \"Bug\",\n        \"Grass\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"29.5 kg\",\n      \"candy\": \"Paras Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.074,\n      \"avg_spawns\": 7.4,\n      \"spawn_time\": \"01:22\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Poison\",\n        \"Flying\",\n        \"Bug\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"046\",\n          \"name\": \"Paras\"\n        }\n      ]\n    },\n    {\n      \"id\": 48,\n      \"num\": \"048\",\n      \"name\": \"Venonat\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/048.png\",\n      \"type\": [\n        \"Bug\",\n        \"Poison\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"30.0 kg\",\n      \"candy\": \"Venonat Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 2.28,\n      \"avg_spawns\": 228,\n      \"spawn_time\": \"02:31\",\n      \"multipliers\": [\n        1.86,\n        1.9\n      ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Psychic\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"049\",\n          \"name\": \"Venomoth\"\n        }\n      ]\n    },\n    {\n      \"id\": 49,\n      \"num\": \"049\",\n      \"name\": \"Venomoth\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/049.png\",\n      \"type\": [\n        \"Bug\",\n        \"Poison\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"12.5 kg\",\n      \"candy\": \"Venonat Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.072,\n      \"avg_spawns\": 7.2,\n      \"spawn_time\": \"23:40\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Psychic\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"048\",\n          \"name\": \"Venonat\"\n        }\n      ]\n    },\n    {\n      \"id\": 50,\n      \"num\": \"050\",\n      \"name\": \"Diglett\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/050.png\",\n      \"type\": [\n        \"Ground\"\n      ],\n      \"height\": \"0.20 m\",\n      \"weight\": \"0.8 kg\",\n      \"candy\": \"Diglett Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.40,\n      \"avg_spawns\": 40,\n      \"spawn_time\": \"02:22\",\n      \"multipliers\": [ 2.69 ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"051\",\n          \"name\": \"Dugtrio\"\n        }\n      ]\n    },\n    {\n      \"id\": 51,\n      \"num\": \"051\",\n      \"name\": \"Dugtrio\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/051.png\",\n      \"type\": [\n        \"Ground\"\n      ],\n      \"height\": \"0.71 m\",\n      \"weight\": \"33.3 kg\",\n      \"candy\": \"Dugtrio\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.014,\n      \"avg_spawns\": 1.4,\n      \"spawn_time\": \"12:37\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"050\",\n          \"name\": \"Diglett\"\n        }\n      ]\n    },\n    {\n      \"id\": 52,\n      \"num\": \"052\",\n      \"name\": \"Meowth\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/052.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"4.2 kg\",\n      \"candy\": \"Meowth Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.86,\n      \"avg_spawns\": 86,\n      \"spawn_time\": \"02:54\",\n      \"multipliers\": [ 1.98 ],\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"053\",\n          \"name\": \"Persian\"\n        }\n      ]\n    },\n    {\n      \"id\": 53,\n      \"num\": \"053\",\n      \"name\": \"Persian\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/053.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"32.0 kg\",\n      \"candy\": \"Meowth Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.022,\n      \"avg_spawns\": 2.2,\n      \"spawn_time\": \"02:44\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"052\",\n          \"name\": \"Meowth\"\n        }\n      ]\n    },\n    {\n      \"id\": 54,\n      \"num\": \"054\",\n      \"name\": \"Psyduck\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/054.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"19.6 kg\",\n      \"candy\": \"Psyduck Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 2.54,\n      \"avg_spawns\": 254,\n      \"spawn_time\": \"03:41\",\n      \"multipliers\": [ 2.27 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"055\",\n          \"name\": \"Golduck\"\n        }\n      ]\n    },\n    {\n      \"id\": 55,\n      \"num\": \"055\",\n      \"name\": \"Golduck\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/055.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"1.70 m\",\n      \"weight\": \"76.6 kg\",\n      \"candy\": \"Psyduck Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.087,\n      \"avg_spawns\": 8.7,\n      \"spawn_time\": \"23:06\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"054\",\n          \"name\": \"Psyduck\"\n        }\n      ]\n    },\n    {\n      \"id\": 56,\n      \"num\": \"056\",\n      \"name\": \"Mankey\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/056.png\",\n      \"type\": [\n        \"Fighting\"\n      ],\n      \"height\": \"0.51 m\",\n      \"weight\": \"28.0 kg\",\n      \"candy\": \"Mankey Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.92,\n      \"avg_spawns\": 92,\n      \"spawn_time\": \"12:52\",\n      \"multipliers\": [\n        2.17,\n        2.28\n      ],\n      \"weaknesses\": [\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"057\",\n          \"name\": \"Primeape\"\n        }\n      ]\n    },\n    {\n      \"id\": 57,\n      \"num\": \"057\",\n      \"name\": \"Primeape\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/057.png\",\n      \"type\": [\n        \"Fighting\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"32.0 kg\",\n      \"candy\": \"Mankey Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.031,\n      \"avg_spawns\": 3.1,\n      \"spawn_time\": \"12:33\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"056\",\n          \"name\": \"Mankey\"\n        }\n      ]\n    },\n    {\n      \"id\": 58,\n      \"num\": \"058\",\n      \"name\": \"Growlithe\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/058.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"0.71 m\",\n      \"weight\": \"19.0 kg\",\n      \"candy\": \"Growlithe Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.92,\n      \"avg_spawns\": 92,\n      \"spawn_time\": \"03:57\",\n      \"multipliers\": [\n        2.31,\n        2.36\n      ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"059\",\n          \"name\": \"Arcanine\"\n        }\n      ]\n    },\n    {\n      \"id\": 59,\n      \"num\": \"059\",\n      \"name\": \"Arcanine\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/059.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"1.91 m\",\n      \"weight\": \"155.0 kg\",\n      \"candy\": \"Growlithe Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.017,\n      \"avg_spawns\": 1.7,\n      \"spawn_time\": \"03:11\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"058\",\n          \"name\": \"Growlithe\"\n        }\n      ]\n    },\n    {\n      \"id\": 60,\n      \"num\": \"060\",\n      \"name\": \"Poliwag\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/060.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"12.4 kg\",\n      \"candy\": \"Poliwag Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 2.19,\n      \"avg_spawns\": 219,\n      \"spawn_time\": \"03:40\",\n      \"multipliers\": [\n        1.72,\n        1.73\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"061\",\n          \"name\": \"Poliwhirl\"\n        },\n        {\n          \"num\": \"062\",\n          \"name\": \"Poliwrath\"\n        }\n      ]\n    },\n    {\n      \"id\": 61,\n      \"num\": \"061\",\n      \"name\": \"Poliwhirl\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/061.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"20.0 kg\",\n      \"candy\": \"Poliwag Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.13,\n      \"avg_spawns\": 13,\n      \"spawn_time\": \"09:14\",\n      \"multipliers\": [ 1.95 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"060\",\n          \"name\": \"Poliwag\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"062\",\n          \"name\": \"Poliwrath\"\n        }\n      ]\n    },\n    {\n      \"id\": 62,\n      \"num\": \"062\",\n      \"name\": \"Poliwrath\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/062.png\",\n      \"type\": [\n        \"Water\",\n        \"Fighting\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"54.0 kg\",\n      \"candy\": \"Poliwag Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.011,\n      \"avg_spawns\": 1.1,\n      \"spawn_time\": \"01:32\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"060\",\n          \"name\": \"Poliwag\"\n        },\n        {\n          \"num\": \"061\",\n          \"name\": \"Poliwhirl\"\n        }\n      ]\n    },\n    {\n      \"id\": 63,\n      \"num\": \"063\",\n      \"name\": \"Abra\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/063.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"0.89 m\",\n      \"weight\": \"19.5 kg\",\n      \"candy\": \"Abra Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.42,\n      \"avg_spawns\": 42,\n      \"spawn_time\": \"04:30\",\n      \"multipliers\": [\n        1.36,\n        1.95\n      ],\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"064\",\n          \"name\": \"Kadabra\"\n        },\n        {\n          \"num\": \"065\",\n          \"name\": \"Alakazam\"\n        }\n      ]\n    },\n    {\n      \"id\": 64,\n      \"num\": \"064\",\n      \"name\": \"Kadabra\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/064.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"56.5 kg\",\n      \"candy\": \"Abra Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.027,\n      \"avg_spawns\": 2.7,\n      \"spawn_time\": \"11:25\",\n      \"multipliers\": [ 1.4 ],\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"063\",\n          \"name\": \"Abra\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"065\",\n          \"name\": \"Alakazam\"\n        }\n      ]\n    },\n    {\n      \"id\": 65,\n      \"num\": \"065\",\n      \"name\": \"Alakazam\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/065.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"48.0 kg\",\n      \"candy\": \"Abra Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0073,\n      \"avg_spawns\": 0.73,\n      \"spawn_time\": \"12:33\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"063\",\n          \"name\": \"Abra\"\n        },\n        {\n          \"num\": \"064\",\n          \"name\": \"Kadabra\"\n        }\n      ]\n    },\n    {\n      \"id\": 66,\n      \"num\": \"066\",\n      \"name\": \"Machop\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/066.png\",\n      \"type\": [\n        \"Fighting\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"19.5 kg\",\n      \"candy\": \"Machop Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.49,\n      \"avg_spawns\": 49,\n      \"spawn_time\": \"01:55\",\n      \"multipliers\": [\n        1.64,\n        1.65\n      ],\n      \"weaknesses\": [\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"067\",\n          \"name\": \"Machoke\"\n        },\n        {\n          \"num\": \"068\",\n          \"name\": \"Machamp\"\n        }\n      ]\n    },\n    {\n      \"id\": 67,\n      \"num\": \"067\",\n      \"name\": \"Machoke\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/067.png\",\n      \"type\": [\n        \"Fighting\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"70.5 kg\",\n      \"candy\": \"Machop Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.034,\n      \"avg_spawns\": 3.4,\n      \"spawn_time\": \"10:32\",\n      \"multipliers\": [ 1.7 ],\n      \"weaknesses\": [\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"066\",\n          \"name\": \"Machop\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"068\",\n          \"name\": \"Machamp\"\n        }\n      ]\n    },\n    {\n      \"id\": 68,\n      \"num\": \"068\",\n      \"name\": \"Machamp\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/068.png\",\n      \"type\": [\n        \"Fighting\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"130.0 kg\",\n      \"candy\": \"Machop Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0068,\n      \"avg_spawns\": 0.68,\n      \"spawn_time\": \"02:55\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"066\",\n          \"name\": \"Machop\"\n        },\n        {\n          \"num\": \"067\",\n          \"name\": \"Machoke\"\n        }\n      ]\n    },\n    {\n      \"id\": 69,\n      \"num\": \"069\",\n      \"name\": \"Bellsprout\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/069.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"0.71 m\",\n      \"weight\": \"4.0 kg\",\n      \"candy\": \"Bellsprout Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.15,\n      \"avg_spawns\": 115,\n      \"spawn_time\": \"04:10\",\n      \"multipliers\": [ 1.57 ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"070\",\n          \"name\": \"Weepinbell\"\n        },\n        {\n          \"num\": \"071\",\n          \"name\": \"Victreebel\"\n        }\n      ]\n    },\n    {\n      \"id\": 70,\n      \"num\": \"070\",\n      \"name\": \"Weepinbell\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/070.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"6.4 kg\",\n      \"candy\": \"Bellsprout Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.072,\n      \"avg_spawns\": 7.2,\n      \"spawn_time\": \"09:45\",\n      \"multipliers\": [ 1.59 ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"069\",\n          \"name\": \"Bellsprout\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"071\",\n          \"name\": \"Victreebel\"\n        }\n      ]\n    },\n    {\n      \"id\": 71,\n      \"num\": \"071\",\n      \"name\": \"Victreebel\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/071.png\",\n      \"type\": [\n        \"Grass\",\n        \"Poison\"\n      ],\n      \"height\": \"1.70 m\",\n      \"weight\": \"15.5 kg\",\n      \"candy\": \"Bellsprout Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0059,\n      \"avg_spawns\": 0.59,\n      \"spawn_time\": \"12:19\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Flying\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"069\",\n          \"name\": \"Bellsprout\"\n        },\n        {\n          \"num\": \"070\",\n          \"name\": \"Weepinbell\"\n        }\n      ]\n    },\n    {\n      \"id\": 72,\n      \"num\": \"072\",\n      \"name\": \"Tentacool\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/072.png\",\n      \"type\": [\n        \"Water\",\n        \"Poison\"\n      ],\n      \"height\": \"0.89 m\",\n      \"weight\": \"45.5 kg\",\n      \"candy\": \"Tentacool Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.81,\n      \"avg_spawns\": 81,\n      \"spawn_time\": \"03:20\",\n      \"multipliers\": [ 2.52 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"073\",\n          \"name\": \"Tentacruel\"\n        }\n      ]\n    },\n    {\n      \"id\": 73,\n      \"num\": \"073\",\n      \"name\": \"Tentacruel\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/073.png\",\n      \"type\": [\n        \"Water\",\n        \"Poison\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"55.0 kg\",\n      \"candy\": \"Tentacool Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.082,\n      \"avg_spawns\": 8.2,\n      \"spawn_time\": \"23:36\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"072\",\n          \"name\": \"Tentacool\"\n        }\n      ]\n    },\n    {\n      \"id\": 74,\n      \"num\": \"074\",\n      \"name\": \"Geodude\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/074.png\",\n      \"type\": [\n        \"Rock\",\n        \"Ground\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"20.0 kg\",\n      \"candy\": \"Geodude Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 1.19,\n      \"avg_spawns\": 119,\n      \"spawn_time\": \"12:40\",\n      \"multipliers\": [\n        1.75,\n        1.76\n      ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\",\n        \"Fighting\",\n        \"Ground\",\n        \"Steel\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"075\",\n          \"name\": \"Graveler\"\n        },\n        {\n          \"num\": \"076\",\n          \"name\": \"Golem\"\n        }\n      ]\n    },\n    {\n      \"id\": 75,\n      \"num\": \"075\",\n      \"name\": \"Graveler\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/075.png\",\n      \"type\": [\n        \"Rock\",\n        \"Ground\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"105.0 kg\",\n      \"candy\": \"Geodude Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.071,\n      \"avg_spawns\": 7.1,\n      \"spawn_time\": \"04:53\",\n      \"multipliers\": [\n        1.64,\n        1.72\n      ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\",\n        \"Fighting\",\n        \"Ground\",\n        \"Steel\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"074\",\n          \"name\": \"Geodude\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"076\",\n          \"name\": \"Golem\"\n        }\n      ]\n    },\n    {\n      \"id\": 76,\n      \"num\": \"076\",\n      \"name\": \"Golem\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/076.png\",\n      \"type\": [\n        \"Rock\",\n        \"Ground\"\n      ],\n      \"height\": \"1.40 m\",\n      \"weight\": \"300.0 kg\",\n      \"candy\": \"Geodude Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0047,\n      \"avg_spawns\": 0.47,\n      \"spawn_time\": \"12:16\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\",\n        \"Fighting\",\n        \"Ground\",\n        \"Steel\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"074\",\n          \"name\": \"Geodude\"\n        },\n        {\n          \"num\": \"075\",\n          \"name\": \"Graveler\"\n        }\n      ]\n    },\n    {\n      \"id\": 77,\n      \"num\": \"077\",\n      \"name\": \"Ponyta\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/077.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"30.0 kg\",\n      \"candy\": \"Ponyta Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.51,\n      \"avg_spawns\": 51,\n      \"spawn_time\": \"02:50\",\n      \"multipliers\": [\n        1.48,\n        1.5\n      ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"078\",\n          \"name\": \"Rapidash\"\n        }\n      ]\n    },\n    {\n      \"id\": 78,\n      \"num\": \"078\",\n      \"name\": \"Rapidash\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/078.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"1.70 m\",\n      \"weight\": \"95.0 kg\",\n      \"candy\": \"Ponyta Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.011,\n      \"avg_spawns\": 1.1,\n      \"spawn_time\": \"04:00\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"077\",\n          \"name\": \"Ponyta\"\n        }\n      ]\n    },\n    {\n      \"id\": 79,\n      \"num\": \"079\",\n      \"name\": \"Slowpoke\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/079.png\",\n      \"type\": [\n        \"Water\",\n        \"Psychic\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"36.0 kg\",\n      \"candy\": \"Slowpoke Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.05,\n      \"avg_spawns\": 105,\n      \"spawn_time\": \"07:12\",\n      \"multipliers\": [ 2.21 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"080\",\n          \"name\": \"Slowbro\"\n        }\n      ]\n    },\n    {\n      \"id\": 80,\n      \"num\": \"080\",\n      \"name\": \"Slowbro\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/080.png\",\n      \"type\": [\n        \"Water\",\n        \"Psychic\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"78.5 kg\",\n      \"candy\": \"Slowpoke Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.036,\n      \"avg_spawns\": 3.6,\n      \"spawn_time\": \"02:56\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"079\",\n          \"name\": \"Slowpoke\"\n        }\n      ]\n    },\n    {\n      \"id\": 81,\n      \"num\": \"081\",\n      \"name\": \"Magnemite\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/081.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"6.0 kg\",\n      \"candy\": \"Magnemite Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.71,\n      \"avg_spawns\": 71,\n      \"spawn_time\": \"04:04\",\n      \"multipliers\": [\n        2.16,\n        2.17\n      ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Water\",\n        \"Ground\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"082\",\n          \"name\": \"Magneton\"\n        }\n      ]\n    },\n    {\n      \"id\": 82,\n      \"num\": \"082\",\n      \"name\": \"Magneton\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/082.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"60.0 kg\",\n      \"candy\": \"Magnemite Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.023,\n      \"avg_spawns\": 2.3,\n      \"spawn_time\": \"15:25\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Water\",\n        \"Ground\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"081\",\n          \"name\": \"Magnemite\"\n        }\n      ]\n    },\n    {\n      \"id\": 83,\n      \"num\": \"083\",\n      \"name\": \"Farfetch'd\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/083.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"15.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.0212,\n      \"avg_spawns\": 2.12,\n      \"spawn_time\": \"01:09\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ]\n    },\n    {\n      \"id\": 84,\n      \"num\": \"084\",\n      \"name\": \"Doduo\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/084.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"1.40 m\",\n      \"weight\": \"39.2 kg\",\n      \"candy\": \"Doduo Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.52,\n      \"avg_spawns\": 52,\n      \"spawn_time\": \"05:10\",\n      \"multipliers\": [\n        2.19,\n        2.24\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"085\",\n          \"name\": \"Dodrio\"\n        }\n      ]\n    },\n    {\n      \"id\": 85,\n      \"num\": \"085\",\n      \"name\": \"Dodrio\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/085.png\",\n      \"type\": [\n        \"Normal\",\n        \"Flying\"\n      ],\n      \"height\": \"1.80 m\",\n      \"weight\": \"85.2 kg\",\n      \"candy\": \"Doduo Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.22,\n      \"avg_spawns\": 22,\n      \"spawn_time\": \"02:12\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"084\",\n          \"name\": \"Doduo\"\n        }\n      ]\n    },\n    {\n      \"id\": 86,\n      \"num\": \"086\",\n      \"name\": \"Seel\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/086.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"90.0 kg\",\n      \"candy\": \"Seel Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.28,\n      \"avg_spawns\": 28,\n      \"spawn_time\": \"06:46\",\n      \"multipliers\": [\n        1.04,\n        1.96\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"087\",\n          \"name\": \"Dewgong\"\n        }\n      ]\n    },\n    {\n      \"id\": 87,\n      \"num\": \"087\",\n      \"name\": \"Dewgong\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/087.png\",\n      \"type\": [\n        \"Water\",\n        \"Ice\"\n      ],\n      \"height\": \"1.70 m\",\n      \"weight\": \"120.0 kg\",\n      \"candy\": \"Seel Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.013,\n      \"avg_spawns\": 1.3,\n      \"spawn_time\": \"06:04\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Fighting\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"086\",\n          \"name\": \"Seel\"\n        }\n      ]\n    },\n    {\n      \"id\": 88,\n      \"num\": \"088\",\n      \"name\": \"Grimer\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/088.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"0.89 m\",\n      \"weight\": \"30.0 kg\",\n      \"candy\": \"Grimer Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.052,\n      \"avg_spawns\": 5.2,\n      \"spawn_time\": \"15:11\",\n      \"multipliers\": [ 2.44 ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"089\",\n          \"name\": \"Muk\"\n        }\n      ]\n    },\n    {\n      \"id\": 89,\n      \"num\": \"089\",\n      \"name\": \"Muk\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/089.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"30.0 kg\",\n      \"candy\": \"Grimer Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0031,\n      \"avg_spawns\": 0.31,\n      \"spawn_time\": \"01:28\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"088\",\n          \"name\": \"Grimer\"\n        }\n      ]\n    },\n    {\n      \"id\": 90,\n      \"num\": \"090\",\n      \"name\": \"Shellder\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/090.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"4.0 kg\",\n      \"candy\": \"Shellder Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.52,\n      \"avg_spawns\": 52,\n      \"spawn_time\": \"07:39\",\n      \"multipliers\": [ 2.65 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"091\",\n          \"name\": \"Cloyster\"\n        }\n      ]\n    },\n    {\n      \"id\": 91,\n      \"num\": \"091\",\n      \"name\": \"Cloyster\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/091.png\",\n      \"type\": [\n        \"Water\",\n        \"Ice\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"132.5 kg\",\n      \"candy\": \"Shellder Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.015,\n      \"avg_spawns\": 1.5,\n      \"spawn_time\": \"02:33\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Fighting\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"090\",\n          \"name\": \"Shellder\"\n        }\n      ]\n    },\n    {\n      \"id\": 92,\n      \"num\": \"092\",\n      \"name\": \"Gastly\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/092.png\",\n      \"type\": [\n        \"Ghost\",\n        \"Poison\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"0.1 kg\",\n      \"candy\": \"Gastly Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.79,\n      \"avg_spawns\": 79,\n      \"spawn_time\": \"04:21\",\n      \"multipliers\": [ 1.78 ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"093\",\n          \"name\": \"Haunter\"\n        },\n        {\n          \"num\": \"094\",\n          \"name\": \"Gengar\"\n        }\n      ]\n    },\n    {\n      \"id\": 93,\n      \"num\": \"093\",\n      \"name\": \"Haunter\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/093.png\",\n      \"type\": [\n        \"Ghost\",\n        \"Poison\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"0.1 kg\",\n      \"candy\": \"Gastly Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.052,\n      \"avg_spawns\": 5.2,\n      \"spawn_time\": \"00:10\",\n      \"multipliers\": [\n        1.56,\n        1.8\n      ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"092\",\n          \"name\": \"Gastly\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"094\",\n          \"name\": \"Gengar\"\n        }\n      ]\n    },\n    {\n      \"id\": 94,\n      \"num\": \"094\",\n      \"name\": \"Gengar\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/094.png\",\n      \"type\": [\n        \"Ghost\",\n        \"Poison\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"40.5 kg\",\n      \"candy\": \"Gastly Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0067,\n      \"avg_spawns\": 0.67,\n      \"spawn_time\": \"03:55\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"092\",\n          \"name\": \"Gastly\"\n        },\n        {\n          \"num\": \"093\",\n          \"name\": \"Haunter\"\n        }\n      ]\n    },\n    {\n      \"id\": 95,\n      \"num\": \"095\",\n      \"name\": \"Onix\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/095.png\",\n      \"type\": [\n        \"Rock\",\n        \"Ground\"\n      ],\n      \"height\": \"8.79 m\",\n      \"weight\": \"210.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.10,\n      \"avg_spawns\": 10,\n      \"spawn_time\": \"01:18\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\",\n        \"Fighting\",\n        \"Ground\",\n        \"Steel\"\n      ]\n    },\n    {\n      \"id\": 96,\n      \"num\": \"096\",\n      \"name\": \"Drowzee\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/096.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"32.4 kg\",\n      \"candy\": \"Drowzee Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 3.21,\n      \"avg_spawns\": 321,\n      \"spawn_time\": \"01:51\",\n      \"multipliers\": [\n        2.08,\n        2.09\n      ],\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"097\",\n          \"name\": \"Hypno\"\n        }\n      ]\n    },\n    {\n      \"id\": 97,\n      \"num\": \"097\",\n      \"name\": \"Hypno\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/097.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"75.6 kg\",\n      \"candy\": \"Drowzee Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.10,\n      \"avg_spawns\": 10,\n      \"spawn_time\": \"02:17\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"096\",\n          \"name\": \"Drowzee\"\n        }\n      ]\n    },\n    {\n      \"id\": 98,\n      \"num\": \"098\",\n      \"name\": \"Krabby\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/098.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"6.5 kg\",\n      \"candy\": \"Krabby Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 2.12,\n      \"avg_spawns\": 212,\n      \"spawn_time\": \"03:33\",\n      \"multipliers\": [\n        2.36,\n        2.4\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"099\",\n          \"name\": \"Kingler\"\n        }\n      ]\n    },\n    {\n      \"id\": 99,\n      \"num\": \"099\",\n      \"name\": \"Kingler\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/099.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"60.0 kg\",\n      \"candy\": \"Krabby Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.062,\n      \"avg_spawns\": 6.2,\n      \"spawn_time\": \"03:44\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"098\",\n          \"name\": \"Krabby\"\n        }\n      ]\n    },\n    {\n      \"id\": 100,\n      \"num\": \"100\",\n      \"name\": \"Voltorb\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/100.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"0.51 m\",\n      \"weight\": \"10.4 kg\",\n      \"candy\": \"Voltorb Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.65,\n      \"avg_spawns\": 65,\n      \"spawn_time\": \"04:36\",\n      \"multipliers\": [\n        2.01,\n        2.02\n      ],\n      \"weaknesses\": [\n        \"Ground\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"101\",\n          \"name\": \"Electrode\"\n        }\n      ]\n    },\n    {\n      \"id\": 101,\n      \"num\": \"101\",\n      \"name\": \"Electrode\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/101.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"66.6 kg\",\n      \"candy\": \"Voltorb Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.02,\n      \"avg_spawns\": 2,\n      \"spawn_time\": \"04:10\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"100\",\n          \"name\": \"Voltorb\"\n        }\n      ]\n    },\n    {\n      \"id\": 102,\n      \"num\": \"102\",\n      \"name\": \"Exeggcute\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/102.png\",\n      \"type\": [\n        \"Grass\",\n        \"Psychic\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"2.5 kg\",\n      \"candy\": \"Exeggcute Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.78,\n      \"avg_spawns\": 78,\n      \"spawn_time\": \"09:09\",\n      \"multipliers\": [\n        2.7,\n        3.18\n      ],\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Poison\",\n        \"Flying\",\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"103\",\n          \"name\": \"Exeggutor\"\n        }\n      ]\n    },\n    {\n      \"id\": 103,\n      \"num\": \"103\",\n      \"name\": \"Exeggutor\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/103.png\",\n      \"type\": [\n        \"Grass\",\n        \"Psychic\"\n      ],\n      \"height\": \"2.01 m\",\n      \"weight\": \"120.0 kg\",\n      \"candy\": \"Exeggcute Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.014,\n      \"avg_spawns\": 1.4,\n      \"spawn_time\": \"12:34\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Poison\",\n        \"Flying\",\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"102\",\n          \"name\": \"Exeggcute\"\n        }\n      ]\n    },\n    {\n      \"id\": 104,\n      \"num\": \"104\",\n      \"name\": \"Cubone\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/104.png\",\n      \"type\": [\n        \"Ground\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"6.5 kg\",\n      \"candy\": \"Cubone Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.61,\n      \"avg_spawns\": 61,\n      \"spawn_time\": \"01:51\",\n      \"multipliers\": [ 1.67 ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"105\",\n          \"name\": \"Marowak\"\n        }\n      ]\n    },\n    {\n      \"id\": 105,\n      \"num\": \"105\",\n      \"name\": \"Marowak\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/105.png\",\n      \"type\": [\n        \"Ground\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"45.0 kg\",\n      \"candy\": \"Cubone Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.02,\n      \"avg_spawns\": 2,\n      \"spawn_time\": \"03:59\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"104\",\n          \"name\": \"Cubone\"\n        }\n      ]\n    },\n    {\n      \"id\": 106,\n      \"num\": \"106\",\n      \"name\": \"Hitmonlee\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/106.png\",\n      \"type\": [\n        \"Fighting\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"49.8 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.02,\n      \"avg_spawns\": 2,\n      \"spawn_time\": \"03:59\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ]\n    },\n    {\n      \"id\": 107,\n      \"num\": \"107\",\n      \"name\": \"Hitmonchan\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/107.png\",\n      \"type\": [\n        \"Fighting\"\n      ],\n      \"height\": \"1.40 m\",\n      \"weight\": \"50.2 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.022,\n      \"avg_spawns\": 2.2,\n      \"spawn_time\": \"05:58\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Flying\",\n        \"Psychic\",\n        \"Fairy\"\n      ]\n    },\n    {\n      \"id\": 108,\n      \"num\": \"108\",\n      \"name\": \"Lickitung\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/108.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"65.5 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.011,\n      \"avg_spawns\": 1.1,\n      \"spawn_time\": \"02:46\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ]\n    },\n    {\n      \"id\": 109,\n      \"num\": \"109\",\n      \"name\": \"Koffing\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/109.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"1.0 kg\",\n      \"candy\": \"Koffing Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.20,\n      \"avg_spawns\": 20,\n      \"spawn_time\": \"08:16\",\n      \"multipliers\": [ 1.11 ],\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"110\",\n          \"name\": \"Weezing\"\n        }\n      ]\n    },\n    {\n      \"id\": 110,\n      \"num\": \"110\",\n      \"name\": \"Weezing\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/110.png\",\n      \"type\": [\n        \"Poison\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"9.5 kg\",\n      \"candy\": \"Koffing Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.016,\n      \"avg_spawns\": 1.6,\n      \"spawn_time\": \"12:17\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\",\n        \"Psychic\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"109\",\n          \"name\": \"Koffing\"\n        }\n      ]\n    },\n    {\n      \"id\": 111,\n      \"num\": \"111\",\n      \"name\": \"Rhyhorn\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/111.png\",\n      \"type\": [\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"115.0 kg\",\n      \"candy\": \"Rhyhorn Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.63,\n      \"avg_spawns\": 63,\n      \"spawn_time\": \"03:21\",\n      \"multipliers\": [ 1.91 ],\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\",\n        \"Fighting\",\n        \"Ground\",\n        \"Steel\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"112\",\n          \"name\": \"Rhydon\"\n        }\n      ]\n    },\n    {\n      \"id\": 112,\n      \"num\": \"112\",\n      \"name\": \"Rhydon\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/112.png\",\n      \"type\": [\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"height\": \"1.91 m\",\n      \"weight\": \"120.0 kg\",\n      \"candy\": \"Rhyhorn Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.022,\n      \"avg_spawns\": 2.2,\n      \"spawn_time\": \"05:50\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Grass\",\n        \"Ice\",\n        \"Fighting\",\n        \"Ground\",\n        \"Steel\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"111\",\n          \"name\": \"Rhyhorn\"\n        }\n      ]\n    },\n    {\n      \"id\": 113,\n      \"num\": \"113\",\n      \"name\": \"Chansey\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/113.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"34.6 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.013,\n      \"avg_spawns\": 1.3,\n      \"spawn_time\": \"04:46\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ]\n    },\n    {\n      \"id\": 114,\n      \"num\": \"114\",\n      \"name\": \"Tangela\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/114.png\",\n      \"type\": [\n        \"Grass\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"35.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.228,\n      \"avg_spawns\": 22.8,\n      \"spawn_time\": \"23:13\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Ice\",\n        \"Poison\",\n        \"Flying\",\n        \"Bug\"\n      ]\n    },\n    {\n      \"id\": 115,\n      \"num\": \"115\",\n      \"name\": \"Kangaskhan\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/115.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"2.21 m\",\n      \"weight\": \"80.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.0086,\n      \"avg_spawns\": 0.86,\n      \"spawn_time\": \"02:40\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ]\n    },\n    {\n      \"id\": 116,\n      \"num\": \"116\",\n      \"name\": \"Horsea\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/116.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"8.0 kg\",\n      \"candy\": \"Horsea Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.13,\n      \"avg_spawns\": 113,\n      \"spawn_time\": \"02:53\",\n      \"multipliers\": [ 2.23 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"117\",\n          \"name\": \"Seadra\"\n        }\n      ]\n    },\n    {\n      \"id\": 117,\n      \"num\": \"117\",\n      \"name\": \"Seadra\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/117.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"1.19 m\",\n      \"weight\": \"25.0 kg\",\n      \"candy\": \"Horsea Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.034,\n      \"avg_spawns\": 3.4,\n      \"spawn_time\": \"03:18\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"116\",\n          \"name\": \"Horsea\"\n        }\n      ]\n    },\n    {\n      \"id\": 118,\n      \"num\": \"118\",\n      \"name\": \"Goldeen\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/118.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.61 m\",\n      \"weight\": \"15.0 kg\",\n      \"candy\": \"Goldeen Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 2.18,\n      \"avg_spawns\": 218,\n      \"spawn_time\": \"03:14\",\n      \"multipliers\": [\n        2.15,\n        2.2\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"119\",\n          \"name\": \"Seaking\"\n        }\n      ]\n    },\n    {\n      \"id\": 119,\n      \"num\": \"119\",\n      \"name\": \"Seaking\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/119.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"39.0 kg\",\n      \"candy\": \"Goldeen Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.08,\n      \"avg_spawns\": 8,\n      \"spawn_time\": \"05:21\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"118\",\n          \"name\": \"Goldeen\"\n        }\n      ]\n    },\n    {\n      \"id\": 120,\n      \"num\": \"120\",\n      \"name\": \"Staryu\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/120.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"34.5 kg\",\n      \"candy\": \"Staryu Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 1.95,\n      \"avg_spawns\": 195,\n      \"spawn_time\": \"22:59\",\n      \"multipliers\": [\n        2.38,\n        2.41\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"121\",\n          \"name\": \"Starmie\"\n        }\n      ]\n    },\n    {\n      \"id\": 121,\n      \"num\": \"121\",\n      \"name\": \"Starmie\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/121.png\",\n      \"type\": [\n        \"Water\",\n        \"Psychic\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"80.0 kg\",\n      \"candy\": \"Staryu Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.034,\n      \"avg_spawns\": 3.4,\n      \"spawn_time\": \"06:57\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"120\",\n          \"name\": \"Staryu\"\n        }\n      ]\n    },\n    {\n      \"id\": 122,\n      \"num\": \"122\",\n      \"name\": \"Mr. Mime\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/122.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"54.5 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.0031,\n      \"avg_spawns\": 0.31,\n      \"spawn_time\": \"01:51\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ]\n    },\n    {\n      \"id\": 123,\n      \"num\": \"123\",\n      \"name\": \"Scyther\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/123.png\",\n      \"type\": [\n        \"Bug\",\n        \"Flying\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"56.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.14,\n      \"avg_spawns\": 14,\n      \"spawn_time\": \"05:43\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Electric\",\n        \"Ice\",\n        \"Flying\",\n        \"Rock\"\n      ]\n    },\n    {\n      \"id\": 124,\n      \"num\": \"124\",\n      \"name\": \"Jynx\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/124.png\",\n      \"type\": [\n        \"Ice\",\n        \"Psychic\"\n      ],\n      \"height\": \"1.40 m\",\n      \"weight\": \"40.6 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.35,\n      \"avg_spawns\": 35,\n      \"spawn_time\": \"05:41\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Bug\",\n        \"Rock\",\n        \"Ghost\",\n        \"Dark\",\n        \"Steel\"\n      ]\n    },\n    {\n      \"id\": 125,\n      \"num\": \"125\",\n      \"name\": \"Electabuzz\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/125.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"1.09 m\",\n      \"weight\": \"30.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.074,\n      \"avg_spawns\": 7.4,\n      \"spawn_time\": \"04:28\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\"\n      ]\n    },\n    {\n      \"id\": 126,\n      \"num\": \"126\",\n      \"name\": \"Magmar\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/126.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"44.5 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.10,\n      \"avg_spawns\": 10,\n      \"spawn_time\": \"20:36\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ]\n    },\n    {\n      \"id\": 127,\n      \"num\": \"127\",\n      \"name\": \"Pinsir\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/127.png\",\n      \"type\": [\n        \"Bug\"\n      ],\n      \"height\": \"1.50 m\",\n      \"weight\": \"55.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.99,\n      \"avg_spawns\": 99,\n      \"spawn_time\": \"03:25\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Flying\",\n        \"Rock\"\n      ]\n    },\n    {\n      \"id\": 128,\n      \"num\": \"128\",\n      \"name\": \"Tauros\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/128.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"1.40 m\",\n      \"weight\": \"88.4 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.12,\n      \"avg_spawns\": 12,\n      \"spawn_time\": \"00:37\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ]\n    },\n    {\n      \"id\": 129,\n      \"num\": \"129\",\n      \"name\": \"Magikarp\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/129.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.89 m\",\n      \"weight\": \"10.0 kg\",\n      \"candy\": \"Magikarp Candy\",\n      \"candy_count\": 400,\n      \"egg\": \"2 km\",\n      \"spawn_chance\": 4.78,\n      \"avg_spawns\": 478,\n      \"spawn_time\": \"14:26\",\n      \"multipliers\": [\n        10.1,\n        11.8\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"130\",\n          \"name\": \"Gyarados\"\n        }\n      ]\n    },\n    {\n      \"id\": 130,\n      \"num\": \"130\",\n      \"name\": \"Gyarados\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/130.png\",\n      \"type\": [\n        \"Water\",\n        \"Flying\"\n      ],\n      \"height\": \"6.50 m\",\n      \"weight\": \"235.0 kg\",\n      \"candy\": \"Magikarp Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0032,\n      \"avg_spawns\": 0.32,\n      \"spawn_time\": \"02:15\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"129\",\n          \"name\": \"Magikarp\"\n        }\n      ]\n    },\n    {\n      \"id\": 131,\n      \"num\": \"131\",\n      \"name\": \"Lapras\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/131.png\",\n      \"type\": [\n        \"Water\",\n        \"Ice\"\n      ],\n      \"height\": \"2.49 m\",\n      \"weight\": \"220.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.006,\n      \"avg_spawns\": 0.6,\n      \"spawn_time\": \"08:59\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Fighting\",\n        \"Rock\"\n      ]\n    },\n    {\n      \"id\": 132,\n      \"num\": \"132\",\n      \"name\": \"Ditto\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/132.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"4.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0,\n      \"avg_spawns\": 0,\n      \"spawn_time\": \"N/A\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ]\n    },\n    {\n      \"id\": 133,\n      \"num\": \"133\",\n      \"name\": \"Eevee\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/133.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.30 m\",\n      \"weight\": \"6.5 kg\",\n      \"candy\": \"Eevee Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 2.75,\n      \"avg_spawns\": 275,\n      \"spawn_time\": \"05:32\",\n      \"multipliers\": [\n        2.02,\n        2.64\n      ],\n      \"weaknesses\": [\n        \"Fighting\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"134\",\n          \"name\": \"Vaporeon\"\n        },\n        {\n          \"num\": \"135\",\n          \"name\": \"Jolteon\"\n        },\n        {\n          \"num\": \"136\",\n          \"name\": \"Flareon\"\n        }\n      ]\n    },\n    {\n      \"id\": 134,\n      \"num\": \"134\",\n      \"name\": \"Vaporeon\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/134.png\",\n      \"type\": [\n        \"Water\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"29.0 kg\",\n      \"candy\": \"Eevee Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.014,\n      \"avg_spawns\": 1.4,\n      \"spawn_time\": \"10:54\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"133\",\n          \"name\": \"Eevee\"\n        }\n      ]\n    },\n    {\n      \"id\": 135,\n      \"num\": \"135\",\n      \"name\": \"Jolteon\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/135.png\",\n      \"type\": [\n        \"Electric\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"24.5 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.012,\n      \"avg_spawns\": 1.2,\n      \"spawn_time\": \"02:30\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ground\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"133\",\n          \"name\": \"Eevee\"\n        }\n      ]\n    },\n    {\n      \"id\": 136,\n      \"num\": \"136\",\n      \"name\": \"Flareon\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/136.png\",\n      \"type\": [\n        \"Fire\"\n      ],\n      \"height\": \"0.89 m\",\n      \"weight\": \"25.0 kg\",\n      \"candy\": \"Eevee Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.017,\n      \"avg_spawns\": 1.7,\n      \"spawn_time\": \"07:02\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Ground\",\n        \"Rock\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"133\",\n          \"name\": \"Eevee\"\n        }\n      ]\n    },\n    {\n      \"id\": 137,\n      \"num\": \"137\",\n      \"name\": \"Porygon\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/137.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"0.79 m\",\n      \"weight\": \"36.5 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"5 km\",\n      \"spawn_chance\": 0.012,\n      \"avg_spawns\": 1.2,\n      \"spawn_time\": \"02:49\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ]\n    },\n    {\n      \"id\": 138,\n      \"num\": \"138\",\n      \"name\": \"Omanyte\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/138.png\",\n      \"type\": [\n        \"Rock\",\n        \"Water\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"7.5 kg\",\n      \"candy\": \"Omanyte Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.14,\n      \"avg_spawns\": 14,\n      \"spawn_time\": \"10:23\",\n      \"multipliers\": [ 2.12 ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Fighting\",\n        \"Ground\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"139\",\n          \"name\": \"Omastar\"\n        }\n      ]\n    },\n    {\n      \"id\": 139,\n      \"num\": \"139\",\n      \"name\": \"Omastar\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/139.png\",\n      \"type\": [\n        \"Rock\",\n        \"Water\"\n      ],\n      \"height\": \"0.99 m\",\n      \"weight\": \"35.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Omanyte Candy\",\n      \"spawn_chance\": 0.0061,\n      \"avg_spawns\": 0.61,\n      \"spawn_time\": \"05:04\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Fighting\",\n        \"Ground\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"138\",\n          \"name\": \"Omanyte\"\n        }\n      ]\n    },\n    {\n      \"id\": 140,\n      \"num\": \"140\",\n      \"name\": \"Kabuto\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/140.png\",\n      \"type\": [\n        \"Rock\",\n        \"Water\"\n      ],\n      \"height\": \"0.51 m\",\n      \"weight\": \"11.5 kg\",\n      \"candy\": \"Kabuto Candy\",\n      \"candy_count\": 50,\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.10,\n      \"avg_spawns\": 10,\n      \"spawn_time\": \"00:05\",\n      \"multipliers\": [\n        1.97,\n        2.37\n      ],\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Fighting\",\n        \"Ground\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"141\",\n          \"name\": \"Kabutops\"\n        }\n      ]\n    },\n    {\n      \"id\": 141,\n      \"num\": \"141\",\n      \"name\": \"Kabutops\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/141.png\",\n      \"type\": [\n        \"Rock\",\n        \"Water\"\n      ],\n      \"height\": \"1.30 m\",\n      \"weight\": \"40.5 kg\",\n      \"candy\": \"Kabuto Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0032,\n      \"avg_spawns\": 0.32,\n      \"spawn_time\": \"23:40\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Electric\",\n        \"Grass\",\n        \"Fighting\",\n        \"Ground\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"140\",\n          \"name\": \"Kabuto\"\n        }\n      ]\n    },\n    {\n      \"id\": 142,\n      \"num\": \"142\",\n      \"name\": \"Aerodactyl\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/142.png\",\n      \"type\": [\n        \"Rock\",\n        \"Flying\"\n      ],\n      \"height\": \"1.80 m\",\n      \"weight\": \"59.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.018,\n      \"avg_spawns\": 1.8,\n      \"spawn_time\": \"23:40\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Electric\",\n        \"Ice\",\n        \"Rock\",\n        \"Steel\"\n      ]\n    },\n    {\n      \"id\": 143,\n      \"num\": \"143\",\n      \"name\": \"Snorlax\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/143.png\",\n      \"type\": [\n        \"Normal\"\n      ],\n      \"height\": \"2.11 m\",\n      \"weight\": \"460.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.016,\n      \"avg_spawns\": 1.6,\n      \"spawn_time\": \"23:40\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fighting\"\n      ]\n    },\n    {\n      \"id\": 144,\n      \"num\": \"144\",\n      \"name\": \"Articuno\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/144.png\",\n      \"type\": [\n        \"Ice\",\n        \"Flying\"\n      ],\n      \"height\": \"1.70 m\",\n      \"weight\": \"55.4 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0,\n      \"avg_spawns\": 0,\n      \"spawn_time\": \"N/A\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Fire\",\n        \"Electric\",\n        \"Rock\",\n        \"Steel\"\n      ]\n    },\n    {\n      \"id\": 145,\n      \"num\": \"145\",\n      \"name\": \"Zapdos\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/145.png\",\n      \"type\": [\n        \"Electric\",\n        \"Flying\"\n      ],\n      \"height\": \"1.60 m\",\n      \"weight\": \"52.6 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0,\n      \"avg_spawns\": 0,\n      \"spawn_time\": \"N/A\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ice\",\n        \"Rock\"\n      ]\n    },\n    {\n      \"id\": 146,\n      \"num\": \"146\",\n      \"name\": \"Moltres\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/146.png\",\n      \"type\": [\n        \"Fire\",\n        \"Flying\"\n      ],\n      \"height\": \"2.01 m\",\n      \"weight\": \"60.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0,\n      \"avg_spawns\": 0,\n      \"spawn_time\": \"N/A\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Water\",\n        \"Electric\",\n        \"Rock\"\n      ]\n    },\n    {\n      \"id\": 147,\n      \"num\": \"147\",\n      \"name\": \"Dratini\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/147.png\",\n      \"type\": [\n        \"Dragon\"\n      ],\n      \"height\": \"1.80 m\",\n      \"weight\": \"3.3 kg\",\n      \"candy\": \"Dratini Candy\",\n      \"candy_count\": 25,\n      \"egg\": \"10 km\",\n      \"spawn_chance\": 0.30,\n      \"avg_spawns\": 30,\n      \"spawn_time\": \"06:41\",\n      \"multipliers\": [\n        1.83,\n        1.84\n      ],\n      \"weaknesses\": [\n        \"Ice\",\n        \"Dragon\",\n        \"Fairy\"\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"148\",\n          \"name\": \"Dragonair\"\n        },\n        {\n          \"num\": \"149\",\n          \"name\": \"Dragonite\"\n        }\n      ]\n    },\n    {\n      \"id\": 148,\n      \"num\": \"148\",\n      \"name\": \"Dragonair\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/148.png\",\n      \"type\": [\n        \"Dragon\"\n      ],\n      \"height\": \"3.99 m\",\n      \"weight\": \"16.5 kg\",\n      \"candy\": \"Dratini Candy\",\n      \"candy_count\": 100,\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.02,\n      \"avg_spawns\": 2,\n      \"spawn_time\": \"11:57\",\n      \"multipliers\": [ 2.05 ],\n      \"weaknesses\": [\n        \"Ice\",\n        \"Dragon\",\n        \"Fairy\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"147\",\n          \"name\": \"Dratini\"\n        }\n      ],\n      \"next_evolution\": [\n        {\n          \"num\": \"149\",\n          \"name\": \"Dragonite\"\n        }\n      ]\n    },\n    {\n      \"id\": 149,\n      \"num\": \"149\",\n      \"name\": \"Dragonite\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/149.png\",\n      \"type\": [\n        \"Dragon\",\n        \"Flying\"\n      ],\n      \"height\": \"2.21 m\",\n      \"weight\": \"210.0 kg\",\n      \"candy\": \"Dratini Candy\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0.0011,\n      \"avg_spawns\": 0.11,\n      \"spawn_time\": \"23:38\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Ice\",\n        \"Rock\",\n        \"Dragon\",\n        \"Fairy\"\n      ],\n      \"prev_evolution\": [\n        {\n          \"num\": \"147\",\n          \"name\": \"Dratini\"\n        },\n        {\n          \"num\": \"148\",\n          \"name\": \"Dragonair\"\n        }\n      ]\n    },\n    {\n      \"id\": 150,\n      \"num\": \"150\",\n      \"name\": \"Mewtwo\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/150.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"2.01 m\",\n      \"weight\": \"122.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0,\n      \"avg_spawns\": 0,\n      \"spawn_time\": \"N/A\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ]\n    },\n    {\n      \"id\": 151,\n      \"num\": \"151\",\n      \"name\": \"Mew\",\n      \"img\": \"http://www.serebii.net/pokemongo/pokemon/151.png\",\n      \"type\": [\n        \"Psychic\"\n      ],\n      \"height\": \"0.41 m\",\n      \"weight\": \"4.0 kg\",\n      \"candy\": \"None\",\n      \"egg\": \"Not in Eggs\",\n      \"spawn_chance\": 0,\n      \"avg_spawns\": 0,\n      \"spawn_time\": \"N/A\",\n      \"multipliers\": null,\n      \"weaknesses\": [\n        \"Bug\",\n        \"Ghost\",\n        \"Dark\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "Scenarios/wwwroot/index.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" />\n    <title></title>\n</head>\n<body>\n    <h2>Scenario: Retrieving and returning JSON response from an external API</h2>\n    <ul>\n        <li><a href=\"/big-json-content-1\">/big-json-content-1</a></li>\n        <li><a href=\"/big-json-content-2\">/big-json-content-2</a></li>\n        <li><a href=\"/big-json-content-3\">/big-json-content-3</a></li>\n        <li><a href=\"/big-json-content-4\">/big-json-content-4</a></li>\n        <li><a href=\"/big-json-content-5\">/big-json-content-5</a></li>\n\n    </ul>\n\n    <h2>Scenario: HTTP client to retrieve a JSON response</h2>\n    <ul>\n        <li><a href=\"/httpclient-1\">/httpclient-1</a></li>\n        <li><a href=\"/httpclient-2\">/httpclient-2</a></li>\n        <li><a href=\"/httpclient-3\">/httpclient-3</a></li>\n    </ul>\n\n    <h2>Scenario: Big JSON POST</h2>\n    <ul>\n        <li><a href=\"/big-json-input-1\">/big-json-input-1</a></li>\n        <li><a href=\"/big-json-input-2\">/big-json-input-2</a></li>\n        <li><a href=\"/big-json-input-3\">/big-json-input-3</a></li>\n        <li><a href=\"/big-json-input-4\">/big-json-input-4</a></li>\n    </ul>\n\n    <h2>Scenario: HTTP client request with cancellation</h2>\n    <ul>\n        <li><a href=\"/cancellation-1\">/cancellation-1</a></li>\n        <li><a href=\"/cancellation-2\">/cancellation-2</a></li>\n        <li><a href=\"/cancellation-3\">/cancellation-3</a></li>\n    </ul>\n\n    <h2>Scenario: Cancelling an async operation without native cancellation support</h2>\n    <ul>\n        <li><a href=\"/legacy-cancellation-1\">/legacy-cancellation-1</a></li>\n        <li><a href=\"/legacy-cancellation-2\">/legacy-cancellation-2</a></li>\n        <li><a href=\"/legacy-cancellation-3\">/legacy-cancellation-3</a></li>\n        <li><a href=\"/legacy-cancellation-4\">/legacy-cancellation-4</a></li>\n    </ul>\n\n    <h2>Scenario: Executing an async operation in business logic</h2>\n    <ul>\n        <li><a href=\"/async-1\">/async-1</a></li>\n        <li><a href=\"/async-2\">/async-2</a></li>\n        <li><a href=\"/async-3\">/async-3</a></li>\n        <li><a href=\"/async-4\">/async-4</a></li>\n        <li><a href=\"/async-5\">/async-5</a></li>\n        <li><a href=\"/async-6\">/async-6</a></li>\n        <li><a href=\"/async-7\">/async-7</a></li>\n        <li><a href=\"/async-8\">/async-8</a></li>\n        <li><a href=\"/async-9\">/async-9</a></li>\n        <li><a href=\"/async-10\">/async-10</a></li>\n        <li><a href=\"/async-11\">/async-11</a></li>\n    </ul>\n\n    <h2>Scenario: Fire and forget database operation</h2>\n    <ul>\n        <li><a href=\"/fire-and-forget-1\">/fire-and-forget-1</a></li>\n        <li><a href=\"/fire-and-forget-2\">/fire-and-forget-2</a></li>\n        <li><a href=\"/fire-and-forget-3\">/fire-and-forget-3</a></li>\n        <li><a href=\"/fire-and-forget-4\">/fire-and-forget-4</a></li>\n        <li><a href=\"/fire-and-forget-5\">/fire-and-forget-5</a></li>\n    </ul>\n\n    <h2>Scenario: Async void in Controllers</h2>\n    <ul>\n        <li><a href=\"/async-void-1\">/async-void-1</a></li>\n        <li><a href=\"/async-void-2\">/async-void-2</a></li>\n    </ul>\n</body>\n</html>\n"
  }
]