diff --git a/src/ProjectPatterns/Patterns/LoggingPattern.props b/src/ProjectPatterns/Patterns/LoggingPattern.props index a289ce2..c8a72f7 100644 --- a/src/ProjectPatterns/Patterns/LoggingPattern.props +++ b/src/ProjectPatterns/Patterns/LoggingPattern.props @@ -2,7 +2,7 @@ - + diff --git a/src/Tests/UL.Core.Tests/Validators/FizzBuzzRequestValidatorTests.cs b/src/Tests/UL.Core.Tests/Validators/FizzBuzzRequestValidatorTests.cs new file mode 100644 index 0000000..4485ad7 --- /dev/null +++ b/src/Tests/UL.Core.Tests/Validators/FizzBuzzRequestValidatorTests.cs @@ -0,0 +1,56 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2023 (c) Bugail Consulting Ltd. All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace UL.Core.Tests.Validators +{ + using Bogus; + using FluentValidation.TestHelper; + using NUnit.Framework; + using UL.Core.Requests; + using UL.Core.Validators; + + public class FizzBuzzRequestValidatorTests + { + private FizzBuzzRequestValidator validator; + + [SetUp] + public void Setup() + { + validator = new FizzBuzzRequestValidator(); + } + + [TestCase("")] + [TestCase(null)] + [TestCase("12x")] + public void Validate_InvalidStart_HasErrors(string value) + { + // Arrange + var model = new FizzBuzzRequest(value, string.Empty); + + // Act + var result = validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(person => person.Start); + } + + [TestCase("")] + [TestCase(null)] + [TestCase("12x")] + [TestCase("0")] + public void Validate_InvalidEnd_HasErrors(string value) + { + // Arrange + var model = new FizzBuzzRequest(string.Empty, value); + + // Act + var result = validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(person => person.End); + } + } +} \ No newline at end of file diff --git a/src/Tests/UL.Services.Tests/FizzBuzzServiceTests.cs b/src/Tests/UL.Services.Tests/FizzBuzzServiceTests.cs index 739aeb8..fdc270f 100644 --- a/src/Tests/UL.Services.Tests/FizzBuzzServiceTests.cs +++ b/src/Tests/UL.Services.Tests/FizzBuzzServiceTests.cs @@ -14,6 +14,7 @@ namespace UL.Services.Tests using NSubstitute; using NUnit.Framework; using UL.Abstractions.Interfaces; + using UL.Core.Requests; using Ul.Services.Strategies; [TestFixture] @@ -21,17 +22,19 @@ public class FizzBuzzServiceTests { private List stategies; private FizzBuzzService target; + private ILogger logger; [SetUp] public void Setup() { + this.logger = Substitute.For>(); this.stategies = new List { new FizzStrategy(), new BuzzStrategy() }; - this.target = new FizzBuzzService(this.stategies); + this.target = new FizzBuzzService(this.stategies, this.logger); } [Test] @@ -76,5 +79,20 @@ public void GetFizzBuzzList_EmptyCollection_ThrowsException() .Throw() .WithMessage("*collection*"); } + + [Test] + public void GetFizzBuzzList_NullRequest_ThrowsException() + { + // Arrange + FizzBuzzRequest request = null; + + // Act + Func> action = () => this.target.GetFizzBuzzList(request); + + // Assert + action.Should() + .Throw() + .WithMessage("*request*"); + } } } \ No newline at end of file diff --git a/src/UL.Abstractions/Interfaces/IFizzBuzzService.cs b/src/UL.Abstractions/Interfaces/IFizzBuzzService.cs index fb703a9..6bdd398 100644 --- a/src/UL.Abstractions/Interfaces/IFizzBuzzService.cs +++ b/src/UL.Abstractions/Interfaces/IFizzBuzzService.cs @@ -7,6 +7,7 @@ namespace UL.Abstractions.Interfaces { using System.Collections.Generic; + using UL.Core.Requests; /// /// The fizz buzz service interface. @@ -18,6 +19,13 @@ public interface IFizzBuzzService /// /// The collection /// A list of . - IList GetFizzBuzzList(IEnumerable collection); + IEnumerable GetFizzBuzzList(IEnumerable collection); + + /// + /// Gets the list of FizzBuzz values. + /// + /// The request object. + /// A list of . + IEnumerable GetFizzBuzzList(FizzBuzzRequest request); } } \ No newline at end of file diff --git a/src/UL.Abstractions/UL.Abstractions.csproj b/src/UL.Abstractions/UL.Abstractions.csproj index 76ca9bb..282f75a 100644 --- a/src/UL.Abstractions/UL.Abstractions.csproj +++ b/src/UL.Abstractions/UL.Abstractions.csproj @@ -2,4 +2,8 @@ + + + + diff --git a/src/UL.Core/Requests/FizzBuzzRequest.cs b/src/UL.Core/Requests/FizzBuzzRequest.cs new file mode 100644 index 0000000..c1d6393 --- /dev/null +++ b/src/UL.Core/Requests/FizzBuzzRequest.cs @@ -0,0 +1,35 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2023 (c) Bugail Consulting Ltd. All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace UL.Core.Requests +{ + /// + /// The fizzbuzz request. + /// + public class FizzBuzzRequest + { + /// + /// Initializes a new instance of the class. + /// + /// The start value. + /// The end value. + public FizzBuzzRequest(string start, string end) + { + this.Start = start; + this.End = end; + } + + /// + /// Gets the start value. + /// + public string Start { get; } + + /// + /// Gets the end value. + /// + public string End { get; } + } +} \ No newline at end of file diff --git a/src/UL.Core/UL.Core.csproj b/src/UL.Core/UL.Core.csproj index 76ca9bb..c145c1f 100644 --- a/src/UL.Core/UL.Core.csproj +++ b/src/UL.Core/UL.Core.csproj @@ -2,4 +2,8 @@ + + + + diff --git a/src/UL.Core/Validators/FizzBuzzRequestValidator.cs b/src/UL.Core/Validators/FizzBuzzRequestValidator.cs new file mode 100644 index 0000000..4d6e73e --- /dev/null +++ b/src/UL.Core/Validators/FizzBuzzRequestValidator.cs @@ -0,0 +1,41 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2023 (c) Bugail Consulting Ltd. All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace UL.Core.Validators +{ + using FluentValidation; + using UL.Core.Extensions; + using UL.Core.Requests; + + /// + /// The fizz buzz validator. + /// + public class FizzBuzzRequestValidator : AbstractValidator + { + /// + /// Initializes a new instance of the class. + /// + public FizzBuzzRequestValidator() + { + this.RuleFor(x => x.Start) + .NotEmpty() + .NotNull() + .Must(x => x.IsNumeric()).WithMessage("Start must be a valid number"); + + this.RuleFor(x => x.End) + .NotEmpty() + .NotNull() + .Must(x => x.IsNumeric()).WithMessage("End must be a valid number") + .Custom((x, context) => + { + if (!int.TryParse(x, out int value) || value <= 0) + { + context.AddFailure($"End must be greater than 0."); + } + }); + } + } +} \ No newline at end of file diff --git a/src/UL.UI.Console/Program.cs b/src/UL.UI.Console/Program.cs index 3a3a122..cc154e7 100644 --- a/src/UL.UI.Console/Program.cs +++ b/src/UL.UI.Console/Program.cs @@ -2,31 +2,54 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Serilog; using UL.Abstractions.Interfaces; using UL.Core.Extensions; +using UL.Core.Requests; using UL.Services.Extensions; internal class Program { static void Main(string[] args) { - using IHost host = Host.CreateDefaultBuilder(args) - .ConfigureServices(services => - { - services.AddServices(); - }) - .Build(); + // Setup serilog static logger + Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .CreateBootstrapLogger(); - var showMenu = true; + try + { + Log.Information("Starting Account Service Web"); + + using IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddServices(); + }) + .Build(); + + var showMenu = true; - while (showMenu) + while (showMenu) + { + showMenu = MainMenu(host.Services); + } + } + catch (Exception ex) { - showMenu = MainMenu(host.Services); + Log.Fatal(ex, "Host terminated unexpectedly"); + } + finally + { + Log.CloseAndFlush(); } } - private static bool MainMenu(IServiceProvider provider) + private static bool MainMenu(IServiceProvider hostProvider) { + using var serviceScope = hostProvider.CreateScope(); + var provider = serviceScope.ServiceProvider; + Console.Clear(); Console.WriteLine("Choose an option:"); Console.WriteLine("1) FizzBuzz"); @@ -49,30 +72,45 @@ private static bool MainMenu(IServiceProvider provider) } } - private static void HandleFizzBuzz(IServiceProvider hostProvider) + private static void HandleFizzBuzz(IServiceProvider provider) { - using var serviceScope = hostProvider.CreateScope(); - var provider = serviceScope.ServiceProvider; - var service = provider.GetRequiredService(); + try + { + var service = provider.GetRequiredService(); + + Console.Write("\n\n"); + Console.Write("Calculate fizzbuzz for a range:\n"); + Console.Write("--------------------------------------------"); + Console.Write("\n\n"); + + Console.Write("Input the start : "); + var startValue = Console.ReadLine(); - var list = Enumerable.Range(1, 100).ToList(); - var result = service.GetFizzBuzzList(list); + Console.Write("Input the end : "); + var endString = Console.ReadLine(); - foreach (var item in result) + var request = new FizzBuzzRequest(startValue, endString); + var result = service.GetFizzBuzzList(request); + + foreach (var item in result) + { + Console.WriteLine(item); + } + } + catch (Exception e) { - Console.WriteLine(item); + Console.WriteLine(e.Message); } + Console.Write("\n\n"); Console.WriteLine("Press Enter to continue."); Console.ReadLine(); } - private static void HandleFactorial(IServiceProvider hostProvider) + private static void HandleFactorial(IServiceProvider provider) { try { - using var serviceScope = hostProvider.CreateScope(); - var provider = serviceScope.ServiceProvider; var service = provider.GetRequiredService(); Console.Write("\n\n"); @@ -98,6 +136,7 @@ private static void HandleFactorial(IServiceProvider hostProvider) Console.WriteLine(e.Message); } + Console.Write("\n\n"); Console.WriteLine("Press Enter to continue."); Console.ReadLine(); diff --git a/src/UL.UI.Console/UL.UI.Console.csproj b/src/UL.UI.Console/UL.UI.Console.csproj index 2eedf65..4cf923e 100644 --- a/src/UL.UI.Console/UL.UI.Console.csproj +++ b/src/UL.UI.Console/UL.UI.Console.csproj @@ -7,8 +7,11 @@ enable + + + diff --git a/src/UL.UI.Web/Pages/Factorial.cshtml b/src/UL.UI.Web/Pages/Factorial.cshtml new file mode 100644 index 0000000..af80c3c --- /dev/null +++ b/src/UL.UI.Web/Pages/Factorial.cshtml @@ -0,0 +1,50 @@ +@page +@model FactorialModel +@{ + ViewData["Title"] = "Factorial Test"; +} +

@ViewData["Title"]

+ +
+
+ + + +
+ + +
+ + +

Results

+ +@if (!string.IsNullOrEmpty(Model.Result)) +{ +

The factorial of @Model.Value is @Model.Result

+} + + + +@if (!string.IsNullOrEmpty(Model.ErrorMessage)) +{ + +} \ No newline at end of file diff --git a/src/UL.UI.Web/Pages/Factorial.cshtml.cs b/src/UL.UI.Web/Pages/Factorial.cshtml.cs new file mode 100644 index 0000000..d3ad430 --- /dev/null +++ b/src/UL.UI.Web/Pages/Factorial.cshtml.cs @@ -0,0 +1,49 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright 2023 (c) Bugail Consulting Ltd. All rights reserved. +// +// -------------------------------------------------------------------------------------------------------------------- + +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace UL.UI.Web.Pages +{ + using System.ComponentModel.DataAnnotations; + using Microsoft.AspNetCore.Mvc; + using UL.Abstractions.Interfaces; + + public class FactorialModel : PageModel + { + private readonly ILogger logger; + private readonly IFactorialService service; + + public FactorialModel(IFactorialService service, ILoggerlogger) + { + this.service = service; + this.logger = logger; + this.Value = 3; + } + + public void OnGet() + { + try + { + this.logger.LogInformation("OnGet - Running Factorial - Value: {Value}", this.Value); + this.Result = this.service.Calculate(this.Value).ToString(); + } + catch (Exception e) + { + this.ErrorMessage = e.Message; + this.logger.LogError(e, "OnGet - Error - Value: {Value}, End: {End}", this.Value ); + } + } + + [FromQuery] + [Range(1,100)] + public int Value { get; set; } + + public string Result { get; set; } + + public string ErrorMessage { get; set; } + } +} \ No newline at end of file diff --git a/src/UL.UI.Web/Pages/FizzBuzz.cshtml.cs b/src/UL.UI.Web/Pages/FizzBuzz.cshtml.cs index d418c06..ea68cb0 100644 --- a/src/UL.UI.Web/Pages/FizzBuzz.cshtml.cs +++ b/src/UL.UI.Web/Pages/FizzBuzz.cshtml.cs @@ -15,10 +15,12 @@ namespace UL.UI.Web.Pages public class FizzBuzzModel : PageModel { private readonly IFizzBuzzService service; + private readonly ILogger logger; - public FizzBuzzModel(IFizzBuzzService service) + public FizzBuzzModel(IFizzBuzzService service, ILoggerlogger) { this.service = service; + this.logger = logger; this.Start = 1; this.End = 100; } @@ -27,16 +29,18 @@ public void OnGet() { try { + this.logger.LogInformation("OnGet - Running FizzBuzz - Start: {Start}, End: {End}", this.Start, this.End); var collection = Enumerable.Range(this.Start, this.End); this.Results = this.service.GetFizzBuzzList(collection); } catch (Exception e) { this.ErrorMessage = e.Message; + this.logger.LogError(e, "OnGet - Error - Start: {Start}, End: {End}", this.Start, this.End); } } - public IList Results { get; set; } + public IEnumerable Results { get; set; } [FromQuery] [Range(1,100)] diff --git a/src/UL.UI.Web/Pages/Shared/_Layout.cshtml b/src/UL.UI.Web/Pages/Shared/_Layout.cshtml index 81f4d5c..ec8f795 100644 --- a/src/UL.UI.Web/Pages/Shared/_Layout.cshtml +++ b/src/UL.UI.Web/Pages/Shared/_Layout.cshtml @@ -25,6 +25,10 @@ + + diff --git a/src/UL.UI.Web/Program.cs b/src/UL.UI.Web/Program.cs index 7f4a467..61acf5c 100644 --- a/src/UL.UI.Web/Program.cs +++ b/src/UL.UI.Web/Program.cs @@ -42,12 +42,7 @@ public static int Main(string[] args) Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .ReadFrom.Configuration(GetLoggerConfiguration()) - .WriteTo.Debug() - .CreateLogger(); - - // Create a Microsoft.Extensions.Logging.ILoggerFactory from the serilog static logger - var loggerFactory = new SerilogLoggerFactory(Log.Logger); - startUpLogger = loggerFactory.CreateLogger(); + .CreateBootstrapLogger(); try { @@ -81,13 +76,9 @@ private static WebApplication CreateWebApplication(string[] args) var builder = WebApplication.CreateBuilder(args); // Add services to the container. - builder.Host - .UseSerilog((hostingContext, loggerConfiguration) => - { - loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration) - .Enrich.FromLogContext() - .Enrich.WithProperty(nameof(hostingContext.HostingEnvironment.EnvironmentName), hostingContext.HostingEnvironment.EnvironmentName); - }) + builder + .Host + .UseSerilog() .ConfigureServices(services => ConfigureServices(services, builder)); var app = BuildWebApplication(builder); diff --git a/src/UL.UI.Web/loggingSettings.json b/src/UL.UI.Web/loggingSettings.json index 64c6f56..03ce319 100644 --- a/src/UL.UI.Web/loggingSettings.json +++ b/src/UL.UI.Web/loggingSettings.json @@ -1,6 +1,6 @@ { "Serilog": { - "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Async", "Serilog.Sinks.ApplicationInsights" ], + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Async" ], "MinimumLevel": { "Default": "Information", "Override": { @@ -17,13 +17,6 @@ "Args": { "outputTemplate": "\"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}" } - }, - { - "Name": "ApplicationInsights", - "Args": { - "telemetryConverter": "Serilog.Sinks.ApplicationInsights.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights", - "outputTemplate": "[{Component}|{MachineName}|{ThreadId}] {Timestamp:yyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] <{SourceContext}> {Message:lj}{NewLine}{Exception}" - } } ], "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], diff --git a/src/Ul.Services/FizzBuzzService.cs b/src/Ul.Services/FizzBuzzService.cs index b174907..84e8a45 100644 --- a/src/Ul.Services/FizzBuzzService.cs +++ b/src/Ul.Services/FizzBuzzService.cs @@ -6,11 +6,16 @@ namespace UL.Services { + using System; using System.Collections.Generic; + using System.Linq; using System.Text; using Ardalis.GuardClauses; + using FluentValidation; using Microsoft.Extensions.Logging; using UL.Abstractions.Interfaces; + using UL.Core.Requests; + using UL.Core.Validators; /// /// The fizzbuzz service. @@ -18,22 +23,21 @@ namespace UL.Services public class FizzBuzzService : IFizzBuzzService { private readonly IEnumerable strategies; + private readonly ILogger logger; /// /// Initializes a new instance of the class. /// /// A list of - public FizzBuzzService(IEnumerable strategies) + /// The logger. + public FizzBuzzService(IEnumerable strategies, ILogger logger) { this.strategies = strategies; + this.logger = logger; } - /// - /// Gets the fizzbuzz list of results. - /// - /// The collection of numbers to check. - /// A List of . - public IList GetFizzBuzzList(IEnumerable collection) + /// + public IEnumerable GetFizzBuzzList(IEnumerable collection) { Guard.Against.NullOrEmpty(collection, nameof(collection)); @@ -56,5 +60,22 @@ public IList GetFizzBuzzList(IEnumerable collection) return list; } + + /// + public IEnumerable GetFizzBuzzList(FizzBuzzRequest request) + { + Guard.Against.Null(request, nameof(request)); + + var validator = new FizzBuzzRequestValidator(); + var result = validator.Validate(request); + + if (result.IsValid) + { + var list = Enumerable.Range(Convert.ToInt32(request.Start), Convert.ToInt32(request.End)).ToList(); + return this.GetFizzBuzzList(list); + } + + throw new ValidationException(result.ToString()); + } } } \ No newline at end of file