From f145e993e387d6e2f22cc01de158be90e47f9ec3 Mon Sep 17 00:00:00 2001
From: wanggaofeng <15601716045@163.com>
Date: Fri, 26 Jul 2024 20:45:41 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0VS=E9=A1=B9=E7=9B=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Polly8Study.Core/GlobalUsing.cs | 24 +
Polly8Study.Core/HttpClientUtil.cs | 12 +
Polly8Study.Core/Polly8Study.Core.csproj | 15 +
Polly8Study.Test/GlobalUsing.cs | 29 +
Polly8Study.Test/Polly8Study.Test.csproj | 27 +
Polly8Study.Test/Polly8TimeoutTest.cs | 495 ++++++++++++++++++
Polly8Study.Test/UseXunit.cs | 21 +
.../Controllers/TimeoutController.cs | 58 ++
.../OperationCancelledExceptionFilter.cs | 25 +
Polly8Study.WebApi/Polly8Study.WebApi.csproj | 13 +
Polly8Study.WebApi/Polly8Study.WebApi.http | 6 +
Polly8Study.WebApi/Program.cs | 50 ++
.../Properties/launchSettings.json | 31 ++
.../appsettings.Development.json | 8 +
Polly8Study.WebApi/appsettings.json | 9 +
Polly8Study.sln | 37 ++
16 files changed, 860 insertions(+)
create mode 100644 Polly8Study.Core/GlobalUsing.cs
create mode 100644 Polly8Study.Core/HttpClientUtil.cs
create mode 100644 Polly8Study.Core/Polly8Study.Core.csproj
create mode 100644 Polly8Study.Test/GlobalUsing.cs
create mode 100644 Polly8Study.Test/Polly8Study.Test.csproj
create mode 100644 Polly8Study.Test/Polly8TimeoutTest.cs
create mode 100644 Polly8Study.Test/UseXunit.cs
create mode 100644 Polly8Study.WebApi/Controllers/TimeoutController.cs
create mode 100644 Polly8Study.WebApi/Filters/OperationCancelledExceptionFilter.cs
create mode 100644 Polly8Study.WebApi/Polly8Study.WebApi.csproj
create mode 100644 Polly8Study.WebApi/Polly8Study.WebApi.http
create mode 100644 Polly8Study.WebApi/Program.cs
create mode 100644 Polly8Study.WebApi/Properties/launchSettings.json
create mode 100644 Polly8Study.WebApi/appsettings.Development.json
create mode 100644 Polly8Study.WebApi/appsettings.json
create mode 100644 Polly8Study.sln
diff --git a/Polly8Study.Core/GlobalUsing.cs b/Polly8Study.Core/GlobalUsing.cs
new file mode 100644
index 0000000..eb4d3ae
--- /dev/null
+++ b/Polly8Study.Core/GlobalUsing.cs
@@ -0,0 +1,24 @@
+global using System.IO;
+global using System.Collections;
+global using System.Collections.Concurrent;
+global using System.Collections.Generic;
+global using System.Collections.Immutable;
+global using System.Collections.Specialized;
+global using System.Configuration;
+global using System.Diagnostics;
+global using System.Dynamic;
+global using System.Threading;
+global using System.Threading.Tasks;
+
+global using Polly;
+global using Polly.CircuitBreaker;
+global using Polly.Fallback;
+global using Polly.Retry;
+global using Polly.Timeout;
+global using Polly.Wrap;
+global using Polly.Extensions.Http;
+global using Polly.Bulkhead;
+
+global using Polly8Study.Core;
+
+
diff --git a/Polly8Study.Core/HttpClientUtil.cs b/Polly8Study.Core/HttpClientUtil.cs
new file mode 100644
index 0000000..20be02b
--- /dev/null
+++ b/Polly8Study.Core/HttpClientUtil.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Polly8Study.Core
+{
+ public class HttpClientUtil
+ {
+ }
+}
diff --git a/Polly8Study.Core/Polly8Study.Core.csproj b/Polly8Study.Core/Polly8Study.Core.csproj
new file mode 100644
index 0000000..8454850
--- /dev/null
+++ b/Polly8Study.Core/Polly8Study.Core.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/Polly8Study.Test/GlobalUsing.cs b/Polly8Study.Test/GlobalUsing.cs
new file mode 100644
index 0000000..cd4383f
--- /dev/null
+++ b/Polly8Study.Test/GlobalUsing.cs
@@ -0,0 +1,29 @@
+global using System.IO;
+global using System.Collections;
+global using System.Collections.Concurrent;
+global using System.Collections.Generic;
+global using System.Collections.Immutable;
+global using System.Collections.Specialized;
+global using System.Configuration;
+global using System.Diagnostics;
+global using System.Dynamic;
+global using System.Threading;
+global using System.Threading.Tasks;
+
+global using Xunit;
+global using Xunit.Sdk;
+global using Xunit.Extensions;
+global using Xunit.Abstractions;
+
+global using Polly;
+global using Polly.CircuitBreaker;
+global using Polly.Fallback;
+global using Polly.Retry;
+global using Polly.Timeout;
+global using Polly.Wrap;
+global using Polly.Extensions.Http;
+global using Polly.Bulkhead;
+
+global using Polly8Study.Core;
+
+
diff --git a/Polly8Study.Test/Polly8Study.Test.csproj b/Polly8Study.Test/Polly8Study.Test.csproj
new file mode 100644
index 0000000..f3542b3
--- /dev/null
+++ b/Polly8Study.Test/Polly8Study.Test.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Polly8Study.Test/Polly8TimeoutTest.cs b/Polly8Study.Test/Polly8TimeoutTest.cs
new file mode 100644
index 0000000..90bd86d
--- /dev/null
+++ b/Polly8Study.Test/Polly8TimeoutTest.cs
@@ -0,0 +1,495 @@
+namespace Polly8Study.Test
+{
+ ///
+ /// Polly8ʱ
+ /// ؼ
+ /// CancellationTokenûDzʹõ
+ /// ֮ǰ汾еֹ۳ʱ۳ʱòȡ
+ ///
+ public class Polly8TimeoutTest
+ {
+ private readonly ITestOutputHelper _output;
+
+ public Polly8TimeoutTest(ITestOutputHelper testOutput)
+ {
+ _output = testOutput;
+ }
+
+ ///
+ /// ʱѡ
+ ///
+ [Fact]
+ public void TimeoutStrategyOptions_Test()
+ {
+ //ֱ
+ new ResiliencePipelineBuilder()
+ .AddTimeout(TimeSpan.FromMilliseconds(100))
+ .Build();
+
+ //Ĭֵ
+ var defaultOption = new TimeoutStrategyOptions();
+
+ //ȫ
+ TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
+ {
+ //ΨһʶضԵضʵҲɸԲɵң
+ Name = "timeout",
+
+ //̶ʱʱ(TimeoutGeneratorЧ)
+ Timeout = TimeSpan.FromSeconds(2),
+
+ //̬ʱʱ
+ //TimeoutGenerator = arg =>
+ //{
+ // var ts = TimeSpan.FromSeconds(Math.Pow(2, 1));
+
+ // return ValueTask.FromResult(ts);
+ //},
+
+ //ʱʱijʱί
+ OnTimeout = (args) =>
+ {
+ var key = args.Context.OperationKey;
+ _output.WriteLine("OnTimeout");
+ return ValueTask.CompletedTask;
+ },
+ };
+
+ //ʹ
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(timeoutOption)
+ .Build();
+ }
+
+ ///
+ /// ǿȡԳʱ
+ /// ִͬ
+ ///
+ [Fact]
+ public void No_CancellationToken_Test()
+ {
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(TimeSpan.FromSeconds(1))
+ .Build();
+
+ //ⲿtcsʡ
+ var tcs = new CancellationTokenSource();
+ try
+ {
+ pipeline.Execute
+ (
+ //˴ǿȡʱΪ(һֱȴ)
+ callback: (CancellationToken innerToken) =>
+ {
+ //ȻӿںʱΪûʹáȡġ Sendʱñԣȴӿȷ
+ HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost:44344/api/Timeout/Slow");
+
+ //ؼSend ûд cancellationToken
+ //ֻͬҪǿȡ
+ HttpResponseMessage response = new HttpClient().Send(requestMessage);
+ if (response.IsSuccessStatusCode == false)
+ {
+ _output.WriteLine($"Ӧ,Ӧ룺{response.StatusCode}");
+ return;
+ }
+ var stream = response.Content.ReadAsStream();
+ var bs = new byte[stream.Length];
+ stream.Read(bs, 0, (int)stream.Length);
+
+ var text = System.Text.UTF8Encoding.UTF8.GetString(bs);
+
+ _output.WriteLine($"Ӧ:{text}");
+
+ _output.WriteLine("ִ");
+ },
+ cancellationToken: tcs.Token
+ );
+ }
+ catch (OperationCanceledException ex)
+ {
+ _output.WriteLine($"ȡ쳣:{ex.Message}");
+ }
+ catch (TimeoutRejectedException ex)
+ {
+ _output.WriteLine($"ʱ쳣:{ex.Message}");
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine($"API쳣{ex.Message}");
+ }
+ finally
+ {
+ tcs.TryReset();
+ }
+ }
+
+ ///
+ /// ȡ:ʱ
+ /// ִͬ
+ ///
+ [Fact]
+ public void Has_CancellationToken_Timeout_Test()
+ {
+ TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
+ {
+ //ΨһʶضԵضʵҲɸԲɵң
+ Name = "timeout",
+
+ //̶ʱʱ(TimeoutGeneratorЧ)
+ Timeout = TimeSpan.FromSeconds(1),
+
+ //̬ʱʱ
+ //TimeoutGenerator = arg =>
+ //{
+ // var ts = TimeSpan.FromSeconds(Math.Pow(2, 1));
+
+ // return ValueTask.FromResult(ts);
+ //},
+
+ //ʱʱijʱί
+ OnTimeout = (args) =>
+ {
+ _output.WriteLine("OnTimeout ʱίִ");
+ return ValueTask.CompletedTask;
+ },
+ };
+
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(timeoutOption)
+ .Build();
+
+ var tcs = new CancellationTokenSource();
+ try
+ {
+ pipeline.Execute
+ (
+ //˴ǿȡʱΪ(һֱȴ)
+ callback: (innerToken) =>
+ {
+ HttpRequestMessage r = new HttpRequestMessage(HttpMethod.Get, "http://localhost:44344/api/Timeout/Slow");
+ var response = new HttpClient().Send(r, innerToken);
+ if (response.IsSuccessStatusCode == false)
+ {
+ Console.WriteLine("Ӧ");
+ return;
+ }
+ var stream = response.Content.ReadAsStream();
+ var bs = new byte[stream.Length];
+ stream.Read(bs, 0, (int)stream.Length);
+
+ var text = System.Text.UTF8Encoding.UTF8.GetString(bs);
+
+ _output.WriteLine($"Ӧ:{text}");
+ _output.WriteLine("ִ");
+ },
+ cancellationToken: tcs.Token
+ );
+ }
+ catch (OperationCanceledException ex)
+ {
+ _output.WriteLine($"ȡ쳣:{ex.Message}");
+ }
+ catch (TimeoutRejectedException ex)
+ {
+ _output.WriteLine($"ʱ쳣:{ex.Message}");
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine($"API쳣{ex.Message}");
+ }
+ finally
+ {
+ tcs.TryReset();
+ }
+ }
+
+ ///
+ /// ȡ:ʱ
+ /// ִͬ
+ ///
+ [Fact]
+ public void Has_CancellationToken_NotTimeout_Test()
+ {
+ TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
+ {
+ //ΨһʶضԵضʵҲɸԲɵң
+ Name = "timeout",
+
+ //̶ʱʱ(TimeoutGeneratorЧ)
+ Timeout = TimeSpan.FromSeconds(5),
+
+ //̬ʱʱ
+ //TimeoutGenerator = arg =>
+ //{
+ // var ts = TimeSpan.FromSeconds(Math.Pow(2, 1));
+
+ // return ValueTask.FromResult(ts);
+ //},
+
+ //ʱʱijʱί
+ OnTimeout = (args) =>
+ {
+ Console.WriteLine("OnTimeout ʱίִ");
+ return ValueTask.CompletedTask;
+ },
+ };
+
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(timeoutOption)
+ .Build();
+
+ var tcs = new CancellationTokenSource();
+ try
+ {
+ pipeline.Execute
+ (
+ //˴ǿȡʱΪ(һֱȴ)
+ callback: (innerToken) =>
+ {
+ HttpRequestMessage r = new HttpRequestMessage(HttpMethod.Get, "http://localhost:44344/api/Timeout/Slow");
+ var response = new HttpClient().Send(r, innerToken);
+ if (response.IsSuccessStatusCode == false)
+ {
+ Console.WriteLine("Ӧ");
+ return;
+ }
+ var stream = response.Content.ReadAsStream();
+ var bs = new byte[stream.Length];
+ stream.Read(bs, 0, (int)stream.Length);
+
+ var text = System.Text.UTF8Encoding.UTF8.GetString(bs);
+
+ _output.WriteLine($"Ӧ:{text}");
+ _output.WriteLine("ִ");
+ },
+ cancellationToken: tcs.Token
+ );
+ }
+ catch (OperationCanceledException ex)
+ {
+ _output.WriteLine($"ȡ쳣:{ex.Message}");
+ }
+ catch (TimeoutRejectedException ex)
+ {
+ _output.WriteLine($"ʱ쳣:{ex.Message}");
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine($"API쳣{ex.Message}");
+ }
+ finally
+ {
+ tcs.TryReset();
+ }
+
+ }
+
+ ///
+ /// ȡ:Գʱ
+ /// 첽ִ
+ ///
+ [Fact]
+ public async void No_CancellationToken_ExecuteAsync_Test()
+ {
+ TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
+ {
+ //ΨһʶضԵضʵҲɸԲɵң
+ Name = "timeout",
+
+ //̶ʱʱ(TimeoutGeneratorЧ)
+ Timeout = TimeSpan.FromSeconds(1),
+
+
+ //ʱʱijʱί
+ OnTimeout = (args) =>
+ {
+ _output.WriteLine("OnTimeout ʱίִ");
+ return ValueTask.CompletedTask;
+ },
+ };
+
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(timeoutOption)
+ .Build();
+
+ var tcs = new CancellationTokenSource();
+ try
+ {
+ await pipeline.ExecuteAsync
+ (
+ //˴ΪȡԳʱ(һֱȴ)
+ callback: async (innerToken) =>
+ {
+ //ɴԼ2࣬dzʱ1
+ await Task.Delay (1000*3);
+ _output.WriteLine("ִ");
+ },
+ cancellationToken: tcs.Token
+ );
+ }
+ catch (OperationCanceledException ex)
+ {
+ _output.WriteLine($"ȡ쳣:{ex.Message}");
+ }
+ catch (TimeoutRejectedException ex)
+ {
+ _output.WriteLine($"ʱ쳣:{ex.Message}");
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine($"API쳣{ex.Message}");
+ }
+ finally
+ {
+ tcs.TryReset();
+ }
+ }
+
+ ///
+ /// ȡ:ʱ
+ /// 첽ִ
+ ///
+ ///
+ [Fact]
+ public async Task Has_CancellationToken_ExecuteAsync_Timeout_Test()
+ {
+ var pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(TimeSpan.FromSeconds(1))
+ .Build();
+
+ await Assert.ThrowsAnyAsync(async () =>
+ {
+ var cts = new CancellationTokenSource();
+ await pipeline.ExecuteAsync
+ (
+ callback: async (innerToken) =>
+ {
+ //ɴԼdzʱ1࣬3
+ await Task.Delay(1000 * 3, innerToken);
+ _output.WriteLine("ִ");
+ },
+ cancellationToken: cts.Token
+ );
+ });
+ }
+
+ ///
+ /// ȡ:ʱ
+ /// 첽ִ
+ ///
+ ///
+ [Fact]
+ public async Task Has_CancellationToken_ExecuteAsync_NoTimeout_Test()
+ {
+ var pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(TimeSpan.FromSeconds(3))
+ .Build();
+
+ try
+ {
+ var cts = new CancellationTokenSource();
+ await pipeline.ExecuteAsync
+ (
+ callback: async (innerToken) =>
+ {
+ //ʱɴԼʱ1
+ await Task.Delay(1000, innerToken);
+ _output.WriteLine("ִ");
+ },
+ cancellationToken: cts.Token
+ );
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine(ex.Message);
+ }
+ }
+
+ //
+ /// ȡ:ʱ
+ /// 첽ִУToken
+ ///
+ ///
+ [Fact]
+ public async Task Has_OuterCancellationToken_ExecuteAsync_Timeout_Test()
+ {
+ var pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(TimeSpan.FromSeconds(1))
+ .Build();
+
+
+ await Assert.ThrowsAnyAsync(async () =>
+ {
+ await pipeline.ExecuteAsync
+ (
+ callback: async (innerToken) =>
+ {
+ //ɴԼdzʱ1࣬3
+ await Task.Delay(1000 * 3, innerToken);
+ _output.WriteLine("ִ");
+ }
+ );
+ });
+ }
+
+
+ [Fact]
+ public void SyncWithCancellationToken_Test()
+ {
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(TimeSpan.FromSeconds(3))
+ .Build();
+
+ var tcs = new CancellationTokenSource();
+ try
+ {
+ pipeline.Execute
+ (
+ //˴ǿȡʱΪ(һֱȴ)
+ callback: SyncWithCancellationToken,
+ cancellationToken: tcs.Token
+ );
+ }
+ catch (OperationCanceledException ex)
+ {
+ _output.WriteLine($"ȡ쳣:{ex.Message}");
+ }
+ catch (TimeoutRejectedException ex)
+ {
+ _output.WriteLine($"ʱ쳣:{ex.Message}");
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine($"API쳣{ex.Message}");
+ }
+ finally
+ {
+ tcs.TryReset();
+ }
+ }
+
+ ///
+ /// ʹCancellationTokenͬ
+ ///
+ private void SyncWithCancellationToken(CancellationToken cancellationToken)
+ {
+ Thread thread = new Thread((token) =>
+ {
+ int max = 500;
+ while (max > 0)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ break;
+ }
+
+ max -= 1;
+ Thread.Sleep(10);
+ }
+ });
+
+ thread.Start();
+ thread.Join();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Polly8Study.Test/UseXunit.cs b/Polly8Study.Test/UseXunit.cs
new file mode 100644
index 0000000..06b60ce
--- /dev/null
+++ b/Polly8Study.Test/UseXunit.cs
@@ -0,0 +1,21 @@
+namespace Polly8Study.Test
+{
+ public class UseXunit
+ {
+ private readonly ITestOutputHelper _output;
+
+ public UseXunit(ITestOutputHelper testOutput)
+ {
+ _output = testOutput;
+ }
+
+ [Fact]
+ public void Use_xUnit_Test()
+ {
+ var msg = "ʹ xUnit ԪԿܣ";
+ Assert.True(true, msg);
+
+ _output.WriteLine(msg);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Polly8Study.WebApi/Controllers/TimeoutController.cs b/Polly8Study.WebApi/Controllers/TimeoutController.cs
new file mode 100644
index 0000000..27c2629
--- /dev/null
+++ b/Polly8Study.WebApi/Controllers/TimeoutController.cs
@@ -0,0 +1,58 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Polly8Study.WebApi.Controllers
+{
+ [Route("api/[controller]/[action]")]
+ [ApiController]
+ public class TimeoutController : ControllerBase
+ {
+ private readonly ILogger _logger;
+ public TimeoutController(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// 快速接口
+ ///
+ ///
+ [HttpGet]
+ public IActionResult Fast()
+ {
+ return Ok("Fast");
+ }
+
+ ///
+ /// 慢接口:耗时约2秒
+ ///
+ ///
+ [HttpGet]
+ public async Task Slow(CancellationToken token)
+ {
+ //使用“异常过滤器”, 全局统一处理
+ try
+ {
+ await Task.Delay(1000*2, token);
+
+ return Ok("Slow");
+ }
+ catch (OperationCanceledException ex)
+ {
+ Console.WriteLine("用户取消操作:" + ex.Message);
+
+ throw;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+
+ return Problem("服务器异常");
+ }
+ finally
+ {
+ Console.WriteLine("请求结束");
+ }
+ }
+ }
+}
diff --git a/Polly8Study.WebApi/Filters/OperationCancelledExceptionFilter.cs b/Polly8Study.WebApi/Filters/OperationCancelledExceptionFilter.cs
new file mode 100644
index 0000000..c423532
--- /dev/null
+++ b/Polly8Study.WebApi/Filters/OperationCancelledExceptionFilter.cs
@@ -0,0 +1,25 @@
+using Microsoft.AspNetCore.Mvc.Authorization;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace Polly8Study.WebApi.Filters
+{
+ ///
+ /// 用户取消调用(刷新浏览器等)异常过滤器
+ ///
+ public class OperationCancelledExceptionFilter : IAsyncExceptionFilter
+ {
+ public Task OnExceptionAsync(ExceptionContext context)
+ {
+ if (context.Exception is OperationCanceledException ex)
+ {
+ var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
+
+ //context.ActionDescriptor.ContrallerName;
+ Console.WriteLine($"全局异常过滤器:{controllerActionDescriptor?.ControllerName}{controllerActionDescriptor?.ActionName} 请求,被用户取消!");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Polly8Study.WebApi/Polly8Study.WebApi.csproj b/Polly8Study.WebApi/Polly8Study.WebApi.csproj
new file mode 100644
index 0000000..9daa180
--- /dev/null
+++ b/Polly8Study.WebApi/Polly8Study.WebApi.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Polly8Study.WebApi/Polly8Study.WebApi.http b/Polly8Study.WebApi/Polly8Study.WebApi.http
new file mode 100644
index 0000000..7d6b3a4
--- /dev/null
+++ b/Polly8Study.WebApi/Polly8Study.WebApi.http
@@ -0,0 +1,6 @@
+@Polly8Study.WebApi_HostAddress = http://localhost:5000
+
+GET {{Polly8Study.WebApi_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/Polly8Study.WebApi/Program.cs b/Polly8Study.WebApi/Program.cs
new file mode 100644
index 0000000..8f38c3e
--- /dev/null
+++ b/Polly8Study.WebApi/Program.cs
@@ -0,0 +1,50 @@
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+using Polly8Study.WebApi.Filters;
+
+namespace Polly8Study.WebApi
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ // Add services to the container.
+
+ builder.Services.AddControllers(option =>
+ {
+ option.Filters.Add();
+ });
+ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen();
+
+ //builder.Services.AddSingleton(new OperationCancelledExceptionFilter);
+
+ builder.Services.Configure(mvcOptions =>
+ {
+ mvcOptions.Filters.Add();
+ });
+
+ var app = builder.Build();
+
+ // Configure the HTTP request pipeline.
+ if (app.Environment.IsDevelopment())
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI();
+ }
+
+ app.UseAuthorization();
+
+
+ app.MapControllers();
+
+ app.Run();
+ }
+ }
+}
diff --git a/Polly8Study.WebApi/Properties/launchSettings.json b/Polly8Study.WebApi/Properties/launchSettings.json
new file mode 100644
index 0000000..992dcfd
--- /dev/null
+++ b/Polly8Study.WebApi/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:46344",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:44344",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/Polly8Study.WebApi/appsettings.Development.json b/Polly8Study.WebApi/appsettings.Development.json
new file mode 100644
index 0000000..3e1a225
--- /dev/null
+++ b/Polly8Study.WebApi/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Information"
+ }
+ }
+}
diff --git a/Polly8Study.WebApi/appsettings.json b/Polly8Study.WebApi/appsettings.json
new file mode 100644
index 0000000..4282acb
--- /dev/null
+++ b/Polly8Study.WebApi/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/Polly8Study.sln b/Polly8Study.sln
new file mode 100644
index 0000000..5d7596a
--- /dev/null
+++ b/Polly8Study.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.35027.167
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly8Study.Test", "Polly8Study.Test\Polly8Study.Test.csproj", "{6BA907CA-61B6-4956-A1FE-9F7596BC10B9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly8Study.Core", "Polly8Study.Core\Polly8Study.Core.csproj", "{F8A1BC0D-978F-42A8-8747-5C8F635060C3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Polly8Study.WebApi", "Polly8Study.WebApi\Polly8Study.WebApi.csproj", "{AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0B24E74B-1EE9-4EEE-B199-8CB3018FA4D5}
+ EndGlobalSection
+EndGlobal