From 2b6d13f7f777e0834d5cc2a105e14b294d090569 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Fri, 13 Mar 2020 12:52:02 -0700 Subject: [PATCH] Ardalis/warnings as errors (#73) * Adding warnings as errors to test project * Adding email notification for new member creation --- DevBetterWeb.sln | 1 - README.txt | 2 + src/DevBetterWeb.Core/Entities/Member.cs | 3 +- .../Interfaces/IDomainEventDispatcher.cs | 3 +- src/DevBetterWeb.Core/Interfaces/IHandle.cs | 3 +- .../DomainEvents/DomainEventDispatcher.cs | 40 ++++++++++++++----- .../NotifyOnNewMemberCreatedHandler.cs | 34 ++++++++++++++++ .../Pages/Admin/Role.cshtml.cs | 4 +- .../Pages/User/Index.cshtml.cs | 2 +- .../ArchiveVideoAddQuestion.cs | 2 +- .../Entities/MemberTests/MemberHelpers.cs | 1 + .../Core/Entities/MemberTests/MemberNew.cs | 2 +- .../MemberTests/MemberUpdateAboutInfo.cs | 6 +-- .../MemberTests/MemberUpdateAddress.cs | 5 +-- .../Entities/MemberTests/MemberUpdateLinks.cs | 4 +- .../Entities/MemberTests/MemberUpdateName.cs | 6 +-- .../DevBetterWeb.Tests.csproj | 4 ++ .../Integration/Data/EfRepositoryShould.cs | 2 +- .../NoOpDomainEventDispatcher.cs | 4 +- 19 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 src/DevBetterWeb.Infrastructure/Services/NotifyOnNewMemberCreatedHandler.cs diff --git a/DevBetterWeb.sln b/DevBetterWeb.sln index 65518a92b..4c4f72a29 100644 --- a/DevBetterWeb.sln +++ b/DevBetterWeb.sln @@ -17,7 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevBetterWeb.Tests", "tests EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "devops", "devops", "{5767F633-33B6-42DE-B19D-7BB11F0124BE}" ProjectSection(SolutionItems) = preProject - azure-pipelines.yml = azure-pipelines.yml README.txt = README.txt EndProjectSection EndProject diff --git a/README.txt b/README.txt index 7013b255d..771d208d7 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,5 @@ +NOTE (13 March 2020): We're not using containers currently, nor Azure DevOps. We're using GitHub Actions and Azure App Service. + Azure Container Registry Instructions devbetterregistry.azurecr.io diff --git a/src/DevBetterWeb.Core/Entities/Member.cs b/src/DevBetterWeb.Core/Entities/Member.cs index 82ca19015..e7781e67c 100644 --- a/src/DevBetterWeb.Core/Entities/Member.cs +++ b/src/DevBetterWeb.Core/Entities/Member.cs @@ -8,6 +8,7 @@ public class Member : BaseEntity { private Member() { + UserId = ""; } /// @@ -21,7 +22,7 @@ internal Member(string userId) Events.Add(new NewMemberCreatedEvent(this)); } - public string? UserId { get; private set; } + public string UserId { get; private set; } public string? FirstName { get; private set; } public string? LastName { get; private set; } public string? AboutInfo { get; private set; } diff --git a/src/DevBetterWeb.Core/Interfaces/IDomainEventDispatcher.cs b/src/DevBetterWeb.Core/Interfaces/IDomainEventDispatcher.cs index 6eaf7049f..ffcae78b6 100644 --- a/src/DevBetterWeb.Core/Interfaces/IDomainEventDispatcher.cs +++ b/src/DevBetterWeb.Core/Interfaces/IDomainEventDispatcher.cs @@ -1,9 +1,10 @@ using DevBetterWeb.Core.SharedKernel; +using System.Threading.Tasks; namespace DevBetterWeb.Core.Interfaces { public interface IDomainEventDispatcher { - void Dispatch(TEvent domainEvent) where TEvent : BaseDomainEvent; + Task Dispatch(BaseDomainEvent domainEvent); } } \ No newline at end of file diff --git a/src/DevBetterWeb.Core/Interfaces/IHandle.cs b/src/DevBetterWeb.Core/Interfaces/IHandle.cs index 89cd53d4c..c2ae9d6c9 100644 --- a/src/DevBetterWeb.Core/Interfaces/IHandle.cs +++ b/src/DevBetterWeb.Core/Interfaces/IHandle.cs @@ -1,9 +1,10 @@ using DevBetterWeb.Core.SharedKernel; +using System.Threading.Tasks; namespace DevBetterWeb.Core.Interfaces { public interface IHandle where T : BaseDomainEvent { - void Handle(T domainEvent); + Task Handle(T domainEvent); } } \ No newline at end of file diff --git a/src/DevBetterWeb.Infrastructure/DomainEvents/DomainEventDispatcher.cs b/src/DevBetterWeb.Infrastructure/DomainEvents/DomainEventDispatcher.cs index e4262ad59..9b9a0f767 100644 --- a/src/DevBetterWeb.Infrastructure/DomainEvents/DomainEventDispatcher.cs +++ b/src/DevBetterWeb.Infrastructure/DomainEvents/DomainEventDispatcher.cs @@ -1,6 +1,8 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Autofac; using DevBetterWeb.Core.Interfaces; using DevBetterWeb.Core.SharedKernel; @@ -11,25 +13,41 @@ namespace DevBetterWeb.Infrastructure.DomainEvents // http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/ public class DomainEventDispatcher : IDomainEventDispatcher { - private readonly ILifetimeScope _scope; + private readonly IComponentContext _container; - public DomainEventDispatcher(ILifetimeScope scope) + public DomainEventDispatcher(IComponentContext container) { - _scope = scope; + _container = container; } - public void Dispatch(TEvent domainEvent) where TEvent : BaseDomainEvent + public async Task Dispatch(BaseDomainEvent domainEvent) { - var handlers = _scope.Resolve>>().ToList(); - handlers.ForEach(handler => handler.Handle(domainEvent)); + var wrappedHandlers = GetWrappedHandlers(domainEvent); + + foreach (DomainEventHandler handler in wrappedHandlers) + { + await handler.Handle(domainEvent).ConfigureAwait(false); + } + } + +#nullable disable + public IEnumerable GetWrappedHandlers(BaseDomainEvent domainEvent) + { + Type handlerType = typeof(IHandle<>).MakeGenericType(domainEvent.GetType()); + Type wrapperType = typeof(DomainEventHandler<>).MakeGenericType(domainEvent.GetType()); + IEnumerable handlers = (IEnumerable)_container.Resolve(typeof(IEnumerable<>).MakeGenericType(handlerType)); + IEnumerable wrappedHandlers = handlers.Cast() + .Select(handler => (DomainEventHandler)Activator.CreateInstance(wrapperType, handler)); +#nullable enable + return wrappedHandlers; } - private abstract class DomainEventHandler + public abstract class DomainEventHandler { - public abstract void Handle(BaseDomainEvent domainEvent); + public abstract Task Handle(BaseDomainEvent domainEvent); } - private class DomainEventHandler : DomainEventHandler + public class DomainEventHandler : DomainEventHandler where T : BaseDomainEvent { private readonly IHandle _handler; @@ -39,9 +57,9 @@ public DomainEventHandler(IHandle handler) _handler = handler; } - public override void Handle(BaseDomainEvent domainEvent) + public override Task Handle(BaseDomainEvent domainEvent) { - _handler.Handle((T)domainEvent); + return _handler.Handle((T)domainEvent); } } } diff --git a/src/DevBetterWeb.Infrastructure/Services/NotifyOnNewMemberCreatedHandler.cs b/src/DevBetterWeb.Infrastructure/Services/NotifyOnNewMemberCreatedHandler.cs new file mode 100644 index 000000000..c108fe17e --- /dev/null +++ b/src/DevBetterWeb.Infrastructure/Services/NotifyOnNewMemberCreatedHandler.cs @@ -0,0 +1,34 @@ +using DevBetterWeb.Core; +using DevBetterWeb.Core.Events; +using DevBetterWeb.Core.Interfaces; +using DevBetterWeb.Web.Areas.Identity.Data; +using Microsoft.AspNetCore.Identity; +using System.Linq; +using System.Threading.Tasks; + +namespace DevBetterWeb.Infrastructure.Services +{ + public class NotifyOnNewMemberCreatedHandler : IHandle + { + private readonly UserManager _userManager; + private readonly IEmailService _emailService; + + public NotifyOnNewMemberCreatedHandler(UserManager userManager, + IEmailService emailService) + { + _userManager = userManager; + _emailService = emailService; + } + public async Task Handle(NewMemberCreatedEvent domainEvent) + { + var usersInAdminRole = await _userManager.GetUsersInRoleAsync(AuthConstants.Roles.ADMINISTRATORS); + + foreach(var emailAddress in usersInAdminRole.Select(user => user.Email)) + { + string subject = $"[devBetter] New Member {domainEvent.Member.UserFullName()}"; + string message = "A new Member has signed up and added their membership profile."; // TODO: Add more member details to email + await _emailService.SendEmailAsync(emailAddress, subject, message); + } + } + } +} diff --git a/src/DevBetterWeb.Web/Pages/Admin/Role.cshtml.cs b/src/DevBetterWeb.Web/Pages/Admin/Role.cshtml.cs index 99ea44f95..4c1126798 100644 --- a/src/DevBetterWeb.Web/Pages/Admin/Role.cshtml.cs +++ b/src/DevBetterWeb.Web/Pages/Admin/Role.cshtml.cs @@ -60,7 +60,7 @@ public async Task OnPostAddUserToRoleAsync(string userId, string } await _userManager.AddToRoleAsync(user, role.Name); - return RedirectToPage("./Role", new { roleId = roleId }); + return RedirectToPage("./Role", new { roleId }); } public async Task OnPostRemoveUserFromRole(string userId, string roleId) @@ -74,7 +74,7 @@ public async Task OnPostRemoveUserFromRole(string userId, string } await _userManager.RemoveFromRoleAsync(user, role.Name); - return RedirectToPage("./Role", new { roleId = roleId }); + return RedirectToPage("./Role", new { roleId }); } } } \ No newline at end of file diff --git a/src/DevBetterWeb.Web/Pages/User/Index.cshtml.cs b/src/DevBetterWeb.Web/Pages/User/Index.cshtml.cs index c0b279406..75a18bab6 100644 --- a/src/DevBetterWeb.Web/Pages/User/Index.cshtml.cs +++ b/src/DevBetterWeb.Web/Pages/User/Index.cshtml.cs @@ -25,7 +25,7 @@ public IndexModel(UserManager userManager, _userManager = userManager; _appDbContext = appDbContext; } - + public async Task OnGet() { var usersInRole = await _userManager.GetUsersInRoleAsync(AuthConstants.Roles.MEMBERS); diff --git a/tests/DevBetterWeb.Tests/Core/Entities/ArchiveVideoTests/ArchiveVideoAddQuestion.cs b/tests/DevBetterWeb.Tests/Core/Entities/ArchiveVideoTests/ArchiveVideoAddQuestion.cs index 7135fd4a0..9ed35f883 100644 --- a/tests/DevBetterWeb.Tests/Core/Entities/ArchiveVideoTests/ArchiveVideoAddQuestion.cs +++ b/tests/DevBetterWeb.Tests/Core/Entities/ArchiveVideoTests/ArchiveVideoAddQuestion.cs @@ -12,7 +12,7 @@ public void ThrowsArgumentNullExceptionGivenNullQuestion() { var video = new ArchiveVideo(); - var exception = Assert.Throws(() => video.AddQuestion(null)); + var exception = Assert.Throws(() => video.AddQuestion(null!)); } [Fact] diff --git a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberHelpers.cs b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberHelpers.cs index e26a16542..dca88a54f 100644 --- a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberHelpers.cs +++ b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberHelpers.cs @@ -8,6 +8,7 @@ public static class MemberHelpers { public const string TEST_USER_ID = "TestUserId"; +#nullable disable public static Member CreateWithDefaultConstructor() { return (Member)Activator.CreateInstance(typeof(Member), true); diff --git a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberNew.cs b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberNew.cs index 1830061cc..53c8b18db 100644 --- a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberNew.cs +++ b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberNew.cs @@ -35,7 +35,7 @@ public void WithUserIdRecordsNewMemberCreatedEvent() { var member = MemberHelpers.CreateWithInternalConstructor(); - var eventCreated = member.Events.FirstOrDefault() as NewMemberCreatedEvent; + var eventCreated = (NewMemberCreatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); } diff --git a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAboutInfo.cs b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAboutInfo.cs index b05d43780..cbdfaf678 100644 --- a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAboutInfo.cs +++ b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAboutInfo.cs @@ -14,7 +14,7 @@ private Member GetMemberWithDefaultAboutInfo() { _initialAboutInfo = Guid.NewGuid().ToString(); - var member = MemberHelpers.CreateWithDefaultConstructor(); + Member? member = MemberHelpers.CreateWithDefaultConstructor(); member.UpdateAboutInfo(_initialAboutInfo); member.Events.Clear(); @@ -39,7 +39,7 @@ public void RecordsEventIfAboutInfoChanges() var member = GetMemberWithDefaultAboutInfo(); member.UpdateAboutInfo(newAboutInfo); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("AboutInfo", eventCreated.UpdateDetails); @@ -62,7 +62,7 @@ public void RecordsEventWithAppendedDetailsIfOtherThingsChanged() var member = GetMemberWithDefaultAboutInfo(); member.UpdateName("kylo", "ren"); member.UpdateAboutInfo(newAboutInfo); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Name,AboutInfo", eventCreated.UpdateDetails); diff --git a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAddress.cs b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAddress.cs index ba36347f4..86dea1f40 100644 --- a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAddress.cs +++ b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateAddress.cs @@ -39,7 +39,7 @@ public void RecordsEventIfAddressChanges() var member = GetMemberWithDefaultAddress(); member.UpdateAddress(newAddress); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Address", eventCreated.UpdateDetails); @@ -62,11 +62,10 @@ public void RecordsEventWithAppendedDetailsIfOtherThingsChanged() var member = GetMemberWithDefaultAddress(); member.UpdateName("kylo", "ren"); member.UpdateAddress(newAddress); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Name,Address", eventCreated.UpdateDetails); } - } } diff --git a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateLinks.cs b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateLinks.cs index a155b19f9..bfc1079b2 100644 --- a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateLinks.cs +++ b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateLinks.cs @@ -59,7 +59,7 @@ public void RecordsEventIfLinksChanges() _initialOtherUrl, _initialTwitchUrl, _initialTwitterUrl); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Links", eventCreated.UpdateDetails); @@ -94,7 +94,7 @@ public void RecordsEventWithAppendedDetailsIfOtherThingsChanged() _initialOtherUrl, _initialTwitchUrl, _initialTwitterUrl); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Name,AboutInfo,Address,Links", eventCreated.UpdateDetails); diff --git a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateName.cs b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateName.cs index d87b5ee2d..99ef3ccc4 100644 --- a/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateName.cs +++ b/tests/DevBetterWeb.Tests/Core/Entities/MemberTests/MemberUpdateName.cs @@ -44,7 +44,7 @@ public void RecordsEventIfFirstNameChanges() var member = GetMemberWithDefaultName(); member.UpdateName(newFirstName, _initialLastName); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Name", eventCreated.UpdateDetails); @@ -57,7 +57,7 @@ public void RecordsEventIfLastNameChanges() var member = GetMemberWithDefaultName(); member.UpdateName(_initialFirstName, newLastName); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Name", eventCreated.UpdateDetails); @@ -80,7 +80,7 @@ public void RecordsEventWithAppendedDetailsIfOtherThingsChanged() var member = GetMemberWithDefaultName(); member.UpdateAddress("new address"); member.UpdateName(_initialFirstName, newLastName); - var eventCreated = member.Events.FirstOrDefault() as MemberUpdatedEvent; + var eventCreated = (MemberUpdatedEvent)member.Events.First(); Assert.Same(member, eventCreated.Member); Assert.Equal("Address,Name", eventCreated.UpdateDetails); diff --git a/tests/DevBetterWeb.Tests/DevBetterWeb.Tests.csproj b/tests/DevBetterWeb.Tests/DevBetterWeb.Tests.csproj index 7c2f8ecd0..bf9c8d218 100644 --- a/tests/DevBetterWeb.Tests/DevBetterWeb.Tests.csproj +++ b/tests/DevBetterWeb.Tests/DevBetterWeb.Tests.csproj @@ -5,6 +5,10 @@ latest enable + + true + + diff --git a/tests/DevBetterWeb.Tests/Integration/Data/EfRepositoryShould.cs b/tests/DevBetterWeb.Tests/Integration/Data/EfRepositoryShould.cs index 680d93af5..73e2ca1f6 100644 --- a/tests/DevBetterWeb.Tests/Integration/Data/EfRepositoryShould.cs +++ b/tests/DevBetterWeb.Tests/Integration/Data/EfRepositoryShould.cs @@ -12,7 +12,7 @@ namespace DevBetterWeb.Tests.Integration.Data { public class EfRepositoryShould { - private AppDbContext _dbContext; + private AppDbContext? _dbContext; private static DbContextOptions CreateNewContextOptions() { diff --git a/tests/DevBetterWeb.Tests/NoOpDomainEventDispatcher.cs b/tests/DevBetterWeb.Tests/NoOpDomainEventDispatcher.cs index 3213e1acd..a338e0ea2 100644 --- a/tests/DevBetterWeb.Tests/NoOpDomainEventDispatcher.cs +++ b/tests/DevBetterWeb.Tests/NoOpDomainEventDispatcher.cs @@ -1,12 +1,14 @@ using DevBetterWeb.Core.Interfaces; using DevBetterWeb.Core.SharedKernel; +using System.Threading.Tasks; namespace DevBetterWeb.Tests { public class NoOpDomainEventDispatcher : IDomainEventDispatcher { - public void Dispatch(TEvent domainEvent) where TEvent : BaseDomainEvent + public Task Dispatch(BaseDomainEvent domainEvent) { + return Task.CompletedTask; } } }