From 42fe8a1ebd326c99156010b0774e1937abda70b0 Mon Sep 17 00:00:00 2001 From: James Randall Date: Sat, 11 Jan 2020 11:42:03 +0100 Subject: [PATCH] Tested MediatR support with a sample --- Directory.Build.props | 2 +- FunctionMonkey.sln | 7 + .../MediatRSample/.gitignore | 264 ++++++++++++++++++ .../Commands/CreateToDoItemCommand.cs | 10 + .../Commands/GetAllToDoItemsQuery.cs | 11 + .../Commands/MarkToDoItemCompleteCommand.cs | 10 + .../MediatRSample/FunctionAppConfiguration.cs | 32 +++ .../Handlers/CreateToDoItemCommandHandler.cs | 24 ++ .../Handlers/GetAllToDoItemsQueryHandler.cs | 25 ++ .../MarkToDoItemCompleteCommandHandler.cs | 23 ++ .../MediatRSample/MediatRSample.csproj | 24 ++ .../MediatRSample/Models/ToDoItem.cs | 13 + .../MediatRSample/Services/IRepository.cs | 16 ++ .../MediatRSample/Services/Repository.cs | 39 +++ .../MediatRSample/host.json | 8 + .../FunctionMonkey.Compiler.nuspec | 2 +- .../MediatRResultTypeExtractor.cs | 4 +- .../MediatRTypeSafetyEnforcer.cs | 2 +- 18 files changed, 511 insertions(+), 5 deletions(-) create mode 100644 Samples/DocumentationSamples/MediatRSample/.gitignore create mode 100644 Samples/DocumentationSamples/MediatRSample/Commands/CreateToDoItemCommand.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/Commands/GetAllToDoItemsQuery.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/Commands/MarkToDoItemCompleteCommand.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/FunctionAppConfiguration.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/Handlers/CreateToDoItemCommandHandler.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/Handlers/GetAllToDoItemsQueryHandler.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/Handlers/MarkToDoItemCompleteCommandHandler.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/MediatRSample.csproj create mode 100644 Samples/DocumentationSamples/MediatRSample/Models/ToDoItem.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/Services/IRepository.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/Services/Repository.cs create mode 100644 Samples/DocumentationSamples/MediatRSample/host.json diff --git a/Directory.Build.props b/Directory.Build.props index 642970ed..d532dab0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 4.0.37-beta.3 + 4.0.38-beta.4 \ No newline at end of file diff --git a/FunctionMonkey.sln b/FunctionMonkey.sln index ebd58e52..94bc7c46 100644 --- a/FunctionMonkey.sln +++ b/FunctionMonkey.sln @@ -89,6 +89,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionMonkey.Tests.Integr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionMonkey.MediatR", "Source\FunctionMonkey.MediatR\FunctionMonkey.MediatR.csproj", "{39738BA3-AA8F-4F6D-9864-2C0839584CC0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatRSample", "Samples\DocumentationSamples\MediatRSample\MediatRSample.csproj", "{C693B34C-DA5E-4C49-8016-5E94F4FA43B1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -232,6 +234,10 @@ Global {39738BA3-AA8F-4F6D-9864-2C0839584CC0}.Debug|Any CPU.Build.0 = Debug|Any CPU {39738BA3-AA8F-4F6D-9864-2C0839584CC0}.Release|Any CPU.ActiveCfg = Release|Any CPU {39738BA3-AA8F-4F6D-9864-2C0839584CC0}.Release|Any CPU.Build.0 = Release|Any CPU + {C693B34C-DA5E-4C49-8016-5E94F4FA43B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C693B34C-DA5E-4C49-8016-5E94F4FA43B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C693B34C-DA5E-4C49-8016-5E94F4FA43B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C693B34C-DA5E-4C49-8016-5E94F4FA43B1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -259,6 +265,7 @@ Global {FFC8BE94-890B-422D-BB64-6CCCAD515B0C} = {4C5AD0FE-C42D-46CD-ADF3-D51356E04F6B} {BD36C0F8-2A2F-4A95-A8BA-D7D5F2C9808A} = {68C555D0-6537-4D5B-B36C-E62F34F5E890} {B61847C6-76D3-43D9-B640-F62DD20905DB} = {68C555D0-6537-4D5B-B36C-E62F34F5E890} + {C693B34C-DA5E-4C49-8016-5E94F4FA43B1} = {CB492EF3-6255-4528-8978-0D0974B7F081} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C9ABA812-671D-4486-B9D8-4F16D4BE11BD} diff --git a/Samples/DocumentationSamples/MediatRSample/.gitignore b/Samples/DocumentationSamples/MediatRSample/.gitignore new file mode 100644 index 00000000..ff5b00c5 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Commands/CreateToDoItemCommand.cs b/Samples/DocumentationSamples/MediatRSample/Commands/CreateToDoItemCommand.cs new file mode 100644 index 00000000..1f21c1c7 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Commands/CreateToDoItemCommand.cs @@ -0,0 +1,10 @@ +using MediatR; +using MediatRSample.Models; + +namespace ToDo.Application.Commands +{ + public class CreateToDoItemCommand : IRequest + { + public string Title { get; set; } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Commands/GetAllToDoItemsQuery.cs b/Samples/DocumentationSamples/MediatRSample/Commands/GetAllToDoItemsQuery.cs new file mode 100644 index 00000000..4510e4ba --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Commands/GetAllToDoItemsQuery.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using MediatR; +using MediatRSample.Models; + +namespace ToDo.Application.Commands +{ + public class GetAllToDoItemsQuery : IRequest> + { + + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Commands/MarkToDoItemCompleteCommand.cs b/Samples/DocumentationSamples/MediatRSample/Commands/MarkToDoItemCompleteCommand.cs new file mode 100644 index 00000000..2fb98b15 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Commands/MarkToDoItemCompleteCommand.cs @@ -0,0 +1,10 @@ +using System; +using MediatR; + +namespace ToDo.Application.Commands +{ + public class MarkToDoItemCompleteCommand : INotification + { + public Guid Id { get; set; } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/FunctionAppConfiguration.cs b/Samples/DocumentationSamples/MediatRSample/FunctionAppConfiguration.cs new file mode 100644 index 00000000..26d271c3 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/FunctionAppConfiguration.cs @@ -0,0 +1,32 @@ +using System.Net.Http; +using FunctionMonkey.Abstractions; +using FunctionMonkey.Abstractions.Builders; +using FunctionMonkey.MediatR; +using MediatR; +using MediatRSample.Services; +using Microsoft.Extensions.DependencyInjection; +using ToDo.Application.Commands; +using ToDo.Application.Services; + +namespace MediatRSample +{ + public class FunctionAppConfiguration : IFunctionAppConfiguration + { + public void Build(IFunctionHostBuilder builder) + { + builder + .Setup(sc => sc + .AddMediatR(typeof(FunctionAppConfiguration).Assembly) + .AddSingleton() + ) + .UseMediatR() + .Functions(functions => functions + .HttpRoute("todo", route => route + .HttpFunction(HttpMethod.Get) + .HttpFunction("complete", HttpMethod.Put) + .HttpFunction(HttpMethod.Post) + ) + ); + } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Handlers/CreateToDoItemCommandHandler.cs b/Samples/DocumentationSamples/MediatRSample/Handlers/CreateToDoItemCommandHandler.cs new file mode 100644 index 00000000..8f008eae --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Handlers/CreateToDoItemCommandHandler.cs @@ -0,0 +1,24 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using MediatRSample.Models; +using MediatRSample.Services; +using ToDo.Application.Commands; + +namespace MediatRSample.Handlers +{ + internal class CreateToDoItemCommandHandler : IRequestHandler + { + private readonly IRepository _repository; + + public CreateToDoItemCommandHandler(IRepository repository) + { + _repository = repository; + } + + public Task Handle(CreateToDoItemCommand request, CancellationToken cancellationToken) + { + return _repository.Create(request.Title); + } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Handlers/GetAllToDoItemsQueryHandler.cs b/Samples/DocumentationSamples/MediatRSample/Handlers/GetAllToDoItemsQueryHandler.cs new file mode 100644 index 00000000..ca4f0d80 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Handlers/GetAllToDoItemsQueryHandler.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using MediatRSample.Models; +using MediatRSample.Services; +using ToDo.Application.Commands; + +namespace MediatRSample.Handlers +{ + internal class GetAllToDoItemsQueryHandler : IRequestHandler> + { + private readonly IRepository _repository; + + public GetAllToDoItemsQueryHandler(IRepository repository) + { + _repository = repository; + } + + public Task> Handle(GetAllToDoItemsQuery request, CancellationToken cancellationToken) + { + return _repository.GetAll(); + } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Handlers/MarkToDoItemCompleteCommandHandler.cs b/Samples/DocumentationSamples/MediatRSample/Handlers/MarkToDoItemCompleteCommandHandler.cs new file mode 100644 index 00000000..af4ddc02 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Handlers/MarkToDoItemCompleteCommandHandler.cs @@ -0,0 +1,23 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using MediatRSample.Services; +using ToDo.Application.Commands; + +namespace MediatRSample.Handlers +{ + internal class MarkToDoItemCompleteCommandHandler : INotificationHandler + { + private readonly IRepository _repository; + + public MarkToDoItemCompleteCommandHandler(IRepository repository) + { + _repository = repository; + } + + public Task Handle(MarkToDoItemCompleteCommand notification, CancellationToken cancellationToken) + { + return _repository.MarkComplete(notification.Id); + } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/MediatRSample.csproj b/Samples/DocumentationSamples/MediatRSample/MediatRSample.csproj new file mode 100644 index 00000000..9ab34687 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/MediatRSample.csproj @@ -0,0 +1,24 @@ + + + netcoreapp3.1 + V3 + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + + \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Models/ToDoItem.cs b/Samples/DocumentationSamples/MediatRSample/Models/ToDoItem.cs new file mode 100644 index 00000000..e6bb3eae --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Models/ToDoItem.cs @@ -0,0 +1,13 @@ +using System; + +namespace MediatRSample.Models +{ + public class ToDoItem + { + public Guid Id { get; set; } + + public string Title { get; set; } + + public bool IsComplete { get; set; } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Services/IRepository.cs b/Samples/DocumentationSamples/MediatRSample/Services/IRepository.cs new file mode 100644 index 00000000..bece517b --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Services/IRepository.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MediatRSample.Models; + +namespace MediatRSample.Services +{ + internal interface IRepository + { + Task Create(string title); + + Task MarkComplete(Guid id); + + Task> GetAll(); + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/Services/Repository.cs b/Samples/DocumentationSamples/MediatRSample/Services/Repository.cs new file mode 100644 index 00000000..f8722548 --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/Services/Repository.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediatRSample.Models; +using MediatRSample.Services; + +namespace ToDo.Application.Services +{ + internal class Repository : IRepository + { + private readonly List _items = new List(); + + public Task Create(string title) + { + ToDoItem newItem = new ToDoItem() + { + Title = title, + Id = Guid.NewGuid(), + IsComplete = false + }; + _items.Add(newItem); + return Task.FromResult(newItem); + } + + public Task MarkComplete(Guid id) + { + ToDoItem item = _items.Single(x => x.Id == id); + item.IsComplete = true; + return Task.CompletedTask; + } + + public Task> GetAll() + { + IReadOnlyCollection items = _items; + return Task.FromResult(items); + } + } +} \ No newline at end of file diff --git a/Samples/DocumentationSamples/MediatRSample/host.json b/Samples/DocumentationSamples/MediatRSample/host.json new file mode 100644 index 00000000..dc0bbb4d --- /dev/null +++ b/Samples/DocumentationSamples/MediatRSample/host.json @@ -0,0 +1,8 @@ +{ + "version": "2.0", + "extensions": { + "http": { + "routePrefix": "" + } + } +} \ No newline at end of file diff --git a/Source/FunctionMonkey.Compiler/FunctionMonkey.Compiler.nuspec b/Source/FunctionMonkey.Compiler/FunctionMonkey.Compiler.nuspec index cc9b402a..3a922f1e 100644 --- a/Source/FunctionMonkey.Compiler/FunctionMonkey.Compiler.nuspec +++ b/Source/FunctionMonkey.Compiler/FunctionMonkey.Compiler.nuspec @@ -2,7 +2,7 @@ FunctionMonkey.Compiler - 4.0.37-beta.3 + 4.0.38-beta.4 James Randall Generates Azure Functions from command registrations https://raw.githubusercontent.com/JamesRandall/FunctionMonkey/master/LICENSE diff --git a/Source/FunctionMonkey.MediatR/MediatRResultTypeExtractor.cs b/Source/FunctionMonkey.MediatR/MediatRResultTypeExtractor.cs index 3890f63f..1199bd9e 100644 --- a/Source/FunctionMonkey.MediatR/MediatRResultTypeExtractor.cs +++ b/Source/FunctionMonkey.MediatR/MediatRResultTypeExtractor.cs @@ -9,11 +9,11 @@ public class MediatRResultTypeExtractor : IMediatorResultTypeExtractor { public Type CommandResultType(Type commandType) { - Type commandInterface = typeof(IRequest); + Type commandInterface = typeof(IRequest<>); Type[] interfaces = commandType.GetInterfaces(); Type[] minimalInterfaces = interfaces.Except(interfaces.SelectMany(i => i.GetInterfaces())).ToArray(); Type genericCommandInterface = minimalInterfaces - .SingleOrDefault(x => x.IsGenericType && commandInterface.IsAssignableFrom(x)); + .SingleOrDefault(x => x.IsGenericType && commandInterface.IsAssignableFrom(x.GetGenericTypeDefinition())); if (genericCommandInterface != null) { diff --git a/Source/FunctionMonkey.MediatR/MediatRTypeSafetyEnforcer.cs b/Source/FunctionMonkey.MediatR/MediatRTypeSafetyEnforcer.cs index 312f3d99..0a2d0c81 100644 --- a/Source/FunctionMonkey.MediatR/MediatRTypeSafetyEnforcer.cs +++ b/Source/FunctionMonkey.MediatR/MediatRTypeSafetyEnforcer.cs @@ -12,7 +12,7 @@ public bool IsValidType(Type commandType) return commandType.GetInterfaces().Any(x => x == typeof(INotification)) || commandType.GetInterfaces().Any(x => - x.IsGenericTypeDefinition && x.GetGenericTypeDefinition() == typeof(IRequest<>)); + x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequest<>)); } public string Requirements => "MediatR commands must implement INotification or IRequest";