├── .gitignore ├── FunctionalCarterProject ├── Features │ └── NamedDelegatesFilms │ │ ├── CastMembers │ │ └── GetCastByFilmIdQuery.cs │ │ ├── Directors │ │ └── GetDirectorByIdQuery.cs │ │ └── Films │ │ ├── CreateFilm │ │ └── CreateFilmRoute.cs │ │ ├── Delegates.cs │ │ ├── DeleteFilm │ │ └── DeleteFilmRoute.cs │ │ ├── FilmsModule.cs │ │ ├── ListFilmById │ │ └── ListFilmByIdRoute.cs │ │ ├── ListFilms │ │ └── ListFilmsRoute.cs │ │ ├── ListFilmsByIdQuery │ │ └── ListFilmsByIdQuery.cs │ │ ├── Permissions │ │ └── ValidUserQuery.cs │ │ ├── RouteHandlers.cs │ │ └── UpdateFilm │ │ └── UpdateFilmRoute.cs ├── FunctionalCarterProject.csproj ├── Program.cs └── Startup.cs ├── FunctionalCarterProjectTests ├── Features │ └── Films │ │ └── FilmTests.cs └── FunctionalCarterProjectTests.csproj ├── FunctionalProject ├── Features │ ├── FuncFilms │ │ ├── CreateFilm │ │ │ └── CreateFilmRoute.cs │ │ ├── DeleteFilm │ │ │ └── DeleteFilmRoute.cs │ │ ├── FilmsController.cs │ │ ├── ListFilmById │ │ │ └── ListFilmByIdRoute.cs │ │ ├── ListFilms │ │ │ └── ListFilmsRoute.cs │ │ ├── RouteHandlers.cs │ │ └── UpdateFilm │ │ │ └── UpdateFilmRoute.cs │ └── NamedDelegatesFilms │ │ ├── CastMembers │ │ └── GetCastByFilmIdQuery.cs │ │ ├── Directors │ │ └── GetDirectorByIdQuery.cs │ │ └── Films │ │ ├── CreateFilm │ │ └── CreateFilmRoute.cs │ │ ├── Delegates.cs │ │ ├── DeleteFilm │ │ └── DeleteFilmRoute.cs │ │ ├── FilmsController.cs │ │ ├── ListFilmById │ │ └── ListFilmByIdRoute.cs │ │ ├── ListFilms │ │ └── ListFilmsRoute.cs │ │ ├── ListFilmsByIdQuery │ │ └── ListFilmsByIdQuery.cs │ │ ├── OoohINeedToDoSomeSpecialLogicJustForThisMethodAttribute.cs │ │ ├── Permissions │ │ └── ValidUserQuery.cs │ │ ├── RouteHandlers.cs │ │ └── UpdateFilm │ │ └── UpdateFilmRoute.cs ├── FunctionalProject.csproj ├── Program.cs └── Startup.cs ├── FunctionalProjectTests ├── Features │ └── Films │ │ └── FilmTests.cs └── FunctionalProjectTests.csproj ├── HandRolledMediator ├── CommandHandler.cs ├── Features │ ├── CastMembers │ │ └── GetCastByFilmIdQuery │ │ │ ├── GetCastByFilmIdQuery.cs │ │ │ └── IGetCastByFilmIdQuery.cs │ ├── Directors │ │ └── GetDirectorByIdQuery │ │ │ ├── GetDirectorByIdQuery.cs │ │ │ └── IGetDirectorByIdQuery.cs │ ├── Films │ │ ├── CreateFilm │ │ │ ├── CreateFilmCommand.cs │ │ │ └── CreateFilmCommandHandler.cs │ │ ├── DeleteFilm │ │ │ ├── DeleteFilmCommand.cs │ │ │ └── DeleteFilmCommandHandler.cs │ │ ├── FilmsController.cs │ │ ├── FilmsModule.cs │ │ ├── ListFilmById │ │ │ ├── ListFilmsByIdCommand.cs │ │ │ └── ListFilmsByIdCommandHandler.cs │ │ ├── ListFilmByIdQuery │ │ │ ├── IListFilmByIdQuery.cs │ │ │ └── ListFilmByIdQuery.cs │ │ ├── ListFilms │ │ │ ├── ListFilmsCommand.cs │ │ │ └── ListFilmsCommandHandler.cs │ │ └── UpdateFilm │ │ │ ├── UpdateFilmCommand.cs │ │ │ └── UpdateFilmCommandHandler.cs │ └── Permissions │ │ ├── IValidUserQuery.cs │ │ └── ValidUserQuery.cs ├── HandRolledMediator.csproj ├── Handler.cs ├── ICommandHandler.cs ├── Program.cs └── Startup.cs ├── MediatRWebAPI.Tests ├── Features │ └── Films │ │ ├── CreateFilm │ │ └── CreateFilmMessageHandlerTests.cs │ │ ├── DeleteFilm │ │ └── DeleteFilmMessageHandlerTests.cs │ │ ├── FilmControllerTests.cs │ │ ├── ListFilmById │ │ └── ListFilmsByIdMessageHandlerTests.cs │ │ ├── ListFilms │ │ └── ListFilmsMessageHandlerTests.cs │ │ └── UpdateFilm │ │ └── UpdateFilmMessageHandlerTests.cs └── MediatRWebAPI.Tests.csproj ├── MediatRWebAPI ├── Features │ ├── CastMembers │ │ └── GetCastByFilmIdQuery │ │ │ ├── GetCastByFilmIdQuery.cs │ │ │ └── IGetCastByFilmIdQuery.cs │ ├── Directors │ │ └── GetDirectorByIdQuery │ │ │ ├── GetDirectorByIdQuery.cs │ │ │ └── IGetDirectorByIdQuery.cs │ ├── Films │ │ ├── CreateFilm │ │ │ ├── CreateFilmMessage.cs │ │ │ └── CreateFilmMessageHandler.cs │ │ ├── DeleteFilm │ │ │ ├── DeleteFilmMessage.cs │ │ │ └── DeleteFilmMessageHandler.cs │ │ ├── FilmsController.cs │ │ ├── ListFilmById │ │ │ ├── ListFilmsByIdMessage.cs │ │ │ └── ListFilmsByIdMessageHandler.cs │ │ ├── ListFilmByIdQuery │ │ │ ├── IListFilmByIdQuery.cs │ │ │ └── ListFilmByIdQuery.cs │ │ ├── ListFilms │ │ │ ├── ListFilmsMessage.cs │ │ │ └── ListFilmsMessageHandler.cs │ │ └── UpdateFilm │ │ │ ├── UpdateFilmMessage.cs │ │ │ └── UpdateFilmMessageHandler.cs │ └── Permissions │ │ ├── IValidUserQuery.cs │ │ └── ValidUserQuery.cs ├── MediatRWebAPI.csproj ├── Program.cs ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── Models ├── CastMember.cs ├── Director.cs ├── Film.cs ├── FilmValidator.cs └── Models.csproj ├── README.md ├── Slides.pptx ├── T1000.sln ├── T1000.sln.DotSettings ├── T1000settings.jar ├── TraditionalWebAPI.Tests ├── Controllers │ └── FilmControllerTests.cs ├── Services │ └── FilmServiceTests.cs └── TraditionalWebAPI.Tests.csproj ├── TraditionalWebAPI ├── Controllers │ └── FilmsController.cs ├── Program.cs ├── Repositories │ ├── CastMemberRepository.cs │ ├── DirectorRepository.cs │ ├── FilmRepository.cs │ ├── ICastMemberRepository.cs │ ├── IDirectorRepository.cs │ └── IFilmRepository.cs ├── Services │ ├── CastMemberService.cs │ ├── DirectorService.cs │ ├── FilmService.cs │ ├── ICastMemberService.cs │ ├── IDirectorService.cs │ ├── IFilmService.cs │ ├── IPermissionService.cs │ └── PermissionService.cs ├── Startup.cs ├── TraditionalWebAPI.csproj ├── appsettings.Development.json └── appsettings.json └── settings.jar /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### VisualStudio template 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | ## 6 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # Benchmark Results 48 | BenchmarkDotNet.Artifacts/ 49 | 50 | # .NET Core 51 | project.lock.json 52 | project.fragment.lock.json 53 | artifacts/ 54 | **/Properties/launchSettings.json 55 | 56 | *_i.c 57 | *_p.c 58 | *_i.h 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.svclog 79 | *.scc 80 | 81 | # Chutzpah Test files 82 | _Chutzpah* 83 | 84 | # Visual C++ cache files 85 | ipch/ 86 | *.aps 87 | *.ncb 88 | *.opendb 89 | *.opensdf 90 | *.sdf 91 | *.cachefile 92 | *.VC.db 93 | *.VC.VC.opendb 94 | 95 | # Visual Studio profiler 96 | *.psess 97 | *.vsp 98 | *.vspx 99 | *.sap 100 | 101 | # Visual Studio Trace Files 102 | *.e2e 103 | 104 | # TFS 2012 Local Workspace 105 | $tf/ 106 | 107 | # Guidance Automation Toolkit 108 | *.gpState 109 | 110 | # ReSharper is a .NET coding add-in 111 | _ReSharper*/ 112 | *.[Rr]e[Ss]harper 113 | *.DotSettings.user 114 | 115 | # JustCode is a .NET coding add-in 116 | .JustCode 117 | 118 | # TeamCity is a build add-in 119 | _TeamCity* 120 | 121 | # DotCover is a Code Coverage Tool 122 | *.dotCover 123 | 124 | # AxoCover is a Code Coverage Tool 125 | .axoCover/* 126 | !.axoCover/settings.json 127 | 128 | # Visual Studio code coverage results 129 | *.coverage 130 | *.coveragexml 131 | 132 | # NCrunch 133 | _NCrunch_* 134 | .*crunch*.local.xml 135 | nCrunchTemp_* 136 | 137 | # MightyMoose 138 | *.mm.* 139 | AutoTest.Net/ 140 | 141 | # Web workbench (sass) 142 | .sass-cache/ 143 | 144 | # Installshield output folder 145 | [Ee]xpress/ 146 | 147 | # DocProject is a documentation generator add-in 148 | DocProject/buildhelp/ 149 | DocProject/Help/*.HxT 150 | DocProject/Help/*.HxC 151 | DocProject/Help/*.hhc 152 | DocProject/Help/*.hhk 153 | DocProject/Help/*.hhp 154 | DocProject/Help/Html2 155 | DocProject/Help/html 156 | 157 | # Click-Once directory 158 | publish/ 159 | 160 | # Publish Web Output 161 | *.[Pp]ublish.xml 162 | *.azurePubxml 163 | # Note: Comment the next line if you want to checkin your web deploy settings, 164 | # but database connection strings (with potential passwords) will be unencrypted 165 | *.pubxml 166 | *.publishproj 167 | 168 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 169 | # checkin your Azure Web App publish settings, but sensitive information contained 170 | # in these scripts will be unencrypted 171 | PublishScripts/ 172 | 173 | # NuGet Packages 174 | *.nupkg 175 | # The packages folder can be ignored because of Package Restore 176 | **/[Pp]ackages/* 177 | # except build/, which is used as an MSBuild target. 178 | !**/[Pp]ackages/build/ 179 | # Uncomment if necessary however generally it will be regenerated when needed 180 | #!**/[Pp]ackages/repositories.config 181 | # NuGet v3's project.json files produces more ignorable files 182 | *.nuget.props 183 | *.nuget.targets 184 | 185 | # Microsoft Azure Build Output 186 | csx/ 187 | *.build.csdef 188 | 189 | # Microsoft Azure Emulator 190 | ecf/ 191 | rcf/ 192 | 193 | # Windows Store app package directories and files 194 | AppPackages/ 195 | BundleArtifacts/ 196 | Package.StoreAssociation.xml 197 | _pkginfo.txt 198 | *.appx 199 | 200 | # Visual Studio cache files 201 | # files ending in .cache can be ignored 202 | *.[Cc]ache 203 | # but keep track of directories ending in .cache 204 | !*.[Cc]ache/ 205 | 206 | # Others 207 | ClientBin/ 208 | ~$* 209 | *~ 210 | *.dbmdl 211 | *.dbproj.schemaview 212 | *.jfm 213 | *.pfx 214 | *.publishsettings 215 | orleans.codegen.cs 216 | 217 | # Since there are multiple workflows, uncomment next line to ignore bower_components 218 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 219 | #bower_components/ 220 | 221 | # RIA/Silverlight projects 222 | Generated_Code/ 223 | 224 | # Backup & report files from converting an old project file 225 | # to a newer Visual Studio version. Backup files are not needed, 226 | # because we have git ;-) 227 | _UpgradeReport_Files/ 228 | Backup*/ 229 | UpgradeLog*.XML 230 | UpgradeLog*.htm 231 | 232 | # SQL Server files 233 | *.mdf 234 | *.ldf 235 | *.ndf 236 | 237 | # Business Intelligence projects 238 | *.rdl.data 239 | *.bim.layout 240 | *.bim_*.settings 241 | 242 | # Microsoft Fakes 243 | FakesAssemblies/ 244 | 245 | # GhostDoc plugin setting file 246 | *.GhostDoc.xml 247 | 248 | # Node.js Tools for Visual Studio 249 | .ntvs_analysis.dat 250 | node_modules/ 251 | 252 | # Typescript v1 declaration files 253 | typings/ 254 | 255 | # Visual Studio 6 build log 256 | *.plg 257 | 258 | # Visual Studio 6 workspace options file 259 | *.opt 260 | 261 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 262 | *.vbw 263 | 264 | # Visual Studio LightSwitch build output 265 | **/*.HTMLClient/GeneratedArtifacts 266 | **/*.DesktopClient/GeneratedArtifacts 267 | **/*.DesktopClient/ModelManifest.xml 268 | **/*.Server/GeneratedArtifacts 269 | **/*.Server/ModelManifest.xml 270 | _Pvt_Extensions 271 | 272 | # Paket dependency manager 273 | .paket/paket.exe 274 | paket-files/ 275 | 276 | # FAKE - F# Make 277 | .fake/ 278 | 279 | # JetBrains Rider 280 | .idea/ 281 | *.sln.iml 282 | 283 | # CodeRush 284 | .cr/ 285 | 286 | # Python Tools for Visual Studio (PTVS) 287 | __pycache__/ 288 | *.pyc 289 | 290 | # Cake - Uncomment if you are using it 291 | # tools/** 292 | # !tools/packages.config 293 | 294 | # Tabs Studio 295 | *.tss 296 | 297 | # Telerik's JustMock configuration file 298 | *.jmconfig 299 | 300 | # BizTalk build output 301 | *.btp.cs 302 | *.btm.cs 303 | *.odx.cs 304 | *.xsd.cs 305 | 306 | # OpenCover UI analysis results 307 | OpenCover/ 308 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/CastMembers/GetCastByFilmIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.CastMembers 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public static class GetCastByFilmIdQuery 7 | { 8 | public static IEnumerable Execute(int filmId) 9 | { 10 | //Do some SQL 11 | 12 | return new[] { new CastMember { Name = "John Travolta" }, new CastMember { Name = "Samuel L Jackson" } }; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Directors/GetDirectorByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Directors 2 | { 3 | using Models; 4 | 5 | public static class GetDirectorByIdQuery 6 | { 7 | public static Director Execute(int id) 8 | { 9 | //Do some SQL 10 | 11 | return new Director { Name = "Steven Spielberg" }; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/CreateFilm/CreateFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films.CreateFilm 2 | { 3 | using System; 4 | using Models; 5 | 6 | public static class CreateFilmRoute 7 | { 8 | public static void Handle(Film film, ValidUserDelegate validUserQuery) 9 | { 10 | if (!validUserQuery()) 11 | { 12 | throw new InvalidOperationException(); 13 | } 14 | 15 | //Do some special MEGA CORP business validation 16 | 17 | //Save to database by writing SQL here 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/Delegates.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public delegate Film ListFilmByIdDelegate(int id); 7 | 8 | public delegate void CreateFilmDelegate(Film film); 9 | 10 | public delegate void DeleteFilmDelegate(int id); 11 | 12 | public delegate IEnumerable ListFilmsDelegate(); 13 | 14 | public delegate void UpdateFilmDelegate(int id, Film film); 15 | 16 | public delegate bool ValidUserDelegate(); 17 | 18 | public delegate Director GetDirectorByIdDelegate(int id); 19 | 20 | public delegate IEnumerable GetCastByFilmIdDelegate(int filmId); 21 | } 22 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/DeleteFilm/DeleteFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films.DeleteFilm 2 | { 3 | using System; 4 | 5 | public static class DeleteFilmRoute 6 | { 7 | public static void Handle(int id, ValidUserDelegate validUserQuery) 8 | { 9 | if (!validUserQuery()) 10 | { 11 | throw new InvalidOperationException(); 12 | } 13 | 14 | //Write some SQL to delete from DB 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/FilmsModule.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using Carter; 6 | using Carter.ModelBinding; 7 | using Carter.Request; 8 | using Carter.Response; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.Routing; 11 | using Models; 12 | 13 | /*** NO ATTRIBUTES ANYWHERE!!! ***/ 14 | 15 | public class FilmsModule : CarterModule 16 | { 17 | public FilmsModule() : base("/api/delegate/films") // --> REPLACE ATTRIBUTES WITH METHOD CALLS ***/ 18 | { 19 | /*** REPLACE ATTRIBUTES WITH METHOD CALLS ***/ 20 | //this.RequiresAuthentication(); 21 | 22 | 23 | /*** REPLACE ATTRIBUTES WITH METHOD CALLS ***/ 24 | this.Get("/", this.GetFilms); 25 | this.Get("/{id:int}", this.GetFilmById); 26 | this.Put("/{id:int}", this.UpdateFilm); 27 | this.Post("/", this.CreateFilm); 28 | this.Delete("/{id:int}", this.DeleteFilm); 29 | } 30 | 31 | private async Task GetFilms(HttpContext context) 32 | { 33 | var handler = RouteHandlers.ListFilmsHandler; 34 | 35 | var films = handler(); 36 | 37 | await context.Response.AsJson(films); 38 | } 39 | 40 | private async Task GetFilmById(HttpContext context) 41 | { 42 | var handler = RouteHandlers.ListFilmByIdHandler; 43 | 44 | var film = handler(context.GetRouteData().As("id")); 45 | 46 | if (film == null) 47 | { 48 | context.Response.StatusCode = 404; 49 | return; 50 | } 51 | 52 | await context.Response.AsJson(film); 53 | } 54 | 55 | private async Task UpdateFilm(HttpContext context) 56 | { 57 | var result = context.Request.BindAndValidate(); 58 | 59 | if (!result.ValidationResult.IsValid) 60 | { 61 | context.Response.StatusCode = 422; 62 | await context.Response.Negotiate(result.ValidationResult.GetFormattedErrors()); 63 | return; 64 | } 65 | 66 | try 67 | { 68 | var handler = RouteHandlers.UpdateFilmHandler; 69 | 70 | handler(context.GetRouteData().As("id"), result.Data); 71 | 72 | context.Response.StatusCode = 204; 73 | } 74 | catch (Exception) 75 | { 76 | context.Response.StatusCode = 403; 77 | } 78 | } 79 | 80 | private async Task CreateFilm(HttpContext context) 81 | { 82 | /*** REPLACE ATTRIBUTES WITH METHOD CALLS ***/ 83 | var result = context.Request.BindAndValidate(); 84 | 85 | if (!result.ValidationResult.IsValid) 86 | { 87 | context.Response.StatusCode = 422; 88 | await context.Response.Negotiate(result.ValidationResult.GetFormattedErrors()); 89 | return; 90 | } 91 | 92 | try 93 | { 94 | var handler = RouteHandlers.CreateFilmHandler; 95 | 96 | handler(result.Data); 97 | 98 | context.Response.StatusCode = 201; 99 | } 100 | catch (Exception) 101 | { 102 | context.Response.StatusCode = 403; 103 | } 104 | } 105 | 106 | private Task DeleteFilm(HttpContext context) 107 | { 108 | try 109 | { 110 | var handler = RouteHandlers.DeleteFilmHandler; 111 | 112 | handler(context.GetRouteData().As("id")); 113 | 114 | context.Response.StatusCode = 204; 115 | } 116 | catch (Exception) 117 | { 118 | context.Response.StatusCode = 403; 119 | } 120 | 121 | return Task.CompletedTask; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/ListFilmById/ListFilmByIdRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films.ListFilmById 2 | { 3 | using Models; 4 | 5 | public static class ListFilmByIdRoute 6 | { 7 | public static Film Handle(int id, ListFilmByIdDelegate listFilmById, GetDirectorByIdDelegate getDirectorByIdDelegate, GetCastByFilmIdDelegate getCastByFilmIdDelegateQuery) 8 | { 9 | var film = listFilmById(id); 10 | 11 | if (film == null) 12 | { 13 | return null; 14 | } 15 | 16 | var director = getDirectorByIdDelegate(film.DirectorId); 17 | film.Director = director; 18 | 19 | var cast = getCastByFilmIdDelegateQuery(id); 20 | film.Cast = cast; 21 | 22 | return film; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/ListFilms/ListFilmsRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films.ListFilms 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public static class ListFilmsRoute 7 | { 8 | public static IEnumerable Handle() 9 | { 10 | return new[] { new Film { Id = 1, Name = "Pulp Fiction" }, new Film { Id = 2, Name = "Trainspotting" } }; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/ListFilmsByIdQuery/ListFilmsByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films.ListFilmsByIdQuery 2 | { 3 | using Models; 4 | 5 | public static class ListFilmsByIdQuery 6 | { 7 | public static Film Execute(int id) 8 | { 9 | return new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1 }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/Permissions/ValidUserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films.Permissions 2 | { 3 | using System; 4 | 5 | public static class ValidUserQuery 6 | { 7 | public static bool Execute() 8 | { 9 | return new Random().Next() % 2 == 0; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/RouteHandlers.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films 2 | { 3 | using FunctionalCarterProject.Features.NamedDelegatesFilms.CastMembers; 4 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Directors; 5 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.CreateFilm; 6 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.DeleteFilm; 7 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.ListFilmById; 8 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.ListFilms; 9 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.Permissions; 10 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.UpdateFilm; 11 | 12 | public static class RouteHandlers 13 | { 14 | public static CreateFilmDelegate CreateFilmHandler; 15 | 16 | public static ListFilmByIdDelegate ListFilmByIdHandler; 17 | 18 | public static DeleteFilmDelegate DeleteFilmHandler; 19 | 20 | public static ListFilmsDelegate ListFilmsHandler; 21 | 22 | public static UpdateFilmDelegate UpdateFilmHandler; 23 | 24 | static RouteHandlers() 25 | { 26 | CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => ValidUserQuery.Execute()); 27 | 28 | DeleteFilmHandler = id => DeleteFilmRoute.Handle(id, () => ValidUserQuery.Execute()); 29 | 30 | ListFilmByIdHandler = id => ListFilmByIdRoute.Handle( 31 | id, 32 | filmId => ListFilmsByIdQuery.ListFilmsByIdQuery.Execute(id), 33 | dirId => GetDirectorByIdQuery.Execute(dirId), 34 | filmId => GetCastByFilmIdQuery.Execute(id) 35 | ); 36 | 37 | ListFilmsHandler = () => ListFilmsRoute.Handle(); 38 | 39 | UpdateFilmHandler = (id, film) => UpdateFilmRoute.Handle( 40 | id, 41 | film, 42 | () => ValidUserQuery.Execute(), 43 | filmId => ListFilmsByIdQuery.ListFilmsByIdQuery.Execute(filmId)); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Features/NamedDelegatesFilms/Films/UpdateFilm/UpdateFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject.Features.NamedDelegatesFilms.Films.UpdateFilm 2 | { 3 | using System; 4 | using Models; 5 | 6 | public static class UpdateFilmRoute 7 | { 8 | public static void Handle(int id, Film film, ValidUserDelegate validUserQuery, ListFilmByIdDelegate listFilmById) 9 | { 10 | if (!validUserQuery()) 11 | { 12 | throw new InvalidOperationException(); 13 | } 14 | 15 | //Do some special MEGA CORP business validation 16 | 17 | var existingFilm = listFilmById(id); 18 | 19 | existingFilm.Name = film.Name; 20 | existingFilm.Budget = film.Budget; 21 | existingFilm.Language = film.Language; 22 | 23 | //Write some SQL to store in db 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FunctionalCarterProject/FunctionalCarterProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | FunctionalCarterProject 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Program.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject 2 | { 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | WebHost.CreateDefaultBuilder(args) 11 | .UseStartup() 12 | .Build() 13 | .Run(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FunctionalCarterProject/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProject 2 | { 3 | using Carter; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | public class Startup 8 | { 9 | public void ConfigureServices(IServiceCollection services) 10 | { 11 | //services.AddCarter(Assembly.GetCallingAssembly(), typeof(FilmValidator).Assembly); 12 | services.AddCarter(); 13 | } 14 | 15 | public void Configure(IApplicationBuilder app) 16 | { 17 | app.UseCarter(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FunctionalCarterProjectTests/Features/Films/FilmTests.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalCarterProjectTests.Features.Films 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Carter; 8 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films; 9 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.CreateFilm; 10 | using FunctionalCarterProject.Features.NamedDelegatesFilms.Films.ListFilmById; 11 | using Microsoft.AspNetCore; 12 | using Microsoft.AspNetCore.Hosting; 13 | using Microsoft.AspNetCore.TestHost; 14 | using Models; 15 | using Newtonsoft.Json; 16 | using Xunit; 17 | 18 | public class FilmTests 19 | { 20 | private TestServer server; 21 | 22 | private HttpClient client; 23 | 24 | public FilmTests() 25 | { 26 | this.server = new TestServer(WebHost.CreateDefaultBuilder() 27 | .ConfigureServices(services => services.AddCarter()) 28 | .Configure(app => app.UseCarter()) 29 | ); 30 | 31 | this.client = this.server.CreateClient(); 32 | } 33 | 34 | [Fact] 35 | public async Task Should_return_422_on_invalid_data_when_creating_film() 36 | { 37 | //Given 38 | 39 | //No mock library required to fake the UserValidQuery we just invoke a func to return true/false 40 | 41 | RouteHandlers.CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => true); 42 | 43 | var newFilm = new Film { Name = "" }; 44 | 45 | //When 46 | var response = await this.client.PostAsync("/api/delegate/films", new StringContent(JsonConvert.SerializeObject(newFilm), Encoding.UTF8, "application/json")); 47 | 48 | //Then 49 | Assert.Equal(422, (int)response.StatusCode); 50 | } 51 | 52 | [Fact] 53 | public async Task Should_return_403_on_invalid_user_when_creating_film() 54 | { 55 | //Given 56 | RouteHandlers.CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => false); 57 | 58 | var newFilm = new Film { Name = "Shrek" }; 59 | 60 | //When 61 | var response = await this.client.PostAsync("/api/delegate/films", new StringContent(JsonConvert.SerializeObject(newFilm), Encoding.UTF8, "application/json")); 62 | 63 | //Then 64 | Assert.Equal(403, (int)response.StatusCode); 65 | } 66 | 67 | [Fact] 68 | public async Task Should_return_201_when_creating_film() 69 | { 70 | //Given 71 | RouteHandlers.CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => true); 72 | 73 | var newFilm = new Film { Name = "Shrek" }; 74 | 75 | //When 76 | var response = await this.client.PostAsync("/api/delegate/films", new StringContent(JsonConvert.SerializeObject(newFilm), Encoding.UTF8, "application/json")); 77 | 78 | //Then 79 | Assert.Equal(201, (int)response.StatusCode); 80 | } 81 | 82 | [Fact] 83 | public async Task Should_get_film_by_id() 84 | { 85 | //Given 86 | 87 | //No mock library required to fake the GetFilmByIdDelegate, GetDirectorById, GetCastMembersByFilmId we just invoke a func 88 | 89 | RouteHandlers.ListFilmByIdHandler = id => ListFilmByIdRoute.Handle(id, filmid => new Film { Name = "Blade Runner" }, i => new Director(), filmId => new[] { new CastMember() }); 90 | 91 | //When 92 | var response = await this.client.GetAsync("/api/delegate/films/1"); 93 | var contents = await response.Content.ReadAsStringAsync(); 94 | 95 | //Then 96 | Assert.Contains("Blade Runner", contents, StringComparison.OrdinalIgnoreCase); 97 | } 98 | 99 | [Fact] 100 | public async Task Should_return_404_when_no_film_found_via_get_film_by_id() 101 | { 102 | //Given 103 | 104 | //No mock library required to fake the GetFilmByIdDelegate, GetDirectorById, GetCastMembersByFilmId we just invoke a func 105 | 106 | RouteHandlers.ListFilmByIdHandler = id => ListFilmByIdRoute.Handle(id, filmid => null, i => new Director(), filmId => new[] { new CastMember() }); 107 | 108 | //When 109 | var response = await this.client.GetAsync("/api/delegate/films/1"); 110 | 111 | //Then 112 | Assert.Equal(404, (int)response.StatusCode); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /FunctionalCarterProjectTests/FunctionalCarterProjectTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /FunctionalProject/Features/FuncFilms/CreateFilm/CreateFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.FuncFilms.CreateFilm 2 | { 3 | using System; 4 | using Models; 5 | 6 | public static class CreateFilmRoute 7 | { 8 | public static void Handle(Film film, Func validUserQuery) 9 | { 10 | if (!validUserQuery()) 11 | { 12 | throw new InvalidOperationException(); 13 | } 14 | 15 | //Do some special MEGA CORP business validation 16 | 17 | //Save to database by writing SQL here 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FunctionalProject/Features/FuncFilms/DeleteFilm/DeleteFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.FuncFilms.DeleteFilm 2 | { 3 | using System; 4 | 5 | public static class DeleteFilmRoute 6 | { 7 | public static void Handle(int id, Func validUserQuery) 8 | { 9 | if (!validUserQuery()) 10 | { 11 | throw new InvalidOperationException(); 12 | } 13 | 14 | //Write some SQL to delete from DB 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FunctionalProject/Features/FuncFilms/FilmsController.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.FuncFilms 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Models; 7 | 8 | [Route("api/[controller]")] 9 | public class FilmsController : Controller 10 | { 11 | // GET api/films 12 | [HttpGet] 13 | public IEnumerable Get() 14 | { 15 | var handler = RouteHandlers.ListFilmsHandler; 16 | 17 | var films = handler(); 18 | 19 | return films; 20 | } 21 | 22 | // GET api/films/5 23 | [HttpGet("{id}")] 24 | public IActionResult Get(int id) 25 | { 26 | var handler = RouteHandlers.ListFilmByIdHandler; 27 | 28 | var film = handler(id); 29 | 30 | if (film == null) 31 | { 32 | return this.NotFound(); 33 | } 34 | 35 | return this.Ok(film); 36 | } 37 | 38 | // POST api/films 39 | [HttpPost] 40 | public IActionResult Post([FromBody] Film film) 41 | { 42 | if (!this.ModelState.IsValid) 43 | { 44 | return this.BadRequest(this.ModelState); 45 | } 46 | 47 | try 48 | { 49 | var handler = RouteHandlers.CreateFilmHandler; 50 | 51 | handler(film); 52 | } 53 | catch (InvalidOperationException) 54 | { 55 | return this.StatusCode(403); 56 | } 57 | 58 | return this.StatusCode(201); 59 | } 60 | 61 | // PUT api/films/5 62 | [HttpPut("{id}")] 63 | public IActionResult Put(int id, [FromBody] Film film) 64 | { 65 | if (!this.ModelState.IsValid) 66 | { 67 | return this.BadRequest(this.ModelState); 68 | } 69 | 70 | try 71 | { 72 | var handler = RouteHandlers.UpdateFilmHandler; 73 | 74 | handler(id, film); 75 | } 76 | catch (InvalidOperationException) 77 | { 78 | return this.StatusCode(403); 79 | } 80 | 81 | return this.StatusCode(204); 82 | } 83 | 84 | // DELETE api/films/5 85 | [HttpDelete("{id}")] 86 | public IActionResult Delete(int id) 87 | { 88 | try 89 | { 90 | var handler = RouteHandlers.DeleteFilmHandler; 91 | 92 | handler(id); 93 | } 94 | catch (InvalidOperationException) 95 | { 96 | return this.StatusCode(403); 97 | } 98 | 99 | return this.StatusCode(204); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /FunctionalProject/Features/FuncFilms/ListFilmById/ListFilmByIdRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.FuncFilms.ListFilmById 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Models; 6 | 7 | public static class ListFilmByIdRoute 8 | { 9 | public static Film Handle(int id, Func listFilmById, Func getDirectorById, Func> getCastByFilmIdQuery) 10 | { 11 | var film = listFilmById(id); 12 | 13 | if (film == null) 14 | { 15 | return null; 16 | } 17 | 18 | var director = getDirectorById(film.DirectorId); 19 | film.Director = director; 20 | 21 | var cast = getCastByFilmIdQuery(id); 22 | film.Cast = cast; 23 | 24 | return film; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FunctionalProject/Features/FuncFilms/ListFilms/ListFilmsRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.FuncFilms.ListFilms 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public static class ListFilmsRoute 7 | { 8 | public static IEnumerable Handle() 9 | { 10 | return new[] { new Film { Id = 1, Name = "Pulp Fiction" }, new Film { Id = 2, Name = "Trainspotting" } }; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /FunctionalProject/Features/FuncFilms/RouteHandlers.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.FuncFilms 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using FunctionalProject.Features.FuncFilms.CreateFilm; 6 | using FunctionalProject.Features.FuncFilms.DeleteFilm; 7 | using FunctionalProject.Features.FuncFilms.ListFilmById; 8 | using FunctionalProject.Features.FuncFilms.ListFilms; 9 | using FunctionalProject.Features.FuncFilms.UpdateFilm; 10 | using Models; 11 | 12 | public static class RouteHandlers 13 | { 14 | public static Func> ListFilmsHandler; 15 | 16 | public static Func ListFilmByIdHandler; 17 | 18 | public static Action UpdateFilmHandler; 19 | 20 | public static Action CreateFilmHandler; 21 | 22 | public static Action DeleteFilmHandler; 23 | 24 | //private static Func getFilmyById = i => new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1 }; 25 | 26 | static RouteHandlers() 27 | { 28 | ListFilmsHandler = () => ListFilmsRoute.Handle(); 29 | 30 | ListFilmByIdHandler = filmId => ListFilmByIdRoute.Handle(filmId, 31 | //Write some SQL to get the film 32 | fId => new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1 }, 33 | //Write some SQL to get the director 34 | dirId => new Director { Name = "Steven Spielberg" }, 35 | //Write some SQL to get the cast 36 | fId => new[] { new CastMember { Name = "John Travolta" }, new CastMember { Name = "Samuel L Jackson" } } 37 | ); 38 | 39 | UpdateFilmHandler = (filmId, film) => UpdateFilmRoute.Handle( 40 | filmId, 41 | film, 42 | () => new Random().Next() % 2 == 0, 43 | fId => new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1 } 44 | ); 45 | 46 | CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => new Random().Next() % 2 == 0); 47 | 48 | DeleteFilmHandler = id => DeleteFilmRoute.Handle(id, () => new Random().Next() % 2 == 0); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /FunctionalProject/Features/FuncFilms/UpdateFilm/UpdateFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.FuncFilms.UpdateFilm 2 | { 3 | using System; 4 | using Models; 5 | 6 | public static class UpdateFilmRoute 7 | { 8 | public static void Handle(int id, Film film, Func validUserQuery, Func listFilmById) 9 | { 10 | if (!validUserQuery()) 11 | { 12 | throw new InvalidOperationException(); 13 | } 14 | 15 | //Do some special MEGA CORP business validation 16 | 17 | var existingFilm = listFilmById(id); 18 | 19 | existingFilm.Name = film.Name; 20 | existingFilm.Budget = film.Budget; 21 | existingFilm.Language = film.Language; 22 | 23 | //Write some SQL to store in db 24 | 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/CastMembers/GetCastByFilmIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.CastMembers 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public static class GetCastByFilmIdQuery 7 | { 8 | public static IEnumerable Execute(int filmId) 9 | { 10 | //Do some SQL 11 | 12 | return new[] { new CastMember { Name = "John Travolta" }, new CastMember { Name = "Samuel L Jackson" } }; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Directors/GetDirectorByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Directors 2 | { 3 | using Models; 4 | 5 | public static class GetDirectorByIdQuery 6 | { 7 | public static Director Execute(int id) 8 | { 9 | //Do some SQL 10 | 11 | return new Director { Name = "Steven Spielberg" }; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/CreateFilm/CreateFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films.CreateFilm 2 | { 3 | using System; 4 | using Models; 5 | 6 | public static class CreateFilmRoute 7 | { 8 | public static void Handle(Film film, ValidUserDelegate validUserQuery) 9 | { 10 | if (!validUserQuery()) 11 | { 12 | throw new InvalidOperationException(); 13 | } 14 | 15 | //Do some special MEGA CORP business validation 16 | 17 | //Save to database by writing SQL here 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/Delegates.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public delegate Film ListFilmByIdDelegate(int id); 7 | 8 | public delegate void CreateFilmDelegate(Film film); 9 | 10 | public delegate void DeleteFilmDelegate(int id); 11 | 12 | public delegate IEnumerable ListFilmsDelegate(); 13 | 14 | public delegate void UpdateFilmDelegate(int id, Film film); 15 | 16 | public delegate bool ValidUserDelegate(); 17 | 18 | public delegate Director GetDirectorByIdDelegate(int id); 19 | 20 | public delegate IEnumerable GetCastByFilmIdDelegate(int filmId); 21 | } 22 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/DeleteFilm/DeleteFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films.DeleteFilm 2 | { 3 | using System; 4 | 5 | public static class DeleteFilmRoute 6 | { 7 | public static void Handle(int id, ValidUserDelegate validUserQuery) 8 | { 9 | if (!validUserQuery()) 10 | { 11 | throw new InvalidOperationException(); 12 | } 13 | 14 | //Write some SQL to delete from DB 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/FilmsController.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Models; 8 | 9 | [Route("api/delegate/[controller]")] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 10 | [AllowAnonymous] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 11 | [Controller] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 12 | public class FilmsController : Controller 13 | { 14 | // GET api/films 15 | [HttpGet] 16 | public IEnumerable Get() 17 | { 18 | var handler = RouteHandlers.ListFilmsHandler; 19 | 20 | var films = handler(); 21 | 22 | return films; 23 | } 24 | 25 | // GET api/films/5 26 | [HttpGet("{id}")] 27 | public IActionResult Get(int id) 28 | { 29 | var handler = RouteHandlers.ListFilmByIdHandler; 30 | 31 | var film = handler(id); 32 | 33 | if (film == null) 34 | { 35 | return this.NotFound(); 36 | } 37 | 38 | return this.Ok(film); 39 | } 40 | 41 | // POST api/films 42 | [HttpPost] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 43 | [Produces("application/json")] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 44 | [Consumes("application/json")] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 45 | [ProducesResponseType(201)] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 46 | 47 | [OoohINeedToDoSomeSpecialLogicJustForThisMethod] /**** WHAT IS THIS MAGIC ATTRIBUTE DOING?? ****/ 48 | 49 | public IActionResult Post([FromBody] Film film) /**** OH GOD ATTRIBUTES IN METHODS!! ****/ 50 | { 51 | if (!this.ModelState.IsValid) 52 | { 53 | return this.BadRequest(this.ModelState); 54 | } 55 | 56 | try 57 | { 58 | var handler = RouteHandlers.CreateFilmHandler; 59 | 60 | handler(film); 61 | } 62 | catch (InvalidOperationException) 63 | { 64 | return this.StatusCode(403); 65 | } 66 | 67 | return this.StatusCode(201); 68 | } 69 | 70 | // PUT api/films/5 71 | [HttpPut("{id}")] 72 | public IActionResult Put(int id, [FromBody] Film film) 73 | { 74 | if (!this.ModelState.IsValid) 75 | { 76 | return this.BadRequest(this.ModelState); 77 | } 78 | 79 | try 80 | { 81 | var handler = RouteHandlers.UpdateFilmHandler; 82 | 83 | handler(id, film); 84 | } 85 | catch (InvalidOperationException) 86 | { 87 | return this.StatusCode(403); 88 | } 89 | 90 | return this.StatusCode(204); 91 | } 92 | 93 | // DELETE api/films/5 94 | [HttpDelete("{id}")] 95 | public IActionResult Delete(int id) 96 | { 97 | try 98 | { 99 | var handler = RouteHandlers.DeleteFilmHandler; 100 | 101 | handler(id); 102 | } 103 | catch (InvalidOperationException) 104 | { 105 | return this.StatusCode(403); 106 | } 107 | 108 | return this.StatusCode(204); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/ListFilmById/ListFilmByIdRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films.ListFilmById 2 | { 3 | using Models; 4 | 5 | public static class ListFilmByIdRoute 6 | { 7 | public static Film Handle(int id, ListFilmByIdDelegate listFilmById, GetDirectorByIdDelegate getDirectorByIdDelegate, GetCastByFilmIdDelegate getCastByFilmIdDelegateQuery) 8 | { 9 | var film = listFilmById(id); 10 | 11 | if (film == null) 12 | { 13 | return null; 14 | } 15 | 16 | var director = getDirectorByIdDelegate(film.DirectorId); 17 | film.Director = director; 18 | 19 | var cast = getCastByFilmIdDelegateQuery(id); 20 | film.Cast = cast; 21 | 22 | return film; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/ListFilms/ListFilmsRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films.ListFilms 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public static class ListFilmsRoute 7 | { 8 | public static IEnumerable Handle() 9 | { 10 | return new[] { new Film { Id = 1, Name = "Pulp Fiction" }, new Film { Id = 2, Name = "Trainspotting" } }; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/ListFilmsByIdQuery/ListFilmsByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films.ListFilmsByIdQuery 2 | { 3 | using Models; 4 | 5 | public static class ListFilmsByIdQuery 6 | { 7 | public static Film Execute(int id) 8 | { 9 | return new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1 }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/OoohINeedToDoSomeSpecialLogicJustForThisMethodAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films 2 | { 3 | using System; 4 | 5 | public class OoohINeedToDoSomeSpecialLogicJustForThisMethodAttribute : Attribute 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/Permissions/ValidUserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films.Permissions 2 | { 3 | using System; 4 | 5 | public static class ValidUserQuery 6 | { 7 | public static bool Execute() 8 | { 9 | return new Random().Next() % 2 == 0; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/RouteHandlers.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films 2 | { 3 | using FunctionalProject.Features.FuncFilms.UpdateFilm; 4 | using FunctionalProject.Features.NamedDelegatesFilms.CastMembers; 5 | using FunctionalProject.Features.NamedDelegatesFilms.Directors; 6 | using FunctionalProject.Features.NamedDelegatesFilms.Films.CreateFilm; 7 | using FunctionalProject.Features.NamedDelegatesFilms.Films.DeleteFilm; 8 | using FunctionalProject.Features.NamedDelegatesFilms.Films.ListFilmById; 9 | using FunctionalProject.Features.NamedDelegatesFilms.Films.ListFilms; 10 | using FunctionalProject.Features.NamedDelegatesFilms.Films.Permissions; 11 | 12 | public static class RouteHandlers 13 | { 14 | public static CreateFilmDelegate CreateFilmHandler; 15 | 16 | public static ListFilmByIdDelegate ListFilmByIdHandler; 17 | 18 | public static DeleteFilmDelegate DeleteFilmHandler; 19 | 20 | public static ListFilmsDelegate ListFilmsHandler; 21 | 22 | public static UpdateFilmDelegate UpdateFilmHandler; 23 | 24 | static RouteHandlers() 25 | { 26 | ListFilmsHandler = () => ListFilmsRoute.Handle(); 27 | 28 | ListFilmByIdHandler = id => ListFilmByIdRoute.Handle( 29 | id, 30 | filmId => ListFilmsByIdQuery.ListFilmsByIdQuery.Execute(id), 31 | dirId => GetDirectorByIdQuery.Execute(dirId), 32 | filmId => GetCastByFilmIdQuery.Execute(id) 33 | ); 34 | 35 | UpdateFilmHandler = (id, film) => UpdateFilmRoute.Handle( 36 | id, 37 | film, 38 | () => ValidUserQuery.Execute(), 39 | filmId => ListFilmsByIdQuery.ListFilmsByIdQuery.Execute(filmId)); 40 | 41 | CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => ValidUserQuery.Execute()); 42 | 43 | DeleteFilmHandler = id => DeleteFilmRoute.Handle(id, () => ValidUserQuery.Execute()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FunctionalProject/Features/NamedDelegatesFilms/Films/UpdateFilm/UpdateFilmRoute.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject.Features.NamedDelegatesFilms.Films.UpdateFilm 2 | { 3 | using System; 4 | using Models; 5 | 6 | public static class UpdateFilmRoute 7 | { 8 | public static void Handle(int id, Film film, ValidUserDelegate validUserQuery, ListFilmByIdDelegate listFilmById) 9 | { 10 | if (!validUserQuery()) 11 | { 12 | throw new InvalidOperationException(); 13 | } 14 | 15 | //Do some special MEGA CORP business validation 16 | 17 | var existingFilm = listFilmById(id); 18 | 19 | existingFilm.Name = film.Name; 20 | existingFilm.Budget = film.Budget; 21 | existingFilm.Language = film.Language; 22 | 23 | //Write some SQL to store in db 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FunctionalProject/FunctionalProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | FunctionalProject 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FunctionalProject/Program.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject 2 | { 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | WebHost.CreateDefaultBuilder(args) 11 | .UseStartup() 12 | .Build() 13 | .Run(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FunctionalProject/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProject 2 | { 3 | using FluentValidation; 4 | using FluentValidation.AspNetCore; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Models; 8 | 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | services.AddTransient, FilmValidator>(); 14 | 15 | services.AddMvc().AddFluentValidation(); 16 | } 17 | 18 | public void Configure(IApplicationBuilder app) 19 | { 20 | app.UseMvc(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FunctionalProjectTests/Features/Films/FilmTests.cs: -------------------------------------------------------------------------------- 1 | namespace FunctionalProjectTests.Features.Films 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using FluentValidation; 8 | using FluentValidation.AspNetCore; 9 | using FunctionalProject.Features.NamedDelegatesFilms.Films; 10 | using FunctionalProject.Features.NamedDelegatesFilms.Films.CreateFilm; 11 | using FunctionalProject.Features.NamedDelegatesFilms.Films.ListFilmById; 12 | using Microsoft.AspNetCore; 13 | using Microsoft.AspNetCore.Builder; 14 | using Microsoft.AspNetCore.Hosting; 15 | using Microsoft.AspNetCore.TestHost; 16 | using Microsoft.Extensions.DependencyInjection; 17 | using Models; 18 | using Newtonsoft.Json; 19 | using Xunit; 20 | 21 | public class FilmTests 22 | { 23 | private readonly HttpClient client; 24 | 25 | public FilmTests() 26 | { 27 | var server = new TestServer(WebHost.CreateDefaultBuilder() 28 | .ConfigureServices(services => 29 | { 30 | services.AddMvc().AddFluentValidation(); 31 | services.AddTransient, FilmValidator>(); 32 | }) 33 | .Configure(app => app.UseMvc()) 34 | ); 35 | 36 | this.client = server.CreateClient(); 37 | } 38 | 39 | [Fact] 40 | public async Task Should_return_400_on_invalid_data_when_creating_film() 41 | { 42 | //Given 43 | 44 | //No mock library required to fake the UserValidQuery we just invoke a func to return true/false 45 | 46 | RouteHandlers.CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => true); 47 | 48 | var newFilm = new Film { Name = "" }; 49 | 50 | //When 51 | var response = await this.client.PostAsync("/api/delegate/films", new StringContent(JsonConvert.SerializeObject(newFilm), Encoding.UTF8, "application/json")); 52 | 53 | //Then 54 | Assert.Equal(400, (int)response.StatusCode); 55 | } 56 | 57 | [Fact] 58 | public async Task Should_return_403_on_invalid_user_when_creating_film() 59 | { 60 | //Given 61 | RouteHandlers.CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => false); 62 | 63 | var newFilm = new Film { Name = "Shrek" }; 64 | 65 | //When 66 | var response = await this.client.PostAsync("/api/delegate/films", new StringContent(JsonConvert.SerializeObject(newFilm), Encoding.UTF8, "application/json")); 67 | 68 | //Then 69 | Assert.Equal(403, (int)response.StatusCode); 70 | } 71 | 72 | [Fact] 73 | public async Task Should_return_201_when_creating_film() 74 | { 75 | //Given 76 | RouteHandlers.CreateFilmHandler = film => CreateFilmRoute.Handle(film, () => true); 77 | 78 | var newFilm = new Film { Name = "Shrek" }; 79 | 80 | //When 81 | var response = await this.client.PostAsync("/api/delegate/films", new StringContent(JsonConvert.SerializeObject(newFilm), Encoding.UTF8, "application/json")); 82 | 83 | //Then 84 | Assert.Equal(201, (int)response.StatusCode); 85 | } 86 | 87 | [Fact] 88 | public async Task Should_get_film_by_id() 89 | { 90 | //Given 91 | 92 | //No mock library required to fake the GetFilmByIdDelegate, GetDirectorById, GetCastMembersByFilmId we just invoke a func 93 | 94 | RouteHandlers.ListFilmByIdHandler = id => ListFilmByIdRoute.Handle(id, filmid => new Film { Name = "Blade Runner" }, i => new Director(), filmId => new[] { new CastMember() }); 95 | 96 | //When 97 | var response = await this.client.GetAsync("/api/delegate/films/1"); 98 | var contents = await response.Content.ReadAsStringAsync(); 99 | 100 | //Then 101 | Assert.Contains("Blade Runner", contents, StringComparison.OrdinalIgnoreCase); 102 | } 103 | 104 | [Fact] 105 | public async Task Should_return_404_when_no_film_found_via_get_film_by_id() 106 | { 107 | //Given 108 | 109 | //No mock library required to fake the GetFilmByIdDelegate, GetDirectorById, GetCastMembersByFilmId we just invoke a func 110 | 111 | RouteHandlers.ListFilmByIdHandler = id => ListFilmByIdRoute.Handle(id, filmid => null, i => new Director(), filmId => new[] { new CastMember() }); 112 | 113 | //When 114 | var response = await this.client.GetAsync("/api/delegate/films/1"); 115 | 116 | //Then 117 | Assert.Equal(404, (int)response.StatusCode); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /FunctionalProjectTests/FunctionalProjectTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HandRolledMediator/CommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator 2 | { 3 | public abstract class CommandHandler : ICommandHandler 4 | { 5 | protected virtual void Handle(T command) 6 | { 7 | } 8 | 9 | protected virtual object Execute(T command) 10 | { 11 | return default; 12 | } 13 | 14 | object ICommandHandler.Execute(object command) 15 | { 16 | return this.Execute((T)command); 17 | } 18 | 19 | void ICommandHandler.Handle(object command) 20 | { 21 | this.Handle((T)command); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/CastMembers/GetCastByFilmIdQuery/GetCastByFilmIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.CastMembers.GetCastByFilmIdQuery 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public class GetCastByFilmIdQuery : IGetCastByFilmIdQuery 7 | { 8 | public IEnumerable Execute(int filmId) 9 | { 10 | //Do some SQL 11 | 12 | return new[] { new CastMember { Name = "John Travolta" }, new CastMember { Name = "Samuel L Jackson" } }; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /HandRolledMediator/Features/CastMembers/GetCastByFilmIdQuery/IGetCastByFilmIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.CastMembers.GetCastByFilmIdQuery 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public interface IGetCastByFilmIdQuery 7 | { 8 | IEnumerable Execute(int filmId); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Directors/GetDirectorByIdQuery/GetDirectorByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Directors.GetDirectorByIdQuery 2 | { 3 | using Models; 4 | 5 | public class GetDirectorByIdQuery : IGetDirectorByIdQuery 6 | { 7 | public Director Execute(int id) 8 | { 9 | //Do some SQL 10 | 11 | return new Director { Name = "Steven Spielberg" }; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /HandRolledMediator/Features/Directors/GetDirectorByIdQuery/IGetDirectorByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Directors.GetDirectorByIdQuery 2 | { 3 | using Models; 4 | 5 | public interface IGetDirectorByIdQuery 6 | { 7 | Director Execute(int id); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/CreateFilm/CreateFilmCommand.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.CreateFilm 2 | { 3 | using Models; 4 | 5 | public class CreateFilmCommand 6 | { 7 | public Film Film { get; } 8 | 9 | public CreateFilmCommand(Film film) 10 | { 11 | this.Film = film; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/CreateFilm/CreateFilmCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.CreateFilm 2 | { 3 | using System; 4 | using HandRolledMediator.Features.Permissions; 5 | 6 | public class CreateFilmCommandHandler : CommandHandler 7 | { 8 | private readonly IValidUserQuery validUserQuery; 9 | 10 | public CreateFilmCommandHandler(IValidUserQuery validUserQuery) 11 | { 12 | this.validUserQuery = validUserQuery; 13 | } 14 | 15 | protected override void Handle(CreateFilmCommand command) 16 | { 17 | if (!this.validUserQuery.Execute()) 18 | { 19 | throw new InvalidOperationException(); 20 | } 21 | 22 | //Do some special MEGA CORP business validation 23 | 24 | //Save to database by writing SQL here 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/DeleteFilm/DeleteFilmCommand.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.DeleteFilm 2 | { 3 | public class DeleteFilmCommand 4 | { 5 | public int Id { get; } 6 | 7 | public DeleteFilmCommand(int id) 8 | { 9 | this.Id = id; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/DeleteFilm/DeleteFilmCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.DeleteFilm 2 | { 3 | using System; 4 | using HandRolledMediator.Features.Permissions; 5 | 6 | public class DeleteFilmCommandHandler : CommandHandler 7 | { 8 | private readonly IValidUserQuery validUserQuery; 9 | 10 | public DeleteFilmCommandHandler(IValidUserQuery validUserQuery) 11 | { 12 | this.validUserQuery = validUserQuery; 13 | } 14 | 15 | protected override void Handle(DeleteFilmCommand command) 16 | { 17 | if (!this.validUserQuery.Execute()) 18 | { 19 | throw new InvalidOperationException(); 20 | } 21 | 22 | //Write some SQL to delete from DB 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/FilmsController.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using HandRolledMediator.Features.Films.CreateFilm; 6 | using HandRolledMediator.Features.Films.DeleteFilm; 7 | using HandRolledMediator.Features.Films.ListFilmById; 8 | using HandRolledMediator.Features.Films.ListFilms; 9 | using HandRolledMediator.Features.Films.UpdateFilm; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Models; 12 | 13 | [Route("api/[controller]")] 14 | public class FilmsController : Controller 15 | { 16 | private readonly Handler handler; 17 | 18 | public FilmsController(Handler handler) 19 | { 20 | this.handler = handler; 21 | } 22 | 23 | // GET api/films 24 | [HttpGet] 25 | public IEnumerable Get() 26 | { 27 | var command = new ListFilmsCommand(); 28 | return this.handler.Execute>(command); 29 | } 30 | 31 | // GET api/films/5 32 | [HttpGet("{id}")] 33 | public IActionResult Get(int id) 34 | { 35 | var command = new ListFilmsByIdCommand(id); 36 | 37 | var film = this.handler.Execute(command); 38 | 39 | if (film == null) 40 | { 41 | return this.NotFound(); 42 | } 43 | 44 | return this.Ok(film); 45 | } 46 | 47 | // POST api/films 48 | [HttpPost] 49 | public IActionResult Post([FromBody] Film film) 50 | { 51 | if (!this.ModelState.IsValid) 52 | { 53 | return this.BadRequest(this.ModelState); 54 | } 55 | 56 | try 57 | { 58 | var command = new CreateFilmCommand(film); 59 | this.handler.Handle(command); 60 | } 61 | catch (InvalidOperationException) 62 | { 63 | return this.StatusCode(403); 64 | } 65 | 66 | return this.StatusCode(201); 67 | } 68 | 69 | // PUT api/films/5 70 | [HttpPut("{id}")] 71 | public IActionResult Put(int id, [FromBody] Film film) 72 | { 73 | if (!this.ModelState.IsValid) 74 | { 75 | return this.BadRequest(this.ModelState); 76 | } 77 | 78 | try 79 | { 80 | var command = new UpdateFilmCommand(id, film); 81 | this.handler.Handle(command); 82 | } 83 | catch (InvalidOperationException) 84 | { 85 | return this.StatusCode(403); 86 | } 87 | 88 | return this.StatusCode(204); 89 | } 90 | 91 | // DELETE api/films/5 92 | [HttpDelete("{id}")] 93 | public IActionResult Delete(int id) 94 | { 95 | try 96 | { 97 | var command = new DeleteFilmCommand(id); 98 | this.handler.Handle(command); 99 | } 100 | catch (InvalidOperationException) 101 | { 102 | return this.StatusCode(403); 103 | } 104 | 105 | return this.StatusCode(204); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/FilmsModule.cs: -------------------------------------------------------------------------------- 1 | namespace BotwinMediator.Features.Films 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using Botwin; 7 | using Botwin.ModelBinding; 8 | using Botwin.Request; 9 | using Botwin.Response; 10 | using BotwinMediator.Features.Films.CreateFilm; 11 | using BotwinMediator.Features.Films.DeleteFilm; 12 | using BotwinMediator.Features.Films.ListFilmById; 13 | using BotwinMediator.Features.Films.ListFilms; 14 | using BotwinMediator.Features.Films.UpdateFilm; 15 | using Models; 16 | 17 | public class FilmsModule : BotwinModule 18 | { 19 | private readonly Handler handler; 20 | 21 | public FilmsModule(Handler handler) : base("/api/films") 22 | { 23 | this.handler = handler; 24 | 25 | this.Get("/", async (request, response, routeData) => 26 | { 27 | var command = new ListFilmsCommand(); 28 | var films = this.handler.Execute>(command); 29 | await response.AsJson(films); 30 | }); 31 | 32 | this.Get("/{id:int}", async (request, response, routeData) => 33 | { 34 | var command = new ListFilmsByIdCommand(routeData.As("id")); 35 | 36 | var film = this.handler.Execute(command); 37 | 38 | if (film == null) 39 | { 40 | response.StatusCode = 404; 41 | return; 42 | } 43 | 44 | await response.AsJson(film); 45 | }); 46 | 47 | this.Post("/", async (req, res, routeData) => 48 | { 49 | var result = req.BindAndValidate(); 50 | 51 | if (!result.ValidationResult.IsValid) 52 | { 53 | res.StatusCode = 422; 54 | await res.Negotiate(result.ValidationResult.GetFormattedErrors()); 55 | return; 56 | } 57 | 58 | try 59 | { 60 | var command = new CreateFilmCommand(result.Data); 61 | this.handler.Handle(command); 62 | res.StatusCode = 201; 63 | } 64 | catch (Exception) 65 | { 66 | res.StatusCode = 403; 67 | } 68 | }); 69 | 70 | this.Put("/{id:int}", async (req, res, routeData) => 71 | { 72 | var result = req.BindAndValidate(); 73 | 74 | if (!result.ValidationResult.IsValid) 75 | { 76 | res.StatusCode = 422; 77 | await res.Negotiate(result.ValidationResult.GetFormattedErrors()); 78 | return; 79 | } 80 | 81 | try 82 | { 83 | var command = new UpdateFilmCommand(routeData.As("id"), result.Data); 84 | this.handler.Handle(command); 85 | res.StatusCode = 204; 86 | } 87 | catch (Exception) 88 | { 89 | res.StatusCode = 403; 90 | } 91 | }); 92 | 93 | this.Delete("/{id:int}", (req, res, routeData) => 94 | { 95 | try 96 | { 97 | var command = new DeleteFilmCommand(routeData.As("id")); 98 | this.handler.Handle(command); 99 | res.StatusCode = 204; 100 | } 101 | catch (Exception) 102 | { 103 | res.StatusCode = 403; 104 | } 105 | 106 | return Task.CompletedTask; 107 | }); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/ListFilmById/ListFilmsByIdCommand.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.ListFilmById 2 | { 3 | public class ListFilmsByIdCommand 4 | { 5 | public int Id { get; } 6 | 7 | public ListFilmsByIdCommand(int id) 8 | { 9 | this.Id = id; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/ListFilmById/ListFilmsByIdCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.ListFilmById 2 | { 3 | using HandRolledMediator.Features.CastMembers.GetCastByFilmIdQuery; 4 | using HandRolledMediator.Features.Directors.GetDirectorByIdQuery; 5 | using HandRolledMediator.Features.Films.ListFilmByIdQuery; 6 | 7 | public class ListFilmsByIdCommandHandler : CommandHandler 8 | { 9 | private readonly IListFilmByIdQuery listFilmByIdQuery; 10 | 11 | private readonly IGetDirectorByIdQuery getDirectorByIdQuery; 12 | 13 | private readonly IGetCastByFilmIdQuery getCastByFilmIdQuery; 14 | 15 | public ListFilmsByIdCommandHandler(IListFilmByIdQuery listFilmByIdQuery, IGetDirectorByIdQuery getDirectorByIdQuery, IGetCastByFilmIdQuery getCastByFilmIdQuery) 16 | { 17 | this.listFilmByIdQuery = listFilmByIdQuery; 18 | this.getDirectorByIdQuery = getDirectorByIdQuery; 19 | this.getCastByFilmIdQuery = getCastByFilmIdQuery; 20 | 21 | //No need to inject IPermissionService as we don't need it 22 | } 23 | 24 | protected override object Execute(ListFilmsByIdCommand command) 25 | { 26 | //Use shared query to get film 27 | var film = this.listFilmByIdQuery.Execute(command.Id); 28 | 29 | if (film == null) 30 | { 31 | return null; 32 | } 33 | 34 | var director = this.getDirectorByIdQuery.Execute(film.DirectorId); 35 | film.Director = director; 36 | 37 | var cast = this.getCastByFilmIdQuery.Execute(command.Id); 38 | film.Cast = cast; 39 | 40 | return film; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/ListFilmByIdQuery/IListFilmByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.ListFilmByIdQuery 2 | { 3 | using Models; 4 | 5 | public interface IListFilmByIdQuery 6 | { 7 | Film Execute(int id); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/ListFilmByIdQuery/ListFilmByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.ListFilmByIdQuery 2 | { 3 | using Models; 4 | 5 | public class ListFilmByIdQuery : IListFilmByIdQuery 6 | { 7 | public Film Execute(int id) 8 | { 9 | return new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1 }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/ListFilms/ListFilmsCommand.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.ListFilms 2 | { 3 | public class ListFilmsCommand 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/ListFilms/ListFilmsCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.ListFilms 2 | { 3 | using Models; 4 | 5 | public class ListFilmsCommandHandler : CommandHandler 6 | { 7 | protected override object Execute(ListFilmsCommand command) 8 | { 9 | return new[] { new Film { Id = 1, Name = "Pulp Fiction" }, new Film { Id = 2, Name = "Trainspotting" } }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/UpdateFilm/UpdateFilmCommand.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.UpdateFilm 2 | { 3 | using Models; 4 | 5 | public class UpdateFilmCommand 6 | { 7 | public int Id { get; } 8 | 9 | public Film Film { get; } 10 | 11 | public UpdateFilmCommand(int id, Film film) 12 | { 13 | this.Id = id; 14 | this.Film = film; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Films/UpdateFilm/UpdateFilmCommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Films.UpdateFilm 2 | { 3 | using System; 4 | using HandRolledMediator.Features.Films.ListFilmByIdQuery; 5 | using HandRolledMediator.Features.Permissions; 6 | 7 | public class UpdateFilmCommandHandler : CommandHandler 8 | { 9 | private readonly IListFilmByIdQuery listFilmByIdQuery; 10 | 11 | private readonly IValidUserQuery validUserQuery; 12 | 13 | public UpdateFilmCommandHandler(IListFilmByIdQuery listFilmByIdQuery, IValidUserQuery validUserQuery) 14 | { 15 | this.listFilmByIdQuery = listFilmByIdQuery; 16 | this.validUserQuery = validUserQuery; 17 | } 18 | 19 | protected override void Handle(UpdateFilmCommand command) 20 | { 21 | if (!this.validUserQuery.Execute()) 22 | { 23 | throw new InvalidOperationException(); 24 | } 25 | 26 | //Do some special MEGA CORP business validation 27 | 28 | var existingFilm = this.listFilmByIdQuery.Execute(command.Id); 29 | 30 | existingFilm.Name = command.Film.Name; 31 | existingFilm.Budget = command.Film.Budget; 32 | existingFilm.Language = command.Film.Language; 33 | 34 | //Write some SQL to store in db 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Permissions/IValidUserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Permissions 2 | { 3 | public interface IValidUserQuery 4 | { 5 | bool Execute(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /HandRolledMediator/Features/Permissions/ValidUserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator.Features.Permissions 2 | { 3 | using System; 4 | 5 | public class ValidUserQuery : IValidUserQuery 6 | { 7 | public bool Execute() 8 | { 9 | return new Random().Next() % 2 == 0; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /HandRolledMediator/HandRolledMediator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | BotwinMediator 5 | Exe 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /HandRolledMediator/Handler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | public class Handler 8 | { 9 | private readonly IEnumerable handlers; 10 | 11 | public Handler(IEnumerable handlers) 12 | { 13 | this.handlers = handlers; 14 | } 15 | 16 | public U Execute(T command) where T : class 17 | { 18 | var handler = this.handlers.FirstOrDefault(x => x.GetType().BaseType.GenericTypeArguments.Any(y => y == typeof(T))); 19 | 20 | if (handler == null) 21 | { 22 | throw new Exception($"Unfound handler for {typeof(T).Name}"); 23 | } 24 | 25 | return (U)handler.Execute(command); 26 | } 27 | 28 | public void Handle(T command) where T : class 29 | { 30 | var handler = this.handlers.FirstOrDefault(x => x.GetType().BaseType.GenericTypeArguments.Any(y => y == typeof(T))); 31 | 32 | if (handler == null) 33 | { 34 | throw new Exception($"Unfound handler for {typeof(T).Name}"); 35 | } 36 | 37 | handler.Handle(command); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /HandRolledMediator/ICommandHandler.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator 2 | { 3 | public interface ICommandHandler 4 | { 5 | object Execute(object command); 6 | void Handle(object command); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /HandRolledMediator/Program.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator 2 | { 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | WebHost.CreateDefaultBuilder(args) 11 | .UseStartup() 12 | .Build() 13 | .Run(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /HandRolledMediator/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace HandRolledMediator 2 | { 3 | using FluentValidation; 4 | using FluentValidation.AspNetCore; 5 | using HandRolledMediator.Features.CastMembers.GetCastByFilmIdQuery; 6 | using HandRolledMediator.Features.Directors.GetDirectorByIdQuery; 7 | using HandRolledMediator.Features.Films.CreateFilm; 8 | using HandRolledMediator.Features.Films.DeleteFilm; 9 | using HandRolledMediator.Features.Films.ListFilmById; 10 | using HandRolledMediator.Features.Films.ListFilmByIdQuery; 11 | using HandRolledMediator.Features.Films.ListFilms; 12 | using HandRolledMediator.Features.Films.UpdateFilm; 13 | using HandRolledMediator.Features.Permissions; 14 | using Microsoft.AspNetCore.Builder; 15 | using Microsoft.Extensions.DependencyInjection; 16 | using Models; 17 | 18 | public class Startup 19 | { 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddSingleton(); 23 | services.AddSingleton(); 24 | services.AddSingleton(); 25 | services.AddSingleton(); 26 | 27 | services.AddSingleton(s => new Handler(new ICommandHandler[] 28 | { 29 | new CreateFilmCommandHandler(s.GetRequiredService()), 30 | new DeleteFilmCommandHandler(s.GetRequiredService()), 31 | new ListFilmsByIdCommandHandler(s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService()), 32 | new ListFilmsCommandHandler(), 33 | new UpdateFilmCommandHandler(s.GetRequiredService(),s.GetRequiredService()) 34 | })); 35 | 36 | services.AddTransient, FilmValidator>(); 37 | 38 | services.AddMvc().AddFluentValidation(); 39 | 40 | } 41 | 42 | public void Configure(IApplicationBuilder app) 43 | { 44 | app.UseMvc(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MediatRWebAPI.Tests/Features/Films/CreateFilm/CreateFilmMessageHandlerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Tests.Features.Films.CreateFilm 2 | { 3 | using System; 4 | using FakeItEasy; 5 | using MediatRWebAPI.Features.Films.CreateFilm; 6 | using MediatRWebAPI.Features.Permissions; 7 | using Models; 8 | using Xunit; 9 | 10 | public class CreateFilmMessageHandlerTests 11 | { 12 | //This class can be unit test and/or integration test as our handler class is responsible for doing whatever it takes 13 | 14 | [Fact] 15 | public void Should_throw_exception_if_user_invalid() 16 | { 17 | //Given 18 | var fakeValidUserQuery = A.Fake(); 19 | A.CallTo(() => fakeValidUserQuery.Execute()).Returns(false); 20 | var handler = new CreateFilmMessageHandler(fakeValidUserQuery); 21 | 22 | //When & Then 23 | Assert.Throws(() => handler.Handle(new CreateFilmMessage(new Film()))); 24 | } 25 | 26 | //Could write an integration test to see if the database has a new film in it or use the API tests to POST then GET the film it created 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MediatRWebAPI.Tests/Features/Films/DeleteFilm/DeleteFilmMessageHandlerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Tests.Features.Films.DeleteFilm 2 | { 3 | using System; 4 | using FakeItEasy; 5 | using MediatRWebAPI.Features.Films.DeleteFilm; 6 | using MediatRWebAPI.Features.Permissions; 7 | using Xunit; 8 | 9 | public class DeleteFilmMessageHandlerTests 10 | { 11 | //This class can be unit test and/or integration test as our handler class is responsible for doing whatever it takes 12 | 13 | [Fact] 14 | public void Should_throw_exception_if_user_invalid() 15 | { 16 | //Given 17 | var fakeValidUserQuery = A.Fake(); 18 | A.CallTo(() => fakeValidUserQuery.Execute()).Returns(false); 19 | var handler = new DeleteFilmMessageHandler(fakeValidUserQuery); 20 | 21 | //When & Then 22 | Assert.Throws(() => handler.Handle(new DeleteFilmMessage(1))); 23 | } 24 | 25 | //Could write an integration test to see if the database has a new film in it or use the API tests to POST then GET the film it created 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MediatRWebAPI.Tests/Features/Films/FilmControllerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Tests.Features.Films 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using FakeItEasy; 8 | using FluentValidation; 9 | using FluentValidation.AspNetCore; 10 | using MediatR; 11 | using MediatRWebAPI.Features.Films.CreateFilm; 12 | using MediatRWebAPI.Features.Films.DeleteFilm; 13 | using MediatRWebAPI.Features.Films.ListFilmById; 14 | using MediatRWebAPI.Features.Films.ListFilms; 15 | using MediatRWebAPI.Features.Films.UpdateFilm; 16 | using Microsoft.AspNetCore; 17 | using Microsoft.AspNetCore.Builder; 18 | using Microsoft.AspNetCore.Hosting; 19 | using Microsoft.AspNetCore.TestHost; 20 | using Microsoft.Extensions.DependencyInjection; 21 | using Models; 22 | using Newtonsoft.Json; 23 | using Xunit; 24 | 25 | public class FilmControllerTests 26 | { 27 | [Fact] 28 | public async Task Should_get_list_of_films() 29 | { 30 | //Given 31 | var fakeMediatR = A.Fake(); 32 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Returns(new[] { new Film { Name = "Goodfellas" } }); 33 | var client = this.GetClient(fakeMediatR); 34 | 35 | //When 36 | var response = await client.GetAsync("/api/films"); 37 | var contents = await response.Content.ReadAsStringAsync(); 38 | 39 | //Then 40 | Assert.Contains("Goodfellas", contents, StringComparison.OrdinalIgnoreCase); 41 | } 42 | 43 | [Fact] 44 | public async Task Should_get_film_by_id() 45 | { 46 | //Given 47 | var fakeMediatR = A.Fake(); 48 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Returns(new Film { Name = "Blade Runner" }); 49 | var client = this.GetClient(fakeMediatR); 50 | 51 | //When 52 | var response = await client.GetAsync("/api/films/1"); 53 | var contents = await response.Content.ReadAsStringAsync(); 54 | 55 | //Then 56 | Assert.Contains("Blade Runner", contents, StringComparison.OrdinalIgnoreCase); 57 | } 58 | 59 | [Fact] 60 | public async Task Should_return_404_when_no_film_found_via_get_film_by_id() 61 | { 62 | //Given 63 | var fakeMediatR = A.Fake(); 64 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Returns(null); 65 | 66 | var client = this.GetClient(fakeMediatR); 67 | 68 | //When 69 | var response = await client.GetAsync("/api/films/1"); 70 | 71 | //Then 72 | Assert.Equal(404, (int)response.StatusCode); 73 | } 74 | 75 | [Fact] 76 | public async Task Should_return_400_on_invalid_data_when_creating_film() 77 | { 78 | //Given 79 | var fakeMediatR = A.Fake(); 80 | var client = this.GetClient(fakeMediatR); 81 | var film = new Film { Name = "" }; 82 | 83 | //When 84 | var response = await client.PostAsync("/api/films", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 85 | 86 | //Then 87 | Assert.Equal(400, (int)response.StatusCode); 88 | } 89 | 90 | [Fact] 91 | public async Task Should_return_403_on_invalid_user_when_creating_film() 92 | { 93 | //Given 94 | var fakeMediatR = A.Fake(); 95 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Throws(); 96 | 97 | var client = this.GetClient(fakeMediatR); 98 | 99 | var film = new Film { Name = "Shrek" }; 100 | 101 | //When 102 | var response = await client.PostAsync("/api/films", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 103 | 104 | //Then 105 | Assert.Equal(403, (int)response.StatusCode); 106 | } 107 | 108 | [Fact] 109 | public async Task Should_return_201_when_creating_film() 110 | { 111 | //Given 112 | var fakeMediatR = A.Fake(); 113 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Returns(new Unit()); 114 | var client = this.GetClient(fakeMediatR); 115 | 116 | var film = new Film { Name = "Shrek" }; 117 | 118 | //When 119 | var response = await client.PostAsync("/api/films", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 120 | 121 | //Then 122 | Assert.Equal(201, (int)response.StatusCode); 123 | } 124 | 125 | [Fact] 126 | public async Task Should_return_400_on_invalid_data_when_updating_film() 127 | { 128 | //Given 129 | var fakeMediatR = A.Fake(); 130 | var client = this.GetClient(fakeMediatR); 131 | var film = new Film { Name = "" }; 132 | 133 | //When 134 | var response = await client.PutAsync("/api/films/1", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 135 | 136 | //Then 137 | Assert.Equal(400, (int)response.StatusCode); 138 | } 139 | 140 | [Fact] 141 | public async Task Should_return_403_on_invalid_user_when_updating_film() 142 | { 143 | //Given 144 | var fakeMediatR = A.Fake(); 145 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Throws(); 146 | 147 | var client = this.GetClient(fakeMediatR); 148 | 149 | var film = new Film { Name = "Shrek" }; 150 | 151 | //When 152 | var response = await client.PutAsync("/api/films/1", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 153 | 154 | //Then 155 | Assert.Equal(403, (int)response.StatusCode); 156 | } 157 | 158 | [Fact] 159 | public async Task Should_return_204_when_deleting_film() 160 | { 161 | //Given 162 | var fakeMediatR = A.Fake(); 163 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Returns(new Unit()); 164 | var client = this.GetClient(fakeMediatR); 165 | 166 | //When 167 | var response = await client.DeleteAsync("/api/films/1"); 168 | 169 | //Then 170 | Assert.Equal(204, (int)response.StatusCode); 171 | } 172 | 173 | [Fact] 174 | public async Task Should_return_403_on_invalid_user_when_deleting_film() 175 | { 176 | //Given 177 | var fakeMediatR = A.Fake(); 178 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Throws(); 179 | 180 | var client = this.GetClient(fakeMediatR); 181 | 182 | //When 183 | var response = await client.DeleteAsync("/api/films/1"); 184 | 185 | //Then 186 | Assert.Equal(403, (int)response.StatusCode); 187 | } 188 | 189 | [Fact] 190 | public async Task Should_return_204_when_updating_film() 191 | { 192 | //Given 193 | var fakeMediatR = A.Fake(); 194 | A.CallTo(() => fakeMediatR.Send(A.Ignored)).Returns(new Unit()); 195 | var client = this.GetClient(fakeMediatR); 196 | 197 | var film = new Film { Name = "Shrek" }; 198 | 199 | //When 200 | var response = await client.PutAsync("/api/films/1", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 201 | 202 | //Then 203 | Assert.Equal(204, (int)response.StatusCode); 204 | } 205 | 206 | private HttpClient GetClient(IMediator fakeMediatR) 207 | { 208 | var server = new TestServer(WebHost.CreateDefaultBuilder() 209 | .Configure(app => { app.UseMvc(); }) 210 | .ConfigureServices(services => 211 | { 212 | services.AddSingleton(fakeMediatR); 213 | 214 | services.AddTransient, FilmValidator>(); 215 | 216 | services.AddMvc().AddFluentValidation(); 217 | }) 218 | ); 219 | 220 | return server.CreateClient(); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /MediatRWebAPI.Tests/Features/Films/ListFilmById/ListFilmsByIdMessageHandlerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Tests.Features.Films.ListFilmById 2 | { 3 | using FakeItEasy; 4 | using MediatRWebAPI.Features.CastMembers.GetCastByFilmIdQuery; 5 | using MediatRWebAPI.Features.Directors.GetDirectorByIdQuery; 6 | using MediatRWebAPI.Features.Films.ListFilmById; 7 | using MediatRWebAPI.Features.Films.ListFilmByIdQuery; 8 | using Xunit; 9 | 10 | public class ListFilmsByIdMessageHandlerTests 11 | { 12 | //This class can be unit test and/or integration test as our handler class is responsible for doing whatever it takes 13 | 14 | [Fact] 15 | public void Should_return_film() 16 | { 17 | //Given 18 | var fakeListFilmByIdQuery = A.Fake(); 19 | var fakeGetDirectorByIdQuery = A.Fake(); 20 | var fakeGetCastByIdDirectory = A.Fake(); 21 | var handler = new ListFilmsByIdMessageHandler(fakeListFilmByIdQuery, fakeGetDirectorByIdQuery, fakeGetCastByIdDirectory); 22 | 23 | //When 24 | var film = handler.Handle(new ListFilmsByIdMessage(1)); 25 | 26 | //Then 27 | Assert.NotNull(film); 28 | 29 | //The issue here, like the service tests are that we are testing the fakes are called which doesn't offer much value 30 | 31 | //If there is a bit more logic in the function then a unit test can be valuable, we can see that in the Create/Update/Delete handlers that do a valid user check 32 | 33 | //You are better off having an integration test at this point possibly with an in-memory sql database or Before/After xUnit attribute to setup db state 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MediatRWebAPI.Tests/Features/Films/ListFilms/ListFilmsMessageHandlerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Tests.Features.Films.ListFilms 2 | { 3 | using System.Linq; 4 | using MediatRWebAPI.Features.Films.ListFilms; 5 | using Xunit; 6 | 7 | public class ListFilmsMessageHandlerTests 8 | { 9 | //This class can be unit test and/or integration test as our handler class is responsible for doing whatever it takes 10 | 11 | [Fact] 12 | public void Should_return_list_of_films() 13 | { 14 | //Given 15 | var handler = new ListFilmsMessageHandler(); 16 | 17 | //When 18 | var films = handler.Handle(new ListFilmsMessage()); 19 | 20 | //Then 21 | Assert.True(films.Any()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MediatRWebAPI.Tests/Features/Films/UpdateFilm/UpdateFilmMessageHandlerTests.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Tests.Features.Films.UpdateFilm 2 | { 3 | using System; 4 | using FakeItEasy; 5 | using MediatRWebAPI.Features.Films.ListFilmByIdQuery; 6 | using MediatRWebAPI.Features.Films.UpdateFilm; 7 | using MediatRWebAPI.Features.Permissions; 8 | using Models; 9 | using Xunit; 10 | 11 | public class UpdateFilmMessageHandlerTests 12 | { 13 | //This class can be unit test and/or integration test as our handler class is responsible for doing whatever it takes 14 | 15 | [Fact] 16 | public void Should_throw_exception_if_user_invalid() 17 | { 18 | //Given 19 | var fakeValidUserQuery = A.Fake(); 20 | A.CallTo(() => fakeValidUserQuery.Execute()).Returns(false); 21 | var fakeListFilmByIdQuery = A.Fake(); 22 | 23 | var handler = new UpdateFilmMessageHandler(fakeListFilmByIdQuery, fakeValidUserQuery); 24 | 25 | //When & Then 26 | Assert.Throws(() => handler.Handle(new UpdateFilmMessage(1, new Film()))); 27 | } 28 | 29 | //Could write an integration test to see if the database has a new film in it or use the API tests to POST then GET the film it created 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MediatRWebAPI.Tests/MediatRWebAPI.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/CastMembers/GetCastByFilmIdQuery/GetCastByFilmIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.CastMembers.GetCastByFilmIdQuery 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public class GetCastByFilmIdQuery : IGetCastByFilmIdQuery 7 | { 8 | public IEnumerable Execute(int filmId) 9 | { 10 | //Do some SQL 11 | 12 | return new[] { new CastMember { Name = "John Travolta" }, new CastMember { Name = "Samuel L Jackson" } }; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /MediatRWebAPI/Features/CastMembers/GetCastByFilmIdQuery/IGetCastByFilmIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.CastMembers.GetCastByFilmIdQuery 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public interface IGetCastByFilmIdQuery 7 | { 8 | IEnumerable Execute(int filmId); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Directors/GetDirectorByIdQuery/GetDirectorByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Directors.GetDirectorByIdQuery 2 | { 3 | using Models; 4 | 5 | public class GetDirectorByIdQuery : IGetDirectorByIdQuery 6 | { 7 | public Director Execute(int id) 8 | { 9 | //Do some SQL 10 | 11 | return new Director { Name = "Steven Spielberg" }; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Directors/GetDirectorByIdQuery/IGetDirectorByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Directors.GetDirectorByIdQuery 2 | { 3 | using Models; 4 | 5 | public interface IGetDirectorByIdQuery 6 | { 7 | Director Execute(int id); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/CreateFilm/CreateFilmMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.CreateFilm 2 | { 3 | using MediatR; 4 | using Models; 5 | 6 | public class CreateFilmMessage : IRequest 7 | { 8 | public Film Film { get; } 9 | 10 | public CreateFilmMessage(Film film) 11 | { 12 | this.Film = film; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/CreateFilm/CreateFilmMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.CreateFilm 2 | { 3 | using System; 4 | using MediatR; 5 | using MediatRWebAPI.Features.Permissions; 6 | 7 | public class CreateFilmMessageHandler : IRequestHandler 8 | { 9 | private readonly IValidUserQuery validUserQuery; 10 | 11 | public CreateFilmMessageHandler(IValidUserQuery validUserQuery) 12 | { 13 | this.validUserQuery = validUserQuery; 14 | } 15 | public Unit Handle(CreateFilmMessage message) 16 | { 17 | if (!this.validUserQuery.Execute()) 18 | { 19 | throw new InvalidOperationException(); 20 | } 21 | 22 | //Do some special MEGA CORP business validation 23 | 24 | //Save to database by writing SQL here 25 | 26 | return new Unit(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/DeleteFilm/DeleteFilmMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.DeleteFilm 2 | { 3 | using MediatR; 4 | 5 | public class DeleteFilmMessage : IRequest 6 | { 7 | public int Id { get; } 8 | 9 | public DeleteFilmMessage(int id) 10 | { 11 | this.Id = id; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/DeleteFilm/DeleteFilmMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.DeleteFilm 2 | { 3 | using System; 4 | using MediatR; 5 | using MediatRWebAPI.Features.Permissions; 6 | 7 | public class DeleteFilmMessageHandler : IRequestHandler 8 | { 9 | private readonly IValidUserQuery validUserQuery; 10 | 11 | public DeleteFilmMessageHandler(IValidUserQuery validUserQuery) 12 | { 13 | this.validUserQuery = validUserQuery; 14 | } 15 | 16 | public Unit Handle(DeleteFilmMessage message) 17 | { 18 | if (!this.validUserQuery.Execute()) 19 | { 20 | throw new InvalidOperationException(); 21 | } 22 | 23 | //Write some SQL to delete from DB 24 | 25 | return new Unit(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/FilmsController.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using MediatR; 6 | using MediatRWebAPI.Features.Films.CreateFilm; 7 | using MediatRWebAPI.Features.Films.DeleteFilm; 8 | using MediatRWebAPI.Features.Films.ListFilmById; 9 | using MediatRWebAPI.Features.Films.ListFilms; 10 | using MediatRWebAPI.Features.Films.UpdateFilm; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Models; 13 | 14 | [Route("api/[controller]")] 15 | public class FilmsController : Controller 16 | { 17 | private readonly IMediator mediator; 18 | 19 | public FilmsController(IMediator mediator) 20 | { 21 | this.mediator = mediator; 22 | } 23 | 24 | // GET api/films 25 | [HttpGet] 26 | public IEnumerable Get() 27 | { 28 | var message = new ListFilmsMessage(); 29 | 30 | return this.mediator.Send(message); 31 | } 32 | 33 | // GET api/films/5 34 | [HttpGet("{id}")] 35 | public IActionResult Get(int id) 36 | { 37 | var message = new ListFilmsByIdMessage(id); 38 | 39 | var film = this.mediator.Send(message); 40 | 41 | if (film == null) 42 | { 43 | return this.NotFound(); 44 | } 45 | 46 | return this.Ok(film); 47 | } 48 | 49 | // POST api/films 50 | [HttpPost] 51 | public IActionResult Post([FromBody] Film film) 52 | { 53 | if (!this.ModelState.IsValid) 54 | { 55 | return this.BadRequest(this.ModelState); 56 | } 57 | 58 | try 59 | { 60 | var message = new CreateFilmMessage(film); 61 | this.mediator.Send(message); 62 | } 63 | catch (InvalidOperationException) 64 | { 65 | return StatusCode(403); 66 | } 67 | 68 | return StatusCode(201); 69 | } 70 | 71 | // PUT api/films/5 72 | [HttpPut("{id}")] 73 | public IActionResult Put(int id, [FromBody] Film film) 74 | { 75 | if (!this.ModelState.IsValid) 76 | { 77 | return this.BadRequest(this.ModelState); 78 | } 79 | 80 | try 81 | { 82 | var message = new UpdateFilmMessage(id, film); 83 | this.mediator.Send(message); 84 | } 85 | catch (InvalidOperationException) 86 | { 87 | return StatusCode(403); 88 | } 89 | 90 | return StatusCode(204); 91 | } 92 | 93 | // DELETE api/films/5 94 | [HttpDelete("{id}")] 95 | public IActionResult Delete(int id) 96 | { 97 | try 98 | { 99 | var message = new DeleteFilmMessage(id); 100 | this.mediator.Send(message); 101 | } 102 | catch (InvalidOperationException) 103 | { 104 | return StatusCode(403); 105 | } 106 | 107 | return StatusCode(204); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/ListFilmById/ListFilmsByIdMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.ListFilmById 2 | { 3 | using MediatR; 4 | using Models; 5 | 6 | public class ListFilmsByIdMessage : IRequest 7 | { 8 | public int Id { get; } 9 | 10 | public ListFilmsByIdMessage(int id) 11 | { 12 | this.Id = id; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/ListFilmById/ListFilmsByIdMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.ListFilmById 2 | { 3 | using MediatR; 4 | using MediatRWebAPI.Features.CastMembers.GetCastByFilmIdQuery; 5 | using MediatRWebAPI.Features.Directors.GetDirectorByIdQuery; 6 | using MediatRWebAPI.Features.Films.ListFilmByIdQuery; 7 | using Models; 8 | 9 | public class ListFilmsByIdMessageHandler : IRequestHandler 10 | { 11 | private readonly IListFilmByIdQuery listFilmByIdQuery; 12 | 13 | private readonly IGetDirectorByIdQuery getDirectorByIdQuery; 14 | 15 | private readonly IGetCastByFilmIdQuery getCastByFilmIdQuery; 16 | 17 | public ListFilmsByIdMessageHandler(IListFilmByIdQuery listFilmByIdQuery, IGetDirectorByIdQuery getDirectorByIdQuery, IGetCastByFilmIdQuery getCastByFilmIdQuery) 18 | { 19 | this.listFilmByIdQuery = listFilmByIdQuery; 20 | this.getDirectorByIdQuery = getDirectorByIdQuery; 21 | this.getCastByFilmIdQuery = getCastByFilmIdQuery; 22 | 23 | //No need to inject IPermissionService as we don't need it 24 | } 25 | 26 | public Film Handle(ListFilmsByIdMessage message) 27 | { 28 | //Use shared query to get film 29 | var film = this.listFilmByIdQuery.Execute(message.Id); 30 | 31 | var director = this.getDirectorByIdQuery.Execute(film.DirectorId); 32 | film.Director = director; 33 | 34 | var cast = this.getCastByFilmIdQuery.Execute(message.Id); 35 | film.Cast = cast; 36 | 37 | return film; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/ListFilmByIdQuery/IListFilmByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.ListFilmByIdQuery 2 | { 3 | using Models; 4 | 5 | public interface IListFilmByIdQuery 6 | { 7 | Film Execute(int id); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/ListFilmByIdQuery/ListFilmByIdQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.ListFilmByIdQuery 2 | { 3 | using Models; 4 | 5 | public class ListFilmByIdQuery : IListFilmByIdQuery 6 | { 7 | public Film Execute(int id) 8 | { 9 | return new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1 }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/ListFilms/ListFilmsMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.ListFilms 2 | { 3 | using System.Collections.Generic; 4 | using MediatR; 5 | using Models; 6 | 7 | public class ListFilmsMessage : IRequest> 8 | { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/ListFilms/ListFilmsMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.ListFilms 2 | { 3 | using System.Collections.Generic; 4 | using MediatR; 5 | using Models; 6 | 7 | public class ListFilmsMessageHandler : IRequestHandler> 8 | { 9 | public IEnumerable Handle(ListFilmsMessage message) 10 | { 11 | return new[] { new Film { Id = 1, Name = "Pulp Fiction" }, new Film { Id = 2, Name = "Trainspotting" } }; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/UpdateFilm/UpdateFilmMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.UpdateFilm 2 | { 3 | using MediatR; 4 | using Models; 5 | 6 | public class UpdateFilmMessage : IRequest 7 | { 8 | public int Id { get; } 9 | 10 | public Film Film { get; } 11 | 12 | public UpdateFilmMessage(int id, Film film) 13 | { 14 | this.Id = id; 15 | this.Film = film; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Films/UpdateFilm/UpdateFilmMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Films.UpdateFilm 2 | { 3 | using System; 4 | using MediatR; 5 | using MediatRWebAPI.Features.Films.ListFilmByIdQuery; 6 | using MediatRWebAPI.Features.Permissions; 7 | 8 | public class UpdateFilmMessageHandler : IRequestHandler 9 | { 10 | private readonly IListFilmByIdQuery listFilmByIdQuery; 11 | 12 | private readonly IValidUserQuery validUserQuery; 13 | 14 | public UpdateFilmMessageHandler(IListFilmByIdQuery listFilmByIdQuery, IValidUserQuery validUserQuery) 15 | { 16 | this.listFilmByIdQuery = listFilmByIdQuery; 17 | this.validUserQuery = validUserQuery; 18 | } 19 | 20 | public Unit Handle(UpdateFilmMessage message) 21 | { 22 | if (!this.validUserQuery.Execute()) 23 | { 24 | throw new InvalidOperationException(); 25 | } 26 | 27 | //Do some special MEGA CORP business validation 28 | 29 | var existingFilm = this.listFilmByIdQuery.Execute(message.Id); 30 | 31 | existingFilm.Name = message.Film.Name; 32 | existingFilm.Budget = message.Film.Budget; 33 | existingFilm.Language = message.Film.Language; 34 | 35 | //Write some SQL to store in db 36 | 37 | return new Unit(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Permissions/IValidUserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Permissions 2 | { 3 | public interface IValidUserQuery 4 | { 5 | bool Execute(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /MediatRWebAPI/Features/Permissions/ValidUserQuery.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI.Features.Permissions 2 | { 3 | using System; 4 | 5 | public class ValidUserQuery : IValidUserQuery 6 | { 7 | public bool Execute() 8 | { 9 | return new Random().Next() % 2 == 0; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MediatRWebAPI/MediatRWebAPI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp2.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MediatRWebAPI/Program.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI 2 | { 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MediatRWebAPI/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace MediatRWebAPI 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using FluentValidation; 6 | using FluentValidation.AspNetCore; 7 | using MediatR; 8 | using MediatRWebAPI.Features.CastMembers.GetCastByFilmIdQuery; 9 | using MediatRWebAPI.Features.Directors.GetDirectorByIdQuery; 10 | using MediatRWebAPI.Features.Films.CreateFilm; 11 | using MediatRWebAPI.Features.Films.DeleteFilm; 12 | using MediatRWebAPI.Features.Films.ListFilmById; 13 | using MediatRWebAPI.Features.Films.ListFilmByIdQuery; 14 | using MediatRWebAPI.Features.Films.ListFilms; 15 | using MediatRWebAPI.Features.Films.UpdateFilm; 16 | using MediatRWebAPI.Features.Permissions; 17 | using Microsoft.AspNetCore.Builder; 18 | using Microsoft.Extensions.Configuration; 19 | using Microsoft.Extensions.DependencyInjection; 20 | using Models; 21 | 22 | public class Startup 23 | { 24 | public Startup(IConfiguration configuration) => Configuration = configuration; 25 | 26 | public IConfiguration Configuration { get; } 27 | 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | services.AddSingleton(); 31 | services.AddSingleton(); 32 | services.AddSingleton(); 33 | services.AddSingleton(); 34 | 35 | services.AddSingleton, ListFilmsByIdMessageHandler>(); 36 | services.AddSingleton>, ListFilmsMessageHandler>(); 37 | services.AddSingleton, CreateFilmMessageHandler>(); 38 | services.AddSingleton, DeleteFilmMessageHandler>(); 39 | services.AddSingleton, UpdateFilmMessageHandler>(); 40 | 41 | services.AddScoped(p => p.GetRequiredService); 42 | services.AddScoped(p => p.GetRequiredServices); 43 | services.AddSingleton(); 44 | 45 | services.AddTransient, FilmValidator>(); 46 | 47 | services.AddMvc().AddFluentValidation(); 48 | 49 | 50 | } 51 | 52 | public void Configure(IApplicationBuilder app) 53 | { 54 | app.UseMvc(); 55 | } 56 | 57 | 58 | } 59 | 60 | public static class Foo 61 | { 62 | public static IEnumerable GetRequiredServices(this IServiceProvider provider, Type serviceType) 63 | { 64 | return (IEnumerable) provider.GetRequiredService(typeof(IEnumerable<>).MakeGenericType(serviceType)); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /MediatRWebAPI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MediatRWebAPI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Models/CastMember.cs: -------------------------------------------------------------------------------- 1 | namespace Models 2 | { 3 | public class CastMember 4 | { 5 | public string Name { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /Models/Director.cs: -------------------------------------------------------------------------------- 1 | namespace Models 2 | { 3 | public class Director 4 | { 5 | public string Name { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /Models/Film.cs: -------------------------------------------------------------------------------- 1 | namespace Models 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public class Film 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | 12 | public DateTime ReleaseDate { get; set; } 13 | 14 | public string Synopsis { get; set; } 15 | 16 | public string Language { get; set; } 17 | 18 | public int Budget { get; set; } 19 | 20 | public string Website { get; set; } 21 | 22 | public int RunTime { get; set; } 23 | 24 | public Director Director { get; set; } 25 | 26 | public IEnumerable Cast { get; set; } 27 | 28 | public int DirectorId { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /Models/FilmValidator.cs: -------------------------------------------------------------------------------- 1 | namespace Models 2 | { 3 | using FluentValidation; 4 | 5 | public class FilmValidator : AbstractValidator 6 | { 7 | public FilmValidator() 8 | { 9 | this.RuleFor(x => x.Name).NotEmpty(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Models/Models.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simpler ASP.Net Core 2 | 3 | Video of this code at NDC Oslo 2018 is here [https://www.youtube.com/watch?v=IROSd7uB-tg](https://www.youtube.com/watch?v=IROSd7uB-tg) 4 | -------------------------------------------------------------------------------- /Slides.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchannon/T1000/b448028dbeb01c597a5d40542f5b513719abc2a7/Slides.pptx -------------------------------------------------------------------------------- /T1000.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraditionalWebAPI", "TraditionalWebAPI\TraditionalWebAPI.csproj", "{626AD192-F5D0-4AE1-AEEC-7B8FE40E927F}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Models", "Models\Models.csproj", "{02A2B651-0B7C-498B-A671-80CF50EFC017}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Traditional Project", "1. Traditional Project", "{66D3DCED-78BD-4DB8-8C8D-3A5FA09543C6}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. MediatR Project", "2. MediatR Project", "{48EF8D64-76E9-4E2A-9939-7D50C7E320B8}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatRWebAPI", "MediatRWebAPI\MediatRWebAPI.csproj", "{40EC1455-176C-4B55-A550-E6F54AFD4491}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3. Mediator Project (no external deps)", "3. Mediator Project (no external deps)", "{892479C5-1CB2-4748-A910-89A57995D73C}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HandRolledMediator", "HandRolledMediator\HandRolledMediator.csproj", "{3442E574-7406-4DD0-A28A-96534CE47310}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5. Functional Carter Project", "5. Functional Carter Project", "{345FCBAD-019D-4A9B-B7A3-05A536BECBA4}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionalCarterProject", "FunctionalCarterProject\FunctionalCarterProject.csproj", "{A511EF90-1484-45BA-8D8E-2BB733597A84}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E2EE7155-DAB9-4A7F-9D2E-2A38ECCCC566}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{44C1DD68-1371-456C-9541-BCC3CEE8A2BE}" 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1. Traditional Project Tests", "1. Traditional Project Tests", "{DD7F5385-6594-4AF5-ADE9-ABC65C2A29BC}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraditionalWebAPI.Tests", "TraditionalWebAPI.Tests\TraditionalWebAPI.Tests.csproj", "{2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}" 29 | EndProject 30 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. MediatR Project Tests", "2. MediatR Project Tests", "{694570A7-D60F-4058-A86F-B146204891B7}" 31 | EndProject 32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatRWebAPI.Tests", "MediatRWebAPI.Tests\MediatRWebAPI.Tests.csproj", "{E9EA372B-B08D-4C2E-B78A-948810D79F02}" 33 | EndProject 34 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4. Functional Carter Project Tests", "4. Functional Carter Project Tests", "{F03EA84D-15EF-4E1D-8FBE-79FFC0FDC277}" 35 | EndProject 36 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionalCarterProjectTests", "FunctionalCarterProjectTests\FunctionalCarterProjectTests.csproj", "{0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}" 37 | EndProject 38 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4. Functional Project", "4. Functional Project", "{FA6BC71B-8C97-477D-8391-1DB5BC4FD9EF}" 39 | EndProject 40 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionalProject", "FunctionalProject\FunctionalProject.csproj", "{B74551EE-9A45-4198-A745-7304029376B9}" 41 | EndProject 42 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3. Functional Project Tests", "3. Functional Project Tests", "{0EA3053A-0977-4915-AEEA-9B5510B25D00}" 43 | EndProject 44 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionalProjectTests", "FunctionalProjectTests\FunctionalProjectTests.csproj", "{69BEA771-2C21-4FDF-B68C-2531812AB003}" 45 | EndProject 46 | Global 47 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 48 | Debug|Any CPU = Debug|Any CPU 49 | Release|Any CPU = Release|Any CPU 50 | Debug|x64 = Debug|x64 51 | Debug|x86 = Debug|x86 52 | Release|x64 = Release|x64 53 | Release|x86 = Release|x86 54 | EndGlobalSection 55 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 56 | {626AD192-F5D0-4AE1-AEEC-7B8FE40E927F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {626AD192-F5D0-4AE1-AEEC-7B8FE40E927F}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {626AD192-F5D0-4AE1-AEEC-7B8FE40E927F}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {626AD192-F5D0-4AE1-AEEC-7B8FE40E927F}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {02A2B651-0B7C-498B-A671-80CF50EFC017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {02A2B651-0B7C-498B-A671-80CF50EFC017}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {02A2B651-0B7C-498B-A671-80CF50EFC017}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {02A2B651-0B7C-498B-A671-80CF50EFC017}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {40EC1455-176C-4B55-A550-E6F54AFD4491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {40EC1455-176C-4B55-A550-E6F54AFD4491}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {40EC1455-176C-4B55-A550-E6F54AFD4491}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {40EC1455-176C-4B55-A550-E6F54AFD4491}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {3442E574-7406-4DD0-A28A-96534CE47310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {3442E574-7406-4DD0-A28A-96534CE47310}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {3442E574-7406-4DD0-A28A-96534CE47310}.Debug|x64.ActiveCfg = Debug|x64 71 | {3442E574-7406-4DD0-A28A-96534CE47310}.Debug|x64.Build.0 = Debug|x64 72 | {3442E574-7406-4DD0-A28A-96534CE47310}.Debug|x86.ActiveCfg = Debug|x86 73 | {3442E574-7406-4DD0-A28A-96534CE47310}.Debug|x86.Build.0 = Debug|x86 74 | {3442E574-7406-4DD0-A28A-96534CE47310}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {3442E574-7406-4DD0-A28A-96534CE47310}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {3442E574-7406-4DD0-A28A-96534CE47310}.Release|x64.ActiveCfg = Release|x64 77 | {3442E574-7406-4DD0-A28A-96534CE47310}.Release|x64.Build.0 = Release|x64 78 | {3442E574-7406-4DD0-A28A-96534CE47310}.Release|x86.ActiveCfg = Release|x86 79 | {3442E574-7406-4DD0-A28A-96534CE47310}.Release|x86.Build.0 = Release|x86 80 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Debug|x64.ActiveCfg = Debug|Any CPU 85 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Debug|x64.Build.0 = Debug|Any CPU 86 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Debug|x86.ActiveCfg = Debug|Any CPU 87 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Debug|x86.Build.0 = Debug|Any CPU 88 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Release|x64.ActiveCfg = Release|Any CPU 89 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Release|x64.Build.0 = Release|Any CPU 90 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Release|x86.ActiveCfg = Release|Any CPU 91 | {A511EF90-1484-45BA-8D8E-2BB733597A84}.Release|x86.Build.0 = Release|Any CPU 92 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 93 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 94 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Debug|x64.ActiveCfg = Debug|Any CPU 97 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Debug|x64.Build.0 = Debug|Any CPU 98 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Debug|x86.ActiveCfg = Debug|Any CPU 99 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Debug|x86.Build.0 = Debug|Any CPU 100 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Release|x64.ActiveCfg = Release|Any CPU 101 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Release|x64.Build.0 = Release|Any CPU 102 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Release|x86.ActiveCfg = Release|Any CPU 103 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF}.Release|x86.Build.0 = Release|Any CPU 104 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 105 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Debug|Any CPU.Build.0 = Debug|Any CPU 106 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Release|Any CPU.Build.0 = Release|Any CPU 108 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Debug|x64.ActiveCfg = Debug|Any CPU 109 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Debug|x64.Build.0 = Debug|Any CPU 110 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Debug|x86.ActiveCfg = Debug|Any CPU 111 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Debug|x86.Build.0 = Debug|Any CPU 112 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Release|x64.ActiveCfg = Release|Any CPU 113 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Release|x64.Build.0 = Release|Any CPU 114 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Release|x86.ActiveCfg = Release|Any CPU 115 | {E9EA372B-B08D-4C2E-B78A-948810D79F02}.Release|x86.Build.0 = Release|Any CPU 116 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 117 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Debug|Any CPU.Build.0 = Debug|Any CPU 118 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Release|Any CPU.ActiveCfg = Release|Any CPU 119 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Release|Any CPU.Build.0 = Release|Any CPU 120 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Debug|x64.ActiveCfg = Debug|Any CPU 121 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Debug|x64.Build.0 = Debug|Any CPU 122 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Debug|x86.ActiveCfg = Debug|Any CPU 123 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Debug|x86.Build.0 = Debug|Any CPU 124 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Release|x64.ActiveCfg = Release|Any CPU 125 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Release|x64.Build.0 = Release|Any CPU 126 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Release|x86.ActiveCfg = Release|Any CPU 127 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A}.Release|x86.Build.0 = Release|Any CPU 128 | {B74551EE-9A45-4198-A745-7304029376B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 129 | {B74551EE-9A45-4198-A745-7304029376B9}.Debug|Any CPU.Build.0 = Debug|Any CPU 130 | {B74551EE-9A45-4198-A745-7304029376B9}.Release|Any CPU.ActiveCfg = Release|Any CPU 131 | {B74551EE-9A45-4198-A745-7304029376B9}.Release|Any CPU.Build.0 = Release|Any CPU 132 | {B74551EE-9A45-4198-A745-7304029376B9}.Debug|x64.ActiveCfg = Debug|Any CPU 133 | {B74551EE-9A45-4198-A745-7304029376B9}.Debug|x64.Build.0 = Debug|Any CPU 134 | {B74551EE-9A45-4198-A745-7304029376B9}.Debug|x86.ActiveCfg = Debug|Any CPU 135 | {B74551EE-9A45-4198-A745-7304029376B9}.Debug|x86.Build.0 = Debug|Any CPU 136 | {B74551EE-9A45-4198-A745-7304029376B9}.Release|x64.ActiveCfg = Release|Any CPU 137 | {B74551EE-9A45-4198-A745-7304029376B9}.Release|x64.Build.0 = Release|Any CPU 138 | {B74551EE-9A45-4198-A745-7304029376B9}.Release|x86.ActiveCfg = Release|Any CPU 139 | {B74551EE-9A45-4198-A745-7304029376B9}.Release|x86.Build.0 = Release|Any CPU 140 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 141 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Debug|Any CPU.Build.0 = Debug|Any CPU 142 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Release|Any CPU.ActiveCfg = Release|Any CPU 143 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Release|Any CPU.Build.0 = Release|Any CPU 144 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Debug|x64.ActiveCfg = Debug|Any CPU 145 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Debug|x64.Build.0 = Debug|Any CPU 146 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Debug|x86.ActiveCfg = Debug|Any CPU 147 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Debug|x86.Build.0 = Debug|Any CPU 148 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Release|x64.ActiveCfg = Release|Any CPU 149 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Release|x64.Build.0 = Release|Any CPU 150 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Release|x86.ActiveCfg = Release|Any CPU 151 | {69BEA771-2C21-4FDF-B68C-2531812AB003}.Release|x86.Build.0 = Release|Any CPU 152 | EndGlobalSection 153 | GlobalSection(NestedProjects) = preSolution 154 | {626AD192-F5D0-4AE1-AEEC-7B8FE40E927F} = {66D3DCED-78BD-4DB8-8C8D-3A5FA09543C6} 155 | {40EC1455-176C-4B55-A550-E6F54AFD4491} = {48EF8D64-76E9-4E2A-9939-7D50C7E320B8} 156 | {3442E574-7406-4DD0-A28A-96534CE47310} = {892479C5-1CB2-4748-A910-89A57995D73C} 157 | {A511EF90-1484-45BA-8D8E-2BB733597A84} = {345FCBAD-019D-4A9B-B7A3-05A536BECBA4} 158 | {345FCBAD-019D-4A9B-B7A3-05A536BECBA4} = {E2EE7155-DAB9-4A7F-9D2E-2A38ECCCC566} 159 | {892479C5-1CB2-4748-A910-89A57995D73C} = {E2EE7155-DAB9-4A7F-9D2E-2A38ECCCC566} 160 | {48EF8D64-76E9-4E2A-9939-7D50C7E320B8} = {E2EE7155-DAB9-4A7F-9D2E-2A38ECCCC566} 161 | {66D3DCED-78BD-4DB8-8C8D-3A5FA09543C6} = {E2EE7155-DAB9-4A7F-9D2E-2A38ECCCC566} 162 | {DD7F5385-6594-4AF5-ADE9-ABC65C2A29BC} = {44C1DD68-1371-456C-9541-BCC3CEE8A2BE} 163 | {2974A21D-76F8-4248-BF01-E7B5BDA6A6FF} = {DD7F5385-6594-4AF5-ADE9-ABC65C2A29BC} 164 | {694570A7-D60F-4058-A86F-B146204891B7} = {44C1DD68-1371-456C-9541-BCC3CEE8A2BE} 165 | {E9EA372B-B08D-4C2E-B78A-948810D79F02} = {694570A7-D60F-4058-A86F-B146204891B7} 166 | {F03EA84D-15EF-4E1D-8FBE-79FFC0FDC277} = {44C1DD68-1371-456C-9541-BCC3CEE8A2BE} 167 | {0A4CAD93-00B1-4ED6-9C92-3DB4C4F8509A} = {F03EA84D-15EF-4E1D-8FBE-79FFC0FDC277} 168 | {FA6BC71B-8C97-477D-8391-1DB5BC4FD9EF} = {E2EE7155-DAB9-4A7F-9D2E-2A38ECCCC566} 169 | {B74551EE-9A45-4198-A745-7304029376B9} = {FA6BC71B-8C97-477D-8391-1DB5BC4FD9EF} 170 | {0EA3053A-0977-4915-AEEA-9B5510B25D00} = {44C1DD68-1371-456C-9541-BCC3CEE8A2BE} 171 | {69BEA771-2C21-4FDF-B68C-2531812AB003} = {0EA3053A-0977-4915-AEEA-9B5510B25D00} 172 | EndGlobalSection 173 | EndGlobal 174 | -------------------------------------------------------------------------------- /T1000.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | WARNING 4 | WARNING 5 | WARNING 6 | WARNING 7 | WARNING 8 | WARNING 9 | <?xml version="1.0" encoding="utf-16"?><Profile name="NancyStandard"><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSShortenReferences>True</CSShortenReferences><CSReorderTypeMembers>True</CSReorderTypeMembers><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /></Profile> 10 | Required 11 | Required 12 | Required 13 | Required 14 | Required 15 | Required 16 | Required 17 | Required 18 | All 19 | False 20 | NEXT_LINE 21 | 1 22 | 1 23 | 1 24 | 1 25 | 1 26 | 0 27 | ALWAYS_ADD 28 | ALWAYS_ADD 29 | ALWAYS_ADD 30 | ALWAYS_ADD 31 | ALWAYS_ADD 32 | ALWAYS_ADD 33 | NEXT_LINE 34 | 1 35 | 1 36 | True 37 | NEVER 38 | NEVER 39 | ALWAYS_USE 40 | LINE_BREAK 41 | False 42 | True 43 | False 44 | False 45 | True 46 | False 47 | True 48 | True 49 | 50 | 51 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="Aa_bb" /></Policy> 52 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 53 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 54 | True 55 | True 56 | True 57 | True 58 | True 59 | True 60 | True 61 | True 62 | <data><IncludeFilters /><ExcludeFilters /></data> 63 | <data /> 64 | 65 | -------------------------------------------------------------------------------- /T1000settings.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchannon/T1000/b448028dbeb01c597a5d40542f5b513719abc2a7/T1000settings.jar -------------------------------------------------------------------------------- /TraditionalWebAPI.Tests/Controllers/FilmControllerTests.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Tests.Controllers 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using FakeItEasy; 8 | using FluentValidation; 9 | using FluentValidation.AspNetCore; 10 | using Microsoft.AspNetCore; 11 | using Microsoft.AspNetCore.Builder; 12 | using Microsoft.AspNetCore.Hosting; 13 | using Microsoft.AspNetCore.TestHost; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Models; 16 | using Newtonsoft.Json; 17 | using TraditionalWebAPI.Services; 18 | using Xunit; 19 | 20 | public class FilmControllerTests 21 | { 22 | 23 | [Fact] 24 | public async Task Should_get_list_of_films() 25 | { 26 | //Given 27 | var client = this.GetClient(); 28 | 29 | //When 30 | var response = await client.GetAsync("/api/films"); 31 | var contents = await response.Content.ReadAsStringAsync(); 32 | 33 | //Then 34 | Assert.Contains("Goodfellas", contents, StringComparison.OrdinalIgnoreCase); 35 | } 36 | 37 | [Fact] 38 | public async Task Should_get_film_by_id() 39 | { 40 | //Given 41 | var client = this.GetClient(); 42 | 43 | //When 44 | var response = await client.GetAsync("/api/films/1"); 45 | var contents = await response.Content.ReadAsStringAsync(); 46 | 47 | //Then 48 | Assert.Contains("Blade Runner", contents, StringComparison.OrdinalIgnoreCase); 49 | } 50 | 51 | [Fact] 52 | public async Task Should_return_404_when_no_film_found_via_get_film_by_id() 53 | { 54 | //Given 55 | var fakeFilmService = A.Fake(); 56 | A.CallTo(() => fakeFilmService.ListFilmById(1)).Returns(null); 57 | 58 | var client = this.GetClient(fakeFilmService); 59 | 60 | //When 61 | var response = await client.GetAsync("/api/films/1"); 62 | 63 | //Then 64 | Assert.Equal(404, (int)response.StatusCode); 65 | } 66 | 67 | [Fact] 68 | public async Task Should_return_400_on_invalid_data_when_creating_film() 69 | { 70 | //Given 71 | var client = this.GetClient(); 72 | var film = new Film { Name = "" }; 73 | 74 | //When 75 | var response = await client.PostAsync("/api/films", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 76 | 77 | //Then 78 | Assert.Equal(400, (int)response.StatusCode); 79 | } 80 | 81 | [Fact] 82 | public async Task Should_return_403_on_invalid_user_when_creating_film() 83 | { 84 | //Given 85 | var fakeFilmService = A.Fake(); 86 | A.CallTo(() => fakeFilmService.CreateFilm(A.Ignored)).Throws(); 87 | 88 | var client = this.GetClient(fakeFilmService); 89 | 90 | var film = new Film { Name = "Shrek" }; 91 | 92 | //When 93 | var response = await client.PostAsync("/api/films", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 94 | 95 | //Then 96 | Assert.Equal(403, (int)response.StatusCode); 97 | } 98 | 99 | [Fact] 100 | public async Task Should_return_201_when_creating_film() 101 | { 102 | //Given 103 | var client = this.GetClient(); 104 | var film = new Film { Name = "Shrek" }; 105 | 106 | //When 107 | var response = await client.PostAsync("/api/films", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 108 | 109 | //Then 110 | Assert.Equal(201, (int)response.StatusCode); 111 | } 112 | 113 | [Fact] 114 | public async Task Should_return_400_on_invalid_data_when_updating_film() 115 | { 116 | //Given 117 | var client = this.GetClient(); 118 | var film = new Film { Name = "" }; 119 | 120 | //When 121 | var response = await client.PutAsync("/api/films/1", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 122 | 123 | //Then 124 | Assert.Equal(400, (int)response.StatusCode); 125 | } 126 | 127 | [Fact] 128 | public async Task Should_return_403_on_invalid_user_when_updating_film() 129 | { 130 | //Given 131 | var fakeFilmService = A.Fake(); 132 | A.CallTo(() => fakeFilmService.UpdateFilm(1, A.Ignored)).Throws(); 133 | 134 | var client = this.GetClient(fakeFilmService); 135 | 136 | var film = new Film { Name = "Shrek" }; 137 | 138 | //When 139 | var response = await client.PutAsync("/api/films/1", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 140 | 141 | //Then 142 | Assert.Equal(403, (int)response.StatusCode); 143 | } 144 | 145 | [Fact] 146 | public async Task Should_return_204_when_updating_film() 147 | { 148 | //Given 149 | var client = this.GetClient(); 150 | var film = new Film { Name = "Shrek" }; 151 | 152 | //When 153 | var response = await client.PutAsync("/api/films/1", new StringContent(JsonConvert.SerializeObject(film), Encoding.UTF8, "application/json")); 154 | 155 | //Then 156 | Assert.Equal(204, (int)response.StatusCode); 157 | } 158 | 159 | [Fact] 160 | public async Task Should_return_204_when_deleting_film() 161 | { 162 | //Given 163 | var client = this.GetClient(); 164 | 165 | //When 166 | var response = await client.DeleteAsync("/api/films/1"); 167 | 168 | //Then 169 | Assert.Equal(204, (int)response.StatusCode); 170 | } 171 | 172 | [Fact] 173 | public async Task Should_return_403_on_invalid_user_when_deleting_film() 174 | { 175 | //Given 176 | var fakeFilmService = A.Fake(); 177 | A.CallTo(() => fakeFilmService.DeleteFilm(1)).Throws(); 178 | 179 | var client = this.GetClient(fakeFilmService); 180 | 181 | //When 182 | var response = await client.DeleteAsync("/api/films/1"); 183 | 184 | //Then 185 | Assert.Equal(403, (int)response.StatusCode); 186 | } 187 | 188 | private HttpClient GetClient(IFilmService filmService = null) 189 | { 190 | if (filmService == null) 191 | { 192 | filmService = A.Fake(); 193 | A.CallTo(() => filmService.ListFilms()).Returns(new[] { new Film { Name = "Goodfellas" } }); 194 | A.CallTo(() => filmService.ListFilmById(1)).Returns(new Film { Name = "Blade Runner" }); 195 | } 196 | 197 | var server = new TestServer(WebHost.CreateDefaultBuilder() 198 | .Configure(app => { app.UseMvc(); }) 199 | .ConfigureServices(services => 200 | { 201 | services.AddSingleton(filmService); 202 | 203 | services.AddTransient, FilmValidator>(); 204 | 205 | services.AddMvc().AddFluentValidation(); 206 | }) 207 | ); 208 | 209 | return server.CreateClient(); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /TraditionalWebAPI.Tests/Services/FilmServiceTests.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Tests.Services 2 | { 3 | using System; 4 | using FakeItEasy; 5 | using Models; 6 | using TraditionalWebAPI.Repositories; 7 | using TraditionalWebAPI.Services; 8 | using Xunit; 9 | 10 | public class FilmServiceTests 11 | { 12 | [Fact] 13 | public void Should_call_film_repo_to_get_films() 14 | { 15 | //Test fakes to see if they are called, integration test repositories 16 | 17 | //Given 18 | var fakeFilmRepository = A.Fake(); 19 | var fakeFilmService = this.GetFilmService(fakeFilmRepository); 20 | 21 | //When 22 | fakeFilmService.ListFilms(); 23 | 24 | //Then 25 | A.CallTo(() => fakeFilmRepository.ListFilms()).MustHaveHappened(); 26 | } 27 | 28 | [Fact] 29 | public void Should_call_film_repo_to_get_film_by_id() 30 | { 31 | //Test fakes to see if they are called, integration test repositories 32 | 33 | //Given 34 | var fakeFilmRepository = A.Fake(); 35 | var fakeFilmService = this.GetFilmService(fakeFilmRepository); 36 | 37 | //When 38 | fakeFilmService.ListFilmById(1); 39 | 40 | //Then 41 | A.CallTo(() => fakeFilmRepository.ListFilmById(1)).MustHaveHappened(); 42 | } 43 | 44 | [Fact] 45 | public void Should_call_film_repo_to_save_new_film() 46 | { 47 | //Test fakes to see if they are called, integration test repositories 48 | 49 | //Given 50 | var fakeFilmRepository = A.Fake(); 51 | var fakeFilmService = this.GetFilmService(fakeFilmRepository); 52 | 53 | //When 54 | fakeFilmService.CreateFilm(new Film()); 55 | 56 | //Then 57 | A.CallTo(() => fakeFilmRepository.CreateFilm(A.Ignored)).MustHaveHappened(); 58 | } 59 | 60 | [Fact] 61 | public void Should_throw_exception_when_creating_film_with_invalid_user() 62 | { 63 | //Given 64 | var fakePermissionService = A.Fake(); 65 | A.CallTo(() => fakePermissionService.ValidUser()).Returns(false); 66 | var filmService = this.GetFilmService(permissionService: fakePermissionService); 67 | 68 | //When & Then 69 | Assert.Throws(() => filmService.CreateFilm(new Film())); 70 | } 71 | 72 | [Fact] 73 | public void Should_throw_exception_when_updating_film_with_invalid_user() 74 | { 75 | //Given 76 | var fakePermissionService = A.Fake(); 77 | A.CallTo(() => fakePermissionService.ValidUser()).Returns(false); 78 | var filmService = this.GetFilmService(permissionService: fakePermissionService); 79 | 80 | //When & Then 81 | Assert.Throws(() => filmService.UpdateFilm(1, new Film())); 82 | } 83 | 84 | [Fact] 85 | public void Should_call_film_repo_to_update_film() 86 | { 87 | //Test fakes to see if they are called, integration test repositories 88 | 89 | //Given 90 | var fakeFilmRepository = A.Fake(); 91 | var fakeFilmService = this.GetFilmService(fakeFilmRepository); 92 | 93 | //When 94 | fakeFilmService.UpdateFilm(1, new Film()); 95 | 96 | //Then 97 | A.CallTo(() => fakeFilmRepository.UpdateFilm(A.Ignored)).MustHaveHappened(); 98 | } 99 | 100 | [Fact] 101 | public void Should_throw_exception_when_deleting_film_with_invalid_user() 102 | { 103 | //Given 104 | var fakePermissionService = A.Fake(); 105 | A.CallTo(() => fakePermissionService.ValidUser()).Returns(false); 106 | var filmService = this.GetFilmService(permissionService: fakePermissionService); 107 | 108 | //When & Then 109 | Assert.Throws(() => filmService.DeleteFilm(1)); 110 | } 111 | 112 | [Fact] 113 | public void Should_call_film_repo_to_delete_film() 114 | { 115 | //Test fakes to see if they are called, integration test repositories 116 | 117 | //Given 118 | var fakeFilmRepository = A.Fake(); 119 | var fakeFilmService = this.GetFilmService(fakeFilmRepository); 120 | 121 | //When 122 | fakeFilmService.DeleteFilm(1); 123 | 124 | //Then 125 | A.CallTo(() => fakeFilmRepository.DeleteFilm(1)).MustHaveHappened(); 126 | } 127 | 128 | private FilmService GetFilmService(IFilmRepository filmRepository = null, IPermissionService permissionService = null) 129 | { 130 | var fakeFilmRepository = filmRepository ?? A.Fake(); 131 | var fakeDirectorService = A.Fake(); 132 | var fakeCastMemberService = A.Fake(); 133 | 134 | if (permissionService == null) 135 | { 136 | permissionService = A.Fake(); 137 | A.CallTo(() => permissionService.ValidUser()).Returns(true); 138 | } 139 | 140 | return new FilmService(fakeFilmRepository, fakeDirectorService, fakeCastMemberService, permissionService); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /TraditionalWebAPI.Tests/TraditionalWebAPI.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Controllers/FilmsController.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Controllers 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Models; 8 | using TraditionalWebAPI.Services; 9 | 10 | [AllowAnonymous] 11 | [Route("api/[controller]")] 12 | public class FilmsController : Controller 13 | { 14 | private readonly IFilmService filmService; 15 | 16 | public FilmsController(IFilmService filmService) 17 | { 18 | this.filmService = filmService; 19 | } 20 | 21 | // GET api/films 22 | [HttpGet] 23 | public IEnumerable Get() 24 | { 25 | return this.filmService.ListFilms(); 26 | } 27 | 28 | // GET api/films/5 29 | [HttpGet("{id}")] 30 | public IActionResult Get(int id) 31 | { 32 | var film = this.filmService.ListFilmById(id); 33 | if (film == null) 34 | { 35 | return this.NotFound(); 36 | } 37 | 38 | return this.Ok(film); 39 | } 40 | 41 | // POST api/films 42 | [HttpPost] 43 | public IActionResult Post([FromBody] Film film) 44 | { 45 | if (!this.ModelState.IsValid) 46 | { 47 | return this.BadRequest(this.ModelState); 48 | } 49 | 50 | try 51 | { 52 | this.filmService.CreateFilm(film); 53 | } 54 | catch (InvalidOperationException) 55 | { 56 | return this.StatusCode(403); 57 | } 58 | 59 | return this.StatusCode(201); 60 | } 61 | 62 | // PUT api/films/5 63 | [HttpPut("{id}")] 64 | public IActionResult Put(int id, [FromBody] Film film) 65 | { 66 | if (!this.ModelState.IsValid) 67 | { 68 | return this.BadRequest(this.ModelState); 69 | } 70 | 71 | try 72 | { 73 | this.filmService.UpdateFilm(id, film); 74 | } 75 | catch (InvalidOperationException) 76 | { 77 | return this.StatusCode(403); 78 | } 79 | 80 | return this.StatusCode(204); 81 | } 82 | 83 | // DELETE api/films/5 84 | [HttpDelete("{id}")] 85 | public IActionResult Delete(int id) 86 | { 87 | try 88 | { 89 | this.filmService.DeleteFilm(id); 90 | } 91 | catch (InvalidOperationException) 92 | { 93 | return this.StatusCode(403); 94 | } 95 | 96 | return this.StatusCode(204); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Program.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI 2 | { 3 | using Microsoft.AspNetCore; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Repositories/CastMemberRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Repositories 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public class CastMemberRepository : ICastMemberRepository 7 | { 8 | public IEnumerable ListAllCastMembers() 9 | { 10 | throw new System.NotImplementedException(); 11 | } 12 | 13 | public IEnumerable ListCastMembersByFilmId(int filmId) 14 | { 15 | return new[] { new CastMember { Name = "John Travolta" }, new CastMember { Name = "Samuel L Jackson" } }; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Repositories/DirectorRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Repositories 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public class DirectorRepository : IDirectorRepository 7 | { 8 | public IEnumerable ListAllDirectors() 9 | { 10 | throw new System.NotImplementedException(); 11 | } 12 | 13 | public Director ListDirectorById(int id) 14 | { 15 | return new Director { Name = "Steven Spielberg" }; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Repositories/FilmRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Repositories 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public class FilmRepository : IFilmRepository 7 | { 8 | public IEnumerable ListFilms() 9 | { 10 | return new[] { new Film { Id = 1, Name = "Pulp Fiction" }, new Film { Id = 2, Name = "Trainspotting" } }; 11 | } 12 | 13 | public Film ListFilmById(int id) 14 | { 15 | return new Film { Id = 1, Name = "Pulp Fiction", DirectorId = 1}; 16 | } 17 | 18 | public void CreateFilm(Film film) 19 | { 20 | //Save to DB 21 | } 22 | 23 | public void UpdateFilm(Film existingFilm) 24 | { 25 | //Update DB 26 | } 27 | 28 | public void DeleteFilm(int id) 29 | { 30 | //Delete from DB 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Repositories/ICastMemberRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Repositories 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public interface ICastMemberRepository 7 | { 8 | IEnumerable ListAllCastMembers(); 9 | 10 | IEnumerable ListCastMembersByFilmId(int filmId); 11 | 12 | //Lots of other methods 13 | } 14 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Repositories/IDirectorRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Repositories 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public interface IDirectorRepository 7 | { 8 | IEnumerable ListAllDirectors(); 9 | 10 | Director ListDirectorById(int id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Repositories/IFilmRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Repositories 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public interface IFilmRepository 7 | { 8 | IEnumerable ListFilms(); 9 | 10 | Film ListFilmById(int id); 11 | 12 | void CreateFilm(Film film); 13 | 14 | void UpdateFilm(Film existingFilm); 15 | 16 | void DeleteFilm(int id); 17 | } 18 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/CastMemberService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | using TraditionalWebAPI.Repositories; 6 | 7 | public class CastMemberService : ICastMemberService 8 | { 9 | private readonly ICastMemberRepository castMemberRepository; 10 | 11 | public CastMemberService(ICastMemberRepository castMemberRepository) 12 | { 13 | this.castMemberRepository = castMemberRepository; 14 | } 15 | 16 | public IEnumerable ListCastMembersByFilmId(int filmId) 17 | { 18 | return this.castMemberRepository.ListCastMembersByFilmId(filmId); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/DirectorService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | using Models; 4 | using TraditionalWebAPI.Repositories; 5 | 6 | public class DirectorService : IDirectorService 7 | { 8 | private readonly IDirectorRepository directorRepository; 9 | 10 | public DirectorService(IDirectorRepository directorRepository) 11 | { 12 | this.directorRepository = directorRepository; 13 | } 14 | 15 | public Director ListDirectorById(int id) 16 | { 17 | return this.directorRepository.ListDirectorById(id); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/FilmService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Models; 6 | using TraditionalWebAPI.Repositories; 7 | 8 | public class FilmService : IFilmService 9 | { 10 | private readonly IFilmRepository filmRepository; 11 | 12 | private readonly IDirectorService directorService; 13 | 14 | private readonly ICastMemberService castMemberService; 15 | 16 | private readonly IPermissionService permissionService; 17 | 18 | public FilmService(IFilmRepository filmRepository, IDirectorService directorService, ICastMemberService castMemberService, IPermissionService permissionService) 19 | { 20 | this.filmRepository = filmRepository; 21 | this.directorService = directorService; 22 | this.castMemberService = castMemberService; 23 | this.permissionService = permissionService; 24 | } 25 | 26 | public IEnumerable ListFilms() 27 | { 28 | return this.filmRepository.ListFilms(); 29 | } 30 | 31 | public Film ListFilmById(int id) 32 | { 33 | var film = this.filmRepository.ListFilmById(id); 34 | 35 | var director = this.directorService.ListDirectorById(film.DirectorId); 36 | film.Director = director; 37 | 38 | var cast = this.castMemberService.ListCastMembersByFilmId(id); 39 | film.Cast = cast; 40 | 41 | return film; 42 | } 43 | 44 | public void CreateFilm(Film film) 45 | { 46 | if (!this.permissionService.ValidUser()) 47 | { 48 | throw new InvalidOperationException(); 49 | } 50 | 51 | //Do some special MEGA CORP business validation 52 | 53 | this.filmRepository.CreateFilm(film); 54 | } 55 | 56 | public void UpdateFilm(int id, Film film) 57 | { 58 | if (!this.permissionService.ValidUser()) 59 | { 60 | throw new InvalidOperationException(); 61 | } 62 | 63 | //Do some special MEGA CORP business validation 64 | 65 | var existingFilm = this.filmRepository.ListFilmById(id); 66 | 67 | existingFilm.Name = film.Name; 68 | existingFilm.Budget = film.Budget; 69 | existingFilm.Language = film.Language; 70 | 71 | this.filmRepository.UpdateFilm(existingFilm); 72 | } 73 | 74 | public void DeleteFilm(int id) 75 | { 76 | if (!this.permissionService.ValidUser()) 77 | { 78 | throw new InvalidOperationException(); 79 | } 80 | 81 | this.filmRepository.DeleteFilm(id); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/ICastMemberService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public interface ICastMemberService 7 | { 8 | IEnumerable ListCastMembersByFilmId(int filmId); 9 | 10 | //Lots of other methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/IDirectorService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | using Models; 4 | 5 | public interface IDirectorService 6 | { 7 | Director ListDirectorById(int id); 8 | 9 | //Lots of other methods 10 | } 11 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/IFilmService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | using System.Collections.Generic; 4 | using Models; 5 | 6 | public interface IFilmService 7 | { 8 | IEnumerable ListFilms(); 9 | 10 | Film ListFilmById(int id); 11 | 12 | void CreateFilm(Film film); 13 | 14 | void UpdateFilm(int id, Film film); 15 | 16 | void DeleteFilm(int id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/IPermissionService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | public interface IPermissionService 4 | { 5 | bool ValidUser(); 6 | } 7 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Services/PermissionService.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI.Services 2 | { 3 | using System; 4 | 5 | public class PermissionService : IPermissionService 6 | { 7 | 8 | public bool ValidUser() 9 | { 10 | return new Random().Next() % 2 == 0; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /TraditionalWebAPI/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace TraditionalWebAPI 2 | { 3 | using FluentValidation; 4 | using FluentValidation.AspNetCore; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Models; 9 | using TraditionalWebAPI.Repositories; 10 | using TraditionalWebAPI.Services; 11 | 12 | public class Startup 13 | { 14 | public Startup(IConfiguration configuration) => this.Configuration = configuration; 15 | 16 | public IConfiguration Configuration { get; } 17 | 18 | public void ConfigureServices(IServiceCollection services) 19 | { 20 | services.AddSingleton(); 21 | services.AddSingleton(); 22 | services.AddSingleton(); 23 | 24 | services.AddSingleton(); 25 | services.AddSingleton(); 26 | services.AddSingleton(); 27 | 28 | services.AddSingleton(); 29 | 30 | services.AddTransient, FilmValidator>(); 31 | 32 | services.AddMvc().AddFluentValidation(); 33 | } 34 | 35 | public void Configure(IApplicationBuilder app) 36 | { 37 | app.UseMvc(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TraditionalWebAPI/TraditionalWebAPI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp2.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /TraditionalWebAPI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TraditionalWebAPI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /settings.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchannon/T1000/b448028dbeb01c597a5d40542f5b513719abc2a7/settings.jar --------------------------------------------------------------------------------