From db99620711e135698aaf05f258d107e67f9efbed Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Tue, 26 Sep 2017 16:00:48 -0700 Subject: [PATCH 01/39] Added means of subscribing to events via websocket connection and added publishing of events to subscribers via signalR when an event is posted to the API. --- .../Controllers/EventsController.cs | 17 +++++++++- src/Sia.Gateway/Hubs/EventsHub.cs | 32 +++++++++++++++++++ .../Initialization/MiddlewareStartup.cs | 8 +++++ .../Initialization/ServicesStartup.cs | 2 ++ src/Sia.Gateway/Sia.Gateway.csproj | 2 ++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/Sia.Gateway/Hubs/EventsHub.cs diff --git a/src/Sia.Gateway/Controllers/EventsController.cs b/src/Sia.Gateway/Controllers/EventsController.cs index ba35c9c..5578022 100644 --- a/src/Sia.Gateway/Controllers/EventsController.cs +++ b/src/Sia.Gateway/Controllers/EventsController.cs @@ -1,7 +1,9 @@ using MediatR; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR.Client; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; +using Sia.Gateway.Hubs; using Sia.Gateway.Requests; using System.Threading.Tasks; @@ -12,9 +14,11 @@ namespace Sia.Gateway.Controllers { private const string notFoundMessage = "Incident or event not found"; - public EventsController(IMediator mediator, AzureActiveDirectoryAuthenticationInfo authConfig) + public EventsController(IMediator mediator, + AzureActiveDirectoryAuthenticationInfo authConfig) : base(mediator, authConfig) { + } [HttpGet("{id}")] @@ -36,7 +40,18 @@ namespace Sia.Gateway.Controllers { return NotFound(notFoundMessage); } + await SendEventToSubscribers(result); return Created($"api/incidents/{result.IncidentId}/events/{result.Id}", result); } + + private async Task SendEventToSubscribers(Domain.Event result) + { + var eventHubConnection = new HubConnectionBuilder() + .WithUrl($"{Request.Scheme}://{Request.Host}/{EventsHub.HubPath}") + .Build(); + await eventHubConnection.StartAsync(); + await eventHubConnection.SendAsync("Send", result); + eventHubConnection.DisposeAsync(); + } } } diff --git a/src/Sia.Gateway/Hubs/EventsHub.cs b/src/Sia.Gateway/Hubs/EventsHub.cs new file mode 100644 index 0000000..22e345f --- /dev/null +++ b/src/Sia.Gateway/Hubs/EventsHub.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.SignalR; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Sia.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.Hubs +{ + public class EventsHub : Hub + { + public const string HubPath = "events/live"; + public EventsHub() : base() + { + } + + public Task Send(Event ev) + { + return Clients.All.InvokeAsync("Send", JsonConvert.SerializeObject(ev, new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + })); + } + + public override Task OnConnectedAsync() + { + return base.OnConnectedAsync(); + } + } +} diff --git a/src/Sia.Gateway/Initialization/MiddlewareStartup.cs b/src/Sia.Gateway/Initialization/MiddlewareStartup.cs index 612b88f..d79b159 100644 --- a/src/Sia.Gateway/Initialization/MiddlewareStartup.cs +++ b/src/Sia.Gateway/Initialization/MiddlewareStartup.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; +using Sia.Gateway.Hubs; using Sia.Gateway.Middleware; using System; using System.Collections.Generic; @@ -16,6 +17,7 @@ namespace Sia.Gateway.Initialization { app.UseAuthentication(); app.UseSession(); + app.UseCors(builder => builder .WithOrigins(LoadAcceptableOriginsFromConfig(configuration)) @@ -23,6 +25,12 @@ namespace Sia.Gateway.Initialization .AllowAnyMethod() .AllowCredentials() ); + + app.UseSignalR(routes => + { + routes.MapHub(EventsHub.HubPath); + }); + if (env.IsDevelopment() || env.IsStaging()) app.UseDeveloperExceptionPage(); app.UseMiddleware(); diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index 7e0fd22..ed82b9b 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -102,6 +102,8 @@ namespace Sia.Gateway.Initialization services.AddDistributedMemoryCache(); services.AddSession(); services.AddCors(); + services.AddSockets(); + services.AddSignalR(); } } } \ No newline at end of file diff --git a/src/Sia.Gateway/Sia.Gateway.csproj b/src/Sia.Gateway/Sia.Gateway.csproj index d14ce2a..1a5a948 100644 --- a/src/Sia.Gateway/Sia.Gateway.csproj +++ b/src/Sia.Gateway/Sia.Gateway.csproj @@ -14,6 +14,8 @@ + + From 13bd2f5dbb7dd8684f0061bbcadbaf4056c255f1 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Thu, 28 Sep 2017 11:24:00 -0700 Subject: [PATCH 02/39] Wired up signalR to use Redis --- src/Sia.Gateway/Hubs/EventsHub.cs | 1 + src/Sia.Gateway/Initialization/ServicesStartup.cs | 10 +++++++++- src/Sia.Gateway/Sia.Gateway.csproj | 1 + src/Sia.Gateway/usersecrets.template.json | 4 ++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Sia.Gateway/Hubs/EventsHub.cs b/src/Sia.Gateway/Hubs/EventsHub.cs index 22e345f..fee9a0c 100644 --- a/src/Sia.Gateway/Hubs/EventsHub.cs +++ b/src/Sia.Gateway/Hubs/EventsHub.cs @@ -14,6 +14,7 @@ namespace Sia.Gateway.Hubs public const string HubPath = "events/live"; public EventsHub() : base() { + } public Task Send(Event ev) diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index ed82b9b..11aca8a 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -10,7 +10,10 @@ using Sia.Data.Incidents; using Sia.Gateway.Authentication; using Sia.Gateway.Requests; using Sia.Gateway.ServiceRepositories; +using StackExchange.Redis; using System; +using System.Collections.Generic; +using System.Net; using System.Reflection; using System.Runtime.Loader; @@ -103,7 +106,12 @@ namespace Sia.Gateway.Initialization services.AddSession(); services.AddCors(); services.AddSockets(); - services.AddSignalR(); + services.AddSignalR().AddRedis(redisOptions => + { + redisOptions.Options.EndPoints.Add(config["Redis:CacheEndpoint"]); + redisOptions.Options.Ssl = true; + redisOptions.Options.Password = config["Redis:Password"]; + }); } } } \ No newline at end of file diff --git a/src/Sia.Gateway/Sia.Gateway.csproj b/src/Sia.Gateway/Sia.Gateway.csproj index 1a5a948..836a75e 100644 --- a/src/Sia.Gateway/Sia.Gateway.csproj +++ b/src/Sia.Gateway/Sia.Gateway.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Sia.Gateway/usersecrets.template.json b/src/Sia.Gateway/usersecrets.template.json index 958c047..473e3ed 100644 --- a/src/Sia.Gateway/usersecrets.template.json +++ b/src/Sia.Gateway/usersecrets.template.json @@ -15,5 +15,9 @@ "Your uri here" //Replace with your frontend uri ] }, + "Redis": { + "CacheEndpoint": "yourCache.redis.cache.windows.net:6380", + "Password": "YOURPASSWORD" + }, "ClientSecret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" //Replace with your client secret } From 3b0817d2ad3b276ec1522d922d654463e2834015 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Thu, 28 Sep 2017 11:36:59 -0700 Subject: [PATCH 03/39] Cleanup of EventsHub --- src/Sia.Gateway/Hubs/EventsHub.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Sia.Gateway/Hubs/EventsHub.cs b/src/Sia.Gateway/Hubs/EventsHub.cs index fee9a0c..d4e4765 100644 --- a/src/Sia.Gateway/Hubs/EventsHub.cs +++ b/src/Sia.Gateway/Hubs/EventsHub.cs @@ -19,15 +19,12 @@ namespace Sia.Gateway.Hubs public Task Send(Event ev) { - return Clients.All.InvokeAsync("Send", JsonConvert.SerializeObject(ev, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - })); + return Clients.All.InvokeAsync("Send", Json(ev)); } - public override Task OnConnectedAsync() + private string Json(T toSerialize) => JsonConvert.SerializeObject(toSerialize, new JsonSerializerSettings { - return base.OnConnectedAsync(); - } + ContractResolver = new CamelCasePropertyNamesContractResolver() + }); } } From 441a7dd5d52a87fb61840ae14f3634f646a58bbf Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Thu, 28 Sep 2017 14:40:00 -0700 Subject: [PATCH 04/39] Graceful failure when keyVault config is not provided. --- src/Sia.Gateway/Initialization/SecretVaultStartup.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Sia.Gateway/Initialization/SecretVaultStartup.cs b/src/Sia.Gateway/Initialization/SecretVaultStartup.cs index 5a2d3ea..9ac1c80 100644 --- a/src/Sia.Gateway/Initialization/SecretVaultStartup.cs +++ b/src/Sia.Gateway/Initialization/SecretVaultStartup.cs @@ -12,9 +12,13 @@ namespace Sia.Gateway.Initialization //to ConfigureServices being run var secrets = new AzureSecretVault(configuration); - var vaultTask = secrets.Get(configuration.GetSection("KeyVault")["InstrumentationKeyName"]); - vaultTask.Wait(); - configuration.GetSection("ApplicationInsights")["InstrumentationKey"] = vaultTask.Result; + var instrumentationKey = configuration.GetSection("KeyVault")["InstrumentationKeyName"]; + if (!string.IsNullOrWhiteSpace(instrumentationKey)) + { + var vaultTask = secrets.Get(instrumentationKey); + vaultTask.Wait(); + configuration.GetSection("ApplicationInsights")["InstrumentationKey"] = vaultTask.Result; + } return secrets; } From 9f1767e864d6bc063c5bef47a60c106b73a0668e Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Fri, 29 Sep 2017 12:24:02 -0700 Subject: [PATCH 05/39] Slight cleanup by breaking out functions from a large method. --- .../Initialization/ServicesStartup.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index 7e0fd22..80f1426 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -22,11 +22,22 @@ namespace Sia.Gateway.Initialization public static void AddFirstPartyServices(this IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config) { - var incidentAuthConfig = new AzureActiveDirectoryAuthenticationInfo(config["Incident:ClientId"], config["Incident:ClientSecret"], config["AzureAd:Tenant"]); if (env.IsDevelopment()) services.AddDbContext(options => options.UseInMemoryDatabase("Live")); if (env.IsStaging()) services.AddDbContext(options => options.UseSqlServer(config.GetConnectionString("incidentStaging"))); + AddTicketConnector(services, env, config); + + services.AddScoped(); + services.AddScoped(); + + services.AddSingleton(i => config); + + ConfigureAuth(services, config); + } + + private static void AddTicketConnector(IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config) + { var ticketConnectorAssemblyPath = config["Connector:Ticket:Path"]; if (!string.IsNullOrEmpty(ticketConnectorAssemblyPath)) @@ -55,11 +66,11 @@ namespace Sia.Gateway.Initialization services.AddNoTicketingSystem(); } } + } - services.AddScoped(); - services.AddScoped(); - - services.AddSingleton(i => config); + private static void ConfigureAuth(IServiceCollection services, IConfigurationRoot config) + { + var incidentAuthConfig = new AzureActiveDirectoryAuthenticationInfo(config["Incident:ClientId"], config["Incident:ClientSecret"], config["AzureAd:Tenant"]); services.AddSingleton(i => incidentAuthConfig); } From 1deedca853610e9ee7495364ae314bd1903aae3c Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 2 Oct 2017 16:12:37 -0700 Subject: [PATCH 06/39] Reverted unintentionally committed temporary change.3 --- src/Sia.Gateway/Controllers/BaseController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sia.Gateway/Controllers/BaseController.cs b/src/Sia.Gateway/Controllers/BaseController.cs index a616608..f51f294 100644 --- a/src/Sia.Gateway/Controllers/BaseController.cs +++ b/src/Sia.Gateway/Controllers/BaseController.cs @@ -8,7 +8,7 @@ using Sia.Gateway.Validation.Filters; namespace Sia.Gateway.Controllers { [Return400BadRequestWhenModelStateInvalid] - //[Authorize()] + [Authorize()] public abstract class BaseController : Controller { protected readonly IMediator _mediator; From ef526bee144b7b656c2ed25325a7d34a9be44e22 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 2 Oct 2017 16:29:09 -0700 Subject: [PATCH 07/39] Injecting hubConnectionBuilder into EventsController to allow testing with mock hubConnectionBuilder that returns a mock hubConnection. --- src/Sia.Gateway/Controllers/EventsController.cs | 8 +++++--- src/Sia.Gateway/Initialization/ServicesStartup.cs | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Sia.Gateway/Controllers/EventsController.cs b/src/Sia.Gateway/Controllers/EventsController.cs index 5578022..8f65efb 100644 --- a/src/Sia.Gateway/Controllers/EventsController.cs +++ b/src/Sia.Gateway/Controllers/EventsController.cs @@ -13,12 +13,14 @@ namespace Sia.Gateway.Controllers public class EventsController : BaseController { private const string notFoundMessage = "Incident or event not found"; + private readonly HubConnectionBuilder _hubConnectionBuilder; public EventsController(IMediator mediator, - AzureActiveDirectoryAuthenticationInfo authConfig) + AzureActiveDirectoryAuthenticationInfo authConfig, + HubConnectionBuilder hubConnectionBuilder) : base(mediator, authConfig) { - + _hubConnectionBuilder = hubConnectionBuilder; } [HttpGet("{id}")] @@ -46,7 +48,7 @@ namespace Sia.Gateway.Controllers private async Task SendEventToSubscribers(Domain.Event result) { - var eventHubConnection = new HubConnectionBuilder() + var eventHubConnection = _hubConnectionBuilder .WithUrl($"{Request.Scheme}://{Request.Host}/{EventsHub.HubPath}") .Build(); await eventHubConnection.StartAsync(); diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index 11aca8a..18ef447 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.SignalR.Client; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -112,6 +113,7 @@ namespace Sia.Gateway.Initialization redisOptions.Options.Ssl = true; redisOptions.Options.Password = config["Redis:Password"]; }); + services.AddScoped(); } } } \ No newline at end of file From 9faa14d240ccec89524a0d0209ad7af355a2c956 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 2 Oct 2017 16:56:56 -0700 Subject: [PATCH 08/39] Renamed paginationHeader to linksHeader, reduced magic strings. --- src/Sia.Gateway/Controllers/EventsController.cs | 2 +- .../Protocol/{PaginationHeader.cs => LinksHeader.cs} | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) rename src/Sia.Gateway/Protocol/{PaginationHeader.cs => LinksHeader.cs} (76%) diff --git a/src/Sia.Gateway/Controllers/EventsController.cs b/src/Sia.Gateway/Controllers/EventsController.cs index 562e157..058f0cc 100644 --- a/src/Sia.Gateway/Controllers/EventsController.cs +++ b/src/Sia.Gateway/Controllers/EventsController.cs @@ -23,7 +23,7 @@ namespace Sia.Gateway.Controllers public async Task GetEvents([FromRoute]long incidentId, [FromQuery]PaginationMetadata pagination) { var result = await _mediator.Send(new GetEventsRequest(incidentId, pagination, _authContext)); - Response.Headers.AddPagination(new PaginationHeader(pagination, _urlHelper, nameof(GetEvents))); + Response.Headers.AddPagination(new LinksHeader(pagination, _urlHelper, nameof(GetEvents))); return Ok(result); } diff --git a/src/Sia.Gateway/Protocol/PaginationHeader.cs b/src/Sia.Gateway/Protocol/LinksHeader.cs similarity index 76% rename from src/Sia.Gateway/Protocol/PaginationHeader.cs rename to src/Sia.Gateway/Protocol/LinksHeader.cs index 36c9e4c..d8c220c 100644 --- a/src/Sia.Gateway/Protocol/PaginationHeader.cs +++ b/src/Sia.Gateway/Protocol/LinksHeader.cs @@ -4,20 +4,20 @@ using Newtonsoft.Json; namespace Sia.Gateway.Protocol { - public class PaginationHeader + public class LinksHeader { private PaginationMetadata _metadata; private IUrlHelper _urlHelper; private string _routeName; - public PaginationHeader(PaginationMetadata metadata, IUrlHelper urlHelper, string routeName) + public LinksHeader(PaginationMetadata metadata, IUrlHelper urlHelper, string routeName) { _metadata = metadata; _urlHelper = urlHelper; _routeName = routeName; } - public string HeaderName => "X-Pagination"; + public const string HeaderName = "links"; public StringValues HeaderValues => JsonConvert.SerializeObject(new { PageNumber = _metadata.PageNumber, @@ -37,10 +37,10 @@ namespace Microsoft.AspNetCore.Mvc public static class PaginationExtensions { - public static void AddPagination(this IHeaderDictionary headers, PaginationHeader header) + public static void AddPagination(this IHeaderDictionary headers, LinksHeader header) { - headers.Add("Access-Control-Expose-Headers", "X-Pagination"); - headers.Add(header.HeaderName, header.HeaderValues); + headers.Add("Access-Control-Expose-Headers", LinksHeader.HeaderName); + headers.Add(LinksHeader.HeaderName, header.HeaderValues); } } } \ No newline at end of file From 1196f6461bdcecd27cc393521ad01d01b6f8d643 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Tue, 3 Oct 2017 14:21:15 -0700 Subject: [PATCH 09/39] Prepared Sia.Shared for adding KeyVaultCertificateRetreiver while remaining compatible with the manyEvents branch. --- src/Sia.Shared/Data/IEntity.cs | 11 +++++++++++ src/Sia.Shared/Sia.Shared.csproj | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/Sia.Shared/Data/IEntity.cs diff --git a/src/Sia.Shared/Data/IEntity.cs b/src/Sia.Shared/Data/IEntity.cs new file mode 100644 index 0000000..edced91 --- /dev/null +++ b/src/Sia.Shared/Data/IEntity.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sia.Shared.Data +{ + public interface IEntity + { + long Id { get; } + } +} diff --git a/src/Sia.Shared/Sia.Shared.csproj b/src/Sia.Shared/Sia.Shared.csproj index bbbff7b..6097181 100644 --- a/src/Sia.Shared/Sia.Shared.csproj +++ b/src/Sia.Shared/Sia.Shared.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 1.0.6-alpha + 1.0.8-alpha Microsoft.Sia.Shared pdimit, magpint, jache, chtownes Microsoft From 93b0bfe1fe43e938e0d7724bb87552c9a4ce9512 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Tue, 3 Oct 2017 16:55:41 -0700 Subject: [PATCH 10/39] Added KeyVaultCertificateRetriever, started wiring up. --- .../Sia.Connectors.Tickets.csproj | 2 +- .../TicketProxy/AuthenticationType.cs | 3 +- .../TicketProxy/Initialization.cs | 8 ++- .../TicketProxy/ProxyConnectionInfo.cs | 39 +++++++++-- .../Sia.Domain.ApiModels.csproj | 2 +- .../Initialization/ServicesStartup.cs | 68 +++++++++++-------- .../Authentication/AzureSecretVault.cs | 25 +++++-- .../KeyVaultCertificateRetriever.cs | 26 +++++++ 8 files changed, 130 insertions(+), 43 deletions(-) create mode 100644 src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs diff --git a/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj b/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj index bce95c0..8e10b47 100644 --- a/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj +++ b/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj @@ -15,13 +15,13 @@ - + \ No newline at end of file diff --git a/src/Sia.Connectors.Tickets/TicketProxy/AuthenticationType.cs b/src/Sia.Connectors.Tickets/TicketProxy/AuthenticationType.cs index 33ed6f0..3b169fc 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/AuthenticationType.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/AuthenticationType.cs @@ -3,6 +3,7 @@ public enum AuthenticationType { None, - Certificate + Certificate, + CertificateFromKeyVault } } diff --git a/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs b/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs index 21a9b99..269be4f 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Sia.Connectors.Tickets; using Sia.Connectors.Tickets.TicketProxy; @@ -12,7 +13,10 @@ namespace Sia.Gateway.Initialization public static IServiceCollection AddProxyWithCert(this IServiceCollection services, string endpoint, string certThumbprint) => services.AddProxy(new ProxyConnectionInfo(endpoint, certThumbprint)); - + + + public static IServiceCollection AddProxyWithCertFromKeyVault(this IServiceCollection services, string endpoint, IConfigurationRoot config, string vaultName) + => services.AddProxy(new ProxyConnectionInfo(endpoint, config, vaultName)); private static IServiceCollection AddProxy(this IServiceCollection services, ProxyConnectionInfo proxyConnection) { diff --git a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs index a8f9f18..8046fd0 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs @@ -1,4 +1,5 @@ -using Sia.Shared.Authentication; +using Microsoft.Extensions.Configuration; +using Sia.Shared.Authentication; using Sia.Shared.Validation; using System; @@ -6,14 +7,38 @@ namespace Sia.Connectors.Tickets.TicketProxy { public class ProxyConnectionInfo { + private AzureSecretVault _keyVault; + + /// + /// Instantiates ProxyConnectionInfo with no Authentication + /// + /// Proxy Endpoint public ProxyConnectionInfo(string endpoint) : this(endpoint, AuthenticationType.None) { } + /// + /// Instantiates ProxyConnectionInfo with certificate authentication from a local cert + /// + /// Proxy Endpoint + /// Thumbprint for searching local certificate store public ProxyConnectionInfo(string endpoint, string certThumbprint) : this(endpoint, AuthenticationType.Certificate) { - CertThumbprint = ThrowIf.NullOrWhiteSpace(certThumbprint, nameof(certThumbprint)); + CertIdentifier = ThrowIf.NullOrWhiteSpace(certThumbprint, nameof(certThumbprint)); + } + + /// + /// Instantiates ProxyConnectionInfo with certificate authentication using a certificate retrieved from keyvault + /// + /// Proxy Endpoint + /// Configuration root for initialization + /// Key vault name + public ProxyConnectionInfo(string endpoint, IConfigurationRoot config, string vaultName) + : this(endpoint, AuthenticationType.CertificateFromKeyVault) + { + _keyVault = new AzureSecretVault(config, vaultName); + CertIdentifier = vaultName; } protected ProxyConnectionInfo(string endpoint, AuthenticationType authType) @@ -23,9 +48,9 @@ namespace Sia.Connectors.Tickets.TicketProxy } public ProxyClient GetClient() => new ProxyClient(ClientFactory.GetClient(), Endpoint); - public AuthenticationType AuthenticationType { get; set; } - public string Endpoint { get; set; } - public string CertThumbprint { get; set; } + public AuthenticationType AuthenticationType { get; protected set; } + public string Endpoint { get; protected set; } + public string CertIdentifier { get; protected set; } protected IHttpClientFactory ClientFactory { @@ -34,7 +59,9 @@ namespace Sia.Connectors.Tickets.TicketProxy switch (AuthenticationType) { case AuthenticationType.Certificate: - return new LocalCertificateRetriever(CertThumbprint); + return new LocalCertificateRetriever(CertIdentifier); + case AuthenticationType.CertificateFromKeyVault: + return new KeyVaultCertificateRetriever(_keyVault, CertIdentifier); case AuthenticationType.None: return new UnauthenticatedClientFactory(); default: diff --git a/src/Sia.Domain.ApiModels/Sia.Domain.ApiModels.csproj b/src/Sia.Domain.ApiModels/Sia.Domain.ApiModels.csproj index 3eacaac..10641a6 100644 --- a/src/Sia.Domain.ApiModels/Sia.Domain.ApiModels.csproj +++ b/src/Sia.Domain.ApiModels/Sia.Domain.ApiModels.csproj @@ -15,11 +15,11 @@ - + \ No newline at end of file diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index 7e0fd22..cc790d6 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -27,34 +27,7 @@ namespace Sia.Gateway.Initialization if (env.IsDevelopment()) services.AddDbContext(options => options.UseInMemoryDatabase("Live")); if (env.IsStaging()) services.AddDbContext(options => options.UseSqlServer(config.GetConnectionString("incidentStaging"))); - var ticketConnectorAssemblyPath = config["Connector:Ticket:Path"]; - - if (!string.IsNullOrEmpty(ticketConnectorAssemblyPath)) - { - LoadConnectorFromAssembly(services, env, config, ticketConnectorAssemblyPath); - } - else - { - var proxyEndpoint = config["Connector:Ticket:ProxyEndpoint"]; - if (!string.IsNullOrEmpty(proxyEndpoint)) - { - services.AddIncidentClient(typeof(Ticket)); - var proxyAuthType = config["Connector:Ticket:ProxyAuthType"]; - if (proxyAuthType == "Certificate") - { - services.AddProxyWithCert(proxyEndpoint, config["Connector:Ticket:ProxyCertThumbprint"]); - } - else - { - services.AddProxyWithoutAuth(proxyEndpoint); - } - } - else - { - services.AddIncidentClient(typeof(EmptyTicket)); - services.AddNoTicketingSystem(); - } - } + AddTicketingConnector(services, env, config); services.AddScoped(); services.AddScoped(); @@ -63,6 +36,45 @@ namespace Sia.Gateway.Initialization services.AddSingleton(i => incidentAuthConfig); } + private static void AddTicketingConnector(IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config) + { + if (TryGetConfigValue(config, "Connector:Ticket:Path", out var ticketConnectorAssemblyPath)) + { + LoadConnectorFromAssembly(services, env, config, ticketConnectorAssemblyPath); + return; + } + + if (TryGetConfigValue(config, "Connector:Ticket:ProxyEndpoint", out var proxyEndpoint)) + { + AddProxyConnector(services, config, proxyEndpoint); + return; + } + + services.AddIncidentClient(typeof(EmptyTicket)); + services.AddNoTicketingSystem(); + } + + private static bool TryGetConfigValue(IConfigurationRoot config, string configName, out string configValue) + { + configValue = config[configName]; + return !string.IsNullOrEmpty(configValue); + } + + private static void AddProxyConnector(IServiceCollection services, IConfigurationRoot config, string proxyEndpoint) + { + services.AddIncidentClient(typeof(Ticket)); + var proxyAuthType = config["Connector:Ticket:ProxyAuthType"]; + switch(proxyAuthType) + { + case "Certificate": + services.AddProxyWithCert(proxyEndpoint, config["Connector:Ticket:ProxyCertThumbprint"]); + return; + default: + services.AddProxyWithoutAuth(proxyEndpoint); + return; + } + } + private static void LoadConnectorFromAssembly(IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config, string ticketConnectorAssemblyPath) { var connectorAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(ticketConnectorAssemblyPath); diff --git a/src/Sia.Shared/Authentication/AzureSecretVault.cs b/src/Sia.Shared/Authentication/AzureSecretVault.cs index 663d932..a9cd4ee 100644 --- a/src/Sia.Shared/Authentication/AzureSecretVault.cs +++ b/src/Sia.Shared/Authentication/AzureSecretVault.cs @@ -3,6 +3,7 @@ using Microsoft.Azure.KeyVault.Models; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Clients.ActiveDirectory; using System; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace Sia.Shared.Authentication @@ -17,11 +18,14 @@ namespace Sia.Shared.Authentication private readonly string _clientId; private readonly string _secret; - public AzureSecretVault(IConfigurationRoot configuration) + public AzureSecretVault(IConfigurationRoot configuration, + string vaultNameKey = "KeyVault:VaultName", + string clientIdKey = "ClientId", + string clientSecretKey = "ClientSecret") { - _clientId = configuration["ClientId"]; - _secret = configuration["ClientSecret"]; - _vault = String.Format(secretUriBase, configuration.GetSection("KeyVault")["VaultName"]); + _clientId = configuration[clientIdKey]; + _secret = configuration[clientSecretKey]; + _vault = String.Format(secretUriBase, configuration[vaultNameKey]); } public async Task Get(string secretName) @@ -37,6 +41,19 @@ namespace Sia.Shared.Authentication } } + public async Task GetCertificate(string certificateName) + { + try + { + var cert = await GetKeyVaultClient().GetCertificateAsync(_vault, certificateName).ConfigureAwait(false); + return new X509Certificate2(cert.Cer); + } + catch (KeyVaultErrorException) + { + return null; + } + } + private async Task GetToken(string authority, string resource, string scope) { var authContext = new AuthenticationContext(authority); diff --git a/src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs b/src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs new file mode 100644 index 0000000..3527cd7 --- /dev/null +++ b/src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace Sia.Shared.Authentication +{ + public class KeyVaultCertificateRetriever + : CertificateRetriever + { + private readonly X509Certificate2 _certificate; + + public KeyVaultCertificateRetriever(AzureSecretVault certificateVault, string certificateName) + { + var certTask = certificateVault.GetCertificate(certificateName); + Task.WaitAll(new Task[] { certTask }); + if (certTask.IsCompleted) + { + _certificate = certTask.Result; + } + } + + public override X509Certificate2 Certificate => _certificate; + } +} From 310e5499eb0ab52686702330d723f86b310f5fb5 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 4 Oct 2017 10:17:49 -0700 Subject: [PATCH 11/39] Successfully retrieving certificate via KeyVault. --- .../TicketProxy/Initialization.cs | 5 +-- .../TicketProxy/ProxyConnectionInfo.cs | 4 +-- src/Sia.Gateway/Controllers/BaseController.cs | 2 +- .../Initialization/SecretVaultStartup.cs | 4 +-- .../Initialization/ServicesStartup.cs | 12 +++++++ src/Sia.Gateway/Initialization/Startup.cs | 2 +- src/Sia.Gateway/Sia.Gateway.csproj | 2 +- src/Sia.Gateway/usersecrets.template.json | 17 +++++++++ .../Authentication/AzureSecretVault.cs | 36 ++++++++++--------- .../Authentication/KeyVaultConfiguration.cs | 24 +++++++++++++ 10 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 src/Sia.Shared/Authentication/KeyVaultConfiguration.cs diff --git a/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs b/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs index 269be4f..bcc3bfd 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Sia.Connectors.Tickets; using Sia.Connectors.Tickets.TicketProxy; +using Sia.Shared.Authentication; namespace Sia.Gateway.Initialization { @@ -15,8 +16,8 @@ namespace Sia.Gateway.Initialization => services.AddProxy(new ProxyConnectionInfo(endpoint, certThumbprint)); - public static IServiceCollection AddProxyWithCertFromKeyVault(this IServiceCollection services, string endpoint, IConfigurationRoot config, string vaultName) - => services.AddProxy(new ProxyConnectionInfo(endpoint, config, vaultName)); + public static IServiceCollection AddProxyWithCertFromKeyVault(this IServiceCollection services, string endpoint, KeyVaultConfiguration config, string certName) + => services.AddProxy(new ProxyConnectionInfo(endpoint, config, certName)); private static IServiceCollection AddProxy(this IServiceCollection services, ProxyConnectionInfo proxyConnection) { diff --git a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs index 8046fd0..4e92f89 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs @@ -34,10 +34,10 @@ namespace Sia.Connectors.Tickets.TicketProxy /// Proxy Endpoint /// Configuration root for initialization /// Key vault name - public ProxyConnectionInfo(string endpoint, IConfigurationRoot config, string vaultName) + public ProxyConnectionInfo(string endpoint, KeyVaultConfiguration config, string vaultName) : this(endpoint, AuthenticationType.CertificateFromKeyVault) { - _keyVault = new AzureSecretVault(config, vaultName); + _keyVault = new AzureSecretVault(config); CertIdentifier = vaultName; } diff --git a/src/Sia.Gateway/Controllers/BaseController.cs b/src/Sia.Gateway/Controllers/BaseController.cs index 5b1d118..480bea4 100644 --- a/src/Sia.Gateway/Controllers/BaseController.cs +++ b/src/Sia.Gateway/Controllers/BaseController.cs @@ -8,7 +8,7 @@ using Sia.Gateway.Validation.Filters; namespace Sia.Gateway.Controllers { [Return400BadRequestWhenModelStateInvalid] - [Authorize()] + //[Authorize()] public abstract class BaseController : Controller { protected readonly IMediator _mediator; diff --git a/src/Sia.Gateway/Initialization/SecretVaultStartup.cs b/src/Sia.Gateway/Initialization/SecretVaultStartup.cs index 5a2d3ea..f1b41d2 100644 --- a/src/Sia.Gateway/Initialization/SecretVaultStartup.cs +++ b/src/Sia.Gateway/Initialization/SecretVaultStartup.cs @@ -4,13 +4,13 @@ using Sia.Shared.Authentication; namespace Sia.Gateway.Initialization { - public static class SecretVaultStartup + public static class ApplicationInsightsStartup { public static AzureSecretVault Initialize(IHostingEnvironment env, IConfigurationRoot configuration) { //Needs to be done in the initial Startup.Startup() method because Application Insights registers itself prior //to ConfigureServices being run - var secrets = new AzureSecretVault(configuration); + var secrets = new AzureSecretVault(new KeyVaultConfiguration(configuration["ClientId"], configuration["ClientSecret"], configuration["KeyVault:VaultName"])); var vaultTask = secrets.Get(configuration.GetSection("KeyVault")["InstrumentationKeyName"]); vaultTask.Wait(); diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index cc790d6..5f199db 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -10,6 +10,7 @@ using Sia.Data.Incidents; using Sia.Gateway.Authentication; using Sia.Gateway.Requests; using Sia.Gateway.ServiceRepositories; +using Sia.Shared.Authentication; using System; using System.Reflection; using System.Runtime.Loader; @@ -69,6 +70,17 @@ namespace Sia.Gateway.Initialization case "Certificate": services.AddProxyWithCert(proxyEndpoint, config["Connector:Ticket:ProxyCertThumbprint"]); return; + case "VaultCertificate": + services.AddProxyWithCertFromKeyVault( + proxyEndpoint, + new KeyVaultConfiguration( + config["ClientId"], + config["ClientSecret"], + config["Connector:Ticket:VaultName"] + ), + config["Connector:Ticket:CertName"] + ); + return; default: services.AddProxyWithoutAuth(proxyEndpoint); return; diff --git a/src/Sia.Gateway/Initialization/Startup.cs b/src/Sia.Gateway/Initialization/Startup.cs index fc00d28..971598a 100644 --- a/src/Sia.Gateway/Initialization/Startup.cs +++ b/src/Sia.Gateway/Initialization/Startup.cs @@ -25,7 +25,7 @@ namespace Sia.Gateway } _configuration = builder.Build(); - _secrets = SecretVaultStartup.Initialize(env, _configuration); + _secrets = ApplicationInsightsStartup.Initialize(env, _configuration); _env = env; } diff --git a/src/Sia.Gateway/Sia.Gateway.csproj b/src/Sia.Gateway/Sia.Gateway.csproj index d14ce2a..cf96285 100644 --- a/src/Sia.Gateway/Sia.Gateway.csproj +++ b/src/Sia.Gateway/Sia.Gateway.csproj @@ -21,13 +21,13 @@ - + diff --git a/src/Sia.Gateway/usersecrets.template.json b/src/Sia.Gateway/usersecrets.template.json index 958c047..11e3fef 100644 --- a/src/Sia.Gateway/usersecrets.template.json +++ b/src/Sia.Gateway/usersecrets.template.json @@ -15,5 +15,22 @@ "Your uri here" //Replace with your frontend uri ] }, + "Connector": { + "Ticket": { + //If loading from assembly + "Path": "Path to connector DLL", + + //If using proxy + "ProxyEndpoint": "endpoint URL", + "ProxyAuthType": "Proxy authentication type (Certificate, VaultCertificate, or None)", + + //If using 'Certificate' proxy auth type + "ProxyCertThumbprint": "Certificate thumbprint", + + //If using VaultCertificate auth with either assembly or proxy + "CertName": "certificate name as stored in KeyVault", + "VaultName": "name of KeyVault instance to use when retrieving certificate" + } + }, "ClientSecret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" //Replace with your client secret } diff --git a/src/Sia.Shared/Authentication/AzureSecretVault.cs b/src/Sia.Shared/Authentication/AzureSecretVault.cs index a9cd4ee..96410d3 100644 --- a/src/Sia.Shared/Authentication/AzureSecretVault.cs +++ b/src/Sia.Shared/Authentication/AzureSecretVault.cs @@ -2,6 +2,7 @@ using Microsoft.Azure.KeyVault.Models; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Sia.Shared.Validation; using System; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; @@ -14,28 +15,32 @@ namespace Sia.Shared.Authentication } public class AzureSecretVault : ISecretVault { - private readonly string _vault; - private readonly string _clientId; - private readonly string _secret; - - public AzureSecretVault(IConfigurationRoot configuration, - string vaultNameKey = "KeyVault:VaultName", + private readonly KeyVaultConfiguration _config; + private const string _secretsEndpoint = "/secrets/"; + private const string _keysEndpoint = "/keys/"; + private const string _certificatesEndpoint = "/certificates/"; + /* + string vaultNameKey = "KeyVault:VaultName", string clientIdKey = "ClientId", - string clientSecretKey = "ClientSecret") - { + string clientSecretKey = "ClientSecret" + _clientId = configuration[clientIdKey]; _secret = configuration[clientSecretKey]; _vault = String.Format(secretUriBase, configuration[vaultNameKey]); + */ + public AzureSecretVault(KeyVaultConfiguration configuration) + { + _config = ThrowIf.Null(configuration, nameof(configuration)); } public async Task Get(string secretName) { try { - var secret = await GetKeyVaultClient().GetSecretAsync(_vault + secretName).ConfigureAwait(false); + var secret = await GetKeyVaultClient().GetSecretAsync(_config.Vault + _secretsEndpoint + secretName).ConfigureAwait(false); return secret.Value; } - catch (KeyVaultErrorException) + catch (KeyVaultErrorException ex) { return string.Empty; } @@ -45,10 +50,12 @@ namespace Sia.Shared.Authentication { try { - var cert = await GetKeyVaultClient().GetCertificateAsync(_vault, certificateName).ConfigureAwait(false); + var cert = await GetKeyVaultClient() + .GetCertificateAsync(_config.Vault, certificateName) + .ConfigureAwait(false); return new X509Certificate2(cert.Cer); } - catch (KeyVaultErrorException) + catch (KeyVaultErrorException ex) { return null; } @@ -57,8 +64,7 @@ namespace Sia.Shared.Authentication private async Task GetToken(string authority, string resource, string scope) { var authContext = new AuthenticationContext(authority); - ClientCredential clientCred = new ClientCredential(_clientId, - _secret); + ClientCredential clientCred = new ClientCredential(_config.ClientId, _config.ClientSecret); AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred); if (result == null) @@ -67,8 +73,6 @@ namespace Sia.Shared.Authentication return result.AccessToken; } - private const string secretUriBase = "https://{0}.vault.azure.net/secrets/"; - private KeyVaultClient GetKeyVaultClient() => new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken)); } diff --git a/src/Sia.Shared/Authentication/KeyVaultConfiguration.cs b/src/Sia.Shared/Authentication/KeyVaultConfiguration.cs new file mode 100644 index 0000000..4cfe043 --- /dev/null +++ b/src/Sia.Shared/Authentication/KeyVaultConfiguration.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Configuration; +using Sia.Shared.Validation; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sia.Shared.Authentication +{ + public class KeyVaultConfiguration + { + public KeyVaultConfiguration(string clientId, string clientSecret, string vault) + { + ClientId = ThrowIf.NullOrWhiteSpace(clientId, nameof(clientId)); + ClientSecret = ThrowIf.NullOrWhiteSpace(clientSecret, nameof(clientSecret)); + Vault = String.Format(secretUriBase, ThrowIf.NullOrWhiteSpace(vault, nameof(vault))); + } + + private const string secretUriBase = "https://{0}.vault.azure.net"; + + public readonly string Vault; + public readonly string ClientId; + public readonly string ClientSecret; + } +} From a41e52a513f94eef7d7b2cdee41ad41f7ba95619 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 4 Oct 2017 10:38:53 -0700 Subject: [PATCH 12/39] Additional guard clauses, reverted unintentionally committed temporary disabling of auth. --- .../Sia.Connectors.Tickets.csproj | 2 +- .../TicketProxy/ProxyConnectionInfo.cs | 2 +- src/Sia.Gateway/Controllers/BaseController.cs | 2 +- ...etVaultStartup.cs => ApplicationInsightsStartup.cs} | 10 ++++++++-- src/Sia.Gateway/Initialization/ServicesStartup.cs | 2 ++ src/Sia.Gateway/Initialization/Startup.cs | 3 +-- .../Authentication/KeyVaultCertificateRetriever.cs | 5 ++++- 7 files changed, 18 insertions(+), 8 deletions(-) rename src/Sia.Gateway/Initialization/{SecretVaultStartup.cs => ApplicationInsightsStartup.cs} (62%) diff --git a/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj b/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj index 8e10b47..eec4c2d 100644 --- a/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj +++ b/src/Sia.Connectors.Tickets/Sia.Connectors.Tickets.csproj @@ -3,7 +3,7 @@ netcoreapp2.0 Microsoft.Sia.Connectors.Tickets - 1.0.5-alpha + 1.0.6-alpha pdimit, magpint, jache, chtownes Microsoft SRE Incident Assistant diff --git a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs index 4e92f89..b71a298 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnectionInfo.cs @@ -38,7 +38,7 @@ namespace Sia.Connectors.Tickets.TicketProxy : this(endpoint, AuthenticationType.CertificateFromKeyVault) { _keyVault = new AzureSecretVault(config); - CertIdentifier = vaultName; + CertIdentifier = ThrowIf.NullOrWhiteSpace(vaultName, nameof(vaultName)); } protected ProxyConnectionInfo(string endpoint, AuthenticationType authType) diff --git a/src/Sia.Gateway/Controllers/BaseController.cs b/src/Sia.Gateway/Controllers/BaseController.cs index 480bea4..5b1d118 100644 --- a/src/Sia.Gateway/Controllers/BaseController.cs +++ b/src/Sia.Gateway/Controllers/BaseController.cs @@ -8,7 +8,7 @@ using Sia.Gateway.Validation.Filters; namespace Sia.Gateway.Controllers { [Return400BadRequestWhenModelStateInvalid] - //[Authorize()] + [Authorize()] public abstract class BaseController : Controller { protected readonly IMediator _mediator; diff --git a/src/Sia.Gateway/Initialization/SecretVaultStartup.cs b/src/Sia.Gateway/Initialization/ApplicationInsightsStartup.cs similarity index 62% rename from src/Sia.Gateway/Initialization/SecretVaultStartup.cs rename to src/Sia.Gateway/Initialization/ApplicationInsightsStartup.cs index f1b41d2..582a03a 100644 --- a/src/Sia.Gateway/Initialization/SecretVaultStartup.cs +++ b/src/Sia.Gateway/Initialization/ApplicationInsightsStartup.cs @@ -6,11 +6,17 @@ namespace Sia.Gateway.Initialization { public static class ApplicationInsightsStartup { - public static AzureSecretVault Initialize(IHostingEnvironment env, IConfigurationRoot configuration) + public static AzureSecretVault InitializeApplicationInsights(this IHostingEnvironment env, IConfigurationRoot configuration) { //Needs to be done in the initial Startup.Startup() method because Application Insights registers itself prior //to ConfigureServices being run - var secrets = new AzureSecretVault(new KeyVaultConfiguration(configuration["ClientId"], configuration["ClientSecret"], configuration["KeyVault:VaultName"])); + var secrets = new AzureSecretVault( + new KeyVaultConfiguration( + configuration["ClientId"], + configuration["ClientSecret"], + configuration["KeyVault:VaultName"] + ) + ); var vaultTask = secrets.Get(configuration.GetSection("KeyVault")["InstrumentationKeyName"]); vaultTask.Wait(); diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index 5f199db..66f219c 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -11,6 +11,7 @@ using Sia.Gateway.Authentication; using Sia.Gateway.Requests; using Sia.Gateway.ServiceRepositories; using Sia.Shared.Authentication; +using Sia.Shared.Validation; using System; using System.Reflection; using System.Runtime.Loader; @@ -57,6 +58,7 @@ namespace Sia.Gateway.Initialization private static bool TryGetConfigValue(IConfigurationRoot config, string configName, out string configValue) { + ThrowIf.NullOrWhiteSpace(configName, nameof(configName)); configValue = config[configName]; return !string.IsNullOrEmpty(configValue); } diff --git a/src/Sia.Gateway/Initialization/Startup.cs b/src/Sia.Gateway/Initialization/Startup.cs index 971598a..221cb25 100644 --- a/src/Sia.Gateway/Initialization/Startup.cs +++ b/src/Sia.Gateway/Initialization/Startup.cs @@ -25,13 +25,12 @@ namespace Sia.Gateway } _configuration = builder.Build(); - _secrets = ApplicationInsightsStartup.Initialize(env, _configuration); + env.InitializeApplicationInsights(_configuration); _env = env; } private IHostingEnvironment _env { get; } private IConfigurationRoot _configuration { get; } - private ISecretVault _secrets { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) diff --git a/src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs b/src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs index 3527cd7..d8c811c 100644 --- a/src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs +++ b/src/Sia.Shared/Authentication/KeyVaultCertificateRetriever.cs @@ -1,4 +1,5 @@ -using System; +using Sia.Shared.Validation; +using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -13,6 +14,8 @@ namespace Sia.Shared.Authentication public KeyVaultCertificateRetriever(AzureSecretVault certificateVault, string certificateName) { + ThrowIf.NullOrWhiteSpace(certificateName, nameof(certificateName)); + var certTask = certificateVault.GetCertificate(certificateName); Task.WaitAll(new Task[] { certTask }); if (certTask.IsCompleted) From ce0ac34fab3a900f6b67ae19abf67a51a0214dc5 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Fri, 6 Oct 2017 10:50:58 -0700 Subject: [PATCH 13/39] Attempted to reduce boilerplate, improve standardization without reducing extensibility, --- .../Requests/Engagements/GetEngagement.cs | 2 +- .../Requests/Engagements/PostEngagement.cs | 2 +- .../Requests/Engagements/PutEngagement.cs | 2 +- src/Sia.Gateway/Requests/Events/GetEvent.cs | 4 +- src/Sia.Gateway/Requests/Events/GetEvents.cs | 2 +- src/Sia.Gateway/Requests/Events/PostEvent.cs | 2 +- .../Requests/Incidents/GetIncident.cs | 2 +- .../Requests/Incidents/GetIncidents.cs | 4 +- .../Incidents/GetIncidentsByTicket.cs | 2 +- .../Requests/Incidents/PostIncident.cs | 2 +- .../IEngagementRepository.cs | 28 +++++++------ .../ServiceRepositories/IEventRepository.cs | 26 +++++++----- .../IIncidentRepository.cs | 42 +++++++++++-------- .../ServiceRepositories/Operations/IGet.cs | 12 ++++++ .../Operations/IGetMany.cs | 12 ++++++ .../ServiceRepositories/Operations/IPost.cs | 12 ++++++ .../ServiceRepositories/Operations/IPut.cs | 12 ++++++ .../TestDoubles/StubEventRepository.cs | 17 +++++--- .../TestDoubles/StubIncidentRepository.cs | 14 +++---- 19 files changed, 133 insertions(+), 66 deletions(-) create mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs create mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs create mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs create mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs diff --git a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs index 4049967..474dc8d 100644 --- a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs @@ -27,7 +27,7 @@ namespace Sia.Gateway.Requests } public async Task Handle(GetEngagementRequest request) { - return await _incidentRepository.GetEngagementAsync(request.IncidentId, request.Id, request.UserContext); + return await _incidentRepository.GetAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs index 74f9e79..6218b46 100644 --- a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs @@ -29,7 +29,7 @@ namespace Sia.Gateway.Requests public async Task Handle(PostEngagementRequest request) { - return await _engagementRepository.PostEngagementAsync(request.IncidentId, request.NewEngagement, request.UserContext); + return await _engagementRepository.PostAsync(request); } } }; diff --git a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs index 6feb212..ef8b319 100644 --- a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs @@ -30,7 +30,7 @@ namespace Sia.Gateway.Requests public async Task Handle(PutEngagementRequest request) { - await _engagementClient.PutEngagementAsync(request.IncidentId, request.EngagementId, request.UpdateEngagement, request.UserContext); + await _engagementClient.PutAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Events/GetEvent.cs b/src/Sia.Gateway/Requests/Events/GetEvent.cs index d6912dd..0c1cb74 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvent.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvent.cs @@ -21,14 +21,14 @@ namespace Sia.Gateway.Requests } public class GetEventHandler : EventHandler { - protected GetEventHandler(IEventRepository eventRepository) + public GetEventHandler(IEventRepository eventRepository) : base(eventRepository) { } public override async Task Handle(GetEventRequest request) { - return await _eventRepository.GetEvent(request.IncidentId, request.Id, request.UserContext); + return await _eventRepository.GetAsync(request); } } diff --git a/src/Sia.Gateway/Requests/Events/GetEvents.cs b/src/Sia.Gateway/Requests/Events/GetEvents.cs index 2b484e4..1333cc1 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvents.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvents.cs @@ -32,7 +32,7 @@ namespace Sia.Gateway.Requests.Events public override async Task> Handle(GetEventsRequest request) { - return await _eventRepository.GetEventsAsync(request.IncidentId, request.Pagination, request.UserContext); + return await _eventRepository.GetManyAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Events/PostEvent.cs b/src/Sia.Gateway/Requests/Events/PostEvent.cs index 4a111e7..80d6021 100644 --- a/src/Sia.Gateway/Requests/Events/PostEvent.cs +++ b/src/Sia.Gateway/Requests/Events/PostEvent.cs @@ -30,7 +30,7 @@ namespace Sia.Gateway.Requests public async Task Handle(PostEventRequest request) { - return await _incidentRepository.PostEvent(request.IncidentId, request.NewEvent, request.UserContext); + return await _incidentRepository.PostAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs index 08a0a7d..3f13f19 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs @@ -26,7 +26,7 @@ namespace Sia.Gateway.Requests public async Task Handle(GetIncidentRequest request) { - return await _incidentRepository.GetIncidentAsync(request.Id, request.UserContext); + return await _incidentRepository.GetAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs index b4652d1..4ee3ce2 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs @@ -22,9 +22,9 @@ namespace Sia.Gateway.Requests { _incidentRepository = incidentClient; } - public async Task> Handle(GetIncidentsRequest message) + public async Task> Handle(GetIncidentsRequest request) { - var incidentResponse = await _incidentRepository.GetIncidentsAsync(message.UserContext); + var incidentResponse = await _incidentRepository.GetManyAsync(request); return incidentResponse; } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs index 39de788..b4762a5 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs @@ -27,7 +27,7 @@ namespace Sia.Gateway.Requests } public async Task> Handle(GetIncidentsByTicketRequest message) { - var incidentResponse = await _incidentRepository.GetIncidentsByTicketAsync(message.TicketId, message.UserContext); + var incidentResponse = await _incidentRepository.GetManyAsync(message); return incidentResponse; } } diff --git a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs index 9532ad5..519943c 100644 --- a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs @@ -29,7 +29,7 @@ namespace Sia.Gateway.Requests } public async Task Handle(PostIncidentRequest message) { - return await _incidentRepository.PostIncidentAsync(message.Incident, message.UserContext); + return await _incidentRepository.PostAsync(message); } } } diff --git a/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs b/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs index b9ef7ab..4b4969b 100644 --- a/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs @@ -4,6 +4,8 @@ using Sia.Data.Incidents; using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; +using Sia.Gateway.Requests; +using Sia.Gateway.ServiceRepositories.Operations; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,10 +13,10 @@ using System.Threading.Tasks; namespace Sia.Gateway.ServiceRepositories { public interface IEngagementRepository + :IGet, + IPost, + IPut { - Task GetEngagementAsync(long incidentId, long id, AuthenticatedUserContext userContext); - Task PostEngagementAsync(long incidentId, NewEngagement newEngagement, AuthenticatedUserContext userContext); - Task PutEngagementAsync(long incidentId, long engagementId, UpdateEngagement updatedEngagement, AuthenticatedUserContext userContext); } public class EngagementRepository : IEngagementRepository @@ -26,27 +28,27 @@ namespace Sia.Gateway.ServiceRepositories _context = context; } - public async Task GetEngagementAsync(long incidentId, long id, AuthenticatedUserContext userContext) + public async Task GetAsync(GetEngagementRequest request) { var EngagementRecord = await _context.Engagements .Include(en => en.Participant) - .FirstOrDefaultAsync(ev => ev.IncidentId == incidentId && ev.Id == id); + .FirstOrDefaultAsync(ev => ev.IncidentId == request.IncidentId && ev.Id == request.Id); if (EngagementRecord == null) throw new KeyNotFoundException(); return Mapper.Map(EngagementRecord); } - public async Task PostEngagementAsync(long incidentId, NewEngagement newEngagement, AuthenticatedUserContext userContext) + public async Task PostAsync(PostEngagementRequest request) { - if (newEngagement == null) throw new ArgumentNullException(nameof(newEngagement)); + if (request.NewEngagement == null) throw new ArgumentNullException(nameof(request.NewEngagement)); var dataIncident = await _context.Incidents .Include(cr => cr.Engagements) .ThenInclude(en => en.Participant) - .FirstOrDefaultAsync(x => x.Id == incidentId); + .FirstOrDefaultAsync(x => x.Id == request.IncidentId); if (dataIncident == null) throw new KeyNotFoundException(); - var dataEngagement = Mapper.Map(newEngagement); + var dataEngagement = Mapper.Map(request.NewEngagement); dataEngagement.TimeEngaged = DateTime.UtcNow; dataIncident.Engagements.Add(dataEngagement); @@ -55,14 +57,14 @@ namespace Sia.Gateway.ServiceRepositories return Mapper.Map(dataEngagement); } - public async Task PutEngagementAsync(long incidentId, long engagementId, UpdateEngagement updatedEngagement, AuthenticatedUserContext userContext) + public async Task PutAsync(PutEngagementRequest request) { - if (updatedEngagement == null) throw new ArgumentNullException(nameof(updatedEngagement)); + if (request.UpdateEngagement == null) throw new ArgumentNullException(nameof(UpdateEngagement)); var existingRecord = await _context.Engagements .Include(en => en.Participant) - .FirstOrDefaultAsync(engagement => engagement.IncidentId == incidentId && engagement.Id == engagementId); + .FirstOrDefaultAsync(engagement => engagement.IncidentId == request.IncidentId && engagement.Id == request.EngagementId); - var updatedModel = Mapper.Map(updatedEngagement, existingRecord); + var updatedModel = Mapper.Map(request.UpdateEngagement, existingRecord); await _context.SaveChangesAsync(); } diff --git a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs b/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs index cf126d9..21fd660 100644 --- a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs @@ -6,6 +6,9 @@ using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; using Sia.Gateway.Protocol; +using Sia.Gateway.Requests; +using Sia.Gateway.Requests.Events; +using Sia.Gateway.ServiceRepositories.Operations; using System; using System.Collections.Generic; using System.Linq; @@ -14,10 +17,10 @@ using System.Threading.Tasks; namespace Sia.Gateway.ServiceRepositories { public interface IEventRepository + : IGet, + IPost, + IGetMany { - Task GetEvent(long incidentId, long id, AuthenticatedUserContext userContext); - Task PostEvent(long incidentId, NewEvent newEvent, AuthenticatedUserContext userContext); - Task> GetEventsAsync(long incidentId, PaginationMetadata pagination, AuthenticatedUserContext userContext); } public class EventRepository : IEventRepository @@ -30,32 +33,33 @@ namespace Sia.Gateway.ServiceRepositories _context = context; } - public async Task GetEvent(long incidentId, long id, AuthenticatedUserContext userContext) + public async Task GetAsync(GetEventRequest request) { - var eventRecord = await _context.Events.FirstOrDefaultAsync(ev => ev.IncidentId == incidentId && ev.Id == id); + var eventRecord = await _context.Events.FirstOrDefaultAsync(ev => ev.IncidentId == request.IncidentId && ev.Id == request.Id); if (eventRecord == null) throw new KeyNotFoundException(); return Mapper.Map(eventRecord); } - public async Task> GetEventsAsync(long incidentId, PaginationMetadata pagination, AuthenticatedUserContext userContext) + public async Task> GetManyAsync(GetEventsRequest request) { return await _context.Events - .WithPagination(pagination) + .Where(ev => ev.IncidentId == request.IncidentId) + .WithPagination(request.Pagination) .ProjectTo() .ToListAsync(); } - public async Task PostEvent(long incidentId, NewEvent newEvent, AuthenticatedUserContext userContext) + public async Task PostAsync(PostEventRequest request) { - if (newEvent == null) throw new ArgumentNullException(nameof(newEvent)); + if (request.NewEvent == null) throw new ArgumentNullException(nameof(request.NewEvent)); var dataCrisis = await _context.Incidents .Include(cr => cr.Events) - .FirstOrDefaultAsync(x => x.Id == incidentId); + .FirstOrDefaultAsync(x => x.Id == request.IncidentId); if (dataCrisis == null) throw new KeyNotFoundException(); - var dataEvent = Mapper.Map(newEvent); + var dataEvent = Mapper.Map(request.NewEvent); dataCrisis.Events.Add(dataEvent); await _context.SaveChangesAsync(); diff --git a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs b/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs index b6aa43e..a589143 100644 --- a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs @@ -6,6 +6,8 @@ using Sia.Data.Incidents; using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; +using Sia.Gateway.Requests; +using Sia.Gateway.ServiceRepositories.Operations; using Sia.Shared.Exceptions; using System; using System.Collections.Generic; @@ -15,12 +17,13 @@ using System.Threading.Tasks; namespace Sia.Gateway.ServiceRepositories { public interface IIncidentRepository + : IGet, + IGetMany, + IGetMany, + IPost { - Task GetIncidentAsync(long id, AuthenticatedUserContext userContext); - Task> GetIncidentsAsync(AuthenticatedUserContext userContext); - Task PostIncidentAsync(NewIncident incident, AuthenticatedUserContext userContext); - Task> GetIncidentsByTicketAsync(string ticketId, AuthenticatedUserContext userContext); } + public class IncidentRepository : IIncidentRepository { private readonly IncidentContext _context; @@ -32,9 +35,9 @@ namespace Sia.Gateway.ServiceRepositories _connector = connector; } - public async Task GetIncidentAsync(long id, AuthenticatedUserContext userContext) + public async Task GetAsync(GetIncidentRequest getIncident) { - var incidentRecord = await _context.Incidents.WithEagerLoading().FirstOrDefaultAsync(cr => cr.Id == id); + var incidentRecord = await _context.Incidents.WithEagerLoading().FirstOrDefaultAsync(cr => cr.Id == getIncident.Id); if (incidentRecord == null) throw new KeyNotFoundException(); var ticket = await _connector.Client.GetAsync(incidentRecord.Tickets.FirstOrDefault(t => t.IsPrimary).OriginId); @@ -42,28 +45,31 @@ namespace Sia.Gateway.ServiceRepositories return _connector.Converter.AssembleIncident(incidentRecord, ticket); } - public async Task> GetIncidentsAsync(AuthenticatedUserContext userContext) - { - var incidentRecords = await _context.Incidents.WithEagerLoading().ProjectTo().ToListAsync(); - return incidentRecords; - } - - public async Task> GetIncidentsByTicketAsync(string ticketId, AuthenticatedUserContext userContext) + public async Task> GetManyAsync(GetIncidentsRequest request) { var incidentRecords = await _context.Incidents .WithEagerLoading() - .Where(incident => incident.Tickets.Any(inc => inc.OriginId == ticketId)) + .ProjectTo() + .ToListAsync(); + return incidentRecords; + } + + public async Task> GetManyAsync(GetIncidentsByTicketRequest request) + { + var incidentRecords = await _context.Incidents + .WithEagerLoading() + .Where(incident => incident.Tickets.Any(inc => inc.OriginId == request.TicketId)) .ProjectTo().ToListAsync(); return incidentRecords; } - public async Task PostIncidentAsync(NewIncident incident, AuthenticatedUserContext userContext) + public async Task PostAsync(PostIncidentRequest request) { - if (incident == null) throw new ArgumentNullException(nameof(incident)); - if (incident?.PrimaryTicket?.OriginId == null) throw new ConflictException("Please provide a primary incident with a valid originId"); + if (request.Incident == null) throw new ArgumentNullException(nameof(request.Incident)); + if (request.Incident?.PrimaryTicket?.OriginId == null) throw new ConflictException("Please provide a primary incident with a valid originId"); - var dataIncident = Mapper.Map(incident); + var dataIncident = Mapper.Map(request.Incident); var result = _context.Incidents.Add(dataIncident); await _context.SaveChangesAsync(); diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs new file mode 100644 index 0000000..3c44da8 --- /dev/null +++ b/src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.ServiceRepositories.Operations +{ + public interface IGet + { + Task GetAsync(TRequest request); + } +} diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs new file mode 100644 index 0000000..affed47 --- /dev/null +++ b/src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.ServiceRepositories.Operations +{ + public interface IGetMany + { + Task> GetManyAsync(TRequest request); + } +} diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs new file mode 100644 index 0000000..738f9d7 --- /dev/null +++ b/src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.ServiceRepositories.Operations +{ + public interface IPost + { + Task PostAsync(TRequest request); + } +} diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs new file mode 100644 index 0000000..e070c50 --- /dev/null +++ b/src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.ServiceRepositories.Operations +{ + public interface IPut + { + Task PutAsync(TRequest request); + } +} diff --git a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs b/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs index 54f5019..d92fe26 100644 --- a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs +++ b/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs @@ -2,11 +2,13 @@ using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; +using Sia.Gateway.Requests; using Sia.Gateway.ServiceRepositories; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; +using Sia.Gateway.Requests.Events; namespace Sia.Gateway.Tests.TestDoubles { @@ -28,14 +30,19 @@ namespace Sia.Gateway.Tests.TestDoubles public bool IsSuccessStatusCodeToRespondWith { get; set; } public string ContentToRespondWith { get; set; } - public Task GetEvent(long incidentId, long id, AuthenticatedUserContext userContext) + public Task GetAsync(GetEventRequest request) { - return Task.FromResult(_events.First(ev => ev.Id == id && ev.IncidentId == incidentId)); + return Task.FromResult(_events.First(ev => ev.Id == request.Id && ev.IncidentId == request.IncidentId)); } - - public Task PostEvent(long incidentId, NewEvent newEvent, AuthenticatedUserContext userContext) + + public Task> GetManyAsync(GetEventsRequest request) { - return Task.FromResult(Mapper.Map(newEvent, new Event())); + return Task.FromResult(_events.AsEnumerable()); + } + + public Task PostAsync(PostEventRequest request) + { + return Task.FromResult(Mapper.Map(request.NewEvent, new Event())); } } } diff --git a/test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs b/test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs index 44a73cf..02bb7fb 100644 --- a/test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs +++ b/test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; +using Sia.Gateway.Requests; namespace Sia.Gateway.Tests.TestDoubles { @@ -36,25 +37,24 @@ namespace Sia.Gateway.Tests.TestDoubles List _incidents { get; set; } - - public Task GetIncidentAsync(long id, AuthenticatedUserContext userContext) + public Task GetAsync(GetIncidentRequest request) { - return Task.FromResult(_incidents.First(cr => cr.Id == id)); + return Task.FromResult(_incidents.First(cr => cr.Id == request.Id)); } - public Task> GetIncidentsAsync(AuthenticatedUserContext userContext) + public Task> GetManyAsync(GetIncidentsRequest request) { return Task.FromResult(_incidents.AsEnumerable()); } - public Task> GetIncidentsByTicketAsync(string ticketId, AuthenticatedUserContext userContext) + public Task> GetManyAsync(GetIncidentsByTicketRequest request) { throw new NotImplementedException(); } - public Task PostIncidentAsync(NewIncident incident, AuthenticatedUserContext userContext) + public Task PostAsync(PostIncidentRequest request) { - return Task.FromResult(_mapper.Map(incident)); + return Task.FromResult(_mapper.Map(request.Incident)); } } } From 45190220070b15bc6b436b3371b5a7566d4abd14 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Fri, 6 Oct 2017 11:16:50 -0700 Subject: [PATCH 14/39] Reduced duplication of boilerplate handler code via generic implementation. --- .../Requests/Engagements/GetEngagement.cs | 13 +++------- .../Requests/Engagements/PostEngagement.cs | 14 +++-------- .../Requests/Engagements/PutEngagement.cs | 15 ++++------- .../Requests/Events/EventHandler.cs | 21 ---------------- src/Sia.Gateway/Requests/Events/GetEvent.cs | 8 ++---- src/Sia.Gateway/Requests/Events/GetEvents.cs | 8 ++---- src/Sia.Gateway/Requests/Events/PostEvent.cs | 14 +++-------- src/Sia.Gateway/Requests/GetHandler.cs | 25 +++++++++++++++++++ src/Sia.Gateway/Requests/GetManyHandler.cs | 25 +++++++++++++++++++ src/Sia.Gateway/Requests/Handler.cs | 22 ++++++++++++++++ .../Requests/Incidents/GetIncident.cs | 14 +++-------- .../Requests/Incidents/GetIncidents.cs | 14 +++-------- .../Incidents/GetIncidentsByTicket.cs | 14 +++-------- .../Requests/Incidents/PostIncident.cs | 13 +++------- src/Sia.Gateway/Requests/PostHandler.cs | 25 +++++++++++++++++++ src/Sia.Gateway/Requests/PutHandler.cs | 25 +++++++++++++++++++ 16 files changed, 159 insertions(+), 111 deletions(-) delete mode 100644 src/Sia.Gateway/Requests/Events/EventHandler.cs create mode 100644 src/Sia.Gateway/Requests/GetHandler.cs create mode 100644 src/Sia.Gateway/Requests/GetManyHandler.cs create mode 100644 src/Sia.Gateway/Requests/Handler.cs create mode 100644 src/Sia.Gateway/Requests/PostHandler.cs create mode 100644 src/Sia.Gateway/Requests/PutHandler.cs diff --git a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs index 474dc8d..80c4041 100644 --- a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs @@ -17,17 +17,12 @@ namespace Sia.Gateway.Requests public long Id { get; } public long IncidentId { get; } } - public class GetEngagementHandler : IAsyncRequestHandler + public class GetEngagementHandler + : GetHandler { - private IEngagementRepository _incidentRepository; - - public GetEngagementHandler(IEngagementRepository incidentRepository) + public GetEngagementHandler(IEngagementRepository repository) + : base(repository) { - _incidentRepository = incidentRepository; - } - public async Task Handle(GetEngagementRequest request) - { - return await _incidentRepository.GetAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs index 6218b46..93d44fb 100644 --- a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs @@ -18,18 +18,12 @@ namespace Sia.Gateway.Requests public NewEngagement NewEngagement { get; } public long IncidentId { get; } } - public class PostEngagementHandler : IAsyncRequestHandler + public class PostEngagementHandler + : PostHandler { - private IEngagementRepository _engagementRepository; - - public PostEngagementHandler(IEngagementRepository engagementRepository) + public PostEngagementHandler(IEngagementRepository repository) + : base(repository) { - _engagementRepository = engagementRepository; - } - - public async Task Handle(PostEngagementRequest request) - { - return await _engagementRepository.PostAsync(request); } } }; diff --git a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs index ef8b319..8231529 100644 --- a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs @@ -1,4 +1,5 @@ using MediatR; +using Sia.Data.Incidents.Models; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; using Sia.Gateway.ServiceRepositories; @@ -19,18 +20,12 @@ namespace Sia.Gateway.Requests public long EngagementId { get; } public long IncidentId { get; } } - public class PutEngagementHandler : IAsyncRequestHandler + public class PutEngagementHandler + : PutHandler { - private IEngagementRepository _engagementClient; - - public PutEngagementHandler(IEngagementRepository incidentClient) + public PutEngagementHandler(IEngagementRepository repository) + : base(repository) { - _engagementClient = incidentClient; - } - - public async Task Handle(PutEngagementRequest request) - { - await _engagementClient.PutAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Events/EventHandler.cs b/src/Sia.Gateway/Requests/Events/EventHandler.cs deleted file mode 100644 index 6ba4b2c..0000000 --- a/src/Sia.Gateway/Requests/Events/EventHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediatR; -using Sia.Gateway.ServiceRepositories; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.Requests.Events -{ - public abstract class EventHandler : IAsyncRequestHandler - where TRequest : IRequest - { - protected EventHandler(IEventRepository eventRepository) - { - _eventRepository = eventRepository; - } - protected IEventRepository _eventRepository; - - public abstract Task Handle(TRequest request); - } -} diff --git a/src/Sia.Gateway/Requests/Events/GetEvent.cs b/src/Sia.Gateway/Requests/Events/GetEvent.cs index 0c1cb74..5003bee 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvent.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvent.cs @@ -19,17 +19,13 @@ namespace Sia.Gateway.Requests public long Id { get; } public long IncidentId { get; } } - public class GetEventHandler : EventHandler + public class GetEventHandler + : GetHandler { public GetEventHandler(IEventRepository eventRepository) : base(eventRepository) { } - - public override async Task Handle(GetEventRequest request) - { - return await _eventRepository.GetAsync(request); - } } } diff --git a/src/Sia.Gateway/Requests/Events/GetEvents.cs b/src/Sia.Gateway/Requests/Events/GetEvents.cs index 1333cc1..40d3294 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvents.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvents.cs @@ -23,16 +23,12 @@ namespace Sia.Gateway.Requests.Events public PaginationMetadata Pagination { get; } } - public class GetEventsHandler : EventHandler> + public class GetEventsHandler + : GetManyHandler { public GetEventsHandler(IEventRepository eventRepository) : base(eventRepository) { } - - public override async Task> Handle(GetEventsRequest request) - { - return await _eventRepository.GetManyAsync(request); - } } } diff --git a/src/Sia.Gateway/Requests/Events/PostEvent.cs b/src/Sia.Gateway/Requests/Events/PostEvent.cs index 80d6021..262f233 100644 --- a/src/Sia.Gateway/Requests/Events/PostEvent.cs +++ b/src/Sia.Gateway/Requests/Events/PostEvent.cs @@ -19,18 +19,12 @@ namespace Sia.Gateway.Requests public NewEvent NewEvent { get; } public long IncidentId { get; } } - public class PostEventHandler : IAsyncRequestHandler + public class PostEventHandler + : PostHandler { - private IEventRepository _incidentRepository; - - public PostEventHandler(IEventRepository incidentRepository) + public PostEventHandler(IEventRepository repository) + : base(repository) { - _incidentRepository = incidentRepository; - } - - public async Task Handle(PostEventRequest request) - { - return await _incidentRepository.PostAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/GetHandler.cs b/src/Sia.Gateway/Requests/GetHandler.cs new file mode 100644 index 0000000..8711125 --- /dev/null +++ b/src/Sia.Gateway/Requests/GetHandler.cs @@ -0,0 +1,25 @@ +using MediatR; +using Sia.Gateway.ServiceRepositories.Operations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.Requests +{ + public abstract class GetHandler + : Handler + where TRequest : IRequest + where TRepository : IGet + { + protected GetHandler(TRepository repository) + { + _repository = repository; + } + + protected TRepository _repository { get; } + + public override Task Handle(TRequest request) + => _repository.GetAsync(request); + } +} diff --git a/src/Sia.Gateway/Requests/GetManyHandler.cs b/src/Sia.Gateway/Requests/GetManyHandler.cs new file mode 100644 index 0000000..c332e84 --- /dev/null +++ b/src/Sia.Gateway/Requests/GetManyHandler.cs @@ -0,0 +1,25 @@ +using MediatR; +using Sia.Gateway.ServiceRepositories.Operations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.Requests +{ + public abstract class GetManyHandler + : Handler> + where TRequest : IRequest> + where TRepository : IGetMany + { + protected GetManyHandler(TRepository repository) + { + _repository = repository; + } + + protected TRepository _repository { get; } + + public override Task> Handle(TRequest request) + => _repository.GetManyAsync(request); + } +} diff --git a/src/Sia.Gateway/Requests/Handler.cs b/src/Sia.Gateway/Requests/Handler.cs new file mode 100644 index 0000000..bcbf862 --- /dev/null +++ b/src/Sia.Gateway/Requests/Handler.cs @@ -0,0 +1,22 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.Requests +{ + public abstract class Handler + : IAsyncRequestHandler + where TRequest : IRequest + { + public abstract Task Handle(TRequest request); + } + + public abstract class Handler + : IAsyncRequestHandler + where TRequest : IRequest + { + public abstract Task Handle(TRequest request); + } +} diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs index 3f13f19..45db527 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs @@ -15,18 +15,12 @@ namespace Sia.Gateway.Requests } public long Id { get; } } - public class GetIncidentHandler : IAsyncRequestHandler + public class GetIncidentHandler : + GetHandler { - private IIncidentRepository _incidentRepository; - - public GetIncidentHandler(IIncidentRepository incidentRepository) + public GetIncidentHandler(IIncidentRepository repository) + : base(repository) { - _incidentRepository = incidentRepository; - } - - public async Task Handle(GetIncidentRequest request) - { - return await _incidentRepository.GetAsync(request); } } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs index 4ee3ce2..21ac145 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs @@ -14,18 +14,12 @@ namespace Sia.Gateway.Requests } } - public class GetIncidentsHandler : IAsyncRequestHandler> + public class GetIncidentsHandler : + GetManyHandler { - private IIncidentRepository _incidentRepository; - - public GetIncidentsHandler(IIncidentRepository incidentClient) + public GetIncidentsHandler(IIncidentRepository repository) + : base(repository) { - _incidentRepository = incidentClient; - } - public async Task> Handle(GetIncidentsRequest request) - { - var incidentResponse = await _incidentRepository.GetManyAsync(request); - return incidentResponse; } } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs index b4762a5..6b64471 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs @@ -17,18 +17,12 @@ namespace Sia.Gateway.Requests } } - public class GetIncidentsByTicketHandler : IAsyncRequestHandler> + public class GetIncidentsByTicketHandler + : GetManyHandler { - private IIncidentRepository _incidentRepository; - - public GetIncidentsByTicketHandler(IIncidentRepository incidentClient) + public GetIncidentsByTicketHandler(IIncidentRepository repository) + : base(repository) { - _incidentRepository = incidentClient; - } - public async Task> Handle(GetIncidentsByTicketRequest message) - { - var incidentResponse = await _incidentRepository.GetManyAsync(message); - return incidentResponse; } } } diff --git a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs index 519943c..0cca9eb 100644 --- a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs @@ -19,17 +19,12 @@ namespace Sia.Gateway.Requests } - public class PostIncidentHandler : IAsyncRequestHandler + public class PostIncidentHandler + : PostHandler { - private IIncidentRepository _incidentRepository; - - public PostIncidentHandler(IIncidentRepository incidentClient) + public PostIncidentHandler(IIncidentRepository repository) + : base(repository) { - _incidentRepository = incidentClient; - } - public async Task Handle(PostIncidentRequest message) - { - return await _incidentRepository.PostAsync(message); } } } diff --git a/src/Sia.Gateway/Requests/PostHandler.cs b/src/Sia.Gateway/Requests/PostHandler.cs new file mode 100644 index 0000000..2ec5198 --- /dev/null +++ b/src/Sia.Gateway/Requests/PostHandler.cs @@ -0,0 +1,25 @@ +using MediatR; +using Sia.Gateway.ServiceRepositories.Operations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.Requests +{ + public abstract class PostHandler + : Handler + where TRequest : IRequest + where TRepository : IPost + { + protected PostHandler(TRepository repository) + { + _repository = repository; + } + + protected TRepository _repository { get; } + + public override Task Handle(TRequest request) + => _repository.PostAsync(request); + } +} diff --git a/src/Sia.Gateway/Requests/PutHandler.cs b/src/Sia.Gateway/Requests/PutHandler.cs new file mode 100644 index 0000000..99e7c65 --- /dev/null +++ b/src/Sia.Gateway/Requests/PutHandler.cs @@ -0,0 +1,25 @@ +using MediatR; +using Sia.Gateway.ServiceRepositories.Operations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.Requests +{ + public class PutHandler + : Handler + where TRequest : IRequest + where TRepository : IPut + { + protected PutHandler(TRepository repository) + { + _repository = repository; + } + + protected TRepository _repository { get; } + + public override Task Handle(TRequest request) + => _repository.PutAsync(request); + } +} From 2a08357f51a049426eeb2da8421d4f36c0ae21e9 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Fri, 6 Oct 2017 11:26:45 -0700 Subject: [PATCH 15/39] Removed unnecessary abstract base class --- src/Sia.Gateway/Requests/GetHandler.cs | 4 ++-- src/Sia.Gateway/Requests/GetManyHandler.cs | 4 ++-- src/Sia.Gateway/Requests/Handler.cs | 22 ---------------------- src/Sia.Gateway/Requests/PostHandler.cs | 4 ++-- src/Sia.Gateway/Requests/PutHandler.cs | 4 ++-- 5 files changed, 8 insertions(+), 30 deletions(-) delete mode 100644 src/Sia.Gateway/Requests/Handler.cs diff --git a/src/Sia.Gateway/Requests/GetHandler.cs b/src/Sia.Gateway/Requests/GetHandler.cs index 8711125..425c7b1 100644 --- a/src/Sia.Gateway/Requests/GetHandler.cs +++ b/src/Sia.Gateway/Requests/GetHandler.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Sia.Gateway.Requests { public abstract class GetHandler - : Handler + : IAsyncRequestHandler where TRequest : IRequest where TRepository : IGet { @@ -19,7 +19,7 @@ namespace Sia.Gateway.Requests protected TRepository _repository { get; } - public override Task Handle(TRequest request) + public virtual Task Handle(TRequest request) => _repository.GetAsync(request); } } diff --git a/src/Sia.Gateway/Requests/GetManyHandler.cs b/src/Sia.Gateway/Requests/GetManyHandler.cs index c332e84..6546799 100644 --- a/src/Sia.Gateway/Requests/GetManyHandler.cs +++ b/src/Sia.Gateway/Requests/GetManyHandler.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Sia.Gateway.Requests { public abstract class GetManyHandler - : Handler> + : IAsyncRequestHandler> where TRequest : IRequest> where TRepository : IGetMany { @@ -19,7 +19,7 @@ namespace Sia.Gateway.Requests protected TRepository _repository { get; } - public override Task> Handle(TRequest request) + public virtual Task> Handle(TRequest request) => _repository.GetManyAsync(request); } } diff --git a/src/Sia.Gateway/Requests/Handler.cs b/src/Sia.Gateway/Requests/Handler.cs deleted file mode 100644 index bcbf862..0000000 --- a/src/Sia.Gateway/Requests/Handler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.Requests -{ - public abstract class Handler - : IAsyncRequestHandler - where TRequest : IRequest - { - public abstract Task Handle(TRequest request); - } - - public abstract class Handler - : IAsyncRequestHandler - where TRequest : IRequest - { - public abstract Task Handle(TRequest request); - } -} diff --git a/src/Sia.Gateway/Requests/PostHandler.cs b/src/Sia.Gateway/Requests/PostHandler.cs index 2ec5198..ee94e89 100644 --- a/src/Sia.Gateway/Requests/PostHandler.cs +++ b/src/Sia.Gateway/Requests/PostHandler.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Sia.Gateway.Requests { public abstract class PostHandler - : Handler + : IAsyncRequestHandler where TRequest : IRequest where TRepository : IPost { @@ -19,7 +19,7 @@ namespace Sia.Gateway.Requests protected TRepository _repository { get; } - public override Task Handle(TRequest request) + public virtual Task Handle(TRequest request) => _repository.PostAsync(request); } } diff --git a/src/Sia.Gateway/Requests/PutHandler.cs b/src/Sia.Gateway/Requests/PutHandler.cs index 99e7c65..d014670 100644 --- a/src/Sia.Gateway/Requests/PutHandler.cs +++ b/src/Sia.Gateway/Requests/PutHandler.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Sia.Gateway.Requests { public class PutHandler - : Handler + : IAsyncRequestHandler where TRequest : IRequest where TRepository : IPut { @@ -19,7 +19,7 @@ namespace Sia.Gateway.Requests protected TRepository _repository { get; } - public override Task Handle(TRequest request) + public virtual Task Handle(TRequest request) => _repository.PutAsync(request); } } From 67e3d35f6958160b350e091e69fcbdf837061641 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Tue, 10 Oct 2017 09:35:24 -0700 Subject: [PATCH 16/39] Made Redis configuration optional; SignalR will start without Redis if Redis config is unavailable. --- .../Initialization/ServicesStartup.cs | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index fa39fbf..8e3941b 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Net; using System.Reflection; using System.Runtime.Loader; +using Sia.Shared.Validation; namespace Sia.Gateway.Initialization { @@ -126,13 +127,36 @@ namespace Sia.Gateway.Initialization services.AddSession(); services.AddCors(); services.AddSockets(); - services.AddSignalR().AddRedis(redisOptions => - { - redisOptions.Options.EndPoints.Add(config["Redis:CacheEndpoint"]); - redisOptions.Options.Ssl = true; - redisOptions.Options.Password = config["Redis:Password"]; - }); + services.AddSignalR(config); services.AddScoped(); } - } + + private static IServiceCollection AddSignalR(this IServiceCollection services, IConfigurationRoot config) + { + var signalRBuilder = services.AddSignalR(); + if (config.TryGetConfigValue("Redis:CacheEndpoint", out string cacheEndpoint) + && config.TryGetConfigValue("Redis:Password", out string cachePassword)) + { + signalRBuilder.AddRedis(redisOptions => + { + redisOptions.Options.EndPoints.Add(cacheEndpoint); + redisOptions.Options.Ssl = true; + redisOptions.Options.Password = cachePassword; + }); + } + + return services; + } + + + + private static bool TryGetConfigValue(this IConfigurationRoot config, string configName, out string configValue) + { + ThrowIf.NullOrWhiteSpace(configName, nameof(configName)); + + configValue = config[configName]; + + return !string.IsNullOrEmpty(configValue); + } +} } \ No newline at end of file From ec93521793e741ce0f34771b4f2c32392d1ee324 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 11 Oct 2017 09:35:37 -0700 Subject: [PATCH 17/39] Fixed issue where base type was not declared as abstract. --- src/Sia.Gateway/Requests/PutHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sia.Gateway/Requests/PutHandler.cs b/src/Sia.Gateway/Requests/PutHandler.cs index d014670..b76c7fc 100644 --- a/src/Sia.Gateway/Requests/PutHandler.cs +++ b/src/Sia.Gateway/Requests/PutHandler.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Sia.Gateway.Requests { - public class PutHandler + public abstract class PutHandler : IAsyncRequestHandler where TRequest : IRequest where TRepository : IPut From f87f98b34cee5b77f21cbb859a61cda36ea7a119 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 11 Oct 2017 09:39:23 -0700 Subject: [PATCH 18/39] Removed unused code --- src/Sia.Shared/Authentication/AzureSecretVault.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Sia.Shared/Authentication/AzureSecretVault.cs b/src/Sia.Shared/Authentication/AzureSecretVault.cs index 96410d3..9fc56cf 100644 --- a/src/Sia.Shared/Authentication/AzureSecretVault.cs +++ b/src/Sia.Shared/Authentication/AzureSecretVault.cs @@ -19,15 +19,7 @@ namespace Sia.Shared.Authentication private const string _secretsEndpoint = "/secrets/"; private const string _keysEndpoint = "/keys/"; private const string _certificatesEndpoint = "/certificates/"; - /* - string vaultNameKey = "KeyVault:VaultName", - string clientIdKey = "ClientId", - string clientSecretKey = "ClientSecret" - _clientId = configuration[clientIdKey]; - _secret = configuration[clientSecretKey]; - _vault = String.Format(secretUriBase, configuration[vaultNameKey]); - */ public AzureSecretVault(KeyVaultConfiguration configuration) { _config = ThrowIf.Null(configuration, nameof(configuration)); From a296ad27dd9c14f5065e1fb9f89a32c5020a5863 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 11 Oct 2017 14:07:17 -0700 Subject: [PATCH 19/39] Fixed errors introduced by merge --- .../Initialization/ServicesStartup.cs | 17 +++-------------- src/Sia.Gateway/Requests/Events/GetEvent.cs | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index d332a0a..f4aa093 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -22,7 +22,6 @@ using System.Collections.Generic; using System.Net; using System.Reflection; using System.Runtime.Loader; -using Sia.Shared.Validation; namespace Sia.Gateway.Initialization { @@ -43,7 +42,6 @@ namespace Sia.Gateway.Initialization services.AddScoped(); services.AddSingleton(i => config); - services.AddSingleton(i => incidentAuthConfig); } private static void AddTicketingConnector(IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config) @@ -64,7 +62,7 @@ namespace Sia.Gateway.Initialization services.AddNoTicketingSystem(); } - private static bool TryGetConfigValue(IConfigurationRoot config, string configName, out string configValue) + private static bool TryGetConfigValue(this IConfigurationRoot config, string configName, out string configValue) { ThrowIf.NullOrWhiteSpace(configName, nameof(configName)); configValue = config[configName]; @@ -169,15 +167,6 @@ namespace Sia.Gateway.Initialization return services; } - - - private static bool TryGetConfigValue(this IConfigurationRoot config, string configName, out string configValue) - { - ThrowIf.NullOrWhiteSpace(configName, nameof(configName)); - - configValue = config[configName]; - - return !string.IsNullOrEmpty(configValue); - } -} + + } } \ No newline at end of file diff --git a/src/Sia.Gateway/Requests/Events/GetEvent.cs b/src/Sia.Gateway/Requests/Events/GetEvent.cs index d6912dd..0b2e012 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvent.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvent.cs @@ -21,7 +21,7 @@ namespace Sia.Gateway.Requests } public class GetEventHandler : EventHandler { - protected GetEventHandler(IEventRepository eventRepository) + public GetEventHandler(IEventRepository eventRepository) : base(eventRepository) { } From daa086a66005dacddb160396782566f3e506f170 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 11 Oct 2017 14:55:55 -0700 Subject: [PATCH 20/39] Fixed test build --- test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs b/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs index 54f5019..ffcdd80 100644 --- a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs +++ b/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; +using Sia.Gateway.Protocol; namespace Sia.Gateway.Tests.TestDoubles { @@ -32,7 +33,9 @@ namespace Sia.Gateway.Tests.TestDoubles { return Task.FromResult(_events.First(ev => ev.Id == id && ev.IncidentId == incidentId)); } - + + public Task> GetEventsAsync(long incidentId, PaginationMetadata pagination, AuthenticatedUserContext userContext) => throw new System.NotImplementedException(); + public Task PostEvent(long incidentId, NewEvent newEvent, AuthenticatedUserContext userContext) { return Task.FromResult(Mapper.Map(newEvent, new Event())); From 64b2e3d7d3776eb304d959fce225c0f48878fd82 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 11 Oct 2017 15:06:53 -0700 Subject: [PATCH 21/39] Removed unnecessary abstractions --- .../Requests/Engagements/GetEngagement.cs | 8 ------ .../Requests/Engagements/PostEngagement.cs | 11 ++------ .../Requests/Engagements/PutEngagement.cs | 8 ------ src/Sia.Gateway/Requests/Events/GetEvent.cs | 9 ------- src/Sia.Gateway/Requests/Events/GetEvents.cs | 9 ------- src/Sia.Gateway/Requests/Events/PostEvent.cs | 8 ------ src/Sia.Gateway/Requests/GetHandler.cs | 25 ------------------- src/Sia.Gateway/Requests/GetManyHandler.cs | 25 ------------------- .../Requests/Incidents/GetIncident.cs | 8 ------ .../Requests/Incidents/GetIncidents.cs | 9 ------- .../Incidents/GetIncidentsByTicket.cs | 9 ------- .../Requests/Incidents/PostIncident.cs | 9 ------- src/Sia.Gateway/Requests/PostHandler.cs | 25 ------------------- src/Sia.Gateway/Requests/PutHandler.cs | 25 ------------------- .../IEngagementRepository.cs | 15 ++++++----- .../ServiceRepositories/IEventRepository.cs | 22 +++++++--------- .../IIncidentRepository.cs | 20 +++++++-------- .../ServiceRepositories/Operations/IGet.cs | 12 --------- .../Operations/IGetMany.cs | 12 --------- .../ServiceRepositories/Operations/IPost.cs | 12 --------- .../ServiceRepositories/Operations/IPut.cs | 12 --------- .../TestDoubles/StubEventRepository.cs | 18 +++++-------- 22 files changed, 33 insertions(+), 278 deletions(-) delete mode 100644 src/Sia.Gateway/Requests/GetHandler.cs delete mode 100644 src/Sia.Gateway/Requests/GetManyHandler.cs delete mode 100644 src/Sia.Gateway/Requests/PostHandler.cs delete mode 100644 src/Sia.Gateway/Requests/PutHandler.cs delete mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs delete mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs delete mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs delete mode 100644 src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs diff --git a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs index 80c4041..4314011e 100644 --- a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs @@ -17,12 +17,4 @@ namespace Sia.Gateway.Requests public long Id { get; } public long IncidentId { get; } } - public class GetEngagementHandler - : GetHandler - { - public GetEngagementHandler(IEngagementRepository repository) - : base(repository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs index 93d44fb..a276acb 100644 --- a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs @@ -4,6 +4,7 @@ using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; using Sia.Gateway.ServiceRepositories; using System.Threading.Tasks; + namespace Sia.Gateway.Requests { public class PostEngagementRequest : AuthenticatedRequest, IRequest @@ -18,12 +19,4 @@ namespace Sia.Gateway.Requests public NewEngagement NewEngagement { get; } public long IncidentId { get; } } - public class PostEngagementHandler - : PostHandler - { - public PostEngagementHandler(IEngagementRepository repository) - : base(repository) - { - } - } -}; +} diff --git a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs index 8231529..b47b2d4 100644 --- a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs @@ -20,12 +20,4 @@ namespace Sia.Gateway.Requests public long EngagementId { get; } public long IncidentId { get; } } - public class PutEngagementHandler - : PutHandler - { - public PutEngagementHandler(IEngagementRepository repository) - : base(repository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/Events/GetEvent.cs b/src/Sia.Gateway/Requests/Events/GetEvent.cs index 5003bee..c186ef1 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvent.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvent.cs @@ -19,13 +19,4 @@ namespace Sia.Gateway.Requests public long Id { get; } public long IncidentId { get; } } - public class GetEventHandler - : GetHandler - { - public GetEventHandler(IEventRepository eventRepository) - : base(eventRepository) - { - } - } - } diff --git a/src/Sia.Gateway/Requests/Events/GetEvents.cs b/src/Sia.Gateway/Requests/Events/GetEvents.cs index 40d3294..4df4bad 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvents.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvents.cs @@ -22,13 +22,4 @@ namespace Sia.Gateway.Requests.Events public long IncidentId { get; } public PaginationMetadata Pagination { get; } } - - public class GetEventsHandler - : GetManyHandler - { - public GetEventsHandler(IEventRepository eventRepository) - : base(eventRepository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/Events/PostEvent.cs b/src/Sia.Gateway/Requests/Events/PostEvent.cs index 262f233..97a28ba 100644 --- a/src/Sia.Gateway/Requests/Events/PostEvent.cs +++ b/src/Sia.Gateway/Requests/Events/PostEvent.cs @@ -19,12 +19,4 @@ namespace Sia.Gateway.Requests public NewEvent NewEvent { get; } public long IncidentId { get; } } - public class PostEventHandler - : PostHandler - { - public PostEventHandler(IEventRepository repository) - : base(repository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/GetHandler.cs b/src/Sia.Gateway/Requests/GetHandler.cs deleted file mode 100644 index 425c7b1..0000000 --- a/src/Sia.Gateway/Requests/GetHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MediatR; -using Sia.Gateway.ServiceRepositories.Operations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.Requests -{ - public abstract class GetHandler - : IAsyncRequestHandler - where TRequest : IRequest - where TRepository : IGet - { - protected GetHandler(TRepository repository) - { - _repository = repository; - } - - protected TRepository _repository { get; } - - public virtual Task Handle(TRequest request) - => _repository.GetAsync(request); - } -} diff --git a/src/Sia.Gateway/Requests/GetManyHandler.cs b/src/Sia.Gateway/Requests/GetManyHandler.cs deleted file mode 100644 index 6546799..0000000 --- a/src/Sia.Gateway/Requests/GetManyHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MediatR; -using Sia.Gateway.ServiceRepositories.Operations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.Requests -{ - public abstract class GetManyHandler - : IAsyncRequestHandler> - where TRequest : IRequest> - where TRepository : IGetMany - { - protected GetManyHandler(TRepository repository) - { - _repository = repository; - } - - protected TRepository _repository { get; } - - public virtual Task> Handle(TRequest request) - => _repository.GetManyAsync(request); - } -} diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs index 45db527..8b51e9f 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs @@ -15,12 +15,4 @@ namespace Sia.Gateway.Requests } public long Id { get; } } - public class GetIncidentHandler : - GetHandler - { - public GetIncidentHandler(IIncidentRepository repository) - : base(repository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs index 21ac145..ceeafe5 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs @@ -13,13 +13,4 @@ namespace Sia.Gateway.Requests { } } - - public class GetIncidentsHandler : - GetManyHandler - { - public GetIncidentsHandler(IIncidentRepository repository) - : base(repository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs index 6b64471..b8395be 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs @@ -16,13 +16,4 @@ namespace Sia.Gateway.Requests TicketId = ticketId; } } - - public class GetIncidentsByTicketHandler - : GetManyHandler - { - public GetIncidentsByTicketHandler(IIncidentRepository repository) - : base(repository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs index 0cca9eb..14ef69a 100644 --- a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs @@ -18,13 +18,4 @@ namespace Sia.Gateway.Requests public NewIncident Incident { get; private set; } } - - public class PostIncidentHandler - : PostHandler - { - public PostIncidentHandler(IIncidentRepository repository) - : base(repository) - { - } - } } diff --git a/src/Sia.Gateway/Requests/PostHandler.cs b/src/Sia.Gateway/Requests/PostHandler.cs deleted file mode 100644 index ee94e89..0000000 --- a/src/Sia.Gateway/Requests/PostHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MediatR; -using Sia.Gateway.ServiceRepositories.Operations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.Requests -{ - public abstract class PostHandler - : IAsyncRequestHandler - where TRequest : IRequest - where TRepository : IPost - { - protected PostHandler(TRepository repository) - { - _repository = repository; - } - - protected TRepository _repository { get; } - - public virtual Task Handle(TRequest request) - => _repository.PostAsync(request); - } -} diff --git a/src/Sia.Gateway/Requests/PutHandler.cs b/src/Sia.Gateway/Requests/PutHandler.cs deleted file mode 100644 index b76c7fc..0000000 --- a/src/Sia.Gateway/Requests/PutHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using MediatR; -using Sia.Gateway.ServiceRepositories.Operations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.Requests -{ - public abstract class PutHandler - : IAsyncRequestHandler - where TRequest : IRequest - where TRepository : IPut - { - protected PutHandler(TRepository repository) - { - _repository = repository; - } - - protected TRepository _repository { get; } - - public virtual Task Handle(TRequest request) - => _repository.PutAsync(request); - } -} diff --git a/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs b/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs index 4b4969b..9a78567 100644 --- a/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs @@ -1,11 +1,10 @@ using AutoMapper; +using MediatR; using Microsoft.EntityFrameworkCore; using Sia.Data.Incidents; using Sia.Domain; using Sia.Domain.ApiModels; -using Sia.Gateway.Authentication; using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories.Operations; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -13,9 +12,9 @@ using System.Threading.Tasks; namespace Sia.Gateway.ServiceRepositories { public interface IEngagementRepository - :IGet, - IPost, - IPut + :IAsyncRequestHandler, + IAsyncRequestHandler, + IAsyncRequestHandler { } @@ -28,7 +27,7 @@ namespace Sia.Gateway.ServiceRepositories _context = context; } - public async Task GetAsync(GetEngagementRequest request) + public async Task Handle(GetEngagementRequest request) { var EngagementRecord = await _context.Engagements .Include(en => en.Participant) @@ -38,7 +37,7 @@ namespace Sia.Gateway.ServiceRepositories return Mapper.Map(EngagementRecord); } - public async Task PostAsync(PostEngagementRequest request) + public async Task Handle (PostEngagementRequest request) { if (request.NewEngagement == null) throw new ArgumentNullException(nameof(request.NewEngagement)); @@ -57,7 +56,7 @@ namespace Sia.Gateway.ServiceRepositories return Mapper.Map(dataEngagement); } - public async Task PutAsync(PutEngagementRequest request) + public async Task Handle(PutEngagementRequest request) { if (request.UpdateEngagement == null) throw new ArgumentNullException(nameof(UpdateEngagement)); var existingRecord = await _context.Engagements diff --git a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs b/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs index 21fd660..e9af5d8 100644 --- a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs @@ -1,14 +1,11 @@ using AutoMapper; using AutoMapper.QueryableExtensions; +using MediatR; using Microsoft.EntityFrameworkCore; using Sia.Data.Incidents; using Sia.Domain; -using Sia.Domain.ApiModels; -using Sia.Gateway.Authentication; -using Sia.Gateway.Protocol; using Sia.Gateway.Requests; using Sia.Gateway.Requests.Events; -using Sia.Gateway.ServiceRepositories.Operations; using System; using System.Collections.Generic; using System.Linq; @@ -17,9 +14,9 @@ using System.Threading.Tasks; namespace Sia.Gateway.ServiceRepositories { public interface IEventRepository - : IGet, - IPost, - IGetMany + : IAsyncRequestHandler, + IAsyncRequestHandler, + IAsyncRequestHandler> { } @@ -33,7 +30,7 @@ namespace Sia.Gateway.ServiceRepositories _context = context; } - public async Task GetAsync(GetEventRequest request) + public async Task Handle(GetEventRequest request) { var eventRecord = await _context.Events.FirstOrDefaultAsync(ev => ev.IncidentId == request.IncidentId && ev.Id == request.Id); if (eventRecord == null) throw new KeyNotFoundException(); @@ -41,16 +38,15 @@ namespace Sia.Gateway.ServiceRepositories return Mapper.Map(eventRecord); } - public async Task> GetManyAsync(GetEventsRequest request) - { - return await _context.Events + public async Task> Handle(GetEventsRequest request) + => await _context.Events .Where(ev => ev.IncidentId == request.IncidentId) .WithPagination(request.Pagination) .ProjectTo() .ToListAsync(); - } + - public async Task PostAsync(PostEventRequest request) + public async Task Handle(PostEventRequest request) { if (request.NewEvent == null) throw new ArgumentNullException(nameof(request.NewEvent)); diff --git a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs b/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs index a589143..67e75e2 100644 --- a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs @@ -1,13 +1,11 @@ using AutoMapper; using AutoMapper.QueryableExtensions; +using MediatR; using Microsoft.EntityFrameworkCore; using Sia.Connectors.Tickets; using Sia.Data.Incidents; using Sia.Domain; -using Sia.Domain.ApiModels; -using Sia.Gateway.Authentication; using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories.Operations; using Sia.Shared.Exceptions; using System; using System.Collections.Generic; @@ -17,10 +15,10 @@ using System.Threading.Tasks; namespace Sia.Gateway.ServiceRepositories { public interface IIncidentRepository - : IGet, - IGetMany, - IGetMany, - IPost + : IAsyncRequestHandler, + IAsyncRequestHandler>, + IAsyncRequestHandler>, + IAsyncRequestHandler { } @@ -35,7 +33,7 @@ namespace Sia.Gateway.ServiceRepositories _connector = connector; } - public async Task GetAsync(GetIncidentRequest getIncident) + public async Task Handle(GetIncidentRequest getIncident) { var incidentRecord = await _context.Incidents.WithEagerLoading().FirstOrDefaultAsync(cr => cr.Id == getIncident.Id); if (incidentRecord == null) throw new KeyNotFoundException(); @@ -45,7 +43,7 @@ namespace Sia.Gateway.ServiceRepositories return _connector.Converter.AssembleIncident(incidentRecord, ticket); } - public async Task> GetManyAsync(GetIncidentsRequest request) + public async Task> Handle(GetIncidentsRequest request) { var incidentRecords = await _context.Incidents .WithEagerLoading() @@ -54,7 +52,7 @@ namespace Sia.Gateway.ServiceRepositories return incidentRecords; } - public async Task> GetManyAsync(GetIncidentsByTicketRequest request) + public async Task> Handle(GetIncidentsByTicketRequest request) { var incidentRecords = await _context.Incidents .WithEagerLoading() @@ -64,7 +62,7 @@ namespace Sia.Gateway.ServiceRepositories return incidentRecords; } - public async Task PostAsync(PostIncidentRequest request) + public async Task Handle(PostIncidentRequest request) { if (request.Incident == null) throw new ArgumentNullException(nameof(request.Incident)); if (request.Incident?.PrimaryTicket?.OriginId == null) throw new ConflictException("Please provide a primary incident with a valid originId"); diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs deleted file mode 100644 index 3c44da8..0000000 --- a/src/Sia.Gateway/ServiceRepositories/Operations/IGet.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.ServiceRepositories.Operations -{ - public interface IGet - { - Task GetAsync(TRequest request); - } -} diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs deleted file mode 100644 index affed47..0000000 --- a/src/Sia.Gateway/ServiceRepositories/Operations/IGetMany.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.ServiceRepositories.Operations -{ - public interface IGetMany - { - Task> GetManyAsync(TRequest request); - } -} diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs deleted file mode 100644 index 738f9d7..0000000 --- a/src/Sia.Gateway/ServiceRepositories/Operations/IPost.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.ServiceRepositories.Operations -{ - public interface IPost - { - Task PostAsync(TRequest request); - } -} diff --git a/src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs b/src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs deleted file mode 100644 index e070c50..0000000 --- a/src/Sia.Gateway/ServiceRepositories/Operations/IPut.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.ServiceRepositories.Operations -{ - public interface IPut - { - Task PutAsync(TRequest request); - } -} diff --git a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs b/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs index d92fe26..d071a09 100644 --- a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs +++ b/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs @@ -30,19 +30,13 @@ namespace Sia.Gateway.Tests.TestDoubles public bool IsSuccessStatusCodeToRespondWith { get; set; } public string ContentToRespondWith { get; set; } - public Task GetAsync(GetEventRequest request) - { - return Task.FromResult(_events.First(ev => ev.Id == request.Id && ev.IncidentId == request.IncidentId)); - } + public Task Handle(GetEventRequest request) + => Task.FromResult(_events.First(ev => ev.Id == request.Id && ev.IncidentId == request.IncidentId)); - public Task> GetManyAsync(GetEventsRequest request) - { - return Task.FromResult(_events.AsEnumerable()); - } + public Task> Handle(GetEventsRequest request) + => Task.FromResult(_events.AsEnumerable()); - public Task PostAsync(PostEventRequest request) - { - return Task.FromResult(Mapper.Map(request.NewEvent, new Event())); - } + public Task Handle(PostEventRequest request) + => Task.FromResult(Mapper.Map(request.NewEvent, new Event())); } } From ad26f86c3bb42d7ed090346298674bd7eb01ffaa Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 11 Oct 2017 15:42:00 -0700 Subject: [PATCH 22/39] Added wrapper class to get handler class to instantiate with correct generic type --- .../Initialization/ServicesStartup.cs | 13 ++++---- .../IIncidentRepository.cs | 33 ++++++++++++++++++- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index dab97a8..e205414 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -36,7 +36,7 @@ namespace Sia.Gateway.Initialization if (env.IsDevelopment()) services.AddDbContext(options => options.UseInMemoryDatabase("Live")); if (env.IsStaging()) services.AddDbContext(options => options.UseSqlServer(config.GetConnectionString("incidentStaging"))); - AddTicketingConnector(services, env, config); + services.AddTicketingConnector(env, config); services.AddScoped(); services.AddScoped(); @@ -45,7 +45,7 @@ namespace Sia.Gateway.Initialization services.AddSingleton(i => incidentAuthConfig); } - private static void AddTicketingConnector(IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config) + private static void AddTicketingConnector(this IServiceCollection services, IHostingEnvironment env, IConfigurationRoot config) { if (TryGetConfigValue(config, "Connector:Ticket:Path", out var ticketConnectorAssemblyPath)) { @@ -111,14 +111,12 @@ namespace Sia.Gateway.Initialization private static void AddIncidentClient(this IServiceCollection services, Type ticketType) { var clientType = typeof(IncidentRepository<>).MakeGenericType(new Type[] { ticketType }); - services.AddScoped(typeof(IIncidentRepository), clientType); + services.AddScoped(typeof(IIncidentRepositoryLogic), clientType); + services.AddScoped(); } public static void AddThirdPartyServices(this IServiceCollection services, IConfigurationRoot config) { - //Adds every request type in the Sia.Gateway assembly - services.AddMediatR(typeof(GetIncidentRequest).GetTypeInfo().Assembly); - services.AddSingleton(); services.AddScoped(iFactory => new UrlHelper(iFactory.GetService().ActionContext) @@ -148,6 +146,9 @@ namespace Sia.Gateway.Initialization redisOptions.Options.Password = config["Redis:Password"]; }); services.AddScoped(); + + //Adds every request type in the Sia.Gateway assembly + services.AddMediatR(typeof(GetIncidentRequest).GetTypeInfo().Assembly); } } } \ No newline at end of file diff --git a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs b/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs index 67e75e2..24ab72b 100644 --- a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs @@ -22,7 +22,7 @@ namespace Sia.Gateway.ServiceRepositories { } - public class IncidentRepository : IIncidentRepository + public class IncidentRepository : IIncidentRepositoryLogic { private readonly IncidentContext _context; private readonly Connector _connector; @@ -74,6 +74,37 @@ namespace Sia.Gateway.ServiceRepositories return Mapper.Map(dataIncident); } + } + //Why does this exist? + //Purely because I haven't been able to get Mediatr to work with generics + public interface IIncidentRepositoryLogic + { + Task Handle(GetIncidentRequest getIncident); + Task> Handle(GetIncidentsRequest request); + Task> Handle(GetIncidentsByTicketRequest request); + Task Handle(PostIncidentRequest request); + } + + public class IncidentRepositoryWrapper : IIncidentRepository + { + private readonly IIncidentRepositoryLogic _actualIncidentRepository; + + public IncidentRepositoryWrapper(IIncidentRepositoryLogic actualIncidentRepository) + { + _actualIncidentRepository = actualIncidentRepository; + } + + public Task Handle(GetIncidentRequest message) + => _actualIncidentRepository.Handle(message); + + public Task> Handle(GetIncidentsRequest message) + => _actualIncidentRepository.Handle(message); + + public Task> Handle(GetIncidentsByTicketRequest message) + => _actualIncidentRepository.Handle(message); + + public Task Handle(PostIncidentRequest message) + => _actualIncidentRepository.Handle(message); } } From c77c77361b385d1b393e503b89b652b7c16ee58d Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Thu, 12 Oct 2017 14:17:20 -0700 Subject: [PATCH 23/39] Added fix that allows NewIncident to correctly save primary ticket to database --- src/Sia.Domain.ApiModels/NewIncident.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Sia.Domain.ApiModels/NewIncident.cs b/src/Sia.Domain.ApiModels/NewIncident.cs index a0f16ec..48b1474 100644 --- a/src/Sia.Domain.ApiModels/NewIncident.cs +++ b/src/Sia.Domain.ApiModels/NewIncident.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; namespace Sia.Domain.ApiModels { @@ -8,7 +9,26 @@ namespace Sia.Domain.ApiModels [Required] public string Title { get; set; } [Required] - public Ticket PrimaryTicket { get; set; } + public Ticket PrimaryTicket + { + get + { + return Tickets.FirstOrDefault(ticket => ticket.IsPrimary); + } + set + { + if (Tickets == null) Tickets = new List(); + foreach (var ticket in Tickets.Where(ticket => ticket.IsPrimary)) + { + ticket.IsPrimary = false; + } + + if (value == null) return; + + if (!Tickets.Contains(value)) Tickets.Add(value); + value.IsPrimary = true; + } + } public IList Tickets { get; set; } = new List(); public IList Events { get; set; } From 708b5cd27a7c568dea39aa50526e9bc14e15bd34 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Thu, 12 Oct 2017 14:18:20 -0700 Subject: [PATCH 24/39] Changed GetEventsAsync endpoint to correctly return only events associated with the incidentId provided --- src/Sia.Gateway/ServiceRepositories/IEventRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs b/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs index cf126d9..1870c43 100644 --- a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs +++ b/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs @@ -41,6 +41,7 @@ namespace Sia.Gateway.ServiceRepositories public async Task> GetEventsAsync(long incidentId, PaginationMetadata pagination, AuthenticatedUserContext userContext) { return await _context.Events + .Where(ev => ev.IncidentId == incidentId) .WithPagination(pagination) .ProjectTo() .ToListAsync(); From 55487b2a34385b5f16313dde80a1ff264fbd95b3 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 16 Oct 2017 12:51:35 -0700 Subject: [PATCH 25/39] Match API signature --- src/Sia.Gateway/Controllers/EngagementsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sia.Gateway/Controllers/EngagementsController.cs b/src/Sia.Gateway/Controllers/EngagementsController.cs index d6b764f..853c271 100644 --- a/src/Sia.Gateway/Controllers/EngagementsController.cs +++ b/src/Sia.Gateway/Controllers/EngagementsController.cs @@ -36,7 +36,7 @@ namespace Sia.Gateway.Controllers { return NotFound(notFoundMessage); } - return Created($"api/incidents/{result.IncidentId}/engagements/{result.Id}", result); + return Created($"incidents/{result.IncidentId}/engagements/{result.Id}", result); } [HttpPut("{engagementId}")] From 01d95fe564d02ea2b1e20fead92fd62f0425225a Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 16 Oct 2017 15:56:14 -0700 Subject: [PATCH 26/39] Added dynamic data field and automapper configuration for converting between data storageobjects with a backing string field (for a JSON column in the database) and DTOs with a true dynamic value. --- src/Sia.Data.Incident/Models/Event.cs | 8 +++- .../Sia.Data.Incidents.csproj | 5 ++- src/Sia.Domain.ApiModels/NewEvent.cs | 6 ++- src/Sia.Domain/Event.cs | 6 ++- src/Sia.Domain/Sia.Domain.csproj | 5 ++- .../Initialization/AutoMapperStartup.cs | 35 ++++++++++------ src/Sia.Shared/Data/DynamicResolver.cs | 42 +++++++++++++++++++ src/Sia.Shared/Sia.Shared.csproj | 1 + 8 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 src/Sia.Shared/Data/DynamicResolver.cs diff --git a/src/Sia.Data.Incident/Models/Event.cs b/src/Sia.Data.Incident/Models/Event.cs index 62f8d59..168f1f7 100644 --- a/src/Sia.Data.Incident/Models/Event.cs +++ b/src/Sia.Data.Incident/Models/Event.cs @@ -1,14 +1,18 @@ -using Sia.Shared.Data; +using Microsoft.EntityFrameworkCore; +using Sia.Shared.Data; using System; +using System.ComponentModel.DataAnnotations.Schema; namespace Sia.Data.Incidents.Models { - public class Event : IEntity + public class Event : IEntity, IDynamicDataStorage { public long Id { get; set; } public long? IncidentId { get; set; } public long EventTypeId { get; set; } public DateTime Occurred { get; set; } public DateTime EventFired { get; set; } + [Column(TypeName = "varchar(max)")] + public string Data { get; set; } } } diff --git a/src/Sia.Data.Incident/Sia.Data.Incidents.csproj b/src/Sia.Data.Incident/Sia.Data.Incidents.csproj index b05b933..6427b11 100644 --- a/src/Sia.Data.Incident/Sia.Data.Incidents.csproj +++ b/src/Sia.Data.Incident/Sia.Data.Incidents.csproj @@ -16,10 +16,13 @@ - + + + + \ No newline at end of file diff --git a/src/Sia.Domain.ApiModels/NewEvent.cs b/src/Sia.Domain.ApiModels/NewEvent.cs index e50ef47..7154ab4 100644 --- a/src/Sia.Domain.ApiModels/NewEvent.cs +++ b/src/Sia.Domain.ApiModels/NewEvent.cs @@ -1,9 +1,12 @@ -using System; +using Sia.Shared.Data; +using System; using System.ComponentModel.DataAnnotations; +using System.Dynamic; namespace Sia.Domain.ApiModels { public class NewEvent + :IDynamicDataSource { [Required] public long? EventTypeId { get; set; } @@ -11,5 +14,6 @@ namespace Sia.Domain.ApiModels public DateTime? Occurred { get; set; } [Required] public DateTime? EventFired { get; set; } + public dynamic Data { get; set; } = new ExpandoObject(); } } diff --git a/src/Sia.Domain/Event.cs b/src/Sia.Domain/Event.cs index 74dca78..0b8eff7 100644 --- a/src/Sia.Domain/Event.cs +++ b/src/Sia.Domain/Event.cs @@ -2,16 +2,20 @@ using Sia.Shared.Data; using System; using System.Collections.Generic; +using System.Dynamic; using System.Text; namespace Sia.Domain { - public class Event : IEntity + public class Event : IEntity, IDynamicDataSource { public long Id { get; set; } public long? IncidentId { get; set; } public long EventTypeId { get; set; } public DateTime Occurred { get; set; } public DateTime EventFired { get; set; } + public dynamic Data { get; set; } = new ExpandoObject(); + + } } diff --git a/src/Sia.Domain/Sia.Domain.csproj b/src/Sia.Domain/Sia.Domain.csproj index b6c7dc2..701b38f 100644 --- a/src/Sia.Domain/Sia.Domain.csproj +++ b/src/Sia.Domain/Sia.Domain.csproj @@ -15,7 +15,10 @@ - + + + + \ No newline at end of file diff --git a/src/Sia.Gateway/Initialization/AutoMapperStartup.cs b/src/Sia.Gateway/Initialization/AutoMapperStartup.cs index a28e68d..3a2f161 100644 --- a/src/Sia.Gateway/Initialization/AutoMapperStartup.cs +++ b/src/Sia.Gateway/Initialization/AutoMapperStartup.cs @@ -2,6 +2,7 @@ using AutoMapper.EquivalencyExpression; using Sia.Domain; using Sia.Domain.ApiModels; +using Sia.Gateway.Protocol; using Sia.Shared.Data; namespace Sia.Gateway.Initialization @@ -32,25 +33,35 @@ namespace Sia.Gateway.Initialization configuration.CreateMap(); configuration.CreateMap(); - - configuration.CreateMap().EqualityInsertOnly(); - configuration.CreateMap().EqualityById(); - configuration.CreateMap().EqualityById(); + configuration.CreateMap().EqualityInsertOnly() + .ResolveFromDynamic(); + configuration.CreateMap().EqualityById() + .ResolveFromDynamic(); + configuration.CreateMap().EqualityById() + .ResolveToDynamic(); }); } + private static IMappingExpression ResolveFromDynamic(this IMappingExpression mapping) + where TSource: IDynamicDataSource + where TDestination: IDynamicDataStorage + => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); + + + private static IMappingExpression ResolveToDynamic(this IMappingExpression mapping) + where TSource : IDynamicDataStorage + where TDestination : IDynamicDataSource + => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); + + public static IMappingExpression EqualityInsertOnly(this IMappingExpression mappingExpression) where T1 : class - where T2 : class - { - return mappingExpression.EqualityComparison((one, two) => false); - } + where T2 : class + => mappingExpression.EqualityComparison((one, two) => false); public static IMappingExpression EqualityById(this IMappingExpression mappingExpression) where T1 : class, IEntity - where T2 : class, IEntity - { - return mappingExpression.EqualityComparison((one, two) => one.Id == two.Id); - } + where T2 : class, IEntity + => mappingExpression.EqualityComparison((one, two) => one.Id == two.Id); } } diff --git a/src/Sia.Shared/Data/DynamicResolver.cs b/src/Sia.Shared/Data/DynamicResolver.cs new file mode 100644 index 0000000..1ba3f82 --- /dev/null +++ b/src/Sia.Shared/Data/DynamicResolver.cs @@ -0,0 +1,42 @@ +using AutoMapper; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Shared.Data +{ + public interface IDynamicDataStorage + { + string Data { get; set; } + } + + public interface IDynamicDataSource + { + dynamic Data { get; set; } + } + + public class ResolveFromDynamic + : IValueResolver + where TSource: IDynamicDataSource + where TDestination: IDynamicDataStorage + { + public string Resolve(TSource source, TDestination destination, string destMember, ResolutionContext context) + => JsonConvert.SerializeObject(source.Data); + + } + + public class ResolveToDynamic + : IValueResolver + where TSource : IDynamicDataStorage + where TDestination : IDynamicDataSource + { + public object Resolve(TSource source, TDestination destination, object destMember, ResolutionContext context) + { + var result = JsonConvert.DeserializeObject(source.Data); + return result; + } + } +} diff --git a/src/Sia.Shared/Sia.Shared.csproj b/src/Sia.Shared/Sia.Shared.csproj index 6097181..dd8fb51 100644 --- a/src/Sia.Shared/Sia.Shared.csproj +++ b/src/Sia.Shared/Sia.Shared.csproj @@ -14,6 +14,7 @@ + From 40deed01ae26cedc2e8e65ba1db122426ccf7768 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 16 Oct 2017 17:18:46 -0700 Subject: [PATCH 27/39] Added dynamic output formatter, which deserializes dynamic data when that data can't be deserialized as part of an Automapper Map operation (such as collections converted using AutoMapper ProjectTo) --- src/Sia.Domain/Event.cs | 2 - .../Initialization/ServicesStartup.cs | 9 +++- .../Protocol/DynamicOutputFormatter.cs | 53 +++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs diff --git a/src/Sia.Domain/Event.cs b/src/Sia.Domain/Event.cs index 0b8eff7..ba51bdd 100644 --- a/src/Sia.Domain/Event.cs +++ b/src/Sia.Domain/Event.cs @@ -15,7 +15,5 @@ namespace Sia.Domain public DateTime Occurred { get; set; } public DateTime EventFired { get; set; } public dynamic Data { get; set; } = new ExpandoObject(); - - } } diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index f4aa093..bd095ea 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -22,6 +22,8 @@ using System.Collections.Generic; using System.Net; using System.Reflection; using System.Runtime.Loader; +using Sia.Gateway.Protocol; +using System.Buffers; namespace Sia.Gateway.Initialization { @@ -129,7 +131,12 @@ namespace Sia.Gateway.Initialization => new UrlHelper(iFactory.GetService().ActionContext) ); - services.AddMvc(); + services.AddMvc(options => + { + options.OutputFormatters.Insert(0, new DynamicOutputFormatter( + new MvcJsonOptions().SerializerSettings, + ArrayPool.Shared)); + }); services .AddAuthentication(authOptions => { diff --git a/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs b/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs new file mode 100644 index 0000000..63d286d --- /dev/null +++ b/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Mvc.Formatters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using System.Buffers; +using System.Text; +using Sia.Shared.Data; + +namespace Sia.Gateway.Protocol +{ + public class DynamicOutputFormatter : JsonOutputFormatter + { + public DynamicOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool charPool) + : base(serializerSettings, charPool) + { + + } + + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + var dataStream = (IEnumerable)context.Object; + + foreach (var objectToWrite in dataStream) + { + var dynamicData = objectToWrite.Data; + if (dynamicData is string) objectToWrite.Data = Deserialize((string)dynamicData); + } + + return base.WriteResponseBodyAsync(context, selectedEncoding); + } + + private const int NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod = 3; + + protected override bool CanWriteType(Type type) + { + if (!type.IsGenericType) return false; + if (type.GetGenericArguments().Count() != 1) return false; + + var enumIntName = typeof(IEnumerable<>).ToString(); + var enumerableInterface = type.GetInterface(enumIntName + .Substring(0, enumIntName.Length - NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod)); + if (enumerableInterface is null) return false; + + var canWrite = !(type.GetGenericArguments()[0].GetInterface(nameof(IDynamicDataSource)) is null); + return canWrite; + + } + + private object Deserialize(string serializedData) => JsonConvert.DeserializeObject(serializedData); + } +} From 49143c1ff3406205ea3f91a57f17b080db268453 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 16 Oct 2017 17:34:55 -0700 Subject: [PATCH 28/39] Slight cleanup of formatting --- src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs b/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs index 63d286d..80ae11c 100644 --- a/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs +++ b/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs @@ -42,10 +42,8 @@ namespace Sia.Gateway.Protocol var enumerableInterface = type.GetInterface(enumIntName .Substring(0, enumIntName.Length - NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod)); if (enumerableInterface is null) return false; - - var canWrite = !(type.GetGenericArguments()[0].GetInterface(nameof(IDynamicDataSource)) is null); - return canWrite; + return !(type.GetGenericArguments()[0].GetInterface(nameof(IDynamicDataSource)) is null); } private object Deserialize(string serializedData) => JsonConvert.DeserializeObject(serializedData); From efc4ea377a748960af23c3f65a19cd2a5ee2d6e9 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 16 Oct 2017 17:36:23 -0700 Subject: [PATCH 29/39] Minor cleanup of formatting --- src/Sia.Shared/Data/DynamicResolver.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Sia.Shared/Data/DynamicResolver.cs b/src/Sia.Shared/Data/DynamicResolver.cs index 1ba3f82..508a3a2 100644 --- a/src/Sia.Shared/Data/DynamicResolver.cs +++ b/src/Sia.Shared/Data/DynamicResolver.cs @@ -34,9 +34,6 @@ namespace Sia.Shared.Data where TDestination : IDynamicDataSource { public object Resolve(TSource source, TDestination destination, object destMember, ResolutionContext context) - { - var result = JsonConvert.DeserializeObject(source.Data); - return result; - } + => JsonConvert.DeserializeObject(source.Data); } } From 15f11400561ac3d6264bfb2252e5c5bf6fe0e977 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Mon, 16 Oct 2017 17:36:59 -0700 Subject: [PATCH 30/39] Even more formatting --- src/Sia.Shared/Data/DynamicResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Sia.Shared/Data/DynamicResolver.cs b/src/Sia.Shared/Data/DynamicResolver.cs index 508a3a2..779df1d 100644 --- a/src/Sia.Shared/Data/DynamicResolver.cs +++ b/src/Sia.Shared/Data/DynamicResolver.cs @@ -25,7 +25,6 @@ namespace Sia.Shared.Data { public string Resolve(TSource source, TDestination destination, string destMember, ResolutionContext context) => JsonConvert.SerializeObject(source.Data); - } public class ResolveToDynamic From 275630cb3734605d0e251ae703b2e5fd2b321179 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Tue, 17 Oct 2017 15:48:27 -0700 Subject: [PATCH 31/39] Dynamic may not be needed --- src/Sia.Data.Incident/Models/Event.cs | 2 +- src/Sia.Domain.ApiModels/NewEvent.cs | 4 ++-- src/Sia.Domain/Event.cs | 4 ++-- .../Initialization/AutoMapperStartup.cs | 22 +++++++++---------- .../Initialization/ServicesStartup.cs | 2 +- ...> PartialSerializedJsonOutputFormatter.cs} | 12 +++++----- ...amicResolver.cs => PartialJsonResolver.cs} | 20 ++++++++--------- 7 files changed, 33 insertions(+), 33 deletions(-) rename src/Sia.Gateway/Protocol/{DynamicOutputFormatter.cs => PartialSerializedJsonOutputFormatter.cs} (76%) rename src/Sia.Shared/Data/{DynamicResolver.cs => PartialJsonResolver.cs} (58%) diff --git a/src/Sia.Data.Incident/Models/Event.cs b/src/Sia.Data.Incident/Models/Event.cs index 168f1f7..4f3c252 100644 --- a/src/Sia.Data.Incident/Models/Event.cs +++ b/src/Sia.Data.Incident/Models/Event.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Sia.Data.Incidents.Models { - public class Event : IEntity, IDynamicDataStorage + public class Event : IEntity, IHasJsonDataString { public long Id { get; set; } public long? IncidentId { get; set; } diff --git a/src/Sia.Domain.ApiModels/NewEvent.cs b/src/Sia.Domain.ApiModels/NewEvent.cs index 7154ab4..df86dcb 100644 --- a/src/Sia.Domain.ApiModels/NewEvent.cs +++ b/src/Sia.Domain.ApiModels/NewEvent.cs @@ -6,7 +6,7 @@ using System.Dynamic; namespace Sia.Domain.ApiModels { public class NewEvent - :IDynamicDataSource + :IHasJsonDataObject { [Required] public long? EventTypeId { get; set; } @@ -14,6 +14,6 @@ namespace Sia.Domain.ApiModels public DateTime? Occurred { get; set; } [Required] public DateTime? EventFired { get; set; } - public dynamic Data { get; set; } = new ExpandoObject(); + public object Data { get; set; } } } diff --git a/src/Sia.Domain/Event.cs b/src/Sia.Domain/Event.cs index ba51bdd..3a2bb40 100644 --- a/src/Sia.Domain/Event.cs +++ b/src/Sia.Domain/Event.cs @@ -7,13 +7,13 @@ using System.Text; namespace Sia.Domain { - public class Event : IEntity, IDynamicDataSource + public class Event : IEntity, IHasJsonDataObject { public long Id { get; set; } public long? IncidentId { get; set; } public long EventTypeId { get; set; } public DateTime Occurred { get; set; } public DateTime EventFired { get; set; } - public dynamic Data { get; set; } = new ExpandoObject(); + public object Data { get; set; } } } diff --git a/src/Sia.Gateway/Initialization/AutoMapperStartup.cs b/src/Sia.Gateway/Initialization/AutoMapperStartup.cs index 3a2f161..330bc2f 100644 --- a/src/Sia.Gateway/Initialization/AutoMapperStartup.cs +++ b/src/Sia.Gateway/Initialization/AutoMapperStartup.cs @@ -34,24 +34,24 @@ namespace Sia.Gateway.Initialization configuration.CreateMap(); configuration.CreateMap(); configuration.CreateMap().EqualityInsertOnly() - .ResolveFromDynamic(); + .UseResolveJsonToString(); configuration.CreateMap().EqualityById() - .ResolveFromDynamic(); + .UseResolveJsonToString(); configuration.CreateMap().EqualityById() - .ResolveToDynamic(); + .UseResolveStringToJson(); }); } - private static IMappingExpression ResolveFromDynamic(this IMappingExpression mapping) - where TSource: IDynamicDataSource - where TDestination: IDynamicDataStorage - => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); + private static IMappingExpression UseResolveJsonToString(this IMappingExpression mapping) + where TSource: IHasJsonDataObject + where TDestination: IHasJsonDataString + => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); - private static IMappingExpression ResolveToDynamic(this IMappingExpression mapping) - where TSource : IDynamicDataStorage - where TDestination : IDynamicDataSource - => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); + private static IMappingExpression UseResolveStringToJson(this IMappingExpression mapping) + where TSource : IHasJsonDataString + where TDestination : IHasJsonDataObject + => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); public static IMappingExpression EqualityInsertOnly(this IMappingExpression mappingExpression) diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index bd095ea..5f926cb 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -133,7 +133,7 @@ namespace Sia.Gateway.Initialization services.AddMvc(options => { - options.OutputFormatters.Insert(0, new DynamicOutputFormatter( + options.OutputFormatters.Insert(0, new PartialSerializedJsonOutputFormatter( new MvcJsonOptions().SerializerSettings, ArrayPool.Shared)); }); diff --git a/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs b/src/Sia.Gateway/Protocol/PartialSerializedJsonOutputFormatter.cs similarity index 76% rename from src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs rename to src/Sia.Gateway/Protocol/PartialSerializedJsonOutputFormatter.cs index 80ae11c..36b9605 100644 --- a/src/Sia.Gateway/Protocol/DynamicOutputFormatter.cs +++ b/src/Sia.Gateway/Protocol/PartialSerializedJsonOutputFormatter.cs @@ -10,9 +10,9 @@ using Sia.Shared.Data; namespace Sia.Gateway.Protocol { - public class DynamicOutputFormatter : JsonOutputFormatter + public class PartialSerializedJsonOutputFormatter : JsonOutputFormatter { - public DynamicOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool charPool) + public PartialSerializedJsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool charPool) : base(serializerSettings, charPool) { @@ -20,12 +20,12 @@ namespace Sia.Gateway.Protocol public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { - var dataStream = (IEnumerable)context.Object; + var dataStream = (IEnumerable)context.Object; foreach (var objectToWrite in dataStream) { - var dynamicData = objectToWrite.Data; - if (dynamicData is string) objectToWrite.Data = Deserialize((string)dynamicData); + var jsonData = objectToWrite.Data; + if (jsonData is string) objectToWrite.Data = Deserialize((string)jsonData); } return base.WriteResponseBodyAsync(context, selectedEncoding); @@ -43,7 +43,7 @@ namespace Sia.Gateway.Protocol .Substring(0, enumIntName.Length - NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod)); if (enumerableInterface is null) return false; - return !(type.GetGenericArguments()[0].GetInterface(nameof(IDynamicDataSource)) is null); + return !(type.GetGenericArguments()[0].GetInterface(nameof(IHasJsonDataObject)) is null); } private object Deserialize(string serializedData) => JsonConvert.DeserializeObject(serializedData); diff --git a/src/Sia.Shared/Data/DynamicResolver.cs b/src/Sia.Shared/Data/PartialJsonResolver.cs similarity index 58% rename from src/Sia.Shared/Data/DynamicResolver.cs rename to src/Sia.Shared/Data/PartialJsonResolver.cs index 779df1d..ed40f3c 100644 --- a/src/Sia.Shared/Data/DynamicResolver.cs +++ b/src/Sia.Shared/Data/PartialJsonResolver.cs @@ -8,31 +8,31 @@ using System.Threading.Tasks; namespace Sia.Shared.Data { - public interface IDynamicDataStorage + public interface IHasJsonDataString { string Data { get; set; } } - public interface IDynamicDataSource + public interface IHasJsonDataObject { - dynamic Data { get; set; } + object Data { get; set; } } - public class ResolveFromDynamic + public class ResolveJsonToString : IValueResolver - where TSource: IDynamicDataSource - where TDestination: IDynamicDataStorage + where TSource: IHasJsonDataObject + where TDestination: IHasJsonDataString { public string Resolve(TSource source, TDestination destination, string destMember, ResolutionContext context) => JsonConvert.SerializeObject(source.Data); } - public class ResolveToDynamic + public class ResolveStringToJson : IValueResolver - where TSource : IDynamicDataStorage - where TDestination : IDynamicDataSource + where TSource : IHasJsonDataString + where TDestination : IHasJsonDataObject { public object Resolve(TSource source, TDestination destination, object destMember, ResolutionContext context) - => JsonConvert.DeserializeObject(source.Data); + => JsonConvert.DeserializeObject(source.Data); } } From 4ec522f66acdb7a4d8701c2c9f65e8c5f49e9435 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Tue, 17 Oct 2017 16:41:39 -0700 Subject: [PATCH 32/39] Added first tests for PartialJsonResolver --- Sia.Gateway.sln | 9 ++- .../Data/PartialJsonResolverTests.cs | 74 +++++++++++++++++++ Sia.Shared.Tests/Sia.Shared.Tests.csproj | 17 +++++ .../Sia.Gateway.Tests.csproj | 4 +- 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 Sia.Shared.Tests/Data/PartialJsonResolverTests.cs create mode 100644 Sia.Shared.Tests/Sia.Shared.Tests.csproj diff --git a/Sia.Gateway.sln b/Sia.Gateway.sln index 27c8239..d533356 100644 --- a/Sia.Gateway.sln +++ b/Sia.Gateway.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 +VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sia.Shared", "src\Sia.Shared\Sia.Shared.csproj", "{C4AA9F6D-DFB1-4044-A7CD-FA3B6D5BA6AE}" EndProject @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{931F26AB-5 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sia.Gateway.Tests", "test\Sia.Gateway.Tests\Sia.Gateway.Tests.csproj", "{7FED8930-1B06-4954-8337-C2DEC6697D6F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sia.Shared.Tests", "Sia.Shared.Tests\Sia.Shared.Tests.csproj", "{BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,10 @@ Global {7FED8930-1B06-4954-8337-C2DEC6697D6F}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED8930-1B06-4954-8337-C2DEC6697D6F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7FED8930-1B06-4954-8337-C2DEC6697D6F}.Release|Any CPU.Build.0 = Release|Any CPU + {BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -67,6 +73,7 @@ Global {B92790C5-C4F1-4052-ABA7-68448F28DDB2} = {09E441A6-06F8-457A-981A-394473FB8CD5} {8D465104-1385-4EE4-A1ED-AEE7708A537F} = {09E441A6-06F8-457A-981A-394473FB8CD5} {7FED8930-1B06-4954-8337-C2DEC6697D6F} = {931F26AB-58CD-4667-A281-EEC186F2FB59} + {BF2EE536-9EB2-4E97-8F6A-BDF6571BAA67} = {931F26AB-58CD-4667-A281-EEC186F2FB59} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {05069EBB-9F94-4DAA-A619-C2F496B37BA4} diff --git a/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs new file mode 100644 index 0000000..10b6fa5 --- /dev/null +++ b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs @@ -0,0 +1,74 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; +using Sia.Shared.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sia.Shared.Tests.Data +{ + + + [TestClass] + public class PartialJsonResolverTests + { + [TestMethod] + public void ResolveJsonToString_Method_Resolve_SerializesSourceArgumentObjectToString() + { + var input = new TestHasJsonDataObject + { + Data = new JsonSerializationTestObject() + }; + var expectedResultDataValue = JsonSerializationTestObject.ExpectedSerialization(); + var objectUnderTest = new ResolveJsonToString(); + + + var result = objectUnderTest.Resolve(input, null, null, null); + + + Assert.AreEqual(expectedResultDataValue, result, false); + } + + [TestMethod] + public void ResolveStringToJson_Method_Resolve_SerializesSourceArgumentObjectToString() + { + var expectedResult = new JsonSerializationTestObject(); + var input = new TestHasJsonDataString() + { + Data = JsonSerializationTestObject.ExpectedSerialization() + }; + var objectUnderTest = new ResolveStringToJson(); + + + var result = objectUnderTest.Resolve(input, null, null, null); + + + Assert.AreEqual(expectedResult.a, ExtractPropertyFromResult(result, "a")); + Assert.AreEqual(expectedResult.b, ExtractPropertyFromResult(result, "b")); + } + + private static JToken ExtractPropertyFromResult(object result, string propName) => ((JObject)result).Property(propName).Value; + } + + internal class JsonSerializationTestObject :IEquatable + { + public static string ExpectedSerialization() + => "{\"a\":\"ValueOfA\",\"b\":1}"; + public bool Equals(JsonSerializationTestObject other) + => a == other.a && b == other.b; + + public string a { get; set; } = "ValueOfA"; + public int b { get; set; } = 1; + } + + internal class TestHasJsonDataString : IHasJsonDataString + { + public string Data { get; set; } + } + + internal class TestHasJsonDataObject : IHasJsonDataObject + { + public object Data { get; set; } + } +} diff --git a/Sia.Shared.Tests/Sia.Shared.Tests.csproj b/Sia.Shared.Tests/Sia.Shared.Tests.csproj new file mode 100644 index 0000000..dcd6d7d --- /dev/null +++ b/Sia.Shared.Tests/Sia.Shared.Tests.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + diff --git a/test/Sia.Gateway.Tests/Sia.Gateway.Tests.csproj b/test/Sia.Gateway.Tests/Sia.Gateway.Tests.csproj index 06a9d32..0f3070c 100644 --- a/test/Sia.Gateway.Tests/Sia.Gateway.Tests.csproj +++ b/test/Sia.Gateway.Tests/Sia.Gateway.Tests.csproj @@ -7,8 +7,8 @@ - - + + From a9e270d36185a86665c9e832f7ca4abf80e6ee43 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Tue, 17 Oct 2017 16:47:28 -0700 Subject: [PATCH 33/39] Slight cleanup of structure and naming on test, no expected change to functionality --- Sia.Shared.Tests/Data/PartialJsonResolverTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs index 10b6fa5..4ad4239 100644 --- a/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs +++ b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs @@ -31,13 +31,13 @@ namespace Sia.Shared.Tests.Data } [TestMethod] - public void ResolveStringToJson_Method_Resolve_SerializesSourceArgumentObjectToString() + public void ResolveStringToJson_Method_Resolve_SerializesSourceArgumentStringToObject() { - var expectedResult = new JsonSerializationTestObject(); var input = new TestHasJsonDataString() { Data = JsonSerializationTestObject.ExpectedSerialization() }; + var expectedResult = new JsonSerializationTestObject(); var objectUnderTest = new ResolveStringToJson(); From 51bc8e8175c2b857e16032cc824c17f01260770a Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 18 Oct 2017 10:53:58 -0700 Subject: [PATCH 34/39] Added tests for ThrowIf validation logic --- .../Data/PartialJsonResolverTests.cs | 2 - Sia.Shared.Tests/Validation/ThrowIfTests.cs | 65 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 Sia.Shared.Tests/Validation/ThrowIfTests.cs diff --git a/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs index 4ad4239..7f7132a 100644 --- a/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs +++ b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs @@ -8,8 +8,6 @@ using System.Text; namespace Sia.Shared.Tests.Data { - - [TestClass] public class PartialJsonResolverTests { diff --git a/Sia.Shared.Tests/Validation/ThrowIfTests.cs b/Sia.Shared.Tests/Validation/ThrowIfTests.cs new file mode 100644 index 0000000..7272c13 --- /dev/null +++ b/Sia.Shared.Tests/Validation/ThrowIfTests.cs @@ -0,0 +1,65 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Sia.Shared.Validation; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sia.Shared.Tests.Validation +{ + [TestClass] + public class ThrowIfTests + { + [TestMethod] + public void Null_StaticMethod_WhenObjectIsNotNull_ReturnsObject() + { + var input = new Object(); + + var result = ThrowIf.Null(input, nameof(input)); + + Assert.AreSame(input, result); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Null_StaticMethod_WhenObjectIsNull_ThrowsArgumentNullException() + { + object input = null; + + var result = ThrowIf.Null(input, nameof(input)); + + //expect exception + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NullOrWhiteSpace_StaticMethod_WhenInputIsNull_ThrowsArgumentException() + { + string input = null; + + var result = ThrowIf.NullOrWhiteSpace(input, nameof(input)); + + //Expect exception + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void NullOrWhiteSpace_StaticMethod_WhenInputIsOnlyWhitespace_ThrowsArgumentException() + { + string input = " "; + + var result = ThrowIf.NullOrWhiteSpace(input, nameof(input)); + + //Expect exception + } + + [TestMethod] + public void NullOrWhiteSpace_StaticMethod_WhenInputStringWithAnyNonWhitespace_ReturnsString() + { + string input = " . "; + + var result = ThrowIf.NullOrWhiteSpace(input, nameof(input)); + + Assert.AreSame(input, result); + } + } +} From b4d5561f192dee5fd1c2f22088b50e444b347775 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 18 Oct 2017 11:55:37 -0700 Subject: [PATCH 35/39] Broke IncidentRepository into separate handlers --- .../TicketProxy/Initialization.cs | 6 +- .../TicketProxy/ProxyClient.cs | 6 +- .../TicketProxy/ProxyConnector.cs | 4 +- .../TicketProxy/ProxyConverter.cs | 4 +- .../TicketProxy/Ticket.cs | 2 +- .../Initialization/ServicesStartup.cs | 9 +- .../Requests/Incidents/GetIncident.cs | 54 +++++++++ .../Requests/Incidents/GetIncidents.cs | 27 ++++- .../Incidents/GetIncidentsByTicket.cs | 26 ++++- .../Requests/Incidents/PostIncident.cs | 28 ++++- .../IIncidentRepository.cs | 110 ------------------ 11 files changed, 146 insertions(+), 130 deletions(-) delete mode 100644 src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs diff --git a/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs b/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs index bcc3bfd..bf5eaaf 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/Initialization.cs @@ -22,9 +22,9 @@ namespace Sia.Gateway.Initialization private static IServiceCollection AddProxy(this IServiceCollection services, ProxyConnectionInfo proxyConnection) { return services - .AddScoped, ProxyConverter>() - .AddScoped>(serv => proxyConnection.GetClient()) - .AddScoped, ProxyConnector>(); + .AddScoped, ProxyConverter>() + .AddScoped>(serv => proxyConnection.GetClient()) + .AddScoped, ProxyConnector>(); } } } diff --git a/src/Sia.Connectors.Tickets/TicketProxy/ProxyClient.cs b/src/Sia.Connectors.Tickets/TicketProxy/ProxyClient.cs index 9f32a58..9457bf3 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/ProxyClient.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/ProxyClient.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace Sia.Connectors.Tickets.TicketProxy { - public class ProxyClient : Client + public class ProxyClient : Client { private readonly string _endpoint; private readonly HttpClient _client; @@ -15,12 +15,12 @@ namespace Sia.Connectors.Tickets.TicketProxy _client = singletonClient; } - public override async Task GetAsync(string originId) + public override async Task GetAsync(string originId) { string incidentUrl = $"{_endpoint}/{originId}"; var response = await _client.GetAsync(incidentUrl); var content = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(content); + return JsonConvert.DeserializeObject(content); } } } diff --git a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnector.cs b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnector.cs index 47409ee..4971a25 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnector.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConnector.cs @@ -1,8 +1,8 @@ namespace Sia.Connectors.Tickets.TicketProxy { - public class ProxyConnector : Connector + public class ProxyConnector : Connector { - public ProxyConnector(Client client, Converter converter) + public ProxyConnector(Client client, Converter converter) : base(client, converter) { } diff --git a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConverter.cs b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConverter.cs index 9b9d369..6937566 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/ProxyConverter.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/ProxyConverter.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; namespace Sia.Connectors.Tickets.TicketProxy { - public class ProxyConverter : Converter + public class ProxyConverter : Converter { - public override ICollection ExtractEvents(Ticket ticket) + public override ICollection ExtractEvents(ProxyTicket ticket) { return new List(); } diff --git a/src/Sia.Connectors.Tickets/TicketProxy/Ticket.cs b/src/Sia.Connectors.Tickets/TicketProxy/Ticket.cs index 0145ed8..8264726 100644 --- a/src/Sia.Connectors.Tickets/TicketProxy/Ticket.cs +++ b/src/Sia.Connectors.Tickets/TicketProxy/Ticket.cs @@ -1,6 +1,6 @@ namespace Sia.Connectors.Tickets.TicketProxy { - public class Ticket + public class ProxyTicket { public string OriginId { get; set; } public long IncidentSystemId { get; set; } diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index e205414..2368ce6 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Net; using System.Reflection; using System.Runtime.Loader; +using Sia.Domain; namespace Sia.Gateway.Initialization { @@ -72,7 +73,7 @@ namespace Sia.Gateway.Initialization private static void AddProxyConnector(IServiceCollection services, IConfigurationRoot config, string proxyEndpoint) { - services.AddIncidentClient(typeof(Ticket)); + services.AddIncidentClient(typeof(ProxyTicket)); var proxyAuthType = config["Connector:Ticket:ProxyAuthType"]; switch(proxyAuthType) { @@ -110,9 +111,9 @@ namespace Sia.Gateway.Initialization private static void AddIncidentClient(this IServiceCollection services, Type ticketType) { - var clientType = typeof(IncidentRepository<>).MakeGenericType(new Type[] { ticketType }); - services.AddScoped(typeof(IIncidentRepositoryLogic), clientType); - services.AddScoped(); + var handlerType = typeof(GetIncidentHandler<>).MakeGenericType(new Type[] { ticketType }); + services.AddScoped(typeof(IGetIncidentHandler), handlerType); + services.AddScoped, GetIncidentHandlerWrapper>(); } public static void AddThirdPartyServices(this IServiceCollection services, IConfigurationRoot config) diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs index 8b51e9f..973a30f 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs @@ -1,7 +1,12 @@ using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Connectors.Tickets; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Gateway.Authentication; using Sia.Gateway.ServiceRepositories; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Sia.Gateway.Requests @@ -15,4 +20,53 @@ namespace Sia.Gateway.Requests } public long Id { get; } } + + public class GetIncidentHandler : IGetIncidentHandler + { + private readonly IncidentContext _context; + private readonly Connector _connector; + public GetIncidentHandler(IncidentContext context, Connector connector) + { + _context = context; + _connector = connector; + } + public async Task Handle(GetIncidentRequest getIncident) + { + var incidentRecord = await _context.Incidents + .WithEagerLoading() + .FirstOrDefaultAsync(cr => cr.Id == getIncident.Id); + if (incidentRecord == null) throw new KeyNotFoundException(); + + var remoteId = incidentRecord + .Tickets + .FirstOrDefault(t => t.IsPrimary) + .OriginId; + + var ticket = await _connector.Client.GetAsync(remoteId); + + return _connector + .Converter + .AssembleIncident(incidentRecord, ticket); + } + } + + public interface IGetIncidentHandler + { + Task Handle(GetIncidentRequest getIncident); + } + + //Why does this exist? + //Purely because I haven't been able to get Mediatr to work with generics + public class GetIncidentHandlerWrapper : IAsyncRequestHandler + { + private readonly IGetIncidentHandler _actualHandler; + + public GetIncidentHandlerWrapper(IGetIncidentHandler actualHandler) + { + _actualHandler = actualHandler; + } + + public Task Handle(GetIncidentRequest message) + => _actualHandler.Handle(message); + } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs index ceeafe5..5032c98 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs @@ -1,4 +1,7 @@ -using MediatR; +using AutoMapper.QueryableExtensions; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Gateway.Authentication; using Sia.Gateway.ServiceRepositories; @@ -9,8 +12,28 @@ namespace Sia.Gateway.Requests { public class GetIncidentsRequest : AuthenticatedRequest, IRequest> { - public GetIncidentsRequest(AuthenticatedUserContext userContext) : base(userContext) + public GetIncidentsRequest(AuthenticatedUserContext userContext) + : base(userContext) { } } + + public class GetIncidentsHandler + : IAsyncRequestHandler> + { + private readonly IncidentContext _context; + public GetIncidentsHandler(IncidentContext context) + { + _context = context; + } + public async Task> Handle(GetIncidentsRequest request) + { + var incidentRecords = await _context.Incidents + .WithEagerLoading() + .ProjectTo() + .ToListAsync(); + return incidentRecords; + } + } + } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs index b8395be..73cee0f 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicket.cs @@ -1,8 +1,11 @@ -using MediatR; +using AutoMapper.QueryableExtensions; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Sia.Gateway.Requests @@ -16,4 +19,23 @@ namespace Sia.Gateway.Requests TicketId = ticketId; } } + + public class GetIncidentsByTicketHandler + : IAsyncRequestHandler> + { + private readonly IncidentContext _context; + public GetIncidentsByTicketHandler(IncidentContext context) + { + _context = context; + } + public async Task> Handle(GetIncidentsByTicketRequest request) + { + var incidentRecords = await _context.Incidents + .WithEagerLoading() + .Where(incident => incident.Tickets.Any(inc => inc.OriginId == request.TicketId)) + .ProjectTo().ToListAsync(); + + return incidentRecords; + } + } } diff --git a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs index 14ef69a..ef67ed4 100644 --- a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs @@ -1,8 +1,13 @@ -using MediatR; +using AutoMapper; +using MediatR; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; using Sia.Gateway.ServiceRepositories; +using Sia.Shared.Exceptions; +using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Sia.Gateway.Requests @@ -16,6 +21,27 @@ namespace Sia.Gateway.Requests } public NewIncident Incident { get; private set; } + } + public class PostIncidentHandler + : IAsyncRequestHandler + { + private readonly IncidentContext _context; + public PostIncidentHandler(IncidentContext context) + { + _context = context; + } + public async Task Handle(PostIncidentRequest request) + { + if (request.Incident == null) throw new ArgumentNullException(nameof(request.Incident)); + if (request.Incident?.PrimaryTicket?.OriginId == null) throw new ConflictException("Please provide a primary incident with a valid originId"); + + var dataIncident = Mapper.Map(request.Incident); + + var result = _context.Incidents.Add(dataIncident); + await _context.SaveChangesAsync(); + + return Mapper.Map(dataIncident); + } } } diff --git a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs b/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs deleted file mode 100644 index 24ab72b..0000000 --- a/src/Sia.Gateway/ServiceRepositories/IIncidentRepository.cs +++ /dev/null @@ -1,110 +0,0 @@ -using AutoMapper; -using AutoMapper.QueryableExtensions; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Sia.Connectors.Tickets; -using Sia.Data.Incidents; -using Sia.Domain; -using Sia.Gateway.Requests; -using Sia.Shared.Exceptions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.ServiceRepositories -{ - public interface IIncidentRepository - : IAsyncRequestHandler, - IAsyncRequestHandler>, - IAsyncRequestHandler>, - IAsyncRequestHandler - { - } - - public class IncidentRepository : IIncidentRepositoryLogic - { - private readonly IncidentContext _context; - private readonly Connector _connector; - - public IncidentRepository(IncidentContext context, Connector connector) - { - _context = context; - _connector = connector; - } - - public async Task Handle(GetIncidentRequest getIncident) - { - var incidentRecord = await _context.Incidents.WithEagerLoading().FirstOrDefaultAsync(cr => cr.Id == getIncident.Id); - if (incidentRecord == null) throw new KeyNotFoundException(); - - var ticket = await _connector.Client.GetAsync(incidentRecord.Tickets.FirstOrDefault(t => t.IsPrimary).OriginId); - - return _connector.Converter.AssembleIncident(incidentRecord, ticket); - } - - public async Task> Handle(GetIncidentsRequest request) - { - var incidentRecords = await _context.Incidents - .WithEagerLoading() - .ProjectTo() - .ToListAsync(); - return incidentRecords; - } - - public async Task> Handle(GetIncidentsByTicketRequest request) - { - var incidentRecords = await _context.Incidents - .WithEagerLoading() - .Where(incident => incident.Tickets.Any(inc => inc.OriginId == request.TicketId)) - .ProjectTo().ToListAsync(); - - return incidentRecords; - } - - public async Task Handle(PostIncidentRequest request) - { - if (request.Incident == null) throw new ArgumentNullException(nameof(request.Incident)); - if (request.Incident?.PrimaryTicket?.OriginId == null) throw new ConflictException("Please provide a primary incident with a valid originId"); - - var dataIncident = Mapper.Map(request.Incident); - - var result = _context.Incidents.Add(dataIncident); - await _context.SaveChangesAsync(); - - return Mapper.Map(dataIncident); - } - } - - //Why does this exist? - //Purely because I haven't been able to get Mediatr to work with generics - public interface IIncidentRepositoryLogic - { - Task Handle(GetIncidentRequest getIncident); - Task> Handle(GetIncidentsRequest request); - Task> Handle(GetIncidentsByTicketRequest request); - Task Handle(PostIncidentRequest request); - } - - public class IncidentRepositoryWrapper : IIncidentRepository - { - private readonly IIncidentRepositoryLogic _actualIncidentRepository; - - public IncidentRepositoryWrapper(IIncidentRepositoryLogic actualIncidentRepository) - { - _actualIncidentRepository = actualIncidentRepository; - } - - public Task Handle(GetIncidentRequest message) - => _actualIncidentRepository.Handle(message); - - public Task> Handle(GetIncidentsRequest message) - => _actualIncidentRepository.Handle(message); - - public Task> Handle(GetIncidentsByTicketRequest message) - => _actualIncidentRepository.Handle(message); - - public Task Handle(PostIncidentRequest message) - => _actualIncidentRepository.Handle(message); - } -} From 7a0d52af91e162ba097dcf6115f21d4bd7348378 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 18 Oct 2017 12:12:36 -0700 Subject: [PATCH 36/39] Switched broke repositories up into separate handlers --- .../Initialization/ServicesStartup.cs | 7 -- .../Requests/Engagements/GetEngagement.cs | 28 +++++++- .../Requests/Engagements/PostEngagement.cs | 37 +++++++++- .../Requests/Engagements/PutEngagement.cs | 31 +++++++- src/Sia.Gateway/Requests/Events/GetEvent.cs | 29 +++++++- src/Sia.Gateway/Requests/Events/GetEvents.cs | 22 +++++- src/Sia.Gateway/Requests/Events/PostEvent.cs | 35 ++++++++- .../Requests/Incidents/GetIncident.cs | 1 - .../Requests/Incidents/GetIncidents.cs | 1 - .../Requests/Incidents/PostIncident.cs | 2 - .../IEngagementRepository.cs | 71 ------------------- .../ServiceRepositories/IEventRepository.cs | 66 ----------------- 12 files changed, 168 insertions(+), 162 deletions(-) delete mode 100644 src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs delete mode 100644 src/Sia.Gateway/ServiceRepositories/IEventRepository.cs diff --git a/src/Sia.Gateway/Initialization/ServicesStartup.cs b/src/Sia.Gateway/Initialization/ServicesStartup.cs index 2368ce6..0742de1 100644 --- a/src/Sia.Gateway/Initialization/ServicesStartup.cs +++ b/src/Sia.Gateway/Initialization/ServicesStartup.cs @@ -13,13 +13,9 @@ using Sia.Connectors.Tickets.TicketProxy; using Sia.Data.Incidents; using Sia.Gateway.Authentication; using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories; using Sia.Shared.Authentication; using Sia.Shared.Validation; -using StackExchange.Redis; using System; -using System.Collections.Generic; -using System.Net; using System.Reflection; using System.Runtime.Loader; using Sia.Domain; @@ -39,9 +35,6 @@ namespace Sia.Gateway.Initialization services.AddTicketingConnector(env, config); - services.AddScoped(); - services.AddScoped(); - services.AddSingleton(i => config); services.AddSingleton(i => incidentAuthConfig); } diff --git a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs index 4314011e..44838ef 100644 --- a/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/GetEngagement.cs @@ -1,9 +1,13 @@ -using MediatR; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; +using System.Collections.Generic; using System.Threading.Tasks; + namespace Sia.Gateway.Requests { public class GetEngagementRequest : AuthenticatedRequest, IRequest @@ -17,4 +21,24 @@ namespace Sia.Gateway.Requests public long Id { get; } public long IncidentId { get; } } + + public class GetEngagementHandler + : IAsyncRequestHandler + { + private readonly IncidentContext _context; + + public GetEngagementHandler(IncidentContext context) + { + _context = context; + } + public async Task Handle(GetEngagementRequest request) + { + var EngagementRecord = await _context.Engagements + .Include(en => en.Participant) + .FirstOrDefaultAsync(ev => ev.IncidentId == request.IncidentId && ev.Id == request.Id); + if (EngagementRecord == null) throw new KeyNotFoundException(); + + return Mapper.Map(EngagementRecord); + } + } } diff --git a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs index a276acb..0736f6b 100644 --- a/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PostEngagement.cs @@ -1,8 +1,12 @@ -using MediatR; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; +using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Sia.Gateway.Requests @@ -19,4 +23,33 @@ namespace Sia.Gateway.Requests public NewEngagement NewEngagement { get; } public long IncidentId { get; } } + + public class PostEngagementHandler + : IAsyncRequestHandler + { + private readonly IncidentContext _context; + + public PostEngagementHandler(IncidentContext context) + { + _context = context; + } + public async Task Handle(PostEngagementRequest request) + { + if (request.NewEngagement == null) throw new ArgumentNullException(nameof(request.NewEngagement)); + + var dataIncident = await _context.Incidents + .Include(cr => cr.Engagements) + .ThenInclude(en => en.Participant) + .FirstOrDefaultAsync(x => x.Id == request.IncidentId); + if (dataIncident == null) throw new KeyNotFoundException(); + + var dataEngagement = Mapper.Map(request.NewEngagement); + dataEngagement.TimeEngaged = DateTime.UtcNow; + + dataIncident.Engagements.Add(dataEngagement); + await _context.SaveChangesAsync(); + + return Mapper.Map(dataEngagement); + } + } } diff --git a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs index b47b2d4..a8caff4 100644 --- a/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs +++ b/src/Sia.Gateway/Requests/Engagements/PutEngagement.cs @@ -1,10 +1,13 @@ -using MediatR; -using Sia.Data.Incidents.Models; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; +using System; using System.Threading.Tasks; + namespace Sia.Gateway.Requests { public class PutEngagementRequest : AuthenticatedRequest, IRequest @@ -20,4 +23,26 @@ namespace Sia.Gateway.Requests public long EngagementId { get; } public long IncidentId { get; } } + + public class PutEngagementHandler + : IAsyncRequestHandler + { + private readonly IncidentContext _context; + + public PutEngagementHandler(IncidentContext context) + { + _context = context; + } + public async Task Handle(PutEngagementRequest request) + { + if (request.UpdateEngagement == null) throw new ArgumentNullException(nameof(UpdateEngagement)); + var existingRecord = await _context.Engagements + .Include(en => en.Participant) + .FirstOrDefaultAsync(engagement => engagement.IncidentId == request.IncidentId && engagement.Id == request.EngagementId); + + var updatedModel = Mapper.Map(request.UpdateEngagement, existingRecord); + + await _context.SaveChangesAsync(); + } + } } diff --git a/src/Sia.Gateway/Requests/Events/GetEvent.cs b/src/Sia.Gateway/Requests/Events/GetEvent.cs index c186ef1..4a4c274 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvent.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvent.cs @@ -1,8 +1,10 @@ -using MediatR; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Gateway.Authentication; -using Sia.Gateway.Requests.Events; -using Sia.Gateway.ServiceRepositories; +using System.Collections.Generic; using System.Threading.Tasks; namespace Sia.Gateway.Requests @@ -19,4 +21,25 @@ namespace Sia.Gateway.Requests public long Id { get; } public long IncidentId { get; } } + + public class GetEventHandler : IAsyncRequestHandler + { + private readonly IncidentContext _context; + + public GetEventHandler(IncidentContext context) + { + _context = context; + } + public async Task Handle(GetEventRequest request) + { + var eventRecord = await _context + .Events + .FirstOrDefaultAsync( ev + => ev.IncidentId == request.IncidentId + && ev.Id == request.Id); + if (eventRecord == null) throw new KeyNotFoundException(); + + return Mapper.Map(eventRecord); + } + } } diff --git a/src/Sia.Gateway/Requests/Events/GetEvents.cs b/src/Sia.Gateway/Requests/Events/GetEvents.cs index 4df4bad..1d699b2 100644 --- a/src/Sia.Gateway/Requests/Events/GetEvents.cs +++ b/src/Sia.Gateway/Requests/Events/GetEvents.cs @@ -1,12 +1,13 @@ using MediatR; using Sia.Domain; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Sia.Gateway.Authentication; using Sia.Gateway.Protocol; -using Sia.Gateway.ServiceRepositories; +using Sia.Data.Incidents; +using AutoMapper.QueryableExtensions; +using Microsoft.EntityFrameworkCore; namespace Sia.Gateway.Requests.Events { @@ -22,4 +23,21 @@ namespace Sia.Gateway.Requests.Events public long IncidentId { get; } public PaginationMetadata Pagination { get; } } + + public class GetEventsHandler + : IAsyncRequestHandler> + { + private readonly IncidentContext _context; + + public GetEventsHandler(IncidentContext context) + { + _context = context; + } + public async Task> Handle(GetEventsRequest request) + => await _context.Events + .Where(ev => ev.IncidentId == request.IncidentId) + .WithPagination(request.Pagination) + .ProjectTo() + .ToListAsync(); + } } diff --git a/src/Sia.Gateway/Requests/Events/PostEvent.cs b/src/Sia.Gateway/Requests/Events/PostEvent.cs index 97a28ba..2705364 100644 --- a/src/Sia.Gateway/Requests/Events/PostEvent.cs +++ b/src/Sia.Gateway/Requests/Events/PostEvent.cs @@ -1,8 +1,12 @@ -using MediatR; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; +using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace Sia.Gateway.Requests @@ -19,4 +23,31 @@ namespace Sia.Gateway.Requests public NewEvent NewEvent { get; } public long IncidentId { get; } } + + public class PostEventHandler : IAsyncRequestHandler + { + private readonly IncidentContext _context; + + public PostEventHandler(IncidentContext context) + { + _context = context; + } + public async Task Handle(PostEventRequest request) + { + if (request.NewEvent == null) throw new ArgumentNullException(nameof(request.NewEvent)); + + var dataCrisis = await _context + .Incidents + .Include(cr => cr.Events) + .FirstOrDefaultAsync(x => x.Id == request.IncidentId); + if (dataCrisis == null) throw new KeyNotFoundException(); + + var dataEvent = Mapper.Map(request.NewEvent); + + dataCrisis.Events.Add(dataEvent); + await _context.SaveChangesAsync(); + + return Mapper.Map(dataEvent); + } + } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs index 973a30f..6bfb5b7 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncident.cs @@ -4,7 +4,6 @@ using Sia.Connectors.Tickets; using Sia.Data.Incidents; using Sia.Domain; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs index 5032c98..e2fea41 100644 --- a/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidents.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore; using Sia.Data.Incidents; using Sia.Domain; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs index ef67ed4..a7c2a2a 100644 --- a/src/Sia.Gateway/Requests/Incidents/PostIncident.cs +++ b/src/Sia.Gateway/Requests/Incidents/PostIncident.cs @@ -4,10 +4,8 @@ using Sia.Data.Incidents; using Sia.Domain; using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; using Sia.Shared.Exceptions; using System; -using System.Collections.Generic; using System.Threading.Tasks; namespace Sia.Gateway.Requests diff --git a/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs b/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs deleted file mode 100644 index 9a78567..0000000 --- a/src/Sia.Gateway/ServiceRepositories/IEngagementRepository.cs +++ /dev/null @@ -1,71 +0,0 @@ -using AutoMapper; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Sia.Data.Incidents; -using Sia.Domain; -using Sia.Domain.ApiModels; -using Sia.Gateway.Requests; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Sia.Gateway.ServiceRepositories -{ - public interface IEngagementRepository - :IAsyncRequestHandler, - IAsyncRequestHandler, - IAsyncRequestHandler - { - } - - public class EngagementRepository : IEngagementRepository - { - private readonly IncidentContext _context; - - public EngagementRepository(IncidentContext context) - { - _context = context; - } - - public async Task Handle(GetEngagementRequest request) - { - var EngagementRecord = await _context.Engagements - .Include(en => en.Participant) - .FirstOrDefaultAsync(ev => ev.IncidentId == request.IncidentId && ev.Id == request.Id); - if (EngagementRecord == null) throw new KeyNotFoundException(); - - return Mapper.Map(EngagementRecord); - } - - public async Task Handle (PostEngagementRequest request) - { - if (request.NewEngagement == null) throw new ArgumentNullException(nameof(request.NewEngagement)); - - var dataIncident = await _context.Incidents - .Include(cr => cr.Engagements) - .ThenInclude(en => en.Participant) - .FirstOrDefaultAsync(x => x.Id == request.IncidentId); - if (dataIncident == null) throw new KeyNotFoundException(); - - var dataEngagement = Mapper.Map(request.NewEngagement); - dataEngagement.TimeEngaged = DateTime.UtcNow; - - dataIncident.Engagements.Add(dataEngagement); - await _context.SaveChangesAsync(); - - return Mapper.Map(dataEngagement); - } - - public async Task Handle(PutEngagementRequest request) - { - if (request.UpdateEngagement == null) throw new ArgumentNullException(nameof(UpdateEngagement)); - var existingRecord = await _context.Engagements - .Include(en => en.Participant) - .FirstOrDefaultAsync(engagement => engagement.IncidentId == request.IncidentId && engagement.Id == request.EngagementId); - - var updatedModel = Mapper.Map(request.UpdateEngagement, existingRecord); - - await _context.SaveChangesAsync(); - } - } -} diff --git a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs b/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs deleted file mode 100644 index e9af5d8..0000000 --- a/src/Sia.Gateway/ServiceRepositories/IEventRepository.cs +++ /dev/null @@ -1,66 +0,0 @@ -using AutoMapper; -using AutoMapper.QueryableExtensions; -using MediatR; -using Microsoft.EntityFrameworkCore; -using Sia.Data.Incidents; -using Sia.Domain; -using Sia.Gateway.Requests; -using Sia.Gateway.Requests.Events; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Sia.Gateway.ServiceRepositories -{ - public interface IEventRepository - : IAsyncRequestHandler, - IAsyncRequestHandler, - IAsyncRequestHandler> - { - } - - public class EventRepository : IEventRepository - { - - private readonly IncidentContext _context; - - public EventRepository(IncidentContext context) - { - _context = context; - } - - public async Task Handle(GetEventRequest request) - { - var eventRecord = await _context.Events.FirstOrDefaultAsync(ev => ev.IncidentId == request.IncidentId && ev.Id == request.Id); - if (eventRecord == null) throw new KeyNotFoundException(); - - return Mapper.Map(eventRecord); - } - - public async Task> Handle(GetEventsRequest request) - => await _context.Events - .Where(ev => ev.IncidentId == request.IncidentId) - .WithPagination(request.Pagination) - .ProjectTo() - .ToListAsync(); - - - public async Task Handle(PostEventRequest request) - { - if (request.NewEvent == null) throw new ArgumentNullException(nameof(request.NewEvent)); - - var dataCrisis = await _context.Incidents - .Include(cr => cr.Events) - .FirstOrDefaultAsync(x => x.Id == request.IncidentId); - if (dataCrisis == null) throw new KeyNotFoundException(); - - var dataEvent = Mapper.Map(request.NewEvent); - - dataCrisis.Events.Add(dataEvent); - await _context.SaveChangesAsync(); - - return Mapper.Map(dataEvent); - } - } -} From 725aaa6ec78ca7361a5436b514cc5f5fb9037bf9 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 18 Oct 2017 13:16:58 -0700 Subject: [PATCH 37/39] Removed 'Has' from interface names --- Sia.Shared.Tests/Data/PartialJsonResolverTests.cs | 4 ++-- src/Sia.Data.Incident/Models/Event.cs | 2 +- src/Sia.Domain.ApiModels/NewEvent.cs | 2 +- src/Sia.Domain/Event.cs | 2 +- src/Sia.Gateway/Initialization/AutoMapperStartup.cs | 8 ++++---- .../Protocol/PartialSerializedJsonOutputFormatter.cs | 4 ++-- src/Sia.Shared/Data/PartialJsonResolver.cs | 12 ++++++------ 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs index 7f7132a..c7cf367 100644 --- a/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs +++ b/Sia.Shared.Tests/Data/PartialJsonResolverTests.cs @@ -60,12 +60,12 @@ namespace Sia.Shared.Tests.Data public int b { get; set; } = 1; } - internal class TestHasJsonDataString : IHasJsonDataString + internal class TestHasJsonDataString : IJsonDataString { public string Data { get; set; } } - internal class TestHasJsonDataObject : IHasJsonDataObject + internal class TestHasJsonDataObject : IJsonDataObject { public object Data { get; set; } } diff --git a/src/Sia.Data.Incident/Models/Event.cs b/src/Sia.Data.Incident/Models/Event.cs index 4f3c252..767333e 100644 --- a/src/Sia.Data.Incident/Models/Event.cs +++ b/src/Sia.Data.Incident/Models/Event.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Sia.Data.Incidents.Models { - public class Event : IEntity, IHasJsonDataString + public class Event : IEntity, IJsonDataString { public long Id { get; set; } public long? IncidentId { get; set; } diff --git a/src/Sia.Domain.ApiModels/NewEvent.cs b/src/Sia.Domain.ApiModels/NewEvent.cs index df86dcb..2955257 100644 --- a/src/Sia.Domain.ApiModels/NewEvent.cs +++ b/src/Sia.Domain.ApiModels/NewEvent.cs @@ -6,7 +6,7 @@ using System.Dynamic; namespace Sia.Domain.ApiModels { public class NewEvent - :IHasJsonDataObject + :IJsonDataObject { [Required] public long? EventTypeId { get; set; } diff --git a/src/Sia.Domain/Event.cs b/src/Sia.Domain/Event.cs index 3a2bb40..e5ffbd5 100644 --- a/src/Sia.Domain/Event.cs +++ b/src/Sia.Domain/Event.cs @@ -7,7 +7,7 @@ using System.Text; namespace Sia.Domain { - public class Event : IEntity, IHasJsonDataObject + public class Event : IEntity, IJsonDataObject { public long Id { get; set; } public long? IncidentId { get; set; } diff --git a/src/Sia.Gateway/Initialization/AutoMapperStartup.cs b/src/Sia.Gateway/Initialization/AutoMapperStartup.cs index 330bc2f..15c13e2 100644 --- a/src/Sia.Gateway/Initialization/AutoMapperStartup.cs +++ b/src/Sia.Gateway/Initialization/AutoMapperStartup.cs @@ -43,14 +43,14 @@ namespace Sia.Gateway.Initialization } private static IMappingExpression UseResolveJsonToString(this IMappingExpression mapping) - where TSource: IHasJsonDataObject - where TDestination: IHasJsonDataString + where TSource: IJsonDataObject + where TDestination: IJsonDataString => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); private static IMappingExpression UseResolveStringToJson(this IMappingExpression mapping) - where TSource : IHasJsonDataString - where TDestination : IHasJsonDataObject + where TSource : IJsonDataString + where TDestination : IJsonDataObject => mapping.ForMember((ev) => ev.Data, (config) => config.ResolveUsing>()); diff --git a/src/Sia.Gateway/Protocol/PartialSerializedJsonOutputFormatter.cs b/src/Sia.Gateway/Protocol/PartialSerializedJsonOutputFormatter.cs index 36b9605..376e030 100644 --- a/src/Sia.Gateway/Protocol/PartialSerializedJsonOutputFormatter.cs +++ b/src/Sia.Gateway/Protocol/PartialSerializedJsonOutputFormatter.cs @@ -20,7 +20,7 @@ namespace Sia.Gateway.Protocol public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { - var dataStream = (IEnumerable)context.Object; + var dataStream = (IEnumerable)context.Object; foreach (var objectToWrite in dataStream) { @@ -43,7 +43,7 @@ namespace Sia.Gateway.Protocol .Substring(0, enumIntName.Length - NumberOfCharactersInGenericTypeNotUsedByGetInterfaceMethod)); if (enumerableInterface is null) return false; - return !(type.GetGenericArguments()[0].GetInterface(nameof(IHasJsonDataObject)) is null); + return !(type.GetGenericArguments()[0].GetInterface(nameof(IJsonDataObject)) is null); } private object Deserialize(string serializedData) => JsonConvert.DeserializeObject(serializedData); diff --git a/src/Sia.Shared/Data/PartialJsonResolver.cs b/src/Sia.Shared/Data/PartialJsonResolver.cs index ed40f3c..3cb169a 100644 --- a/src/Sia.Shared/Data/PartialJsonResolver.cs +++ b/src/Sia.Shared/Data/PartialJsonResolver.cs @@ -8,20 +8,20 @@ using System.Threading.Tasks; namespace Sia.Shared.Data { - public interface IHasJsonDataString + public interface IJsonDataString { string Data { get; set; } } - public interface IHasJsonDataObject + public interface IJsonDataObject { object Data { get; set; } } public class ResolveJsonToString : IValueResolver - where TSource: IHasJsonDataObject - where TDestination: IHasJsonDataString + where TSource: IJsonDataObject + where TDestination: IJsonDataString { public string Resolve(TSource source, TDestination destination, string destMember, ResolutionContext context) => JsonConvert.SerializeObject(source.Data); @@ -29,8 +29,8 @@ namespace Sia.Shared.Data public class ResolveStringToJson : IValueResolver - where TSource : IHasJsonDataString - where TDestination : IHasJsonDataObject + where TSource : IJsonDataString + where TDestination : IJsonDataObject { public object Resolve(TSource source, TDestination destination, object destMember, ResolutionContext context) => JsonConvert.DeserializeObject(source.Data); From 532515ac174e9a1871bd6ef5ab8044a8b9c8d541 Mon Sep 17 00:00:00 2001 From: Philip Dimitratos Date: Wed, 18 Oct 2017 16:07:36 -0700 Subject: [PATCH 38/39] Fixed tests. --- src/Sia.Data.Incident/SeedData.cs | 5 +- src/Sia.Shared/Data/PartialJsonResolver.cs | 5 +- .../Requests/GetEventTests.cs | 15 +++-- .../Requests/GetIncidentTests.cs | 13 ++-- .../Requests/GetIncidentsTests.cs | 17 ++++-- .../Requests/PostIncidentTests.cs | 18 +++--- .../TestDoubles/MockFactory.cs | 27 +++++++++ .../TestDoubles/StubEventRepository.cs | 43 ------------- .../TestDoubles/StubIncidentRepository.cs | 60 ------------------- 9 files changed, 71 insertions(+), 132 deletions(-) create mode 100644 test/Sia.Gateway.Tests/TestDoubles/MockFactory.cs delete mode 100644 test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs delete mode 100644 test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs diff --git a/src/Sia.Data.Incident/SeedData.cs b/src/Sia.Data.Incident/SeedData.cs index c092e7e..1c41484 100644 --- a/src/Sia.Data.Incident/SeedData.cs +++ b/src/Sia.Data.Incident/SeedData.cs @@ -2,6 +2,7 @@ using Sia.Data.Incidents.Models; using System; using System.Collections.Generic; +using System.Linq; namespace Sia.Data.Incidents { @@ -11,8 +12,10 @@ namespace Sia.Data.Incidents const int differentEventTypes = 8; const int eventCountForManyEvents = 1000; //Some dev/test/demo data that was based on actual incidents has been [REDACTED] - public static void Add(IncidentContext incidentContext, SeedType seedtype) + public static void Add(IncidentContext incidentContext, SeedType seedtype = SeedType.Basic) { + if (incidentContext.Incidents.Any()) return; //This context already has seed data loaded + var firstTestIncidentSystem = new TicketingSystem { Name = "Not Our Ticketing System Name" diff --git a/src/Sia.Shared/Data/PartialJsonResolver.cs b/src/Sia.Shared/Data/PartialJsonResolver.cs index 3cb169a..2266482 100644 --- a/src/Sia.Shared/Data/PartialJsonResolver.cs +++ b/src/Sia.Shared/Data/PartialJsonResolver.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; namespace Sia.Shared.Data { + public interface IJsonDataString { string Data { get; set; } @@ -24,7 +25,7 @@ namespace Sia.Shared.Data where TDestination: IJsonDataString { public string Resolve(TSource source, TDestination destination, string destMember, ResolutionContext context) - => JsonConvert.SerializeObject(source.Data); + => source.Data is null ? null : JsonConvert.SerializeObject(source.Data); } public class ResolveStringToJson @@ -33,6 +34,6 @@ namespace Sia.Shared.Data where TDestination : IJsonDataObject { public object Resolve(TSource source, TDestination destination, object destMember, ResolutionContext context) - => JsonConvert.DeserializeObject(source.Data); + => source.Data is null ? null : JsonConvert.DeserializeObject(source.Data); } } diff --git a/test/Sia.Gateway.Tests/Requests/GetEventTests.cs b/test/Sia.Gateway.Tests/Requests/GetEventTests.cs index 401349b..1b25f79 100644 --- a/test/Sia.Gateway.Tests/Requests/GetEventTests.cs +++ b/test/Sia.Gateway.Tests/Requests/GetEventTests.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Sia.Domain; +using Sia.Gateway.Initialization; using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories; using Sia.Gateway.Tests.TestDoubles; using System.Threading.Tasks; @@ -10,20 +10,23 @@ namespace Sia.Gateway.Tests.Requests [TestClass] public class GetEventTests { + [TestInitialize] + public void ConfigureAutomapper() + => AutoMapperStartup.InitializeAutomapper(); + [TestMethod] public async Task Handle_WhenEventClientReturnsSuccessful_ReturnCorrectEvent() { - long expectedEventId = 200; - long expectedEventTypeId = 50; - long expectedIncidentId = 2; + long expectedEventId = 1; + long expectedEventTypeId = 1; + long expectedIncidentId = 1; var expectedEvent = new Event { Id = expectedEventId, EventTypeId = expectedEventTypeId, IncidentId = expectedIncidentId }; - IEventRepository mockRepository = new StubEventRepository(expectedEvent); - var serviceUnderTest = new GetEventHandler(mockRepository); + var serviceUnderTest = new GetEventHandler(MockFactory.IncidentContext("Get")); var request = new GetEventRequest(expectedIncidentId, expectedEventId, new DummyAuthenticatedUserContext()); diff --git a/test/Sia.Gateway.Tests/Requests/GetIncidentTests.cs b/test/Sia.Gateway.Tests/Requests/GetIncidentTests.cs index d2338ae..3a76e07 100644 --- a/test/Sia.Gateway.Tests/Requests/GetIncidentTests.cs +++ b/test/Sia.Gateway.Tests/Requests/GetIncidentTests.cs @@ -1,7 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Sia.Connectors.Tickets.None; using Sia.Domain; +using Sia.Gateway.Initialization; using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories; using Sia.Gateway.Tests.TestDoubles; using System.Threading.Tasks; @@ -10,18 +11,20 @@ namespace Sia.Gateway.Tests.Requests [TestClass] public class GetIncidentTests { + [TestInitialize] + public void ConfigureAutomapper() + => AutoMapperStartup.InitializeAutomapper(); [TestMethod] public async Task Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncident() { - long expectedIncidentId = 200; - string expectedIncidentTitle = "The thing we were looking for"; + long expectedIncidentId = 1; + string expectedIncidentTitle = "Customers are unable to access [REDACTED] from [REDACTED]"; var expectedIncident = new Incident { Id = expectedIncidentId, Title = expectedIncidentTitle }; - IIncidentRepository mockClient = new StubIncidentRepository(expectedIncident, null); - var serviceUnderTest = new GetIncidentHandler(mockClient); + var serviceUnderTest = new GetIncidentHandler(MockFactory.IncidentContext("Get"), new NoConnector(new NoClient(), new NoConverter())); var request = new GetIncidentRequest(expectedIncidentId, new DummyAuthenticatedUserContext()); diff --git a/test/Sia.Gateway.Tests/Requests/GetIncidentsTests.cs b/test/Sia.Gateway.Tests/Requests/GetIncidentsTests.cs index 1777d04..ecba469 100644 --- a/test/Sia.Gateway.Tests/Requests/GetIncidentsTests.cs +++ b/test/Sia.Gateway.Tests/Requests/GetIncidentsTests.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Sia.Domain; +using Sia.Gateway.Initialization; using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories; using Sia.Gateway.Tests.TestDoubles; using System.Linq; using System.Threading.Tasks; @@ -12,11 +12,19 @@ namespace Sia.Gateway.Tests.Requests [TestClass] public class GetIncidentsTests { + [TestInitialize] + public void ConfigureAutomapper() + => AutoMapperStartup.InitializeAutomapper(); + [TestMethod] public async Task Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncidents() { - long[] expectedIncidentIds = { 200, 300, 400 }; - string[] expectedIncidentTitles = { "First", "Second", "Third" }; + long[] expectedIncidentIds = { 1, 2, 3 }; + string[] expectedIncidentTitles = { + "Customers are unable to access [REDACTED] from [REDACTED]", + "Loss of [REDACTED] Connectivity in [REDACTED]", + "[REDACTED] and [REDACTED] service management operations for a subset of users in [REDACTED] are failing" + }; Incident[] expectedIncidents = new Incident[expectedIncidentIds.Length]; for (int i = 0; i < expectedIncidents.Length; i++) { @@ -26,8 +34,7 @@ namespace Sia.Gateway.Tests.Requests Title = expectedIncidentTitles[i] }; } - IIncidentRepository mockRepository = new StubIncidentRepository(expectedIncidents, null); - var serviceUnderTest = new GetIncidentsHandler(mockRepository); + var serviceUnderTest = new GetIncidentsHandler(MockFactory.IncidentContext("Get")); var request = new GetIncidentsRequest(new DummyAuthenticatedUserContext()); diff --git a/test/Sia.Gateway.Tests/Requests/PostIncidentTests.cs b/test/Sia.Gateway.Tests/Requests/PostIncidentTests.cs index 2414315..1baa408 100644 --- a/test/Sia.Gateway.Tests/Requests/PostIncidentTests.cs +++ b/test/Sia.Gateway.Tests/Requests/PostIncidentTests.cs @@ -2,8 +2,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Sia.Domain; using Sia.Domain.ApiModels; +using Sia.Gateway.Initialization; using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories; using Sia.Gateway.Tests.TestDoubles; using System.Collections.Generic; using System.Threading.Tasks; @@ -17,13 +17,7 @@ namespace Sia.Gateway.Tests.Requests [TestInitialize] public void ConfigureAutomapper() - { - Mapper.Initialize(configuration => - { - configuration.CreateMap(); - }); - _mapper = Mapper.Instance; - } + => AutoMapperStartup.InitializeAutomapper(); [TestMethod] public async Task Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncidents() @@ -32,9 +26,13 @@ namespace Sia.Gateway.Tests.Requests var expectedIncident = new NewIncident { Title = expectedIncidentTitle, + PrimaryTicket = new Ticket() + { + OriginId = "testOnlyPleaseIgnore" + } }; - IIncidentRepository mockRepository = new StubIncidentRepository(new List(), _mapper); - var serviceUnderTest = new PostIncidentHandler(mockRepository); + + var serviceUnderTest = new PostIncidentHandler(MockFactory.IncidentContext(nameof(Handle_WhenIncidentClientReturnsSuccessful_ReturnCorrectIncidents))); var request = new PostIncidentRequest(expectedIncident, new DummyAuthenticatedUserContext()); diff --git a/test/Sia.Gateway.Tests/TestDoubles/MockFactory.cs b/test/Sia.Gateway.Tests/TestDoubles/MockFactory.cs new file mode 100644 index 0000000..3fdc636 --- /dev/null +++ b/test/Sia.Gateway.Tests/TestDoubles/MockFactory.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sia.Gateway.Tests.TestDoubles +{ + public static class MockFactory + { + /// + /// Returns an in-memory Incident Context with seed data + /// + /// Name of the particular in-memory store to use. Re-use is not suggested when modifying data during test (nameof() the test method is preferred) + /// + public static IncidentContext IncidentContext(string instance) + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(instance) + .Options; + var context = new IncidentContext(options); + SeedData.Add(context); + + return context; + } + } +} diff --git a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs b/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs deleted file mode 100644 index ca7957e..0000000 --- a/test/Sia.Gateway.Tests/TestDoubles/StubEventRepository.cs +++ /dev/null @@ -1,43 +0,0 @@ -using AutoMapper; -using Sia.Domain; -using Sia.Domain.ApiModels; -using Sia.Gateway.Authentication; -using Sia.Gateway.Requests; -using Sia.Gateway.ServiceRepositories; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Sia.Gateway.Protocol; -using Sia.Gateway.Requests.Events; - -namespace Sia.Gateway.Tests.TestDoubles -{ - public class StubEventRepository : IEventRepository - { - private List _events; - - public StubEventRepository(Event ev) - : this(new List() { ev }) { } - public StubEventRepository(ICollection events) - { - _events = events.ToList(); - StatusCodeToRespondWith = HttpStatusCode.OK; - IsSuccessStatusCodeToRespondWith = true; - ContentToRespondWith = "You weren't going to use this anyway"; - } - - public HttpStatusCode StatusCodeToRespondWith { get; set; } - public bool IsSuccessStatusCodeToRespondWith { get; set; } - public string ContentToRespondWith { get; set; } - - public Task Handle(GetEventRequest request) - => Task.FromResult(_events.First(ev => ev.Id == request.Id && ev.IncidentId == request.IncidentId)); - - public Task> Handle(GetEventsRequest request) - => Task.FromResult(_events.AsEnumerable()); - - public Task Handle(PostEventRequest request) - => Task.FromResult(Mapper.Map(request.NewEvent, new Event())); - } -} diff --git a/test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs b/test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs deleted file mode 100644 index 02bb7fb..0000000 --- a/test/Sia.Gateway.Tests/TestDoubles/StubIncidentRepository.cs +++ /dev/null @@ -1,60 +0,0 @@ -using AutoMapper; -using Sia.Domain; -using Sia.Domain.ApiModels; -using Sia.Gateway.Authentication; -using Sia.Gateway.ServiceRepositories; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Sia.Gateway.Requests; - -namespace Sia.Gateway.Tests.TestDoubles -{ - public class StubIncidentRepository - : IIncidentRepository - - { - private IMapper _mapper; - - public StubIncidentRepository(Incident incident, IMapper mapper) - : this(new List() { incident }, mapper) { } - public StubIncidentRepository(ICollection incidents, IMapper mapper) - { - _mapper = mapper; - _incidents = incidents.ToList(); - StatusCodeToRespondWith = HttpStatusCode.OK; - IsSuccessStatusCodeToRespondWith = true; - ContentToRespondWith = "You weren't going to use this anyway"; - } - - public HttpStatusCode StatusCodeToRespondWith { get; set; } - - public bool IsSuccessStatusCodeToRespondWith { get; set; } - - public string ContentToRespondWith { get; set; } - - List _incidents { get; set; } - - public Task GetAsync(GetIncidentRequest request) - { - return Task.FromResult(_incidents.First(cr => cr.Id == request.Id)); - } - - public Task> GetManyAsync(GetIncidentsRequest request) - { - return Task.FromResult(_incidents.AsEnumerable()); - } - - public Task> GetManyAsync(GetIncidentsByTicketRequest request) - { - throw new NotImplementedException(); - } - - public Task PostAsync(PostIncidentRequest request) - { - return Task.FromResult(_mapper.Map(request.Incident)); - } - } -} From b945b6c1499fc0baed5ab5a841f7ab590c691a3e Mon Sep 17 00:00:00 2001 From: Maggie Pint Date: Thu, 19 Oct 2017 08:14:36 -0700 Subject: [PATCH 39/39] Magpint/getcreateifneeded (#10) * get by ticket create if needed handler * fix tests for incident get/create * simplify ticket model --- .../Controllers/EventsController.cs | 2 +- .../Controllers/TicketsController.cs | 8 +-- ...tIncidentsByTicketCreateIfNeededRequest.cs | 68 +++++++++++++++++++ ...identsByTicketCreateIfNeededRequestTest.cs | 49 +++++++++++++ 4 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicketCreateIfNeededRequest.cs create mode 100644 test/Sia.Gateway.Tests/Requests/GetIncidentsByTicketCreateIfNeededRequestTest.cs diff --git a/src/Sia.Gateway/Controllers/EventsController.cs b/src/Sia.Gateway/Controllers/EventsController.cs index 75f1990..7196927 100644 --- a/src/Sia.Gateway/Controllers/EventsController.cs +++ b/src/Sia.Gateway/Controllers/EventsController.cs @@ -65,7 +65,7 @@ namespace Sia.Gateway.Controllers .Build(); await eventHubConnection.StartAsync(); await eventHubConnection.SendAsync("Send", result); - eventHubConnection.DisposeAsync(); + await eventHubConnection.DisposeAsync(); } } } diff --git a/src/Sia.Gateway/Controllers/TicketsController.cs b/src/Sia.Gateway/Controllers/TicketsController.cs index 176a4d3..7210016 100644 --- a/src/Sia.Gateway/Controllers/TicketsController.cs +++ b/src/Sia.Gateway/Controllers/TicketsController.cs @@ -1,8 +1,10 @@ using MediatR; using Microsoft.AspNetCore.Mvc; using Sia.Domain; +using Sia.Domain.ApiModels; using Sia.Gateway.Authentication; using Sia.Gateway.Requests; +using System.Collections.Generic; using System.Threading.Tasks; @@ -19,11 +21,7 @@ namespace Sia.Gateway.Controllers [HttpGet("{id}")] public async Task Get(string id) { - var result = await _mediator.Send(new GetIncidentsByTicketRequest(id, _authContext)); - if (result == null) - { - return NotFound($"{nameof(Incident)} not found"); - } + var result = await _mediator.Send(new GetIncidentsByTicketCreateIfNeededRequest(id, _authContext)); return Ok(result); } } diff --git a/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicketCreateIfNeededRequest.cs b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicketCreateIfNeededRequest.cs new file mode 100644 index 0000000..583d474 --- /dev/null +++ b/src/Sia.Gateway/Requests/Incidents/GetIncidentsByTicketCreateIfNeededRequest.cs @@ -0,0 +1,68 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Sia.Data.Incidents; +using Sia.Domain; +using Sia.Domain.ApiModels; +using Sia.Gateway.Authentication; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sia.Gateway.Requests +{ + public class GetIncidentsByTicketCreateIfNeededRequest : AuthenticatedRequest, IRequest> + { + public GetIncidentsByTicketCreateIfNeededRequest(string ticketId, AuthenticatedUserContext userContext) + : base(userContext) + { + TicketId = ticketId; + } + + public string TicketId { get; private set; } + + } + + public class GetIncidentsByTicketCreateIfNeededRequestHandler : IAsyncRequestHandler> + { + + private IncidentContext _context; + + public GetIncidentsByTicketCreateIfNeededRequestHandler(IncidentContext context) + { + _context = context; + } + + public IncidentContext Context { get; } + + public async Task> Handle(GetIncidentsByTicketCreateIfNeededRequest message) + { + var incidents = await _context.Incidents + .WithEagerLoading() + .Where(incident => incident.Tickets.Any(inc => inc.OriginId == message.TicketId)) + .ProjectTo().ToListAsync(); + + if (incidents.Any()) + { + return incidents; + } + + var newIncident = new NewIncident + { + PrimaryTicket = new Ticket + { + TicketingSystemId = 1, + OriginId = message.TicketId + } + }; + + var dataIncident = Mapper.Map(newIncident); + + var result = _context.Incidents.Add(dataIncident); + await _context.SaveChangesAsync(); + + return new List { Mapper.Map(result.Entity) }; + } + } +} diff --git a/test/Sia.Gateway.Tests/Requests/GetIncidentsByTicketCreateIfNeededRequestTest.cs b/test/Sia.Gateway.Tests/Requests/GetIncidentsByTicketCreateIfNeededRequestTest.cs new file mode 100644 index 0000000..2633b57 --- /dev/null +++ b/test/Sia.Gateway.Tests/Requests/GetIncidentsByTicketCreateIfNeededRequestTest.cs @@ -0,0 +1,49 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Sia.Domain; +using Sia.Gateway.Initialization; +using Sia.Gateway.Requests; +using Sia.Gateway.Tests.TestDoubles; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sia.Gateway.Tests.Requests +{ + [TestClass] + public class GetIncidentsByTicketCreateIfNeededRequestTest + { + [TestInitialize] + public void ConfigureAutomapper() + => AutoMapperStartup.InitializeAutomapper(); + + [TestMethod] + public async Task Handle_WhenIncidentNotExist_ReturnNewIncident() + { + var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(MockFactory.IncidentContext("Get")); + var request = new GetIncidentsByTicketCreateIfNeededRequest("100", new DummyAuthenticatedUserContext()); + + + var result = (await serviceUnderTest.Handle(request)).ToList(); + + Assert.AreEqual(1, result.Count()); + Assert.AreEqual( "100", result[0].PrimaryTicket.OriginId); + + } + + [TestMethod] + public async Task Handle_WhenIncidentExists_ReturnCorrectIncidents() + { + + var serviceUnderTest = new GetIncidentsByTicketCreateIfNeededRequestHandler(MockFactory.IncidentContext("Get")); + var request = new GetIncidentsByTicketCreateIfNeededRequest("44444444", new DummyAuthenticatedUserContext()); + + var result = (await serviceUnderTest.Handle(request)).ToList(); + + Assert.AreEqual(1, result.Count()); + Assert.AreEqual(1, result[0].Id); + + } + } +}