diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 1f22034e0..18e645818 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -46,6 +46,7 @@ public Task StartAsync() _host = new WebHostBuilder() .Configure(appBuilder => { + appBuilder.UseMiddleware(); _options.PreWireMockMiddlewareInit?.Invoke(appBuilder); appBuilder.UseMiddleware(_options); _options.PostWireMockMiddlewareInit?.Invoke(appBuilder); diff --git a/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs b/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs new file mode 100644 index 000000000..0769ebff1 --- /dev/null +++ b/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Newtonsoft.Json; +#if !NETSTANDARD +using Microsoft.Owin; +#else +using Microsoft.AspNetCore.Http; +#endif + +namespace WireMock.Owin +{ +#if !NETSTANDARD + internal class GlobalExceptionMiddleware : OwinMiddleware +#else + internal class GlobalExceptionMiddleware +#endif + { +#if !NETSTANDARD + public GlobalExceptionMiddleware(OwinMiddleware next) : base(next) { } +#else + public GlobalExceptionMiddleware(RequestDelegate next) + { + Next = next; + } +#endif + +#if NETSTANDARD + public RequestDelegate Next { get; private set; } +#endif + + private readonly OwinResponseMapper _responseMapper = new OwinResponseMapper(); + +#if !NETSTANDARD + public override async Task Invoke(IOwinContext ctx) +#else + public async Task Invoke(HttpContext ctx) +#endif + { + try + { + await Next?.Invoke(ctx); + } + catch (Exception ex) + { + await _responseMapper.MapAsync(new ResponseMessage { StatusCode = 500, Body = JsonConvert.SerializeObject(ex) }, ctx.Response); + } + } + } +} diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net/Owin/OwinSelfHost.cs index 21b2aca04..b4a36390a 100644 --- a/src/WireMock.Net/Owin/OwinSelfHost.cs +++ b/src/WireMock.Net/Owin/OwinSelfHost.cs @@ -61,6 +61,7 @@ private void StartServers() Action startup = app => { + app.Use(); _options.PreWireMockMiddlewareInit?.Invoke(app); app.Use(_options); _options.PostWireMockMiddlewareInit?.Invoke(app); diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 3f601bf3d..881458d8b 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -5,6 +5,7 @@ using System.Linq; using WireMock.Matchers; using WireMock.Util; +using Newtonsoft.Json; #if !NETSTANDARD using Microsoft.Owin; #else @@ -124,7 +125,7 @@ public async Task Invoke(HttpContext ctx) } catch (Exception ex) { - response = new ResponseMessage { StatusCode = 500, Body = ex.ToString() }; + response = new ResponseMessage { StatusCode = 500, Body = JsonConvert.SerializeObject(ex) }; } finally { diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs index 464da3ac1..adfd313e1 100644 --- a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using WireMock.Logging; using WireMock.Matchers; +using WireMock.Util; #if !NETSTANDARD using Owin; #else @@ -22,7 +23,7 @@ internal class WireMockMiddlewareOptions public IList Mappings { get; set; } = new List(); - public ObservableCollection LogEntries { get; } = new ObservableCollection(); + public ObservableCollection LogEntries { get; } = new ConcurentObservableCollection(); public int? RequestLogExpirationDuration { get; set; } diff --git a/src/WireMock.Net/Util/ConcurentObservableCollection.cs b/src/WireMock.Net/Util/ConcurentObservableCollection.cs new file mode 100644 index 000000000..60d2c90d5 --- /dev/null +++ b/src/WireMock.Net/Util/ConcurentObservableCollection.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace WireMock.Util +{ + /// + /// A special Collection that overrides methods of to make them thread safe + /// + /// The type of elements in the collection. + /// + public class ConcurentObservableCollection : ObservableCollection + { + private readonly object _lockObject = new object(); + + /// + /// Initializes a new instance of the class. + /// + public ConcurentObservableCollection() { } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified list. + /// + /// The list from which the elements are copied. + public ConcurentObservableCollection(List list) : base(list) { } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified collection. + /// + /// The collection from which the elements are copied. + public ConcurentObservableCollection(IEnumerable collection) : base(collection) { } + + /// + protected override void ClearItems() + { + lock (_lockObject) + { + base.ClearItems(); + } + } + + /// + protected override void RemoveItem(int index) + { + lock (_lockObject) + { + base.RemoveItem(index); + } + } + + /// + protected override void InsertItem(int index, T item) + { + lock (_lockObject) + { + base.InsertItem(index, item); + } + } + + /// + protected override void SetItem(int index, T item) + { + lock (_lockObject) + { + base.SetItem(index, item); + } + } + + /// + protected override void MoveItem(int oldIndex, int newIndex) + { + lock (_lockObject) + { + base.MoveItem(oldIndex, newIndex); + } + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs b/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs index 1bd2f407e..d5292efe1 100644 --- a/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs +++ b/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs @@ -1,5 +1,10 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using NFluent; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; @@ -8,7 +13,7 @@ namespace WireMock.Net.Tests { - public class ObservableLogEntriesTest: IDisposable + public class ObservableLogEntriesTest : IDisposable { private FluentMockServer _server; @@ -35,6 +40,42 @@ public async void Test() Check.That(count).Equals(1); } + [Fact] + public async Task ParallelTest() + { + var expectedCount = 100; + + // Assign + _server = FluentMockServer.Start(); + + _server + .Given(Request.Create() + .WithPath("/foo") + .UsingGet()) + .RespondWith(Response.Create() + .WithDelay(6) + .WithSuccess()); + + int count = 0; + _server.LogEntriesChanged += (sender, args) => count++; + + var http = new HttpClient(); + + // Act + var listOfTasks = new List>(); + for (var i = 0; i < expectedCount; i++) + { + Thread.Sleep(3); + listOfTasks.Add(http.GetAsync(_server.Urls[0] + $"/foo")); + } + var responses = await Task.WhenAll(listOfTasks); + var countResponsesWithStatusNotOk = responses.Where(r => r.StatusCode != HttpStatusCode.OK).Count(); + + // Assert + Check.That(countResponsesWithStatusNotOk).Equals(0); + Check.That(count).Equals(expectedCount); + } + public void Dispose() { _server?.Dispose();