Skip to content

Commit

Permalink
Ardalis/warnings as errors (DevBetterCom#73)
Browse files Browse the repository at this point in the history
* Adding warnings as errors to test project

* Adding email notification for new member creation
  • Loading branch information
ardalis authored Mar 13, 2020
1 parent b95b33a commit 2b6d13f
Show file tree
Hide file tree
Showing 19 changed files with 95 additions and 33 deletions.
1 change: 0 additions & 1 deletion DevBetterWeb.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/DevBetterWeb.Core/Entities/Member.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class Member : BaseEntity
{
private Member()
{
UserId = "";
}

/// <summary>
Expand All @@ -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; }
Expand Down
3 changes: 2 additions & 1 deletion src/DevBetterWeb.Core/Interfaces/IDomainEventDispatcher.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using DevBetterWeb.Core.SharedKernel;
using System.Threading.Tasks;

namespace DevBetterWeb.Core.Interfaces
{
public interface IDomainEventDispatcher
{
void Dispatch<TEvent>(TEvent domainEvent) where TEvent : BaseDomainEvent;
Task Dispatch(BaseDomainEvent domainEvent);
}
}
3 changes: 2 additions & 1 deletion src/DevBetterWeb.Core/Interfaces/IHandle.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using DevBetterWeb.Core.SharedKernel;
using System.Threading.Tasks;

namespace DevBetterWeb.Core.Interfaces
{
public interface IHandle<T> where T : BaseDomainEvent
{
void Handle(T domainEvent);
Task Handle(T domainEvent);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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>(TEvent domainEvent) where TEvent : BaseDomainEvent
public async Task Dispatch(BaseDomainEvent domainEvent)
{
var handlers = _scope.Resolve<IEnumerable<IHandle<TEvent>>>().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<DomainEventHandler> 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<DomainEventHandler> wrappedHandlers = handlers.Cast<object>()
.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<T> : DomainEventHandler
public class DomainEventHandler<T> : DomainEventHandler
where T : BaseDomainEvent
{
private readonly IHandle<T> _handler;
Expand All @@ -39,9 +57,9 @@ public DomainEventHandler(IHandle<T> handler)
_handler = handler;
}

public override void Handle(BaseDomainEvent domainEvent)
public override Task Handle(BaseDomainEvent domainEvent)
{
_handler.Handle((T)domainEvent);
return _handler.Handle((T)domainEvent);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<NewMemberCreatedEvent>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IEmailService _emailService;

public NotifyOnNewMemberCreatedHandler(UserManager<ApplicationUser> 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);
}
}
}
}
4 changes: 2 additions & 2 deletions src/DevBetterWeb.Web/Pages/Admin/Role.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task<IActionResult> OnPostAddUserToRoleAsync(string userId, string
}

await _userManager.AddToRoleAsync(user, role.Name);
return RedirectToPage("./Role", new { roleId = roleId });
return RedirectToPage("./Role", new { roleId });
}

public async Task<IActionResult> OnPostRemoveUserFromRole(string userId, string roleId)
Expand All @@ -74,7 +74,7 @@ public async Task<IActionResult> OnPostRemoveUserFromRole(string userId, string
}

await _userManager.RemoveFromRoleAsync(user, role.Name);
return RedirectToPage("./Role", new { roleId = roleId });
return RedirectToPage("./Role", new { roleId });
}
}
}
2 changes: 1 addition & 1 deletion src/DevBetterWeb.Web/Pages/User/Index.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public IndexModel(UserManager<ApplicationUser> userManager,
_userManager = userManager;
_appDbContext = appDbContext;
}

public async Task OnGet()
{
var usersInRole = await _userManager.GetUsersInRoleAsync(AuthConstants.Roles.MEMBERS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public void ThrowsArgumentNullExceptionGivenNullQuestion()
{
var video = new ArchiveVideo();

var exception = Assert.Throws<ArgumentNullException>(() => video.AddQuestion(null));
var exception = Assert.Throws<ArgumentNullException>(() => video.AddQuestion(null!));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions tests/DevBetterWeb.Tests/DevBetterWeb.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>

<ItemGroup>
<None Update="xunit.runner.json">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace DevBetterWeb.Tests.Integration.Data
{
public class EfRepositoryShould
{
private AppDbContext _dbContext;
private AppDbContext? _dbContext;

private static DbContextOptions<AppDbContext> CreateNewContextOptions()
{
Expand Down
4 changes: 3 additions & 1 deletion tests/DevBetterWeb.Tests/NoOpDomainEventDispatcher.cs
Original file line number Diff line number Diff line change
@@ -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>(TEvent domainEvent) where TEvent : BaseDomainEvent
public Task Dispatch(BaseDomainEvent domainEvent)
{
return Task.CompletedTask;
}
}
}

0 comments on commit 2b6d13f

Please sign in to comment.