From 81d5d86683ba7b376a0d9407db07fdf4b5956819 Mon Sep 17 00:00:00 2001 From: Nick Blakely Date: Sat, 8 Aug 2020 17:52:26 -0700 Subject: [PATCH] Parameter validation/cleanup. --- Giants.Services/Core/ServicesModule.cs | 2 +- ...HostedService.cs => InitializerService.cs} | 4 +- .../Services/ServerRegistryService.cs | 15 +- Giants.Services/Store/CosmosDbClient.cs | 8 + .../Store/CosmosDbServerRegistryStore.cs | 8 + Giants.Services/Utility/ArgumentUtility.cs | 153 ++++++++++++++++++ .../Controllers/ServersController.cs | 21 ++- 7 files changed, 194 insertions(+), 17 deletions(-) rename Giants.Services/Services/{InitializerHostedService.cs => InitializerService.cs} (80%) create mode 100644 Giants.Services/Utility/ArgumentUtility.cs diff --git a/Giants.Services/Core/ServicesModule.cs b/Giants.Services/Core/ServicesModule.cs index a39344a..a753077 100644 --- a/Giants.Services/Core/ServicesModule.cs +++ b/Giants.Services/Core/ServicesModule.cs @@ -12,7 +12,7 @@ services.AddSingleton(); services.AddSingleton(); - services.AddHostedService(); + services.AddHostedService(); services.AddHostedService(); } } diff --git a/Giants.Services/Services/InitializerHostedService.cs b/Giants.Services/Services/InitializerService.cs similarity index 80% rename from Giants.Services/Services/InitializerHostedService.cs rename to Giants.Services/Services/InitializerService.cs index 3480484..f774b19 100644 --- a/Giants.Services/Services/InitializerHostedService.cs +++ b/Giants.Services/Services/InitializerService.cs @@ -4,11 +4,11 @@ using System.Threading.Tasks; using Microsoft.Extensions.Hosting; - public class InitializerHostedService : IHostedService + public class InitializerService : IHostedService { private readonly IServerRegistryStore serverRegistryStore; - public InitializerHostedService(IServerRegistryStore serverRegistryStore) + public InitializerService(IServerRegistryStore serverRegistryStore) { this.serverRegistryStore = serverRegistryStore; } diff --git a/Giants.Services/Services/ServerRegistryService.cs b/Giants.Services/Services/ServerRegistryService.cs index a740077..76b32dc 100644 --- a/Giants.Services/Services/ServerRegistryService.cs +++ b/Giants.Services/Services/ServerRegistryService.cs @@ -28,19 +28,12 @@ } public async Task AddServer( - ServerInfo server) + ServerInfo serverInfo) { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } + ArgumentUtility.CheckForNull(serverInfo, nameof(serverInfo)); + ArgumentUtility.CheckStringForNullOrEmpty(serverInfo.HostIpAddress, nameof(serverInfo.HostIpAddress)); - if (string.IsNullOrEmpty(server.HostIpAddress)) - { - throw new ArgumentException(nameof(server.HostIpAddress)); - } - - await this.registryStore.UpsertServerInfo(server ?? throw new ArgumentNullException(nameof(server))); + await this.registryStore.UpsertServerInfo(serverInfo); } public async Task> GetAllServers() diff --git a/Giants.Services/Store/CosmosDbClient.cs b/Giants.Services/Store/CosmosDbClient.cs index 94d26a6..79f8087 100644 --- a/Giants.Services/Store/CosmosDbClient.cs +++ b/Giants.Services/Store/CosmosDbClient.cs @@ -36,6 +36,8 @@ string partitionKey = null) where T : IIdentifiable { + ArgumentUtility.CheckForNull(selectExpression, nameof(selectExpression)); + if (partitionKey == null) { partitionKey = typeof(T).Name; @@ -109,6 +111,8 @@ public async Task GetItemById(string id, string partitionKey = null) where T : IIdentifiable { + ArgumentUtility.CheckStringForNullOrEmpty(id, nameof(id)); + return (await this.GetItems(t => t.id == id, partitionKey)).FirstOrDefault(); } @@ -118,6 +122,8 @@ ItemRequestOptions itemRequestOptions = null) where T : IIdentifiable { + ArgumentUtility.CheckForNull(item, nameof(item)); + await this.container.UpsertItemAsync(item, partitionKey, itemRequestOptions); } @@ -142,6 +148,8 @@ string partitionKey = null, ItemRequestOptions requestOptions = null) { + ArgumentUtility.CheckStringForNullOrEmpty(id, nameof(id)); + if (partitionKey == null) { partitionKey = typeof(T).Name; diff --git a/Giants.Services/Store/CosmosDbServerRegistryStore.cs b/Giants.Services/Store/CosmosDbServerRegistryStore.cs index c89575f..f771e76 100644 --- a/Giants.Services/Store/CosmosDbServerRegistryStore.cs +++ b/Giants.Services/Store/CosmosDbServerRegistryStore.cs @@ -34,6 +34,8 @@ Expression> whereExpression = null, string partitionKey = null) { + ArgumentUtility.CheckForNull(selectExpression, nameof(selectExpression)); + return await this.client.GetItems( selectExpression: selectExpression, whereExpression: whereExpression, @@ -42,11 +44,15 @@ public async Task GetServerInfo(string ipAddress) { + ArgumentUtility.CheckStringForNullOrEmpty(ipAddress, nameof(ipAddress)); + return await this.client.GetItemById(ipAddress); } public async Task UpsertServerInfo(ServerInfo serverInfo) { + ArgumentUtility.CheckForNull(serverInfo, nameof(serverInfo)); + await this.client.UpsertItem( item: serverInfo, partitionKey: new PartitionKey(serverInfo.DocumentType)); @@ -54,6 +60,8 @@ public async Task DeleteServers(IEnumerable ids, string partitionKey = null) { + ArgumentUtility.CheckEnumerableForNullOrEmpty(ids, nameof(ids)); + foreach (string id in ids) { this.logger.LogInformation("Deleting server for host IP {IPAddress}", id); diff --git a/Giants.Services/Utility/ArgumentUtility.cs b/Giants.Services/Utility/ArgumentUtility.cs new file mode 100644 index 0000000..37a791b --- /dev/null +++ b/Giants.Services/Utility/ArgumentUtility.cs @@ -0,0 +1,153 @@ +namespace Giants.Services +{ + // Decompiled with JetBrains decompiler + // Type: Microsoft.VisualStudio.Services.Common.ArgumentUtility + // Assembly: Microsoft.VisualStudio.Services.Common, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + // MVID: 8C174B92-2E1F-4F71-9E6B-FC8D9F2C517A + + using System; + using System.Collections; + using System.ComponentModel; + using System.Runtime.CompilerServices; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ArgumentUtility + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckForNull(object var, string varName) + { + if (var == null) + throw new ArgumentNullException(varName); + } + + + public static void CheckStringForNullOrEmpty( + string stringVar, + string stringVarName) + { + ArgumentUtility.CheckStringForNullOrEmpty(stringVar, stringVarName, false); + } + + public static void CheckForNonnegativeInt(int var, string varName) + { + if (var < 0) + throw new ArgumentOutOfRangeException(varName); + } + + public static void CheckForNonPositiveInt(int var, string varName) + { + if (var <= 0) + throw new ArgumentOutOfRangeException(varName); + } + + public static void CheckStringForNullOrEmpty( + string stringVar, + string stringVarName, + bool trim) + { + ArgumentUtility.CheckForNull((object)stringVar, stringVarName); + if (trim) + stringVar = stringVar.Trim(); + if (stringVar.Length == 0) + throw new ArgumentException("Empty string not allowed.", stringVarName); + } + + public static void CheckStringLength( + string stringVar, + string stringVarName, + int maxLength, + int minLength = 0) + { + ArgumentUtility.CheckForNull((object)stringVar, stringVarName); + if (stringVar.Length < minLength || stringVar.Length > maxLength) + throw new ArgumentException("String length not allowed.", stringVarName); + } + + public static void CheckEnumerableForNullOrEmpty( + IEnumerable enumerable, + string enumerableName) + { + ArgumentUtility.CheckForNull((object)enumerable, enumerableName); + if (!enumerable.GetEnumerator().MoveNext()) + throw new ArgumentException("Collection cannot be null or empty.", enumerableName); + } + + public static void CheckEnumerableForNullElement( + IEnumerable enumerable, + string enumerableName) + { + ArgumentUtility.CheckForNull((object)enumerable, enumerableName); + foreach (object obj in enumerable) + { + if (obj == null) + throw new ArgumentException("NullElementNotAllowedInCollection", enumerableName); + } + } + + public static void CheckForEmptyGuid(Guid guid, string varName) + { + if (guid.Equals(Guid.Empty)) + throw new ArgumentException("EmptyGuidNotAllowed", varName); + } + + public static void CheckBoundsInclusive( + int value, + int minValue, + int maxValue, + string varName) + { + if (value < minValue || value > maxValue) + throw new ArgumentOutOfRangeException(varName, "ValueOutOfRange"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckForOutOfRange( + T var, + string varName, + T minimum) + where T : IComparable + { + ArgumentUtility.CheckForNull((object)var, varName); + if (var.CompareTo(minimum) < 0) + throw new ArgumentOutOfRangeException(varName, (object)var, "OutOfRange"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckForOutOfRange( + int var, + string varName, + int minimum, + int maximum) + { + if (var < minimum || var > maximum) + throw new ArgumentOutOfRangeException(varName, (object)var, "OutOfRange"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckForOutOfRange( + long var, + string varName, + long minimum, + long maximum) + { + if (var < minimum || var > maximum) + throw new ArgumentOutOfRangeException(varName, (object)var, "OutOfRange"); + } + + public static void CheckForDateTimeRange( + DateTime var, + string varName, + DateTime minimum, + DateTime maximum) + { + if (var < minimum || var > maximum) + throw new ArgumentOutOfRangeException(varName, (object)var, "OutOfRange"); + } + + public static void EnsureIsNull(object var, string varName) + { + if (var != null) + throw new ArgumentException("NullValueNecessary"); + } + } +} diff --git a/Giants.WebApi/Controllers/ServersController.cs b/Giants.WebApi/Controllers/ServersController.cs index ba3bb9c..60d61b6 100644 --- a/Giants.WebApi/Controllers/ServersController.cs +++ b/Giants.WebApi/Controllers/ServersController.cs @@ -39,7 +39,7 @@ namespace Giants.Web.Controllers IEnumerable serverInfo = await this.serverRegistryService.GetAllServers(); IMapper mapper = Services.Mapper.GetMapper(); - return serverInfo + var mappedServers = serverInfo .Select(x => { var serverInfo = mapper.Map(x); @@ -47,19 +47,34 @@ namespace Giants.Web.Controllers return serverInfo; }) .ToList(); + + string requestIpAddress = this.GetRequestIpAddress(); + logger.LogInformation("Returning {Count} servers to {IPAddress}", mappedServers.Count, requestIpAddress); + + return mappedServers; } [HttpPost] public async Task AddServer([FromBody]DataContract.ServerInfo serverInfo) { - IPAddress requestIpAddress = this.httpContextAccessor.HttpContext.Connection.RemoteIpAddress.MapToIPv4(); - this.logger.LogInformation($"Request to add server from {requestIpAddress}"); + ArgumentUtility.CheckForNull(serverInfo, nameof(serverInfo)); + + string requestIpAddress = this.GetRequestIpAddress(); + + this.logger.LogInformation("Request to add server from {IPAddress}", requestIpAddress); var serverInfoEntity = mapper.Map(serverInfo); serverInfoEntity.HostIpAddress = requestIpAddress.ToString(); serverInfoEntity.LastHeartbeat = DateTime.UtcNow; await this.serverRegistryService.AddServer(serverInfoEntity); + + this.logger.LogInformation("Added server successfully for {IPAddress}", requestIpAddress); + } + + private string GetRequestIpAddress() + { + return this.httpContextAccessor.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(); } } }