├── Chapter01 ├── OpenAPIFunction │ ├── .gitignore │ ├── OpenAPI.sln │ ├── OpenAPIFunctions │ │ ├── .gitignore │ │ ├── AppSettings.cs │ │ ├── BooksFunctions.cs │ │ ├── Models │ │ │ ├── BookModel.cs │ │ │ └── BooksResponseModel.cs │ │ ├── OpenAPIFunctions.cs │ │ ├── OpenAPIFunctions.csproj │ │ ├── Repository │ │ │ └── BooksRepository.cs │ │ └── host.json │ └── README.md └── SimpleFunction │ ├── .vs │ └── SimpleFunction │ │ ├── DesignTimeBuild │ │ └── .dtbcache │ │ └── v16 │ │ ├── .suo │ │ └── Server │ │ └── sqlite3 │ │ ├── db.lock │ │ └── storage.ide │ ├── BindingExpressionsExample │ ├── .gitignore │ ├── BindingExpressionsExample.cs │ ├── BindingExpressionsExample.csproj │ └── host.json │ └── SimpleFunction.sln ├── Chapter02 ├── .gitignore ├── CustomisingAzureFunctions.sln ├── Extensions │ ├── Bindings │ │ ├── TwitterBinder.cs │ │ ├── TwitterBindingAsyncCollector.cs │ │ ├── TwitterBindingAttribute.cs │ │ ├── TwitterBindingCollectorConverter.cs │ │ ├── TwitterBindingConfigProvider.cs │ │ └── TwitterBindingConverter.cs │ ├── DependencyInjection │ │ ├── DependencyInjectionAttribute.cs │ │ └── DependendyInjectionConfigProvider.cs │ ├── Extensions.csproj │ ├── Extensions │ │ ├── TwitterServiceExtensions.cs │ │ └── WebJobBuilderExtensions.cs │ └── Triggers │ │ ├── WeatherParameterDescriptor.cs │ │ ├── WeatherPayload.cs │ │ ├── WeatherTriggerAttribute.cs │ │ ├── WeatherTriggerBinding.cs │ │ ├── WeatherTriggerBindingProvider.cs │ │ ├── WeatherTriggerConfigProvider.cs │ │ └── WeatherTriggerListener.cs ├── Functions │ ├── .gitignore │ ├── DIFunctions.cs │ ├── ExtensionsStartup.cs │ ├── Functions.csproj │ ├── MyServices │ │ ├── IMyService.cs │ │ └── MyService.cs │ ├── ServiceLocator.cs │ ├── Startup.cs │ ├── WeatherFunctions.cs │ └── host.json ├── FunctionsTest │ ├── DIFunctionsTest.cs │ └── FunctionsTest.csproj ├── README.md ├── Twitter │ ├── Services │ │ ├── ITwitterService.cs │ │ └── TwitterService.cs │ ├── Twitter.csproj │ └── TwitterSettings.cs └── WeatherMap │ ├── Entities │ └── CityInfo.cs │ ├── Extensions │ └── UnixTimeExtensions.cs │ ├── Internals │ └── WeatherResponse.cs │ ├── Services │ ├── IWeatherService.cs │ └── WeatherService.cs │ └── WeatherMap.csproj ├── Chapter04 ├── FunctionAppSettings │ ├── .gitignore │ ├── FunctionAppSettings.sln │ ├── FunctionAppSettings │ │ ├── .gitignore │ │ ├── ExecutionContextExtensions.cs │ │ ├── Function.cs │ │ ├── FunctionAppSettings.csproj │ │ └── host.json │ └── Set-AzureFunctionSettings.ps1 └── README.md ├── Chapter05 ├── .gitignore ├── AzureFunction.Code │ ├── .gitignore │ ├── AzureFunction.Code.csproj │ ├── GetVersionFunction.cs │ └── host.json ├── AzureFunction.Resources │ ├── AzureFunction.Resources.deployproj │ ├── Deploy-AzureResourceGroup.ps1 │ ├── Deployment.targets │ ├── azuredeploy.json │ └── azuredeploy.parameters.json ├── AzureFunctionsDevOps.sln ├── Pipelines │ └── SimpleBuildPipeline.yaml ├── azure-pipelines-1.yml ├── azure-pipelines-2.yml └── azure-pipelines.yml ├── Chapter06 ├── MonitoringAzureFunctions │ ├── .gitignore │ ├── MonitoringAzureFunctions.sln │ └── MonitoringAzureFunctions │ │ ├── .gitignore │ │ ├── MonitoringAzureFunctions.csproj │ │ ├── MonitoringFunctions.cs │ │ └── host.json └── TestingAzureFunctions │ ├── .gitignore │ ├── MoneyCalculatorFunctions.Test │ ├── MoneyCalculatorFunctions.Test.csproj │ ├── MortgageFunctionsTest.cs │ ├── Services │ │ └── MortgageCalculatorTest.cs │ ├── bin │ │ └── Debug │ │ │ └── netcoreapp2.2 │ │ │ ├── bin │ │ │ └── MoneyCalculatorFunctions.Test.pdb │ │ │ └── local.settings.json │ └── obj │ │ ├── Debug │ │ └── netcoreapp2.2 │ │ │ ├── MoneyCalculatorFunctions.Test.assets.cache │ │ │ └── MoneyCalculatorFunctions.Test.csproj.FileListAbsolute.txt │ │ ├── MoneyCalculatorFunctions.Test.csproj.nuget.dgspec.json │ │ ├── MoneyCalculatorFunctions.Test.csproj.nuget.g.props │ │ ├── MoneyCalculatorFunctions.Test.csproj.nuget.g.targets │ │ └── project.assets.json │ ├── MoneyCalculatorFunctions │ ├── .gitignore │ ├── Constants.cs │ ├── Entities │ │ └── ExecutionRow.cs │ ├── MoneyCalculatorFunctions.csproj │ ├── MortgageFunctions - Static.cs │ ├── MortgageFunctions.cs │ ├── Services │ │ ├── CalculatorResult.cs │ │ ├── IMortgageCalculator.cs │ │ ├── MortgageCalculator.cs │ │ └── MortgageCalculatorErrors.cs │ ├── Startup.cs │ ├── TestFriend.cs │ └── host.json │ └── TestingAzureFunctions.sln ├── Chapter08 ├── DurableEntities │ ├── .gitignore │ ├── DurableEntities.sln │ └── DurableEntities │ │ ├── Counter.cs │ │ ├── CounterFunctions.cs │ │ ├── DurableEntities.csproj │ │ └── host.json └── OrderManager │ ├── .gitignore │ ├── OrderManager.Functions │ ├── Activities │ │ ├── AddOrder.cs │ │ ├── FinalizeOrder.cs │ │ ├── GenerateInvoice.cs │ │ └── SendMail.cs │ ├── Clients │ │ ├── ClassDiagram.cd │ │ ├── OrderEvents.cs │ │ └── OrderReceiver.cs │ ├── Entities │ │ ├── Order.cs │ │ ├── OrderStateChange.cs │ │ └── OrderStatus.cs │ ├── Events.cs │ ├── Extensions │ │ ├── CloudTableExtensions.cs │ │ ├── OrderDtoExtensions.cs │ │ ├── OrderExtensions.cs │ │ └── TextReaderExtensions.cs │ ├── FunctionNames.cs │ ├── Orchetsrators │ │ ├── ClassDiagram.cd │ │ └── OrderWorkflow.cs │ ├── OrderManager.Functions.csproj │ ├── Rest │ │ ├── OrderDto.cs │ │ └── OrderEventDto.cs │ ├── SourceNames.cs │ ├── Utilities │ │ └── TextUtility.cs │ └── host.json │ └── OrderManager.sln ├── Chapter09 └── VacationRequestLogicApp │ ├── .gitignore │ ├── VacationRequestLogicApp.sln │ └── VacationRequestLogicApp │ ├── Deploy-AzureResourceGroup.ps1 │ ├── Deployment.targets │ ├── LogicApp.json │ ├── LogicApp.parameters.json │ ├── Payloads │ ├── VacationRequest - Schema.json │ ├── VacationRequest.json │ └── VacationRequestRow.json │ ├── VacationRequestLogicApp.deployproj │ └── bin │ └── Debug │ └── staging │ └── VacationRequestLogicApp │ ├── Deploy-AzureResourceGroup.ps1 │ ├── LogicApp.json │ └── LogicApp.parameters.json ├── Chapter11 └── EventGridSolution │ ├── .gitignore │ ├── EventGridSolution.sln │ ├── EventHandlerFunction │ ├── .gitignore │ ├── EventGridFunctions.cs │ ├── EventHandlerFunction.csproj │ └── host.json │ └── ResourceGroupTemplate │ ├── Deploy-AzureResourceGroup.ps1 │ ├── Deployment.targets │ ├── ResourceGroupTemplate.deployproj │ ├── azuredeploy.json │ └── azuredeploy.parameters.json ├── LICENSE └── README.md /Chapter01/OpenAPIFunction/OpenAPI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28714.193 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAPIFunctions", "OpenAPIFunctions\OpenAPIFunctions.csproj", "{C6B40A12-5B36-4E34-BF8F-0714433DDC73}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C6B40A12-5B36-4E34-BF8F-0714433DDC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {C6B40A12-5B36-4E34-BF8F-0714433DDC73}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {C6B40A12-5B36-4E34-BF8F-0714433DDC73}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {C6B40A12-5B36-4E34-BF8F-0714433DDC73}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {07ECED94-F5A0-450A-9EDE-05F7DFCE3C87} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using Aliencube.AzureFunctions.Extensions.OpenApi.Configurations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace OpenAPIFunctions 7 | { 8 | class AppSettings : OpenApiAppSettingsBase 9 | { 10 | public AppSettings() : base() 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/BooksFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Extensions.Http; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Logging; 7 | using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes; 8 | using Microsoft.OpenApi.Models; 9 | using System.Net; 10 | using Models; 11 | using Aliencube.AzureFunctions.Extensions.OpenApi; 12 | using System.Reflection; 13 | using Microsoft.OpenApi; 14 | using Repository; 15 | using System.Linq; 16 | using System.Collections.Generic; 17 | 18 | namespace OpenAPIFunctions 19 | { 20 | public static class BooksFunctions 21 | { 22 | [FunctionName(nameof(GetBooks))] 23 | [OpenApiOperation("books", Description = "Retrieves a list of books filtered by the title")] 24 | [OpenApiParameter("title", In = ParameterLocation.Query, Required = false, Type = typeof(string))] 25 | [OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeof(IEnumerable), Description ="The books that contains the filter in the title")] 26 | public static IActionResult GetBooks( 27 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "books")] HttpRequest req, 28 | ILogger log) 29 | { 30 | log.LogInformation($"Call {nameof(GetBooks)}"); 31 | 32 | var books = BooksRepository.Books; 33 | if (req.Query.ContainsKey("title")) 34 | { 35 | var titleFilter = req.Query["title"]; 36 | books = books.Where(b => b.Title.Contains(titleFilter)); 37 | } 38 | 39 | return (ActionResult)new OkObjectResult(books); 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/Models/BookModel.cs: -------------------------------------------------------------------------------- 1 | namespace Models 2 | { 3 | public class BookModel 4 | { 5 | public string Title { get; set; } 6 | 7 | public decimal Price { get; set; } 8 | 9 | public string Author { get; set; } 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/Models/BooksResponseModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Models 6 | { 7 | public class BooksResponseModel 8 | { 9 | public IEnumerable Books { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/OpenAPIFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Aliencube.AzureFunctions.Extensions.OpenApi; 8 | using Aliencube.AzureFunctions.Extensions.OpenApi.Attributes; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.Azure.WebJobs; 12 | using Microsoft.Azure.WebJobs.Extensions.Http; 13 | using Microsoft.Extensions.Logging; 14 | using Microsoft.OpenApi; 15 | 16 | namespace OpenAPIFunctions 17 | { 18 | public static class OpenAPIFunctions 19 | { 20 | [FunctionName(nameof(RenderOpenApiDocument))] 21 | [OpenApiIgnore] 22 | public static async Task RenderOpenApiDocument( 23 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "openapi")] HttpRequest req, 24 | ILogger log) 25 | { 26 | var ver = OpenApiSpecVersion.OpenApi3_0; 27 | var ext = OpenApiFormat.Json; 28 | 29 | var settings = new AppSettings(); 30 | var helper = new DocumentHelper(); 31 | var document = new Document(helper); 32 | var result = await document.InitialiseDocument() 33 | .AddMetadata(settings.OpenApiInfo) 34 | .AddServer(req, settings.HttpSettings.RoutePrefix) 35 | .Build(Assembly.GetExecutingAssembly()) 36 | .RenderAsync(ver, ext) 37 | .ConfigureAwait(false); 38 | var response = new ContentResult() 39 | { 40 | Content = result, 41 | ContentType = "application/json", 42 | StatusCode = (int)HttpStatusCode.OK 43 | }; 44 | 45 | return response; 46 | } 47 | 48 | [FunctionName(nameof(RenderSwaggerUI))] 49 | [OpenApiIgnore] 50 | public static async Task RenderSwaggerUI( 51 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "openapi/ui")] HttpRequest req, 52 | ILogger log) 53 | { 54 | var settings = new AppSettings(); 55 | var ui = new SwaggerUI(); 56 | var result = await (await ui.AddMetadata(settings.OpenApiInfo) 57 | .AddServer(req, settings.HttpSettings.RoutePrefix) 58 | .BuildAsync()) 59 | .RenderAsync("swagger", settings.SwaggerAuthKey) 60 | .ConfigureAwait(false); 61 | var response = new ContentResult() 62 | { 63 | Content = result, 64 | ContentType = "text/html", 65 | StatusCode = (int)HttpStatusCode.OK 66 | }; 67 | 68 | return response; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/OpenAPIFunctions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | PreserveNewest 14 | 15 | 16 | PreserveNewest 17 | Never 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/Repository/BooksRepository.cs: -------------------------------------------------------------------------------- 1 | using Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Repository 7 | { 8 | public class BooksRepository 9 | { 10 | private static IEnumerable books = new[] { 11 | new BookModel(){ Title="The Great Gatsby",Price=10.5M,Author="F. Scott Fitzgerald"}, 12 | new BookModel(){ Title="The Scarlet Letter",Price=20.0M,Author="Nathaniel Hawthorne"}, 13 | new BookModel(){ Title="The Adventures of Huckleberry Finn",Price=25.0M,Author="Mark Twain"}, 14 | new BookModel(){ Title="Fahrenheit 451",Price=15.0M,Author="Ray Bradbury"}, 15 | new BookModel(){ Title="The Old Man and the Sea",Price=35.5M,Author="Ernest Hemingway"}, 16 | }; 17 | 18 | public static IEnumerable Books 19 | { 20 | get => books; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/OpenAPIFunctions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter01/OpenAPIFunction/README.md: -------------------------------------------------------------------------------- 1 | # CH01-OpenAPI 2 | Chapter 01 code for OpenAPI support in Azure Functions 3 | -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/.vs/SimpleFunction/DesignTimeBuild/.dtbcache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Azure-Serverless-Computing/abe484489d1048c6b5ffa1a261e7fd10ee9ca0d0/Chapter01/SimpleFunction/.vs/SimpleFunction/DesignTimeBuild/.dtbcache -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/.vs/SimpleFunction/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Azure-Serverless-Computing/abe484489d1048c6b5ffa1a261e7fd10ee9ca0d0/Chapter01/SimpleFunction/.vs/SimpleFunction/v16/.suo -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/.vs/SimpleFunction/v16/Server/sqlite3/db.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Azure-Serverless-Computing/abe484489d1048c6b5ffa1a261e7fd10ee9ca0d0/Chapter01/SimpleFunction/.vs/SimpleFunction/v16/Server/sqlite3/db.lock -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/.vs/SimpleFunction/v16/Server/sqlite3/storage.ide: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Azure-Serverless-Computing/abe484489d1048c6b5ffa1a261e7fd10ee9ca0d0/Chapter01/SimpleFunction/.vs/SimpleFunction/v16/Server/sqlite3/storage.ide -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/BindingExpressionsExample/BindingExpressionsExample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Azure.WebJobs; 3 | using Microsoft.Azure.WebJobs.Host; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace BindingExpressionsExample 7 | { 8 | public static class BindingExpressionsExample 9 | { 10 | [FunctionName("LogQueueMessage")] 11 | public static void Run( 12 | [QueueTrigger("%queueappsetting%")]string myQueueItem, 13 | DateTimeOffset insertionTime, 14 | ILogger log) 15 | { 16 | log.LogInformation($"Message content: {myQueueItem}"); 17 | log.LogInformation($"Created at: {insertionTime}"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/BindingExpressionsExample/BindingExpressionsExample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | PreserveNewest 13 | 14 | 15 | PreserveNewest 16 | Never 17 | 18 | 19 | -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/BindingExpressionsExample/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter01/SimpleFunction/SimpleFunction.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29324.140 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingExpressionsExample", "BindingExpressionsExample\BindingExpressionsExample.csproj", "{F9DF9AD2-6C4F-4576-9AA6-7490B07AE6E2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F9DF9AD2-6C4F-4576-9AA6-7490B07AE6E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F9DF9AD2-6C4F-4576-9AA6-7490B07AE6E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F9DF9AD2-6C4F-4576-9AA6-7490B07AE6E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F9DF9AD2-6C4F-4576-9AA6-7490B07AE6E2}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {E399E3F0-6476-4ADD-AD97-1BCEF5F6FEA8} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Chapter02/CustomisingAzureFunctions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28729.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Functions", "Functions\Functions.csproj", "{16F632F8-A61F-43F9-9FE2-A0C2B71FF6C6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensions", "Extensions\Extensions.csproj", "{6F01D1DB-EE1C-4B96-99BE-CE06910205B6}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeatherMap", "WeatherMap\WeatherMap.csproj", "{AA4BE1DB-4478-48C0-841A-462E5074D026}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Twitter", "Twitter\Twitter.csproj", "{A7D54898-76EA-46BE-87F5-D2683E6E0C06}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionsTest", "FunctionsTest\FunctionsTest.csproj", "{187824BA-E03E-4CF3-924C-96781B5C528E}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {16F632F8-A61F-43F9-9FE2-A0C2B71FF6C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {16F632F8-A61F-43F9-9FE2-A0C2B71FF6C6}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {16F632F8-A61F-43F9-9FE2-A0C2B71FF6C6}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {16F632F8-A61F-43F9-9FE2-A0C2B71FF6C6}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {6F01D1DB-EE1C-4B96-99BE-CE06910205B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {6F01D1DB-EE1C-4B96-99BE-CE06910205B6}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {6F01D1DB-EE1C-4B96-99BE-CE06910205B6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6F01D1DB-EE1C-4B96-99BE-CE06910205B6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {AA4BE1DB-4478-48C0-841A-462E5074D026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {AA4BE1DB-4478-48C0-841A-462E5074D026}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {AA4BE1DB-4478-48C0-841A-462E5074D026}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {AA4BE1DB-4478-48C0-841A-462E5074D026}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {A7D54898-76EA-46BE-87F5-D2683E6E0C06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {A7D54898-76EA-46BE-87F5-D2683E6E0C06}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {A7D54898-76EA-46BE-87F5-D2683E6E0C06}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {A7D54898-76EA-46BE-87F5-D2683E6E0C06}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {187824BA-E03E-4CF3-924C-96781B5C528E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {187824BA-E03E-4CF3-924C-96781B5C528E}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {187824BA-E03E-4CF3-924C-96781B5C528E}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {187824BA-E03E-4CF3-924C-96781B5C528E}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {4BC53FFD-2512-46F1-8DB7-EDCA3BFE4E9A} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Bindings/TwitterBinder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using System.Threading.Tasks; 3 | using Twitter; 4 | using Twitter.Services; 5 | 6 | namespace Extensions.Bindings 7 | { 8 | public class TwitterBinder 9 | { 10 | private readonly TwitterBindingAttribute _attribute; 11 | private readonly ITwitterService _twitterService; 12 | private readonly INameResolver _nameResolver; 13 | 14 | public TwitterBinder(TwitterBindingAttribute attribute, 15 | ITwitterService twitterService, INameResolver nameResolver) 16 | { 17 | this._attribute = attribute; 18 | this._twitterService = twitterService; 19 | this._nameResolver = nameResolver; 20 | 21 | this._twitterService.SetSettings(attribute); 22 | } 23 | 24 | public Task TweetAsync(string message) 25 | { 26 | return _twitterService.SendTweetAsync(message); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Chapter02/Extensions/Bindings/TwitterBindingAsyncCollector.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Twitter; 9 | using Twitter.Services; 10 | 11 | namespace Extensions.Bindings 12 | { 13 | public class TwitterBindingAsyncCollector : IAsyncCollector 14 | { 15 | private readonly TwitterBindingAttribute _attribute; 16 | private readonly INameResolver _nameResolver; 17 | private readonly ITwitterService _twitterService; 18 | 19 | private readonly List _tweetsToSend = new List(); 20 | 21 | public TwitterBindingAsyncCollector(TwitterBindingAttribute attribute, 22 | ITwitterService twitterService, INameResolver nameResolver) 23 | { 24 | this._attribute = attribute; 25 | this._nameResolver = nameResolver; 26 | this._twitterService = twitterService; 27 | 28 | this._twitterService.SetSettings(attribute); 29 | } 30 | 31 | public Task AddAsync(string item, CancellationToken cancellationToken = default) 32 | { 33 | _tweetsToSend.Add(item); 34 | return Task.CompletedTask; 35 | } 36 | 37 | public async Task FlushAsync(CancellationToken cancellationToken = default) 38 | { 39 | foreach (var item in this._tweetsToSend) 40 | { 41 | await _twitterService.SendTweetAsync(item); 42 | } 43 | this._tweetsToSend.Clear(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Bindings/TwitterBindingAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs.Description; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Extensions.Bindings 7 | { 8 | [Binding] 9 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)] 10 | public class TwitterBindingAttribute : Attribute 11 | { 12 | [AppSetting(Default = "Twitter.ConsumerKey")] 13 | public string ConsumerKey { get; set; } 14 | 15 | [AppSetting(Default = "Twitter.ConsumerSecret")] 16 | public string ConsumerSecret { get; set; } 17 | 18 | [AppSetting(Default = "Twitter.AccessToken")] 19 | public string AccessToken { get; set; } 20 | 21 | [AppSetting(Default = "Twitter.AccessTokenSecret")] 22 | public string AccessTokenSecret { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Bindings/TwitterBindingCollectorConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using Twitter.Services; 7 | 8 | namespace Extensions.Bindings 9 | { 10 | public class TwitterBindingCollectorConverter : IConverter> 11 | { 12 | private readonly INameResolver _nameResolver; 13 | private readonly ITwitterService _twitterService; 14 | 15 | public TwitterBindingCollectorConverter(INameResolver nameResolver, ITwitterService twitterService) 16 | { 17 | _nameResolver = nameResolver; 18 | _twitterService = twitterService; 19 | } 20 | 21 | public IAsyncCollector Convert(TwitterBindingAttribute attribute) 22 | { 23 | return new TwitterBindingAsyncCollector(attribute, _twitterService, _nameResolver); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Bindings/TwitterBindingConfigProvider.cs: -------------------------------------------------------------------------------- 1 | using Extensions.Triggers; 2 | using Microsoft.Azure.WebJobs; 3 | using Microsoft.Azure.WebJobs.Description; 4 | using Microsoft.Azure.WebJobs.Host.Bindings; 5 | using Microsoft.Azure.WebJobs.Host.Config; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.Options; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using Twitter.Services; 14 | 15 | namespace Extensions.Bindings 16 | { 17 | [Extension("Twitter")] 18 | public class TwitterBindingConfigProvider : IExtensionConfigProvider 19 | { 20 | private readonly INameResolver _nameResolver; 21 | private readonly ILoggerFactory _loggerFactory; 22 | private readonly ITwitterService _twitterService; 23 | 24 | public TwitterBindingConfigProvider(INameResolver nameResolver, 25 | ILoggerFactory loggerFactory, ITwitterService twitterService) 26 | { 27 | this._nameResolver = nameResolver; 28 | this._loggerFactory = loggerFactory; 29 | this._twitterService = twitterService; 30 | } 31 | 32 | public void Initialize(ExtensionConfigContext context) 33 | { 34 | var bindingRule = context.AddBindingRule(); 35 | bindingRule.AddValidator(ValidateTwitterConfig); 36 | bindingRule.BindToCollector(typeof(TwitterBindingCollectorConverter), _nameResolver, _twitterService); 37 | bindingRule.BindToInput(typeof(TwitterBindingConverter), _nameResolver, _twitterService); 38 | } 39 | 40 | 41 | private void ValidateTwitterConfig(TwitterBindingAttribute attribute, Type paramType) 42 | { 43 | if (string.IsNullOrEmpty(attribute.AccessToken)) 44 | throw new InvalidOperationException($"Twitter AccessToken must be set either via the attribute property or via configuration."); 45 | 46 | if (string.IsNullOrEmpty(attribute.AccessTokenSecret)) 47 | throw new InvalidOperationException($"Twitter AccessTokenSecret must be set either via the attribute property or via configuration."); 48 | 49 | if (string.IsNullOrEmpty(attribute.ConsumerKey)) 50 | throw new InvalidOperationException($"Twitter ConsumerKey must be set either via the attribute property or via configuration."); 51 | 52 | if (string.IsNullOrEmpty(attribute.ConsumerSecret)) 53 | throw new InvalidOperationException($"Twitter ConsumerSecret must be set either via the attribute property or via configuration."); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Bindings/TwitterBindingConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Twitter.Services; 3 | 4 | namespace Extensions.Bindings 5 | { 6 | public class TwitterBindingConverter : IConverter 7 | { 8 | private readonly INameResolver _nameResolver; 9 | private readonly ITwitterService _twitterService; 10 | 11 | public TwitterBindingConverter(INameResolver nameResolver, ITwitterService twitterService) 12 | { 13 | _nameResolver = nameResolver; 14 | _twitterService = twitterService; 15 | } 16 | 17 | public TwitterBinder Convert(TwitterBindingAttribute attribute) 18 | { 19 | return new TwitterBinder(attribute, _twitterService, _nameResolver); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter02/Extensions/DependencyInjection/DependencyInjectionAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs.Description; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Extensions.DependencyInjection 7 | { 8 | [Binding] 9 | [AttributeUsage(AttributeTargets.Parameter)] 10 | public class DependencyInjectionAttribute : Attribute 11 | { 12 | public DependencyInjectionAttribute(Type type) 13 | { 14 | Type = type; 15 | } 16 | public Type Type { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter02/Extensions/DependencyInjection/DependendyInjectionConfigProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Azure.WebJobs.Description; 3 | using Microsoft.Azure.WebJobs.Host.Bindings; 4 | using Microsoft.Azure.WebJobs.Host.Config; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.Options; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using WeatherMap.Services; 14 | 15 | namespace Extensions.DependencyInjection 16 | { 17 | [Extension("DependencyInjection")] 18 | public class DependencyInjectionConfigProvider : IExtensionConfigProvider 19 | { 20 | private readonly ServiceProvider _serviceProvider; 21 | 22 | public DependencyInjectionConfigProvider(ServiceProvider serviceProvider) 23 | { 24 | this._serviceProvider = serviceProvider; 25 | } 26 | 27 | public void Initialize(ExtensionConfigContext context) 28 | { 29 | context.AddBindingRule() 30 | .BindToInput((a,c) => ServiceBuilder(a,c)); 31 | 32 | } 33 | 34 | 35 | private Task ServiceBuilder(DependencyInjectionAttribute attribute, ValueBindingContext context) 36 | { 37 | var serviceInstance = _serviceProvider.GetRequiredService(attribute.Type); 38 | 39 | return Task.FromResult(serviceInstance); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Extensions/TwitterServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Extensions.Bindings; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Twitter; 6 | using Twitter.Services; 7 | 8 | namespace Twitter.Services 9 | { 10 | public static class TwitterServiceExtensions 11 | { 12 | public static void SetSettings(this ITwitterService service, TwitterBindingAttribute attribute) 13 | { 14 | if (service == null) 15 | throw new NullReferenceException(nameof(service)); 16 | if (attribute == null) 17 | throw new ArgumentNullException(nameof(attribute)); 18 | 19 | var twitterSettings = new TwitterSettings(); 20 | 21 | twitterSettings.ConsumerKey = attribute.ConsumerKey; 22 | twitterSettings.ConsumerSecret = attribute.ConsumerSecret; 23 | twitterSettings.AccessToken = attribute.AccessToken; 24 | twitterSettings.AccessTokenSecret = attribute.AccessTokenSecret; 25 | 26 | service.Settings = twitterSettings; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Extensions/WebJobBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Microsoft.Extensions.Configuration; 6 | using Extensions.Triggers; 7 | using Extensions.Bindings; 8 | using Extensions; 9 | using Extensions.DependencyInjection; 10 | using Microsoft.Extensions.DependencyInjection; 11 | 12 | namespace Microsoft.Azure.WebJobs 13 | { 14 | public static class WebJobBuilderExtensions 15 | { 16 | public static IWebJobsBuilder UseWeatherTrigger(this IWebJobsBuilder builder) 17 | { 18 | if (builder == null) 19 | throw new NullReferenceException(nameof(builder)); 20 | 21 | builder.AddExtension(); 22 | 23 | // Add here all the configuration code for the extension 24 | 25 | return builder; 26 | } 27 | 28 | public static IWebJobsBuilder UseTwitterBinding(this IWebJobsBuilder builder) 29 | { 30 | if (builder == null) 31 | throw new NullReferenceException(nameof(builder)); 32 | 33 | builder.AddExtension(); 34 | 35 | return builder; 36 | } 37 | 38 | public static IWebJobsBuilder UseDependencyInjection(this IWebJobsBuilder builder) 39 | { 40 | if (builder == null) 41 | throw new NullReferenceException(nameof(builder)); 42 | 43 | var serviceProvider = builder.Services.BuildServiceProvider(); 44 | 45 | builder.AddExtension(new DependencyInjectionConfigProvider(serviceProvider)); 46 | 47 | return builder; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Triggers/WeatherParameterDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Azure.WebJobs.Host.Protocols; 3 | 4 | namespace Extensions.Triggers 5 | { 6 | internal class WeatherTriggerParameterDescriptor : TriggerParameterDescriptor 7 | { 8 | public string CityName { get; internal set; } 9 | 10 | public double TemperatureThreshold { get; internal set; } 11 | 12 | public override string GetTriggerReason(IDictionary arguments) 13 | { 14 | return base.GetTriggerReason(arguments); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Chapter02/Extensions/Triggers/WeatherPayload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Extensions.Triggers 4 | { 5 | public class WeatherPayload 6 | { 7 | public string CityName { get; set; } 8 | 9 | public double CurrentTemperature { get; set; } 10 | 11 | public double? LastTemperature { get; set; } 12 | 13 | public DateTimeOffset Timestamp { get; internal set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Chapter02/Extensions/Triggers/WeatherTriggerAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs.Description; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Extensions.Triggers 7 | { 8 | [Binding] 9 | [AttributeUsage(AttributeTargets.Parameter)] 10 | public class WeatherTriggerAttribute : Attribute 11 | { 12 | public WeatherTriggerAttribute(string cityName, double temperatureThreshold) 13 | { 14 | CityName = cityName; 15 | TemperatureThreshold = temperatureThreshold; 16 | } 17 | 18 | [AppSetting(Default = "Weather.ApiKey")] 19 | [AutoResolve] 20 | public string ApiKey { get; set; } 21 | 22 | public string CityName { get; internal set; } 23 | 24 | public double TemperatureThreshold { get; internal set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Triggers/WeatherTriggerBinding.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Azure.WebJobs.Host.Bindings; 3 | using Microsoft.Azure.WebJobs.Host.Listeners; 4 | using Microsoft.Azure.WebJobs.Host.Protocols; 5 | using Microsoft.Azure.WebJobs.Host.Triggers; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using WeatherMap.Services; 12 | 13 | namespace Extensions.Triggers 14 | { 15 | public class WeatherTriggerBinding : ITriggerBinding 16 | { 17 | public Type TriggerValueType => typeof(WeatherPayload); 18 | 19 | public IReadOnlyDictionary BindingDataContract { get; } = new Dictionary(); 20 | 21 | private readonly WeatherTriggerAttribute _attribute; 22 | private readonly ParameterInfo _parameter; 23 | private readonly INameResolver _nameResolver; 24 | private readonly IWeatherService _weatherService; 25 | 26 | private readonly Task _emptyBindingDataTask = 27 | Task.FromResult(new TriggerData(null, new Dictionary())); 28 | 29 | public WeatherTriggerBinding(ParameterInfo parameter, INameResolver nameResolver, 30 | IWeatherService weatherService, WeatherTriggerAttribute attribute) 31 | { 32 | this._parameter = parameter; 33 | this._nameResolver = nameResolver; 34 | this._attribute = attribute; 35 | this._weatherService = weatherService; 36 | } 37 | public Task BindAsync(object value, ValueBindingContext context) 38 | { 39 | return _emptyBindingDataTask; 40 | } 41 | 42 | public Task CreateListenerAsync(ListenerFactoryContext context) 43 | { 44 | if (context == null) 45 | throw new ArgumentNullException(nameof(context)); 46 | 47 | return Task.FromResult(new WeatherTriggerListener(context.Executor, 48 | this._weatherService, this._attribute)); 49 | } 50 | 51 | public ParameterDescriptor ToParameterDescriptor() 52 | { 53 | return new WeatherTriggerParameterDescriptor() 54 | { 55 | Name = _parameter.Name, 56 | Type = "WeatherTrigger", 57 | CityName = _attribute.CityName, 58 | TemperatureThreshold = _attribute.TemperatureThreshold 59 | }; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Triggers/WeatherTriggerBindingProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Host.Triggers; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Logging; 8 | using WeatherMap.Services; 9 | 10 | namespace Extensions.Triggers 11 | { 12 | public class WeatherTriggerBindingProvider : ITriggerBindingProvider 13 | { 14 | private readonly INameResolver _nameResolver; 15 | private readonly ILoggerFactory _loggerFactory; 16 | private readonly IWeatherService _weatherService; 17 | 18 | public WeatherTriggerBindingProvider(INameResolver nameResolver, 19 | ILoggerFactory loggerFactory, IWeatherService weatherService) 20 | { 21 | this._nameResolver = nameResolver; 22 | this._loggerFactory = loggerFactory; 23 | this._weatherService = weatherService; 24 | } 25 | 26 | public Task TryCreateAsync(TriggerBindingProviderContext context) 27 | { 28 | if (context is null) 29 | throw new ArgumentNullException(nameof(context)); 30 | 31 | var parameter = context.Parameter; 32 | 33 | var triggerAttribute = parameter.GetCustomAttribute(inherit: false); 34 | if (triggerAttribute is null) 35 | return Task.FromResult(null); 36 | 37 | triggerAttribute.ApiKey = GetTriggerAttributeApiKey(triggerAttribute); 38 | 39 | return Task.FromResult( 40 | new WeatherTriggerBinding(parameter, _nameResolver, _weatherService, triggerAttribute)); 41 | } 42 | 43 | private string GetTriggerAttributeApiKey(WeatherTriggerAttribute triggerAttribute) 44 | { 45 | if (string.IsNullOrEmpty(triggerAttribute.ApiKey)) 46 | { 47 | var apiKey = _nameResolver.Resolve("Weather.ApiKey"); 48 | 49 | if (string.IsNullOrEmpty(apiKey)) 50 | throw new InvalidOperationException("ApiKey is mandatory"); 51 | 52 | return apiKey; 53 | } 54 | 55 | return triggerAttribute.ApiKey; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Chapter02/Extensions/Triggers/WeatherTriggerConfigProvider.cs: -------------------------------------------------------------------------------- 1 | using Extensions.Triggers; 2 | using Microsoft.Azure.WebJobs; 3 | using Microsoft.Azure.WebJobs.Description; 4 | using Microsoft.Azure.WebJobs.Host.Config; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Text; 11 | using WeatherMap.Services; 12 | 13 | namespace Extensions.Triggers 14 | { 15 | [Extension("Weather")] 16 | public class WeatherTriggerConfigProvider : IExtensionConfigProvider 17 | { 18 | private readonly INameResolver _nameResolver; 19 | private readonly ILoggerFactory _loggerFactory; 20 | private readonly IWeatherService _weatherService; 21 | 22 | public WeatherTriggerConfigProvider(INameResolver nameResolver, 23 | ILoggerFactory loggerFactory, IWeatherService weatherService) 24 | { 25 | this._nameResolver = nameResolver; 26 | this._loggerFactory = loggerFactory; 27 | this._weatherService = weatherService; 28 | } 29 | 30 | public void Initialize(ExtensionConfigContext context) 31 | { 32 | var triggerAttributeBindingRule = context.AddBindingRule(); 33 | triggerAttributeBindingRule.BindToTrigger( 34 | new WeatherTriggerBindingProvider(this._nameResolver, this._loggerFactory, this._weatherService)); 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter02/Extensions/Triggers/WeatherTriggerListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.WebJobs.Host.Executors; 5 | using Microsoft.Azure.WebJobs.Host.Listeners; 6 | using WeatherMap.Entities; 7 | using WeatherMap.Services; 8 | 9 | namespace Extensions.Triggers 10 | { 11 | public class WeatherTriggerListener : IListener 12 | { 13 | 14 | private readonly ITriggeredFunctionExecutor _executor; 15 | private CancellationTokenSource _listenerStoppingTokenSource; 16 | 17 | private readonly IWeatherService _weatherService; 18 | private readonly WeatherTriggerAttribute _attribute; 19 | 20 | private Task _listenerTask; 21 | 22 | public WeatherTriggerListener(ITriggeredFunctionExecutor executor, 23 | IWeatherService weatherService, WeatherTriggerAttribute attribute) 24 | { 25 | this._executor = executor; 26 | this._weatherService = weatherService; 27 | this._attribute = attribute; 28 | } 29 | 30 | public void Cancel() 31 | { 32 | StopAsync(CancellationToken.None).Wait(); 33 | } 34 | 35 | public void Dispose() 36 | { 37 | StopAsync(CancellationToken.None).Wait(); 38 | } 39 | 40 | public Task StartAsync(CancellationToken cancellationToken) 41 | { 42 | try 43 | { 44 | _listenerStoppingTokenSource = new CancellationTokenSource(); 45 | var factory = new TaskFactory(); 46 | var token = _listenerStoppingTokenSource.Token; 47 | _listenerTask = factory.StartNew(async () => await ListenerAction(token), token); 48 | } 49 | catch (Exception) 50 | { 51 | throw; 52 | } 53 | 54 | return _listenerTask.IsCompleted ? _listenerTask : Task.CompletedTask; 55 | } 56 | 57 | private async Task ListenerAction(CancellationToken token) 58 | { 59 | this._weatherService.ApiKey = this._attribute.ApiKey; 60 | var cityData = new CityInfo(); 61 | double? lastTemperature = null; 62 | 63 | while (!token.IsCancellationRequested) 64 | { 65 | try 66 | { 67 | cityData = await this._weatherService.GetCityInfoAsync(this._attribute.CityName); 68 | } 69 | catch (Exception) 70 | { 71 | cityData = null; 72 | } 73 | 74 | if (!lastTemperature.HasValue || 75 | (cityData != null && Math.Abs(cityData.Temperature - lastTemperature.Value) > this._attribute.TemperatureThreshold)) 76 | { 77 | var weatherPayload = new WeatherPayload() 78 | { 79 | CityName = this._attribute.CityName, 80 | CurrentTemperature = cityData.Temperature, 81 | Timestamp = cityData.Timestamp, 82 | LastTemperature = lastTemperature 83 | }; 84 | 85 | await _executor.TryExecuteAsync(new TriggeredFunctionData() { TriggerValue = weatherPayload }, token); 86 | 87 | lastTemperature = cityData.Temperature; 88 | } 89 | 90 | await Task.Delay(TimeSpan.FromSeconds(30), token); 91 | } 92 | } 93 | 94 | public async Task StopAsync(CancellationToken cancellationToken) 95 | { 96 | if (_listenerTask == null) 97 | return; 98 | 99 | try 100 | { 101 | _listenerStoppingTokenSource.Cancel(); 102 | } 103 | finally 104 | { 105 | await Task.WhenAny(_listenerTask, Task.Delay(Timeout.Infinite, cancellationToken)); 106 | 107 | } 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /Chapter02/Functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /Chapter02/Functions/DIFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json; 10 | using Extensions.DependencyInjection; 11 | using Functions.MyServices; 12 | 13 | namespace Functions 14 | { 15 | public static class DIFunctions 16 | { 17 | [FunctionName(nameof(InjectMyServiceWithBinding))] 18 | public static async Task InjectMyServiceWithBinding( 19 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 20 | [DependencyInjection(typeof(IMyService))] IMyService myService, 21 | ILogger log) 22 | { 23 | log.LogInformation("C# HTTP trigger function processed a request."); 24 | 25 | string name = req.Query["name"]; 26 | 27 | var responseMessage = await myService.DoSomethingAsync(name); 28 | 29 | return responseMessage != null 30 | ? (ActionResult)new OkObjectResult(responseMessage) 31 | : new BadRequestObjectResult("Please pass a name on the query string or in the request body"); 32 | } 33 | 34 | [FunctionName(nameof(InjectMyServiceWithServiceLocator))] 35 | public static async Task InjectMyServiceWithServiceLocator( 36 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 37 | ILogger log) 38 | { 39 | log.LogInformation("C# HTTP trigger function processed a request."); 40 | 41 | var myService = ServiceLocator.GetService(); 42 | 43 | string name = req.Query["name"]; 44 | 45 | var responseMessage = await myService.DoSomethingAsync(name); 46 | 47 | return responseMessage != null 48 | ? (ActionResult)new OkObjectResult(responseMessage) 49 | : new BadRequestObjectResult("Please pass a name on the query string or in the request body"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Chapter02/Functions/ExtensionsStartup.cs: -------------------------------------------------------------------------------- 1 | using Extensions.Triggers; 2 | using Functions; 3 | using Functions.MyServices; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Hosting; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | using Twitter.Services; 11 | using WeatherMap.Services; 12 | 13 | [assembly: WebJobsStartup(typeof(ExtensionsStartup))] 14 | 15 | public class ExtensionsStartup : IWebJobsStartup 16 | { 17 | public void Configure(IWebJobsBuilder builder) 18 | { 19 | builder.UseWeatherTrigger(); 20 | builder.UseTwitterBinding(); 21 | 22 | builder.Services.AddTransient(); 23 | builder.Services.AddTransient(); 24 | 25 | builder.Services.AddScoped(); 26 | 27 | builder.UseDependencyInjection(); 28 | 29 | ServiceLocator.DefaultProvider = builder.Services.BuildServiceProvider(); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Chapter02/Functions/Functions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | PreserveNewest 18 | Never 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter02/Functions/MyServices/IMyService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Functions.MyServices 7 | { 8 | public interface IMyService 9 | { 10 | Task DoSomethingAsync(string name); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter02/Functions/MyServices/MyService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Functions.MyServices 7 | { 8 | public class MyService : IMyService 9 | { 10 | public Task DoSomethingAsync(string name) 11 | { 12 | if (string.IsNullOrWhiteSpace(name)) 13 | return null; 14 | return Task.FromResult($"Hello, {name}!!"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter02/Functions/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Functions 7 | { 8 | public static class ServiceLocator 9 | { 10 | public static IServiceProvider DefaultProvider; 11 | 12 | public static TService GetService() 13 | { 14 | return DefaultProvider.GetRequiredService(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter02/Functions/Startup.cs: -------------------------------------------------------------------------------- 1 | using Extensions.Triggers; 2 | using Functions; 3 | using Functions.MyServices; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Hosting; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | using Twitter.Services; 11 | using WeatherMap.Services; 12 | 13 | [assembly: WebJobsStartup(typeof(Startup))] 14 | 15 | public class Startup : IWebJobsStartup 16 | { 17 | public void Configure(IWebJobsBuilder builder) 18 | { 19 | builder.UseWeatherTrigger(); 20 | builder.UseTwitterBinding(); 21 | 22 | builder.Services.AddTransient(); 23 | builder.Services.AddTransient(); 24 | 25 | builder.Services.AddScoped(); 26 | 27 | builder.UseDependencyInjection(); 28 | 29 | ServiceLocator.DefaultProvider = builder.Services.BuildServiceProvider(); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Chapter02/Functions/WeatherFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json; 10 | using Extensions.Triggers; 11 | using Extensions.Bindings; 12 | 13 | namespace Functions 14 | { 15 | public static class WeatherFunctions 16 | { 17 | #region [ Custom Trigger ] 18 | [FunctionName(nameof(MilanWeatherCheck))] 19 | public static void MilanWeatherCheck( 20 | [WeatherTrigger("Milan,IT", 0.05)] WeatherPayload req, 21 | ILogger log) 22 | { 23 | var message = $"{req.CityName} [{req.CurrentTemperature}] at {req.Timestamp}"; 24 | log.LogWarning(message); 25 | } 26 | #endregion [ Custom Trigger ] 27 | 28 | #region [ Custom Trigger and Binding ] 29 | [FunctionName(nameof(RomeWeatherCheck))] 30 | public static async Task RomeWeatherCheck( 31 | [WeatherTrigger("Rome,IT", 0.05)] WeatherPayload req, 32 | [TwitterBinding] IAsyncCollector tweetMessages, 33 | ILogger log) 34 | { 35 | var message = $"{req.CityName} [{req.CurrentTemperature}] at {req.Timestamp}"; 36 | log.LogWarning(message); 37 | await tweetMessages.AddAsync(message); 38 | } 39 | 40 | [FunctionName(nameof(TurinWeatherCheck))] 41 | public static async Task TurinWeatherCheck( 42 | [WeatherTrigger("Turin,IT", 0.1)] WeatherPayload req, 43 | [TwitterBinding] TwitterBinder tweetMessage, 44 | ILogger log) 45 | { 46 | var message = $"{req.CityName} [{req.CurrentTemperature}] at {req.Timestamp}"; 47 | log.LogWarning(message); 48 | await tweetMessage.TweetAsync(message); 49 | } 50 | #endregion [ Custom Trigger and Binding ] 51 | 52 | #region [ Custom Binding ] 53 | //[FunctionName(nameof(PeriodicTweet))] 54 | //public static async Task PeriodicTweet( 55 | // [TimerTrigger("0 */5 * * * *")] TimerInfo timer, 56 | // [TwitterBinding()] TwitterBinder twitter, 57 | // ILogger log) 58 | //{ 59 | // // ..... 60 | //} 61 | 62 | //[FunctionName(nameof(PeriodicTweets))] 63 | //public static async Task PeriodicTweets( 64 | // [TimerTrigger("0 */5 * * * *")] TimerInfo timer, 65 | // [TwitterBinding] IAsyncCollector tweetMessages, 66 | // ILogger log) 67 | //{ 68 | // // ..... 69 | //} 70 | #endregion [ Custom Binding ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Chapter02/Functions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter02/FunctionsTest/DIFunctionsTest.cs: -------------------------------------------------------------------------------- 1 | using Functions; 2 | using Functions.MyServices; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using Moq; 8 | using System; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace FunctionsTest 13 | { 14 | public class UnitTest1 15 | { 16 | #region [ InjectMyServiceWithBinding ] 17 | [Fact] 18 | public async Task InjectMyServiceWithBinding_RequestWithoutName_ReturnBadRequestObjectResult() 19 | { 20 | var request = new Mock(); 21 | request.Setup(e => e.Query.ContainsKey("name")).Returns(false); 22 | request.Setup(e => e.Query["name"]).Returns((string)null); 23 | 24 | var myService = new Mock(); 25 | myService.Setup(s => s.DoSomethingAsync(null)) 26 | .ReturnsAsync((string)null); 27 | 28 | var logger = new Mock(); 29 | 30 | var actual = await DIFunctions.InjectMyServiceWithBinding(request.Object, myService.Object, logger.Object); 31 | 32 | Assert.IsType(actual); 33 | } 34 | 35 | [Fact] 36 | public async Task InjectMyServiceWithBinding_RequestWithName_ReturnOkObjectResult() 37 | { 38 | var name = "Mastering Serverless"; 39 | var responseMessage = $"Hello, {name}!"; 40 | 41 | var request = new Mock(); 42 | request.Setup(e => e.Query.ContainsKey("name")).Returns(true); 43 | request.Setup(e => e.Query["name"]).Returns(name); 44 | 45 | var myService = new Mock(); 46 | myService.Setup(s => s.DoSomethingAsync(name)) 47 | .ReturnsAsync(responseMessage); 48 | 49 | var logger = new Mock(); 50 | 51 | var actual = await DIFunctions.InjectMyServiceWithBinding(request.Object, myService.Object, logger.Object); 52 | 53 | Assert.IsType(actual); 54 | var response = actual as OkObjectResult; 55 | Assert.Equal(response.Value, responseMessage); 56 | } 57 | #endregion [ InjectMyServiceWithBinding ] 58 | 59 | #region [ InjectMyServiceWithServiceLocator ] 60 | [Fact] 61 | public async Task InjectMyServiceWithServiceLocator_RequestWithoutName_ReturnBadRequestObjectResult() 62 | { 63 | var request = new Mock(); 64 | request.Setup(e => e.Query.ContainsKey("name")).Returns(false); 65 | request.Setup(e => e.Query["name"]).Returns((string)null); 66 | 67 | var myService = new Mock(); 68 | myService.Setup(s => s.DoSomethingAsync(null)) 69 | .ReturnsAsync((string)null); 70 | 71 | var logger = new Mock(); 72 | 73 | var serviceProvider = new Mock(); 74 | serviceProvider.Setup(s => s.GetService(typeof(IMyService))) 75 | .Returns(myService.Object); 76 | 77 | ServiceLocator.DefaultProvider = serviceProvider.Object; 78 | 79 | var actual = await DIFunctions.InjectMyServiceWithServiceLocator(request.Object, logger.Object); 80 | 81 | Assert.IsType(actual); 82 | } 83 | 84 | [Fact] 85 | public async Task InjectMyServiceWithServiceLocator_RequestWithName_ReturnOkObjectResult() 86 | { 87 | var name = "Mastering Serverless"; 88 | var responseMessage = $"Hello, {name}!"; 89 | 90 | var request = new Mock(); 91 | request.Setup(e => e.Query.ContainsKey("name")).Returns(true); 92 | request.Setup(e => e.Query["name"]).Returns(name); 93 | 94 | var myService = new Mock(); 95 | myService.Setup(s => s.DoSomethingAsync(name)) 96 | .ReturnsAsync(responseMessage); 97 | 98 | var logger = new Mock(); 99 | 100 | var serviceProvider = new Mock(); 101 | serviceProvider.Setup(s => s.GetService(typeof(IMyService))) 102 | .Returns(myService.Object); 103 | 104 | ServiceLocator.DefaultProvider = serviceProvider.Object; 105 | 106 | 107 | var actual = await DIFunctions.InjectMyServiceWithServiceLocator(request.Object, logger.Object); 108 | 109 | Assert.IsType(actual); 110 | var response = actual as OkObjectResult; 111 | Assert.Equal(response.Value, responseMessage); 112 | } 113 | #endregion [ InjectMyServiceWithServiceLocator ] 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Chapter02/FunctionsTest/FunctionsTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter02/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 02 - Customising your Azure Functions 2 | This folder contains the sample for the chapter 02 "Customising your Azure Functions". 3 | -------------------------------------------------------------------------------- /Chapter02/Twitter/Services/ITwitterService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Twitter.Services 4 | { 5 | public interface ITwitterService 6 | { 7 | TwitterSettings Settings { get; set; } 8 | Task SendTweetAsync(string tweetMessage); 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter02/Twitter/Services/TwitterService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Tweetinvi; 6 | using Tweetinvi.Models; 7 | 8 | namespace Twitter.Services 9 | { 10 | public class TwitterService : ITwitterService 11 | { 12 | private TaskFactory taskFactory = new TaskFactory(); 13 | 14 | public TwitterSettings Settings { get; set; } 15 | 16 | public Task SendTweetAsync(string tweetMessage) 17 | { 18 | var task = taskFactory.StartNew(() => SendTweetInternal(tweetMessage)); 19 | return task; 20 | } 21 | 22 | private bool SendTweetInternal(string tweetMessage) 23 | { 24 | try 25 | { 26 | var creds = new TwitterCredentials(this.Settings?.ConsumerKey, this.Settings?.ConsumerSecret, 27 | this.Settings?.AccessToken, this.Settings?.AccessTokenSecret); 28 | 29 | var tweet = Auth.ExecuteOperationWithCredentials(creds, () => 30 | { 31 | return Tweet.PublishTweet(tweetMessage); 32 | }); 33 | return true; 34 | } 35 | catch (Exception) 36 | { 37 | return false; 38 | } 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter02/Twitter/Twitter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter02/Twitter/TwitterSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Twitter 2 | { 3 | public class TwitterSettings 4 | { 5 | public string ConsumerKey { get; set; } 6 | public string ConsumerSecret { get; set; } 7 | 8 | public string AccessToken { get; set; } 9 | public string AccessTokenSecret { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Chapter02/WeatherMap/Entities/CityInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WeatherMap.Entities 4 | { 5 | public class CityInfo 6 | { 7 | public string CityCode { get; set; } 8 | 9 | public double Temperature { get; set; } 10 | 11 | public DateTimeOffset Timestamp { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter02/WeatherMap/Extensions/UnixTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace System 6 | { 7 | public static class UnixTimeExtensions 8 | { 9 | public static DateTimeOffset ToUtcDateTimeOffset(this long unixTime) 10 | { 11 | return new DateTimeOffset(1970, 01, 01, 00, 00, 00, TimeSpan.Zero).AddSeconds(unixTime); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter02/WeatherMap/Internals/WeatherResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace WeatherMap.Internals 6 | { 7 | 8 | internal class WeatherData 9 | { 10 | public Coord coord { get; set; } 11 | public Weather[] weather { get; set; } 12 | public string _base { get; set; } 13 | public Main main { get; set; } 14 | public int visibility { get; set; } 15 | public Wind wind { get; set; } 16 | public Clouds clouds { get; set; } 17 | public long dt { get; set; } 18 | public Sys sys { get; set; } 19 | public int id { get; set; } 20 | public string name { get; set; } 21 | public int cod { get; set; } 22 | } 23 | 24 | internal class Coord 25 | { 26 | public float lon { get; set; } 27 | public float lat { get; set; } 28 | } 29 | 30 | internal class Main 31 | { 32 | public float temp { get; set; } 33 | public int pressure { get; set; } 34 | public int humidity { get; set; } 35 | public float temp_min { get; set; } 36 | public float temp_max { get; set; } 37 | } 38 | 39 | internal class Wind 40 | { 41 | public float speed { get; set; } 42 | public int deg { get; set; } 43 | } 44 | 45 | internal class Clouds 46 | { 47 | public int all { get; set; } 48 | } 49 | 50 | internal class Sys 51 | { 52 | public int type { get; set; } 53 | public int id { get; set; } 54 | public float message { get; set; } 55 | public string country { get; set; } 56 | public int sunrise { get; set; } 57 | public int sunset { get; set; } 58 | } 59 | 60 | internal class Weather 61 | { 62 | public int id { get; set; } 63 | public string main { get; set; } 64 | public string description { get; set; } 65 | public string icon { get; set; } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Chapter02/WeatherMap/Services/IWeatherService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using WeatherMap.Entities; 3 | 4 | namespace WeatherMap.Services 5 | { 6 | public interface IWeatherService 7 | { 8 | string ApiKey { get; set; } 9 | 10 | Task GetCityInfoAsync(string cityCode); 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter02/WeatherMap/Services/WeatherService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using WeatherMap.Entities; 9 | using WeatherMap.Internals; 10 | 11 | namespace WeatherMap.Services 12 | { 13 | public class WeatherService : IWeatherService 14 | { 15 | private string baseUri = "https://api.openweathermap.org/data/2.5/weather"; 16 | 17 | public string ApiKey { get; set; } 18 | 19 | private Uri GetApiUrl(string cityCode) 20 | { 21 | return new Uri($"{baseUri}?q={cityCode}&appId={ApiKey}&units=metric"); 22 | } 23 | 24 | 25 | public async Task GetCityInfoAsync(string cityCode) 26 | { 27 | string response = null; 28 | using (var client = new HttpClient()) 29 | { 30 | response = await client.GetStringAsync(GetApiUrl(cityCode)); 31 | } 32 | 33 | var responseObj = JsonConvert.DeserializeObject(response); 34 | 35 | CityInfo city = null; 36 | 37 | if (responseObj.cod == 200) 38 | { 39 | var cityData = responseObj.main; 40 | 41 | city = new CityInfo() 42 | { 43 | CityCode = cityCode, 44 | Temperature = cityData.temp, 45 | Timestamp = responseObj.dt.ToUtcDateTimeOffset() 46 | }; 47 | } 48 | 49 | return city; 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Chapter02/WeatherMap/WeatherMap.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter04/FunctionAppSettings/FunctionAppSettings.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28917.181 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionAppSettings", "FunctionAppSettings\FunctionAppSettings.csproj", "{4680CE65-8F0E-429B-97B6-B4E01785ED9F}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F264AF66-C0A6-4FB1-B04C-07B72459C593}" 9 | ProjectSection(SolutionItems) = preProject 10 | Set-AzureFunctionSettings.ps1 = Set-AzureFunctionSettings.ps1 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {4680CE65-8F0E-429B-97B6-B4E01785ED9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {4680CE65-8F0E-429B-97B6-B4E01785ED9F}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {4680CE65-8F0E-429B-97B6-B4E01785ED9F}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {4680CE65-8F0E-429B-97B6-B4E01785ED9F}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {EF761DF3-E175-469A-9A9C-366931B9E934} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /Chapter04/FunctionAppSettings/FunctionAppSettings/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /Chapter04/FunctionAppSettings/FunctionAppSettings/ExecutionContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Microsoft.Azure.WebJobs 9 | { 10 | public static class ExecutionContextExtensions 11 | { 12 | public static string GetConfig(this ExecutionContext context, string keyName) 13 | { 14 | if (string.IsNullOrWhiteSpace(keyName)) 15 | throw new ArgumentException(nameof(keyName)); 16 | 17 | var config = GetConfigurations(context); 18 | var pair = config.FirstOrDefault(k => k.Key == keyName); 19 | if (pair.Equals(default(KeyValuePair))) 20 | return null; 21 | return pair.Value; 22 | } 23 | 24 | public static IEnumerable> GetConfigurations(this ExecutionContext context) 25 | { 26 | if (context == null) 27 | throw new NullReferenceException(nameof(context)); 28 | 29 | var config = new ConfigurationBuilder() 30 | .SetBasePath(context.FunctionAppDirectory) 31 | .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) 32 | .AddEnvironmentVariables() 33 | .Build(); 34 | 35 | return config.AsEnumerable(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter04/FunctionAppSettings/FunctionAppSettings/Function.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json; 10 | using System.Collections.Generic; 11 | using Microsoft.Extensions.Configuration; 12 | 13 | namespace FunctionAppSettings 14 | { 15 | public static class Function1 16 | { 17 | [FunctionName("GetConfigValue")] 18 | public static IActionResult Run( 19 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 20 | ILogger log, 21 | ExecutionContext context) 22 | { 23 | string key = req.Query["key"]; 24 | 25 | log.LogInformation($"Retrieve config for {key}"); 26 | 27 | if (key == null) 28 | return new BadRequestObjectResult("Please pass a key on the query string"); 29 | 30 | var configValue = context.GetConfig(key); 31 | 32 | return new OkObjectResult(new { key, value = configValue }); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter04/FunctionAppSettings/FunctionAppSettings/FunctionAppSettings.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | PreserveNewest 15 | Never 16 | 17 | 18 | -------------------------------------------------------------------------------- /Chapter04/FunctionAppSettings/FunctionAppSettings/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter04/FunctionAppSettings/Set-AzureFunctionSettings.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$resourceGroupName, 3 | [string]$appFunctionName 4 | ) 5 | 6 | Write-Host "Retrieving settings from webapp" 7 | $webApp = Get-AzWebApp -ResourceGroupName $resourceGroupName -Name $appFunctionName 8 | 9 | $newAppSettings = @{} 10 | ForEach ($item in $webApp.SiteConfig.AppSettings) { 11 | $newAppSettings[$item.Name] = $item.Value 12 | } 13 | 14 | $newAppSettings["newAppSetting01"] = "newSettingValue01" 15 | 16 | Write-Host "Set new settings into web app" 17 | $webApp = Set-AzWebApp -AppSettings $newAppSettings -ResourceGroupName $resourceGroupName -Name $appFunctionName -------------------------------------------------------------------------------- /Chapter04/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 04 - Deploy and configure your Azure Functions 2 | This folder contains the sample code explained in the Chapter 04 "Deploy and configure your Azure Functions". 3 | 4 | * FunctionAppSettings : this solution shows ho to read configuration from an Azure Function. 5 | -------------------------------------------------------------------------------- /Chapter05/AzureFunction.Code/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /Chapter05/AzureFunction.Code/AzureFunction.Code.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | v2 5 | 1.0.0.0 6 | 1.0.0.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | PreserveNewest 14 | 15 | 16 | PreserveNewest 17 | Never 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter05/AzureFunction.Code/GetVersionFunction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json; 10 | using System.Reflection; 11 | 12 | namespace AzureFunction.Code 13 | { 14 | public static class GetVersionFunction 15 | { 16 | private static string Version = "1.1.0.0"; 17 | 18 | [FunctionName("GetVersion")] 19 | public static IActionResult Run( 20 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 21 | ILogger log) 22 | { 23 | log.LogInformation("C# HTTP trigger function processed a request."); 24 | return (ActionResult)new OkObjectResult($"Your awesome function version {Version}"); 25 | } 26 | } 27 | } 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Chapter05/AzureFunction.Code/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter05/AzureFunction.Resources/AzureFunction.Resources.deployproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | Release 10 | AnyCPU 11 | 12 | 13 | 14 | 9477b6fa-f000-49ca-8087-31de68bcfb7b 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | False 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Chapter05/AzureFunction.Resources/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "sites_azurefunctiondevops_name": { 6 | "value": "azdevopstestsite" 7 | }, 8 | "serverfarms_WestEuropePlan_name": { 9 | "value": "azdevopstestplan" 10 | }, 11 | "components_azurefunctiondevops_name": { 12 | "value": "azdevopstest" 13 | }, 14 | "storageAccounts_azurefunctiondevopsstore_name": { 15 | "value": "azdevopsteststore" 16 | }, 17 | "actionGroups_Application%20Insights%20Smart%20Detection_name": { 18 | "value": "azdevopstestai" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Chapter05/AzureFunctionsDevOps.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29025.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunction.Code", "AzureFunction.Code\AzureFunction.Code.csproj", "{A870617A-AB6A-4D07-9897-FA0CB05E0E0B}" 7 | EndProject 8 | Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "AzureFunction.Resources", "AzureFunction.Resources\AzureFunction.Resources.deployproj", "{9477B6FA-F000-49CA-8087-31DE68BCFB7B}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipelines", "Pipelines", "{9443E233-0AAD-429E-BED8-D7E5542E8264}" 11 | ProjectSection(SolutionItems) = preProject 12 | Pipelines\SimpleBuildPipeline.yaml = Pipelines\SimpleBuildPipeline.yaml 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {A870617A-AB6A-4D07-9897-FA0CB05E0E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {A870617A-AB6A-4D07-9897-FA0CB05E0E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {A870617A-AB6A-4D07-9897-FA0CB05E0E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {A870617A-AB6A-4D07-9897-FA0CB05E0E0B}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {9477B6FA-F000-49CA-8087-31DE68BCFB7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {9477B6FA-F000-49CA-8087-31DE68BCFB7B}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {9477B6FA-F000-49CA-8087-31DE68BCFB7B}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {9477B6FA-F000-49CA-8087-31DE68BCFB7B}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {9DAD14DE-0F30-4699-96A4-E83D1F9DC47C} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /Chapter05/Pipelines/SimpleBuildPipeline.yaml: -------------------------------------------------------------------------------- 1 | variables: 2 | ARMProject: 'AzureFunction.Resources/AzureFunction.Resources.csproj' 3 | CodeProject: 'AzureFunction.Code/AzureFunction.Code.csproj' 4 | 5 | trigger: 6 | - master 7 | 8 | jobs: 9 | - job: Build_AzureFunctionCode 10 | displayName: 'Build code project' 11 | pool: 12 | name: Hosted VS2017 13 | steps: 14 | - task: DotNetCoreCLI@2 15 | displayName: 'Restore packages' 16 | inputs: 17 | command: restore 18 | projects: '$(CodeProject)' 19 | 20 | - task: DotNetCoreCLI@2 21 | displayName: 'Build project' 22 | inputs: 23 | projects: '$(CodeProject)' 24 | arguments: '--output $(Agent.TempDirectory)\Code\ --configuration Release' 25 | condition: succeededOrFailed() 26 | 27 | - task: ArchiveFiles@2 28 | displayName: 'Archive files' 29 | inputs: 30 | rootFolderOrFile: '$(Agent.TempDirectory)\Code' 31 | includeRootFolder: false 32 | archiveFile: '$(Build.ArtifactStagingDirectory)/Code/$(Build.BuildId).zip' 33 | 34 | - task: PublishBuildArtifacts@1 35 | displayName: 'Publish Artifact: Code' 36 | inputs: 37 | PathtoPublish: '$(Build.ArtifactStagingDirectory)/Code' 38 | ArtifactName: Code 39 | 40 | - job: Build_AzureFunctionResources 41 | displayName: 'Build ARM project' 42 | pool: 43 | name: Hosted VS2017 44 | demands: 45 | - msbuild 46 | - visualstudio 47 | steps: 48 | - task: NuGetToolInstaller@1 49 | displayName: 'Install NuGet Tool' 50 | 51 | - task: NuGetCommand@2 52 | displayName: 'NuGet restore' 53 | inputs: 54 | restoreSolution: '$(ARMProject)' 55 | 56 | - task: VSBuild@1 57 | displayName: 'Build ARM project' 58 | inputs: 59 | solution: '$(ARMProject)' 60 | msbuildArgs: ' /p:OutputPath="$(build.artifactstagingdirectory)\ARM"' 61 | platform: '$(BuildPlatform)' 62 | configuration: '$(BuildConfiguration)' 63 | 64 | - task: PublishBuildArtifacts@1 65 | displayName: 'Publish Artifact: ARM' 66 | inputs: 67 | PathtoPublish: '$(build.artifactstagingdirectory)\ARM' 68 | ArtifactName: ARM -------------------------------------------------------------------------------- /Chapter05/azure-pipelines-1.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | variables: 7 | CodeProject: 'AzureFunction.Code/AzureFunction.Code.csproj' 8 | ARMProject: 'AzureFunction.Resources/AzureFunction.Resources.deployproj' 9 | 10 | trigger: none 11 | 12 | jobs: 13 | - job: Build_CodeProject 14 | pool: 15 | vmImage: 'vs2017-win2016' 16 | displayName: 'Build $(CodeProject)' 17 | steps: 18 | - task: DotNetCoreCLI@2 19 | displayName: 'dotnet restore' 20 | inputs: 21 | command: restore 22 | projects: '$(CodeProject)' 23 | 24 | - task: DotNetCoreCLI@2 25 | displayName: 'dotnet build' 26 | inputs: 27 | projects: '$(CodeProject)' 28 | arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)/Code' 29 | 30 | - task: DotNetCoreCLI@2 31 | displayName: 'dotnet test' 32 | inputs: 33 | command: test 34 | projects: '$(CodeProject)' 35 | 36 | - task: PublishBuildArtifacts@1 37 | displayName: 'Publish Artifact: Code' 38 | inputs: 39 | PathtoPublish: ' $(build.artifactstagingdirectory)/Code' 40 | ArtifactName: Code 41 | 42 | - job: Build_ARM 43 | pool: 44 | vmImage: 'vs2017-win2016' 45 | displayName: 'Build $(ARMProject)' 46 | steps: 47 | - task: NuGetToolInstaller@1 48 | displayName: 'Use NuGet ' 49 | 50 | - task: NuGetCommand@2 51 | displayName: 'NuGet restore' 52 | inputs: 53 | restoreSolution: '$(ARMProject)' 54 | 55 | - task: VSBuild@1 56 | displayName: 'Build solution $(ARMProject)' 57 | inputs: 58 | solution: '$(ARMProject)' 59 | msbuildArgs: ' /p:OutputPath="$(build.artifactstagingdirectory)\ARM"' 60 | platform: '$(BuildPlatform)' 61 | configuration: '$(BuildConfiguration)' 62 | 63 | - task: PublishBuildArtifacts@1 64 | displayName: 'Publish Artifact: ARM' 65 | inputs: 66 | PathtoPublish: ' $(build.artifactstagingdirectory)/ARM' 67 | ArtifactName: ARM -------------------------------------------------------------------------------- /Chapter05/azure-pipelines-2.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | variables: 7 | CodeProject: 'AzureFunction.Code/AzureFunction.Code.csproj' 8 | ARMProject: 'AzureFunction.Resources/AzureFunction.Resources.deployproj' 9 | 10 | trigger: none 11 | 12 | jobs: 13 | - job: Build_CodeProject 14 | pool: 15 | vmImage: 'vs2017-win2016' 16 | displayName: 'Build $(CodeProject)' 17 | steps: 18 | - task: DotNetCoreCLI@2 19 | displayName: 'dotnet restore' 20 | inputs: 21 | command: restore 22 | projects: '$(CodeProject)' 23 | 24 | - task: DotNetCoreCLI@2 25 | displayName: 'dotnet build' 26 | inputs: 27 | projects: '$(CodeProject)' 28 | arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)/Code' 29 | 30 | - task: DotNetCoreCLI@2 31 | displayName: 'dotnet test' 32 | inputs: 33 | command: test 34 | projects: '$(CodeProject)' 35 | 36 | - task: PublishBuildArtifacts@1 37 | displayName: 'Publish Artifact: Code' 38 | inputs: 39 | PathtoPublish: ' $(build.artifactstagingdirectory)/Code' 40 | ArtifactName: Code 41 | 42 | - job: Build_ARM 43 | pool: 44 | vmImage: 'vs2017-win2016' 45 | displayName: 'Build $(ARMProject)' 46 | steps: 47 | - task: NuGetToolInstaller@1 48 | displayName: 'Use NuGet ' 49 | 50 | - task: NuGetCommand@2 51 | displayName: 'NuGet restore' 52 | inputs: 53 | restoreSolution: '$(ARMProject)' 54 | 55 | - task: VSBuild@1 56 | displayName: 'Build solution $(ARMProject)' 57 | inputs: 58 | solution: '$(ARMProject)' 59 | msbuildArgs: ' /p:OutputPath="$(build.artifactstagingdirectory)\ARM"' 60 | platform: '$(BuildPlatform)' 61 | configuration: '$(BuildConfiguration)' 62 | 63 | - task: PublishBuildArtifacts@1 64 | displayName: 'Publish Artifact: ARM' 65 | inputs: 66 | PathtoPublish: ' $(build.artifactstagingdirectory)/ARM' 67 | ArtifactName: ARM -------------------------------------------------------------------------------- /Chapter05/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | variables: 7 | ARMProject: 'AzureFunction.Resources/AzureFunction.Resources.csproj' 8 | CodeProject: 'AzureFunction.Code/AzureFunction.Code.csproj' 9 | ARMArtifactName: 'resources' 10 | CodeArtifactName: 'code' 11 | 12 | trigger: none 13 | 14 | jobs: 15 | - job: 16 | pool: 17 | vmImage: 'vs2017-win2016' 18 | displayName: 'Build $(ARMProject)' 19 | steps: 20 | - task: NuGetToolInstaller@1 21 | displayName: 'Use NuGet 5.0.0' 22 | inputs: 23 | versionSpec: 5.0.0 24 | 25 | - task: NuGetCommand@2 26 | displayName: 'NuGet restore' 27 | inputs: 28 | restoreSolution: $(ARMProject) 29 | 30 | - task: VSBuild@1 31 | displayName: 'Build solution' 32 | inputs: 33 | solution: '$(ARMProject)' 34 | msbuildArgs: 35 | platform: '$(BuildPlatform)' 36 | configuration: '$(BuildConfiguration)' 37 | 38 | - task: PublishBuildArtifacts@1 39 | displayName: 'Publish Artifact' 40 | inputs: 41 | PathtoPublish: $(build.artifactstagingdirectory) 42 | ArtifactName: $(ARMArtifactName) 43 | 44 | - job: Build_AzureFunctionCode 45 | pool: 46 | vmImage: 'vs2017-win2016' 47 | displayName: 'Build $(CodeProject)' 48 | steps: 49 | 50 | - task: DotNetCoreCLI@2 51 | displayName: Restore packages 52 | inputs: 53 | command: 'restore' 54 | projects: $(CodeProject) 55 | feedsToUse: 'select' 56 | 57 | - task: DotNetCoreCLI@2 58 | displayName: Build AzureFunction.Code project 59 | inputs: 60 | command: 'build' 61 | projects: $(CodeProject) 62 | 63 | - task: DotNetCoreCLI@2 64 | displayName: Publish build files 65 | inputs: 66 | command: publish 67 | arguments: '--configuration Release' 68 | projects: $(CodeProject) 69 | publishWebProjects: false 70 | modifyOutputPath: true 71 | zipAfterPublish: false 72 | 73 | - task: PublishBuildArtifacts@1 74 | displayName: 'Publish Artifact' 75 | inputs: 76 | PathtoPublish: $(build.artifactstagingdirectory) 77 | ArtifactName: $(CodeArtifactName) -------------------------------------------------------------------------------- /Chapter06/MonitoringAzureFunctions/MonitoringAzureFunctions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28922.388 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitoringAzureFunctions", "MonitoringAzureFunctions\MonitoringAzureFunctions.csproj", "{8EEBF9F0-39FB-4333-AE5F-0CB1B6103002}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {8EEBF9F0-39FB-4333-AE5F-0CB1B6103002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {8EEBF9F0-39FB-4333-AE5F-0CB1B6103002}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {8EEBF9F0-39FB-4333-AE5F-0CB1B6103002}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {8EEBF9F0-39FB-4333-AE5F-0CB1B6103002}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3142D4A6-E064-4E0F-A0FD-3F86CEB8FB76} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Chapter06/MonitoringAzureFunctions/MonitoringAzureFunctions/MonitoringAzureFunctions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | PreserveNewest 15 | Never 16 | 17 | 18 | -------------------------------------------------------------------------------- /Chapter06/MonitoringAzureFunctions/MonitoringAzureFunctions/MonitoringFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | using System.Reflection; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Host; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace MonitoringAzureFunctions 9 | { 10 | public static class MonitoringFunctions 11 | { 12 | [FunctionName("TimerTriggerFunction")] 13 | public static void Run([TimerTrigger("0 */2 * * * *")]TimerInfo myTimer, ILogger log) 14 | { 15 | var executionTimestamp = DateTime.Now; 16 | log.LogInformation($"C# Timer trigger function executed at: {executionTimestamp}"); 17 | 18 | log.LogTrace($"Is past due: {myTimer.IsPastDue}"); 19 | log.LogTrace($"Schedule: {myTimer.Schedule}"); 20 | log.LogTrace($"Schedule Status Last: {myTimer.ScheduleStatus.Last}"); 21 | log.LogTrace($"Schedule Status Next: {myTimer.ScheduleStatus.Next}"); 22 | log.LogTrace($"Schedule Status LastUpdated: {myTimer.ScheduleStatus.LastUpdated}"); 23 | if (IsErrorOccurs()) 24 | { 25 | log.LogWarning($"Something happened in your function!!!"); 26 | throw new Exception(); 27 | } 28 | 29 | log.LogMetric("MyCustomMetric", CalculateMyCustomMetric()); 30 | } 31 | 32 | private static readonly Random rand = new Random(DateTime.Now.Millisecond); 33 | 34 | private static double CalculateMyCustomMetric() 35 | { 36 | return rand.NextDouble(); 37 | } 38 | 39 | private static bool IsErrorOccurs() 40 | { 41 | return rand.Next(0, 100) < 20; 42 | } 43 | } 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /Chapter06/MonitoringAzureFunctions/MonitoringAzureFunctions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "fileLoggingMode": "always", 5 | "logLevel": { 6 | "Default": "Information", 7 | "Function": "Trace", 8 | "Host.Results": "Information", 9 | "Host.Aggregator": "Error" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/MoneyCalculatorFunctions.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/MortgageFunctionsTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Azure.WebJobs; 9 | using MoneyCalculatorFunctions.Entities; 10 | using MoneyCalculatorFunctions.Services; 11 | using Moq; 12 | using Xunit; 13 | 14 | namespace MoneyCalculatorFunctions.Test 15 | { 16 | public class MortgageFunctionsTest 17 | { 18 | #region [ Run ] 19 | 20 | public static IEnumerable CalculationData => 21 | new List 22 | { 23 | new object[] { 100000m, 0.06D, 180, 843.86m }, 24 | new object[] { 100000m, 0D, 180, 555.56m }, 25 | new object[] { 100000m, 0D, 360, 277.78m }, 26 | new object[] { 50000m, 0.05D, 24, 2193.57m } 27 | }; 28 | 29 | [Theory] 30 | [MemberData(nameof(CalculationData))] 31 | public async Task Run_RightParametersInQueryString_Calculate(decimal mortgageLoan, 32 | double annualInterest, uint numberOfPayments, decimal rate) 33 | { 34 | #region ARRANGE 35 | var provider = CultureInfo.InvariantCulture; 36 | 37 | var request = new Mock(); 38 | request 39 | .Setup(e => e.Query[MortgageFunctions.LoanQueryKey]) 40 | .Returns(mortgageLoan.ToString(provider)); 41 | request 42 | .Setup(e => e.Query[MortgageFunctions.InterestQueryKey]) 43 | .Returns(annualInterest.ToString(provider)); 44 | request 45 | .Setup(e => e.Query[MortgageFunctions.NumberOfPaymentsQueryKey]) 46 | .Returns(numberOfPayments.ToString(provider)); 47 | 48 | var mortgageCalculator = new Mock(); 49 | mortgageCalculator 50 | .Setup(c => c.CalculateMontlyRateAsync(mortgageLoan, annualInterest, numberOfPayments)) 51 | .ReturnsAsync(new CalculatorResult() { Result = rate }); 52 | 53 | var target = new MortgageFunctions(mortgageCalculator.Object); 54 | 55 | var logger = new Mock(); 56 | var table = new Mock>(); 57 | table 58 | .Setup(t => t.Add(It.IsAny())) 59 | .Verifiable(); 60 | #endregion ARRANGE 61 | 62 | #region ACT 63 | var actual = await target.Run(request.Object, table.Object, logger.Object); 64 | #endregion ACT 65 | 66 | #region ASSERT 67 | Assert.IsType(actual); 68 | var objResponse = actual as OkObjectResult; 69 | Assert.Equal(objResponse.Value, rate); 70 | table.Verify(t => t.Add(It.IsAny()), Times.Once); 71 | #endregion ASSERT 72 | } 73 | 74 | public static IEnumerable WrongCalculationData => 75 | new List 76 | { 77 | new object[] { "", "0.06", "180"}, 78 | new object[] { "value", "0.06", "180"}, 79 | new object[] { null, "0.06", "180"}, 80 | new object[] { "100000", "", "180"}, 81 | new object[] { "100000", null, "180"}, 82 | new object[] { "100000", "value", "180"}, 83 | new object[] { "100000", "0.06", ""}, 84 | new object[] { "100000", "0.06", null}, 85 | new object[] { "100000", "0.06", "value"}, 86 | }; 87 | 88 | [Theory] 89 | [MemberData(nameof(WrongCalculationData))] 90 | public async Task Run_WrongParametersInQueryString_Error(string mortgageLoan, 91 | string annualInterest, string numberOfPayments) 92 | { 93 | #region ARRANGE 94 | var provider = CultureInfo.InvariantCulture; 95 | 96 | var request = new Mock(); 97 | if (mortgageLoan != null) 98 | request.Setup(e => e.Query[MortgageFunctions.LoanQueryKey]).Returns(mortgageLoan); 99 | if (annualInterest != null) 100 | request.Setup(e => e.Query[MortgageFunctions.InterestQueryKey]).Returns(annualInterest); 101 | if (numberOfPayments != null) 102 | request.Setup(e => e.Query[MortgageFunctions.NumberOfPaymentsQueryKey]).Returns(numberOfPayments); 103 | 104 | var mortgageCalculator = new Mock(); 105 | mortgageCalculator 106 | .Setup(c => c.CalculateMontlyRateAsync(It.IsAny(), It.IsAny(), It.IsAny())) 107 | .ReturnsAsync(new CalculatorResult() { Succeed = false }); 108 | 109 | var target = new MortgageFunctions(mortgageCalculator.Object); 110 | 111 | var logger = new Mock(); 112 | var table = new Mock>(); 113 | #endregion ARRANGE 114 | 115 | #region ACT 116 | var actual = await target.Run(request.Object, table.Object, logger.Object); 117 | #endregion ACT 118 | 119 | #region ASSERT 120 | Assert.IsType(actual); 121 | table.Verify(t => t.Add(It.IsAny()), Times.Never); 122 | #endregion ASSERT 123 | } 124 | 125 | 126 | #endregion [ Run ] 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/Services/MortgageCalculatorTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using MoneyCalculatorFunctions.Services; 6 | using Moq; 7 | using Xunit; 8 | 9 | namespace MoneyCalculatorFunctions.Test.Services 10 | { 11 | public class MortgageCalculatorTest 12 | { 13 | #region [ CalculateMontlyRateAsync ] 14 | 15 | [Fact] 16 | public async Task CalculateMontlyRateAsync_MortgageLessThanZero_Error() 17 | { 18 | decimal mortgageLoan = -100; 19 | double annualInterest = 0; 20 | uint numberOfPayments = 10; 21 | 22 | var logFactoryMock = new Mock(); 23 | var logMock = new Mock(); 24 | 25 | logFactoryMock 26 | .Setup(e => e.CreateLogger(nameof(MortgageCalculator))) 27 | .Returns(logMock.Object); 28 | 29 | var target = new MortgageCalculator(logFactoryMock.Object); 30 | 31 | var actual = await target.CalculateMontlyRateAsync(mortgageLoan, annualInterest, numberOfPayments); 32 | 33 | Assert.False(actual.Succeed); 34 | Assert.NotNull(actual.Error); 35 | Assert.Equal(actual.Error.Code, MortgageCalculatorErrors.MortgageNotValid); 36 | } 37 | 38 | [Fact] 39 | public async Task CalculateMontlyRateAsync_AnnualInterestLessThanZero_Error() 40 | { 41 | decimal mortgageLoan = 100000; 42 | double annualInterest = -10; 43 | uint numberOfPayments = 10; 44 | 45 | var logFactoryMock = new Mock(); 46 | var logMock = new Mock(); 47 | 48 | logFactoryMock 49 | .Setup(e => e.CreateLogger(nameof(MortgageCalculator))) 50 | .Returns(logMock.Object); 51 | 52 | var target = new MortgageCalculator(logFactoryMock.Object); 53 | 54 | var actual = await target.CalculateMontlyRateAsync(mortgageLoan, annualInterest, numberOfPayments); 55 | 56 | Assert.False(actual.Succeed); 57 | Assert.NotNull(actual.Error); 58 | Assert.Equal(actual.Error.Code, MortgageCalculatorErrors.AnnualInterestNotValid); 59 | } 60 | 61 | public static IEnumerable CalculationData => 62 | new List 63 | { 64 | new object[] { 100000m, 0.06D, 180, 843.86m }, 65 | new object[] { 100000m, 0D, 180, 555.56m }, 66 | new object[] { 100000m, 0D, 360, 277.78m }, 67 | new object[] { 50000m, 0.05D, 24, 2193.57m } 68 | }; 69 | 70 | [Theory] 71 | [MemberData(nameof(CalculationData))] 72 | public async Task CalculateMontlyRateAsync_ParametersOk_Calculate(decimal mortgageLoan, 73 | double annualInterest, uint numberOfPayments, decimal rate) 74 | { 75 | var logFactoryMock = new Mock(); 76 | var logMock = new Mock(); 77 | 78 | logFactoryMock 79 | .Setup(e => e.CreateLogger(nameof(MortgageCalculator))) 80 | .Returns(logMock.Object); 81 | 82 | var target = new MortgageCalculator(logFactoryMock.Object); 83 | 84 | var actual = await target.CalculateMontlyRateAsync(mortgageLoan, annualInterest, numberOfPayments); 85 | 86 | Assert.True(actual.Succeed); 87 | Assert.Null(actual.Error); 88 | Assert.Equal(actual.Result.Value, rate); 89 | } 90 | 91 | #endregion [ CalculateMontlyRateAsync ] 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/bin/Debug/netcoreapp2.2/bin/MoneyCalculatorFunctions.Test.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Azure-Serverless-Computing/abe484489d1048c6b5ffa1a261e7fd10ee9ca0d0/Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/bin/Debug/netcoreapp2.2/bin/MoneyCalculatorFunctions.Test.pdb -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/bin/Debug/netcoreapp2.2/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet", 6 | "StorageAccount": "UseDevelopmentStorage=true" 7 | } 8 | } -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/obj/Debug/netcoreapp2.2/MoneyCalculatorFunctions.Test.assets.cache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Azure-Serverless-Computing/abe484489d1048c6b5ffa1a261e7fd10ee9ca0d0/Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/obj/Debug/netcoreapp2.2/MoneyCalculatorFunctions.Test.assets.cache -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/obj/MoneyCalculatorFunctions.Test.csproj.nuget.g.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | True 5 | NuGet 6 | $(MSBuildThisFileDirectory)project.assets.json 7 | $(UserProfile)\.nuget\packages\ 8 | C:\Users\massi\.nuget\packages\;C:\Microsoft\Xamarin\NuGet\;C:\Program Files\dotnet\sdk\NuGetFallbackFolder 9 | PackageReference 10 | 5.1.0 11 | 12 | 13 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | C:\Users\massi\.nuget\packages\xunit.analyzers\0.10.0 24 | C:\Users\massi\.nuget\packages\microsoft.azure.webjobs.script.extensionsmetadatagenerator\1.1.1 25 | C:\Users\massi\.nuget\packages\microsoft.net.sdk.functions\1.0.28 26 | 27 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions.Test/obj/MoneyCalculatorFunctions.Test.csproj.nuget.g.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MoneyCalculatorFunctions 6 | { 7 | internal class FunctionNames 8 | { 9 | public const string MortgageCalculatorFunction = "MortgageCalculator"; 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/Entities/ExecutionRow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MoneyCalculatorFunctions.Entities 6 | { 7 | public class ExecutionRow 8 | { 9 | public ExecutionRow(DateTime calculationTimestamp) 10 | { 11 | this.CalculationTimestamp = calculationTimestamp; 12 | this.PartitionKey = this.CalculationTimestamp.ToString("yyyyMMdd"); 13 | this.RowKey = Guid.NewGuid().ToString(); 14 | } 15 | 16 | public string PartitionKey { get; private set; } 17 | 18 | public string RowKey { get; private set; } 19 | 20 | public decimal MortgageLoan { get; set; } 21 | 22 | public DateTime CalculationTimestamp { get; private set; } 23 | 24 | public uint NumberOfPayments { get; set; } 25 | 26 | public double AnnualInterest { get; set; } 27 | 28 | public decimal? MonthlyRate { get; set; } 29 | 30 | public bool Result { get; set; } 31 | 32 | public string ErrorCode { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/MoneyCalculatorFunctions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.2 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | PreserveNewest 14 | 15 | 16 | PreserveNewest 17 | Never 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/MortgageFunctions - Static.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.Logging; 10 | using MoneyCalculatorFunctions.Entities; 11 | using MoneyCalculatorFunctions.Services; 12 | using Newtonsoft.Json; 13 | 14 | namespace MoneyCalculatorFunctions.Static 15 | { 16 | public static class MortgageFunctions 17 | { 18 | internal const string LoanQueryKey = "loan"; 19 | internal const string InterestQueryKey = "interest"; 20 | internal const string NumberOfPaymentsQueryKey = "nPayments"; 21 | 22 | private static readonly IMortgageCalculator mortgageCalculator = 23 | new MortgageCalculator(null); 24 | 25 | [FunctionName(FunctionNames.MortgageCalculatorFunction + "STATIC")] 26 | public static async Task Run( 27 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 28 | [Table("executionsTable", Connection = "StorageAccount")] ICollector outputTable, 29 | ILogger log) 30 | { 31 | log.LogInformation($"{FunctionNames.MortgageCalculatorFunction} start"); 32 | 33 | decimal loan; 34 | if (!GetLoanFromQueryString(req, out loan)) 35 | { 36 | log.LogError($"Loan not valid"); 37 | return new BadRequestObjectResult("Loan not valid"); 38 | } 39 | 40 | double interest; 41 | if (!GetInterestFromQueryString(req, out interest)) 42 | { 43 | log.LogError($"Annual Interest not valid"); 44 | return new BadRequestObjectResult("Annual Interest not valid"); 45 | } 46 | 47 | uint nPayments; 48 | if (!GetNumberOfPaymentsFromQueryString(req, out nPayments)) 49 | { 50 | log.LogError($"Number of payments not valid"); 51 | return new BadRequestObjectResult("Number of payments not valid"); 52 | } 53 | 54 | var calculatorResult = await mortgageCalculator.CalculateMontlyRateAsync(loan, interest, nPayments); 55 | 56 | var executionRow = new ExecutionRow(DateTime.Now) 57 | { 58 | AnnualInterest = interest, 59 | ErrorCode = calculatorResult.Error?.Code, 60 | MonthlyRate = calculatorResult.Result, 61 | MortgageLoan = loan, 62 | NumberOfPayments = nPayments, 63 | Result = calculatorResult.Succeed 64 | }; 65 | 66 | outputTable.Add(executionRow); 67 | 68 | if (calculatorResult.Succeed) 69 | { 70 | return new OkObjectResult(calculatorResult.Result); 71 | } 72 | 73 | return new BadRequestObjectResult(calculatorResult.Error.Message); 74 | } 75 | 76 | #region [ Private Methods ] 77 | private static bool GetLoanFromQueryString(HttpRequest req, out decimal loan) 78 | { 79 | loan = 0; 80 | string queryLoan = req.Query[LoanQueryKey]; 81 | if (queryLoan == null || !decimal.TryParse(queryLoan, out loan)) 82 | return false; 83 | return true; 84 | } 85 | 86 | private static bool GetInterestFromQueryString(HttpRequest req, out double interest) 87 | { 88 | interest = 0; 89 | string queryInterest = req.Query[InterestQueryKey]; 90 | if (queryInterest == null || !double.TryParse(queryInterest, out interest)) 91 | return false; 92 | return true; 93 | } 94 | 95 | private static bool GetNumberOfPaymentsFromQueryString(HttpRequest req, out uint nPayments) 96 | { 97 | nPayments = 0; 98 | string queryInterest = req.Query[InterestQueryKey]; string querynPayments = req.Query[NumberOfPaymentsQueryKey]; 99 | if (querynPayments == null || !uint.TryParse(querynPayments, out nPayments)) 100 | return false; 101 | return true; 102 | } 103 | #endregion [ Private Methods ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/MortgageFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.Logging; 10 | using MoneyCalculatorFunctions.Entities; 11 | using MoneyCalculatorFunctions.Services; 12 | using Newtonsoft.Json; 13 | 14 | namespace MoneyCalculatorFunctions 15 | { 16 | public class MortgageFunctions 17 | { 18 | internal const string LoanQueryKey = "loan"; 19 | internal const string InterestQueryKey = "interest"; 20 | internal const string NumberOfPaymentsQueryKey = "nPayments"; 21 | 22 | 23 | private readonly IMortgageCalculator mortgageCalculator; 24 | 25 | public MortgageFunctions(IMortgageCalculator mortgageCalculator) 26 | { 27 | if (mortgageCalculator == null) 28 | throw new ArgumentNullException(nameof(mortgageCalculator)); 29 | 30 | this.mortgageCalculator = mortgageCalculator; 31 | } 32 | 33 | [FunctionName(FunctionNames.MortgageCalculatorFunction)] 34 | public async Task Run( 35 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 36 | [Table("executionsTable", Connection = "StorageAccount")] ICollector outputTable, 37 | ILogger log) 38 | { 39 | log.LogInformation($"{FunctionNames.MortgageCalculatorFunction} start"); 40 | 41 | decimal loan; 42 | if (!GetLoanFromQueryString(req, out loan)) 43 | { 44 | log.LogError($"Loan not valid"); 45 | return new BadRequestObjectResult("Loan not valid"); 46 | } 47 | 48 | double interest; 49 | if (!GetInterestFromQueryString(req,out interest)) 50 | { 51 | log.LogError($"Annual Interest not valid"); 52 | return new BadRequestObjectResult("Annual Interest not valid"); 53 | } 54 | 55 | uint nPayments; 56 | if (!GetNumberOfPaymentsFromQueryString(req,out nPayments)) 57 | { 58 | log.LogError($"Number of payments not valid"); 59 | return new BadRequestObjectResult("Number of payments not valid"); 60 | } 61 | 62 | var calculatorResult = await this.mortgageCalculator.CalculateMontlyRateAsync(loan, interest, nPayments); 63 | 64 | var executionRow = new ExecutionRow(DateTime.Now) 65 | { 66 | AnnualInterest = interest, 67 | ErrorCode = calculatorResult.Error?.Code, 68 | MonthlyRate = calculatorResult.Result, 69 | MortgageLoan = loan, 70 | NumberOfPayments = nPayments, 71 | Result = calculatorResult.Succeed 72 | }; 73 | 74 | outputTable.Add(executionRow); 75 | 76 | if (calculatorResult.Succeed) 77 | { 78 | return new OkObjectResult(calculatorResult.Result); 79 | } 80 | 81 | return new BadRequestObjectResult(calculatorResult.Error.Message); 82 | } 83 | 84 | #region [ Private Methods ] 85 | private bool GetLoanFromQueryString(HttpRequest req, out decimal loan) 86 | { 87 | loan = 0; 88 | string queryLoan = req.Query[LoanQueryKey]; 89 | if (queryLoan == null || !decimal.TryParse(queryLoan, out loan)) 90 | return false; 91 | return true; 92 | } 93 | 94 | private bool GetInterestFromQueryString(HttpRequest req, out double interest) 95 | { 96 | interest = 0; 97 | string queryInterest = req.Query[InterestQueryKey]; 98 | if (queryInterest == null || !double.TryParse(queryInterest, out interest)) 99 | return false; 100 | return true; 101 | } 102 | 103 | private bool GetNumberOfPaymentsFromQueryString(HttpRequest req, out uint nPayments) 104 | { 105 | nPayments = 0; 106 | string queryInterest = req.Query[InterestQueryKey]; string querynPayments = req.Query[NumberOfPaymentsQueryKey]; 107 | if (querynPayments == null || !uint.TryParse(querynPayments, out nPayments)) 108 | return false; 109 | return true; 110 | } 111 | #endregion [ Private Methods ] 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/Services/CalculatorResult.cs: -------------------------------------------------------------------------------- 1 | namespace MoneyCalculatorFunctions.Services 2 | { 3 | public class CalculatorResult 4 | { 5 | public decimal? Result { get; set; } 6 | 7 | public bool Succeed { get; set; } = true; 8 | 9 | public CalculatorError Error { get; set; } 10 | } 11 | 12 | public class CalculatorError 13 | { 14 | public string Code { get; set; } 15 | public string Message { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/Services/IMortgageCalculator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace MoneyCalculatorFunctions.Services 5 | { 6 | public interface IMortgageCalculator 7 | { 8 | 9 | Task CalculateMontlyRateAsync(decimal mortgageLoan, double annualInterest, 10 | uint numberOfPayments); 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/Services/MortgageCalculator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace MoneyCalculatorFunctions.Services 7 | { 8 | public class MortgageCalculator : IMortgageCalculator 9 | { 10 | private ILogger log; 11 | 12 | public MortgageCalculator(ILoggerFactory factory) 13 | { 14 | if (factory != null) 15 | { 16 | this.log = factory.CreateLogger(nameof(MortgageCalculator)); 17 | } 18 | } 19 | 20 | public Task CalculateMontlyRateAsync(decimal mortgageLoan, double annualInterest, uint numberOfPayments) 21 | { 22 | this.log?.LogTrace($"--> Enter {nameof(CalculateMontlyRateAsync)}", mortgageLoan, annualInterest, 23 | numberOfPayments); 24 | CalculatorResult result = null; 25 | 26 | var sw = Stopwatch.StartNew(); 27 | 28 | result = ValidateInput(mortgageLoan, annualInterest, numberOfPayments); 29 | 30 | if (result.Succeed) 31 | { 32 | double rate = 0; 33 | if (annualInterest == 0) 34 | { 35 | rate = (double)mortgageLoan / numberOfPayments; 36 | } 37 | else 38 | { 39 | var montlyInterest = annualInterest / 12; 40 | var k = Math.Pow(1 + montlyInterest, numberOfPayments); 41 | rate = (double)mortgageLoan * (montlyInterest * k / (k - 1)); 42 | } 43 | 44 | result.Result = (decimal)Math.Round(rate, 2, MidpointRounding.AwayFromZero); 45 | } 46 | 47 | sw.Stop(); 48 | 49 | this.log?.LogMetric($"{nameof (CalculateMontlyRateAsync)} Duration", sw.ElapsedMilliseconds); 50 | 51 | this.log?.LogTrace($"<-- Exit {nameof(CalculateMontlyRateAsync)}", mortgageLoan, annualInterest, 52 | numberOfPayments, result); 53 | 54 | return Task.FromResult(result); 55 | } 56 | 57 | private CalculatorResult ValidateInput(decimal mortgageLoan, double annualInterest, uint numberOfPayments) 58 | { 59 | var result = new CalculatorResult(); 60 | 61 | if (mortgageLoan < 0) 62 | { 63 | result.Succeed = false; 64 | result.Error = new CalculatorError() { Code = MortgageCalculatorErrors.MortgageNotValid, Message = "Mortgage not valid" }; 65 | this.log?.LogError($"{nameof(CalculateMontlyRateAsync)} Mortgage not valid", mortgageLoan); 66 | } 67 | else if (annualInterest < 0) 68 | { 69 | result.Succeed = false; 70 | result.Error = new CalculatorError() { Code = MortgageCalculatorErrors.AnnualInterestNotValid, Message = "Annual interest not valid" }; 71 | this.log?.LogError($"{nameof(CalculateMontlyRateAsync)} Annual interest not valid", annualInterest); 72 | } 73 | 74 | return result; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/Services/MortgageCalculatorErrors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MoneyCalculatorFunctions.Services 6 | { 7 | public static class MortgageCalculatorErrors 8 | { 9 | public const string MortgageNotValid = "MORTGAGENOTVALID"; 10 | 11 | public const string AnnualInterestNotValid = "ANNUALINTERESTNOTVALID"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/Startup.cs: -------------------------------------------------------------------------------- 1 | #define FUNCSTARTUP 2 | using Microsoft.Azure.Functions.Extensions.DependencyInjection; 3 | using Microsoft.Azure.WebJobs; 4 | using Microsoft.Azure.WebJobs.Hosting; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using MoneyCalculatorFunctions; 7 | using MoneyCalculatorFunctions.Services; 8 | 9 | 10 | #if FUNCSTARTUP 11 | [assembly: FunctionsStartup(typeof(Startup))] 12 | namespace MoneyCalculatorFunctions 13 | { 14 | public class Startup : FunctionsStartup 15 | { 16 | public override void Configure(IFunctionsHostBuilder builder) 17 | { 18 | builder.Services.AddTransient(); 19 | } 20 | } 21 | } 22 | #else 23 | [assembly: WebJobsStartup(typeof(Startup))] 24 | namespace MoneyCalculatorFunctions 25 | { 26 | public class Startup : IWebJobsStartup 27 | { 28 | public void Configure(IWebJobsBuilder builder) 29 | { 30 | builder.Services.AddTransient(); 31 | } 32 | } 33 | } 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/TestFriend.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("MoneyCalculatorFunctions.Test")] 4 | -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/MoneyCalculatorFunctions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter06/TestingAzureFunctions/TestingAzureFunctions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28922.388 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoneyCalculatorFunctions", "MoneyCalculatorFunctions\MoneyCalculatorFunctions.csproj", "{B989F2BE-26C6-4BAF-9008-458F95899FAB}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoneyCalculatorFunctions.Test", "MoneyCalculatorFunctions.Test\MoneyCalculatorFunctions.Test.csproj", "{692ABAEA-F651-4AEA-A74D-275AF5B8323F}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B989F2BE-26C6-4BAF-9008-458F95899FAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {B989F2BE-26C6-4BAF-9008-458F95899FAB}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {B989F2BE-26C6-4BAF-9008-458F95899FAB}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {B989F2BE-26C6-4BAF-9008-458F95899FAB}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {692ABAEA-F651-4AEA-A74D-275AF5B8323F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {692ABAEA-F651-4AEA-A74D-275AF5B8323F}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {692ABAEA-F651-4AEA-A74D-275AF5B8323F}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {692ABAEA-F651-4AEA-A74D-275AF5B8323F}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {5E323C1F-319D-4DB9-9136-4AC5E9FA0937} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Chapter08/DurableEntities/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /Chapter08/DurableEntities/DurableEntities.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.87 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableEntities", "DurableEntities\DurableEntities.csproj", "{AF780683-DCE8-4A28-BB80-D48A1906E99C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {AF780683-DCE8-4A28-BB80-D48A1906E99C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AF780683-DCE8-4A28-BB80-D48A1906E99C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AF780683-DCE8-4A28-BB80-D48A1906E99C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AF780683-DCE8-4A28-BB80-D48A1906E99C}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {6020DF62-4783-4411-9E2D-DAB8B850336C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Chapter08/DurableEntities/DurableEntities/Counter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Azure.WebJobs.Extensions.DurableTask; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AzureDayReloaded 10 | { 11 | [JsonObject(MemberSerialization.OptIn)] 12 | public class Counter 13 | { 14 | [JsonProperty("value")] 15 | public int Value { get; set; } 16 | 17 | [JsonProperty("lastUpdate")] 18 | public DateTime? LastUpdate { get; set; } 19 | 20 | public void Add(int amount) 21 | { 22 | this.Value += amount; 23 | this.LastUpdate = DateTime.UtcNow; 24 | } 25 | 26 | public Task Reset() 27 | { 28 | this.Value = 0; 29 | this.LastUpdate = DateTime.UtcNow; 30 | return Task.CompletedTask; 31 | } 32 | 33 | public Task GetValue() 34 | { 35 | return Task.FromResult(this.Value); 36 | } 37 | 38 | public void Delete() 39 | { 40 | Entity.Current.DeleteState(); 41 | } 42 | 43 | [FunctionName(nameof(Counter))] 44 | public static Task Run([EntityTrigger] IDurableEntityContext ctx) 45 | { 46 | ctx.DispatchAsync(); 47 | 48 | return Task.CompletedTask; 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Chapter08/DurableEntities/DurableEntities/CounterFunctions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | using Microsoft.Azure.WebJobs.Extensions.DurableTask; 3 | using Microsoft.Azure.WebJobs.Extensions.Http; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace AzureDayReloaded 12 | { 13 | public class CounterFunctions 14 | { 15 | [FunctionName("DeleteCounter")] 16 | public async Task DeleteCounter( 17 | [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Counter/{entityKey}")] HttpRequestMessage req, 18 | [DurableClient] IDurableEntityClient client, 19 | string entityKey) 20 | { 21 | var entityId = new EntityId("Counter", entityKey); 22 | 23 | await client.SignalEntityAsync(entityId, "Delete"); 24 | 25 | return req.CreateResponse(HttpStatusCode.Accepted); 26 | } 27 | 28 | [FunctionName("GetCounter")] 29 | public static async Task GetCounter( 30 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Counter/{entityKey}")] HttpRequestMessage req, 31 | [DurableClient] IDurableEntityClient client, 32 | string entityKey) 33 | { 34 | var entityId = new EntityId("Counter", entityKey); 35 | 36 | var state = await client.ReadEntityStateAsync(entityId); 37 | 38 | return req.CreateResponse(state); 39 | } 40 | 41 | [FunctionName("IncrementCounter")] 42 | public static async Task IncrementCounter( 43 | [HttpTrigger(AuthorizationLevel.Function, "put", Route = "Counter/{entityKey}/{increment?}")] HttpRequestMessage req, 44 | [DurableClient] IDurableEntityClient client, 45 | string entityKey, 46 | int? increment) 47 | { 48 | var entityId = new EntityId("Counter", entityKey); 49 | if (!increment.HasValue) 50 | increment = 1; 51 | 52 | await client.SignalEntityAsync(entityId, "Add", increment.Value); 53 | 54 | return req.CreateResponse(HttpStatusCode.Accepted); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Chapter08/DurableEntities/DurableEntities/DurableEntities.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | PreserveNewest 14 | 15 | 16 | PreserveNewest 17 | Never 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter08/DurableEntities/DurableEntities/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Activities/AddOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.WindowsAzure.Storage.Table; 10 | using Newtonsoft.Json; 11 | using OrderManager.Core; 12 | using OrderManager.Core.Entities; 13 | using OrderManager.Functions.Extensions; 14 | 15 | namespace OrderManager.Functions.Activities 16 | { 17 | public static class AddOrder 18 | { 19 | [FunctionName(FunctionNames.AddOrderFunction)] 20 | public static async Task Run([ActivityTrigger] Order order, 21 | [Table(SourceNames.OrdersTable, Connection = "StorageAccount")] CloudTable orderTable, 22 | ILogger log) 23 | { 24 | log.LogInformation($"[START ACTIVITY] --> {FunctionNames.AddOrderFunction} for {order}"); 25 | bool retVal = false; 26 | try 27 | { 28 | retVal = await orderTable.InsertAsync(order); 29 | } 30 | catch (Exception ex) 31 | { 32 | log.LogError(ex, $"Error during adding order {order}"); 33 | retVal = false; 34 | } 35 | return retVal; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Activities/FinalizeOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.WindowsAzure.Storage.Table; 10 | using Newtonsoft.Json; 11 | using OrderManager.Core; 12 | using OrderManager.Core.Entities; 13 | using OrderManager.Functions.Extensions; 14 | 15 | namespace OrderManager.Functions.Activities 16 | { 17 | public static class FinalizeOrder 18 | { 19 | [FunctionName(FunctionNames.FinalizeOrderFunction)] 20 | public static async Task Run([ActivityTrigger] OrderStateChange orderStateChange, 21 | [Table(SourceNames.OrdersTable, Connection = "StorageAccount")] CloudTable orderTable, 22 | ILogger log) 23 | { 24 | log.LogInformation($"[START ACTIVITY] --> {FunctionNames.FinalizeOrderFunction} for OrderUd={orderStateChange.OrderId}"); 25 | bool retVal = true; 26 | Order order = null; 27 | 28 | (order, retVal) = await GetOrderFromTableAsync(orderStateChange, orderTable, log); 29 | 30 | if (retVal) 31 | { 32 | if (order != null) 33 | { 34 | order.State = orderStateChange.NewOrderState; 35 | if (!await orderTable.InsertOrReplaceAsync(order)) 36 | { 37 | log.LogError($"Error during Updating Order {orderStateChange.OrderId}"); 38 | } 39 | } 40 | else 41 | { 42 | log.LogWarning($"The Order {orderStateChange.OrderId} doesn't exist in the storage"); 43 | order = null; 44 | } 45 | } 46 | 47 | return order; 48 | } 49 | 50 | private static async Task<(Order order, bool result)> GetOrderFromTableAsync(OrderStateChange orderStateChange, 51 | CloudTable orderTable, ILogger log) 52 | { 53 | var result = true; 54 | Order order = null; 55 | try 56 | { 57 | order = await orderTable.GetOrderByIdAsync(orderStateChange.OrderId); 58 | } 59 | catch (Exception ex) 60 | { 61 | log.LogError(ex, $"Error during retriving Order {orderStateChange.OrderId}"); 62 | result = false; 63 | } 64 | 65 | return (order, result); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Activities/GenerateInvoice.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Azure-Serverless-Computing/abe484489d1048c6b5ffa1a261e7fd10ee9ca0d0/Chapter08/OrderManager/OrderManager.Functions/Activities/GenerateInvoice.cs -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Activities/SendMail.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Azure.WebJobs; 8 | using Microsoft.Azure.WebJobs.Extensions.Http; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.Extensions.Logging; 11 | using Newtonsoft.Json; 12 | using OrderManager.Core; 13 | using OrderManager.Core.Entities; 14 | using SendGrid.Helpers.Mail; 15 | using System.Diagnostics; 16 | 17 | namespace OrderManager.Functions.Activities 18 | { 19 | public static class SendMail 20 | { 21 | [FunctionName(FunctionNames.SendMailFunction)] 22 | public static bool Run([ActivityTrigger] Order order, 23 | [SendGrid(ApiKey = "SendGridApiKey")] out SendGridMessage message, 24 | IBinder invoiceBinder, 25 | ILogger log) 26 | { 27 | log.LogInformation($"[START ACTIVITY] --> {FunctionNames.SendMailFunction} for order: {order}"); 28 | 29 | var invoiceFilename = order.GetInvoicePath(SourceNames.InvoicesContainer); 30 | log.LogInformation($"File Processed : {invoiceFilename}"); 31 | log.LogInformation($"Order: {order}"); 32 | log.LogInformation($"Customer mail: {order.CustomerMail}"); 33 | using (var inputBlob = invoiceBinder.Bind(new BlobAttribute(invoiceFilename))) 34 | { 35 | message = CreateMailMessage(order, inputBlob); 36 | } 37 | return true; 38 | } 39 | 40 | private static SendGridMessage CreateMailMessage(Order order, TextReader inputBlob) 41 | { 42 | var message = new SendGridMessage() 43 | { 44 | Subject = TextUtility.GenerateMailSubject(order), 45 | From = new EmailAddress("order@ordermanager.com") 46 | }; 47 | message.AddTo(new EmailAddress(order.CustomerMail)); 48 | 49 | message.AddContent("text/plain", TextUtility.GenerateMailBody(order)); 50 | 51 | if (order.State == OrderStatus.Paid) 52 | { 53 | var buffer = inputBlob.ReadBuffer(); 54 | var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(buffer); 55 | var text = System.Convert.ToBase64String(plainTextBytes); 56 | 57 | message.AddAttachment(order.GetInvoicePath(SourceNames.InvoicesContainer), 58 | text, "text/plain", "attachment", "Invoice File"); 59 | } 60 | return message; 61 | } 62 | 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Clients/ClassDiagram.cd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Clients/OrderEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.Logging; 10 | using Newtonsoft.Json; 11 | using OrderManager.Core; 12 | using OrderManager.Core.Rest; 13 | using OrderManager.Core.Entities; 14 | 15 | namespace OrderManager.Functions 16 | { 17 | public static class OrderEvents 18 | { 19 | [FunctionName(FunctionNames.OrderConfirmedFunction)] 20 | public static async Task OrderConfirmed( 21 | [HttpTrigger(AuthorizationLevel.Function, "put")] HttpRequestMessage req, 22 | [OrchestrationClient] DurableOrchestrationClient starter, 23 | ILogger log) 24 | { 25 | return await SendEventToOrderAsync(req, starter, Events.OrderPaid, log); 26 | } 27 | 28 | [FunctionName(FunctionNames.OrderCancelledFunction)] 29 | public static async Task OrderCancelled( 30 | [HttpTrigger(AuthorizationLevel.Function, "put")] HttpRequestMessage req, 31 | [OrchestrationClient] DurableOrchestrationClient starter, 32 | ILogger log) 33 | { 34 | return await SendEventToOrderAsync(req, starter, Events.OrderCancelled, log); 35 | } 36 | 37 | private static async Task SendEventToOrderAsync(HttpRequestMessage req, 38 | DurableOrchestrationClient starter, 39 | string orderEvent, 40 | ILogger log) 41 | { 42 | var jsonContent = await req.Content.ReadAsStringAsync(); 43 | OrderEventDto orderEventDto = null; 44 | 45 | orderEventDto = JsonConvert.DeserializeObject(jsonContent); 46 | if (orderEventDto != null && !string.IsNullOrWhiteSpace(orderEventDto.OrderId)) 47 | { 48 | log.LogInformation($"SendEventToOrder {orderEvent} --> Order {orderEventDto.OrderId}!"); 49 | await starter.RaiseEventAsync(orderEventDto.OrderId, orderEvent, null); 50 | return starter.CreateCheckStatusResponse(req, orderEventDto.OrderId); 51 | } 52 | 53 | return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest) { ReasonPhrase = "Order not valid" }; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Clients/OrderReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Extensions.Http; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.Logging; 10 | using Newtonsoft.Json; 11 | using OrderManager.Core; 12 | using OrderManager.Core.Rest; 13 | using OrderManager.Core.Entities; 14 | 15 | namespace OrderManager.Functions 16 | { 17 | public static class OrderReceiver 18 | { 19 | [FunctionName(FunctionNames.OrderReceiverFunction)] 20 | public static async Task Run( 21 | [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestMessage req, 22 | [OrchestrationClient] DurableOrchestrationClient starter, 23 | ILogger log) 24 | { 25 | string instanceId = null; 26 | var orderDto = await ReadOrderFromRequestAsync(req); 27 | if (orderDto != null && orderDto.IsValid()) 28 | { 29 | var order = orderDto.ToOrder(); 30 | instanceId = await starter.StartNewAsync(FunctionNames.OrderWorkflowFunction, order.Id, order); 31 | log.LogInformation($"Order received {orderDto} - started orchestration with ID = '{instanceId}'."); 32 | } 33 | else 34 | { 35 | log.LogError($"Order not valid - {orderDto}"); 36 | return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest) { ReasonPhrase = "Order not valid" }; 37 | } 38 | return starter.CreateCheckStatusResponse(req, instanceId); 39 | } 40 | 41 | private static async Task ReadOrderFromRequestAsync(HttpRequestMessage req) 42 | { 43 | var jsonContent = await req.Content.ReadAsStringAsync(); 44 | var orderDto = JsonConvert.DeserializeObject(jsonContent); 45 | return orderDto; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Entities/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.WindowsAzure.Storage; 4 | using Microsoft.WindowsAzure.Storage.Table; 5 | 6 | namespace OrderManager.Core.Entities 7 | { 8 | public class Order : TableEntity 9 | { 10 | public Order() 11 | { 12 | 13 | } 14 | 15 | public Order(string customer, string orderId) : base(customer, orderId) 16 | { 17 | this.Id = orderId; 18 | 19 | } 20 | 21 | public string Id { get; set; } 22 | 23 | public string Customer { get; set; } 24 | public string CustomerMail { get; set; } 25 | 26 | public decimal Amount { get; set; } 27 | public DateTimeOffset CreationTimestamp { get; set; } 28 | public OrderStatus State { get; set; } 29 | 30 | public override string ToString() 31 | { 32 | return $"{nameof(Id)}={Id}, {nameof(Customer)}={Customer}, {nameof(CustomerMail)}={CustomerMail}, {nameof(Id)}={Id}, {nameof(Amount)}={Amount}, {nameof(CreationTimestamp)}={CreationTimestamp}, {nameof(State)}={State}"; 33 | } 34 | 35 | public override IDictionary WriteEntity(OperationContext operationContext) 36 | { 37 | var results = base.WriteEntity(operationContext); 38 | 39 | var stateProperty = new EntityProperty(this.State.ToString()); 40 | results.Add(nameof(State), stateProperty); 41 | 42 | var amountProperty = new EntityProperty(this.Amount.ToString(System.Globalization.CultureInfo.InvariantCulture)); 43 | results.Add(nameof(Amount), amountProperty); 44 | 45 | return results; 46 | } 47 | 48 | public override void ReadEntity(IDictionary properties, OperationContext operationContext) 49 | { 50 | base.ReadEntity(properties, operationContext); 51 | if (properties.ContainsKey(nameof(State))) 52 | { 53 | var property = properties[nameof(State)]; 54 | this.State = (OrderStatus)Enum.Parse(typeof(OrderStatus), property.StringValue); 55 | properties.Remove(nameof(State)); 56 | } 57 | if (properties.ContainsKey(nameof(Amount))) 58 | { 59 | var property = properties[nameof(Amount)]; 60 | this.Amount = decimal.Parse(property.StringValue, System.Globalization.CultureInfo.InvariantCulture); 61 | properties.Remove(nameof(Amount)); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Entities/OrderStateChange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace OrderManager.Core.Entities 6 | { 7 | public class OrderStateChange 8 | { 9 | public string OrderId { get; set; } 10 | public OrderStatus NewOrderState { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Entities/OrderStatus.cs: -------------------------------------------------------------------------------- 1 | namespace OrderManager.Core 2 | { 3 | public enum OrderStatus 4 | { 5 | Created=0, 6 | Paid=1, 7 | Cancelled=2, 8 | Error=999 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Events.cs: -------------------------------------------------------------------------------- 1 | namespace OrderManager.Core 2 | { 3 | public static class Events 4 | { 5 | public const string OrderPaid = "ORDERPAID"; 6 | public const string OrderCancelled = "ORDERCANCELLED"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Extensions/CloudTableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.WindowsAzure.Storage.Table; 5 | using OrderManager.Core.Entities; 6 | 7 | namespace OrderManager.Functions.Extensions 8 | { 9 | public static class CloudTableExtensions 10 | { 11 | 12 | public static async Task GetOrderByIdAsync(this CloudTable table, string orderId) 13 | { 14 | Order child = null; 15 | 16 | TableQuery rangeQuery = new TableQuery().Where( 17 | TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, 18 | orderId)); 19 | 20 | var token = default(TableContinuationToken); 21 | 22 | var query = await table.ExecuteQuerySegmentedAsync(rangeQuery, token); 23 | 24 | child = query.FirstOrDefault(); 25 | 26 | return child; 27 | } 28 | 29 | public static async Task InsertAsync(this CloudTable table, TableEntity entity) 30 | { 31 | TableOperation operation = TableOperation.Insert(entity); 32 | var result = await table.ExecuteAsync(operation); 33 | 34 | return result.HttpStatusCode >= 200 && result.HttpStatusCode <= 299; 35 | } 36 | 37 | public static async Task UpdateAsync(this CloudTable table, TableEntity entity) 38 | { 39 | TableOperation operation = TableOperation.Replace(entity); 40 | var result = await table.ExecuteAsync(operation); 41 | 42 | return result.HttpStatusCode >= 200 && result.HttpStatusCode <= 299; 43 | } 44 | 45 | public static async Task InsertOrReplaceAsync(this CloudTable table, TableEntity entity) 46 | { 47 | TableOperation operation = TableOperation.InsertOrReplace(entity); 48 | var result = await table.ExecuteAsync(operation); 49 | 50 | return result.HttpStatusCode >= 200 && result.HttpStatusCode <= 299; 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Extensions/OrderDtoExtensions.cs: -------------------------------------------------------------------------------- 1 | using OrderManager.Core.Entities; 2 | using OrderManager.Core.Rest; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace OrderManager.Core.Rest 9 | { 10 | public static class OrderDtoExtensions 11 | { 12 | public static Order ToOrder(this OrderDto dto) 13 | { 14 | if (dto == null) 15 | throw new NullReferenceException(nameof(dto)); 16 | 17 | var orderId = Guid.NewGuid().ToString(); 18 | return new Order() 19 | { 20 | Id = orderId, 21 | RowKey = orderId, 22 | PartitionKey = dto.Customer, 23 | Amount = dto.Amount, 24 | Customer = dto.Customer, 25 | CreationTimestamp = dto.CreationTimestamp, 26 | Timestamp = dto.CreationTimestamp, 27 | State = OrderStatus.Created, 28 | CustomerMail = dto.CustomerMail 29 | }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Extensions/OrderExtensions.cs: -------------------------------------------------------------------------------- 1 | using OrderManager.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace OrderManager.Core.Entities 7 | { 8 | public static class OrderExtensions 9 | { 10 | public static string GetInvoicePath(this Order order, string invoiceContainer) 11 | { 12 | if (order == null) 13 | throw new NullReferenceException(nameof(order)); 14 | 15 | 16 | if (invoiceContainer != null && !invoiceContainer.EndsWith("/")) 17 | { 18 | invoiceContainer = invoiceContainer + "/"; 19 | } 20 | 21 | return $"{invoiceContainer}{order.Customer}-{order.Id}.txt"; 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Extensions/TextReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace System.IO 8 | { 9 | public static class TextReaderExtensions 10 | { 11 | 12 | public static char[] ReadBuffer(this TextReader textReader) 13 | { 14 | List returnArray = new List(); 15 | 16 | char[] buffer = new char[1024]; 17 | var index = 0; 18 | int count = 0; 19 | do 20 | { 21 | count = textReader.ReadBlock(buffer, index, 1024); 22 | index += count; 23 | returnArray.AddRange(buffer.Take(count)); 24 | } while (count == 1024); 25 | 26 | return returnArray.ToArray(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/FunctionNames.cs: -------------------------------------------------------------------------------- 1 | namespace OrderManager.Core 2 | { 3 | public static class FunctionNames 4 | { 5 | public const string OrderReceiverFunction = "OrderReceiver"; 6 | public const string OrderWorkflowFunction = "OrderWorkflow"; 7 | public const string AddOrderFunction = "AddOrder"; 8 | public const string FinalizeOrderFunction = "FinalizeOrder"; 9 | public const string GenerateInvoiceFunction = "GenerateInvoice"; 10 | public const string SendMailFunction = "SendMail"; 11 | public const string OrderConfirmedFunction = "OrderConfirmed"; 12 | public const string OrderCancelledFunction = "OrderCancelled"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Orchetsrators/ClassDiagram.cd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Orchetsrators/OrderWorkflow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json; 10 | using OrderManager.Core; 11 | using OrderManager.Core.Entities; 12 | using System.Threading; 13 | 14 | namespace OrderManager.Functions.Orchetsrators 15 | { 16 | public static class OrderWorkflow 17 | { 18 | [FunctionName(FunctionNames.OrderWorkflowFunction)] 19 | public static async Task Run([OrchestrationTrigger] DurableOrchestrationContext context, 20 | ILogger log) 21 | { 22 | log.LogInformation($"[START ORCHESTRATOR] --> {FunctionNames.OrderWorkflowFunction}"); 23 | var order = context.GetInput(); 24 | 25 | log.LogTrace($"Adding Order {order}"); 26 | var addResult = await context.CallActivityWithRetryAsync(FunctionNames.AddOrderFunction, 27 | new RetryOptions(TimeSpan.FromSeconds(1), 10), order); 28 | 29 | if (addResult) 30 | { 31 | DateTime orderDeadline = GetOrderDeadLine(context); 32 | 33 | var orderPaidEvent = context.WaitForExternalEvent(Events.OrderPaid); 34 | var orderCancelledEvent = context.WaitForExternalEvent(Events.OrderCancelled); 35 | var cancelTimer = context.CreateTimer(orderDeadline, CancellationToken.None); 36 | 37 | var taskCompleted = await Task.WhenAny(orderPaidEvent, orderCancelledEvent, cancelTimer); 38 | if (taskCompleted == orderCancelledEvent || taskCompleted == cancelTimer) 39 | { 40 | log.LogWarning($"Order Cancelled : {order}"); 41 | order = await context.CallActivityAsync(FunctionNames.FinalizeOrderFunction, 42 | new OrderStateChange() 43 | { 44 | NewOrderState = OrderStatus.Cancelled, 45 | OrderId = order.Id 46 | }); 47 | } 48 | else if (taskCompleted == orderPaidEvent) 49 | { 50 | log.LogTrace($"Order Paid : {order}"); 51 | order = await context.CallActivityAsync(FunctionNames.FinalizeOrderFunction, 52 | new OrderStateChange() 53 | { 54 | NewOrderState = OrderStatus.Paid, 55 | OrderId = order.Id 56 | }); 57 | await context.CallActivityAsync(FunctionNames.GenerateInvoiceFunction, order); 58 | } 59 | 60 | if (order != null) 61 | { 62 | var sendMailResult = await context.CallActivityAsync(FunctionNames.SendMailFunction, order); 63 | log.LogTrace($"Sendmail result : {sendMailResult}"); 64 | } 65 | 66 | } 67 | } 68 | 69 | private static DateTime GetOrderDeadLine(DurableOrchestrationContext context) 70 | { 71 | var orderExpireTimeoutConfig = Environment.GetEnvironmentVariable("OrderExpireTimeout"); 72 | int orderExpireTimeout; 73 | if (!int.TryParse(orderExpireTimeoutConfig, out orderExpireTimeout)) 74 | orderExpireTimeout = 1; 75 | 76 | var orderDeadline = context.CurrentUtcDateTime.ToLocalTime().AddMinutes(orderExpireTimeout); 77 | 78 | return orderDeadline; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/OrderManager.Functions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | Always 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Rest/OrderDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OrderManager.Core.Rest 4 | { 5 | public class OrderDto 6 | { 7 | public string Customer { get; set; } 8 | public string CustomerMail { get; set; } 9 | 10 | public decimal Amount { get; set; } 11 | public DateTimeOffset CreationTimestamp { get; set; } 12 | 13 | public bool IsValid() 14 | { 15 | return !string.IsNullOrWhiteSpace(Customer) && 16 | !string.IsNullOrWhiteSpace(CustomerMail) && 17 | Amount >= 0; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Rest/OrderEventDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OrderManager.Core.Rest 4 | { 5 | public class OrderEventDto 6 | { 7 | public string OrderId { get; set; } 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/SourceNames.cs: -------------------------------------------------------------------------------- 1 | namespace OrderManager.Core 2 | { 3 | public static class SourceNames 4 | { 5 | public const string OrdersTable = "orders"; 6 | public const string InvoicesContainer = "invoices"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/Utilities/TextUtility.cs: -------------------------------------------------------------------------------- 1 | using OrderManager.Core.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace OrderManager.Core 7 | { 8 | public static class TextUtility 9 | { 10 | public static string GenerateMailSubject(Order order) 11 | { 12 | if (order == null) 13 | throw new ArgumentNullException(nameof(order)); 14 | 15 | switch (order.State) 16 | { 17 | case OrderStatus.Cancelled: 18 | return $"Order {order.Id } cancelled"; 19 | case OrderStatus.Paid: 20 | return $"Order {order.Id } paid"; 21 | case OrderStatus.Created: 22 | case OrderStatus.Error: 23 | default: 24 | throw new NotSupportedException(); 25 | } 26 | } 27 | 28 | public static string GenerateMailBody(Order order) 29 | { 30 | if (order == null) 31 | throw new ArgumentNullException(nameof(order)); 32 | string body = null; 33 | switch (order.State) 34 | { 35 | case OrderStatus.Cancelled: 36 | body=$"Your order {order.Id} created on {order.CreationTimestamp} is cancelled\n\n"; 37 | body+=$"Customer : {order.Customer}\n\n"; 38 | body += $"Email: {order.CustomerMail}\n\n"; 39 | body += $"Amount : {order.Amount}€\n\n"; 40 | return body; 41 | case OrderStatus.Paid: 42 | body = $"Your order {order.Id} created on {order.CreationTimestamp} is paid on {DateTime.Now}\n\n"; 43 | body += $"Customer : {order.Customer}\n\n"; 44 | body += $"Email: {order.CustomerMail}\n\n"; 45 | body += $"Amount : {order.Amount}€\n\n"; 46 | return body; 47 | case OrderStatus.Created: 48 | case OrderStatus.Error: 49 | default: 50 | throw new NotSupportedException(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.Functions/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter08/OrderManager/OrderManager.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29001.49 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderManager.Functions", "OrderManager.Functions\OrderManager.Functions.csproj", "{28A9610E-EBD0-4FE6-8526-BEBE7E54E05D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {28A9610E-EBD0-4FE6-8526-BEBE7E54E05D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {28A9610E-EBD0-4FE6-8526-BEBE7E54E05D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {28A9610E-EBD0-4FE6-8526-BEBE7E54E05D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {28A9610E-EBD0-4FE6-8526-BEBE7E54E05D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9D8D9C56-0796-4925-B344-2718FD582F38} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Chapter09/VacationRequestLogicApp/VacationRequestLogicApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "VacationRequestLogicApp", "VacationRequestLogicApp\VacationRequestLogicApp.deployproj", "{03B563EB-0C87-45A0-8F60-D4612A841582}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {03B563EB-0C87-45A0-8F60-D4612A841582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {03B563EB-0C87-45A0-8F60-D4612A841582}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {03B563EB-0C87-45A0-8F60-D4612A841582}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {03B563EB-0C87-45A0-8F60-D4612A841582}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {E5566C33-5657-407C-A425-7F13924CD363} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Chapter09/VacationRequestLogicApp/VacationRequestLogicApp/LogicApp.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "logicAppName": { 6 | "value": "VacationRequestLogicApp" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Chapter09/VacationRequestLogicApp/VacationRequestLogicApp/Payloads/VacationRequest - Schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": {}, 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "http://example.com/root.json", 5 | "type": "object", 6 | "title": "Vacation Request", 7 | "required": [ 8 | "EmployeeId", 9 | "RequestDate", 10 | "VacationType" 11 | ], 12 | "properties": { 13 | "EmployeeId": { 14 | "$id": "#/properties/EmployeeId", 15 | "type": "string", 16 | "title": "EmployeeId", 17 | "default": "", 18 | "examples": [ 19 | "111111" 20 | ], 21 | "pattern": "^(.*)$" 22 | }, 23 | "RequestDate": { 24 | "$id": "#/properties/RequestDate", 25 | "type": "string", 26 | "title": "Requestdate", 27 | "default": "", 28 | "examples": [ 29 | "2019-06-06" 30 | ], 31 | "pattern": "^(.*)$" 32 | }, 33 | "VacationType": { 34 | "$id": "#/properties/VacationType", 35 | "type": "string", 36 | "title": "VacationType", 37 | "default": "", 38 | "examples": [ 39 | "Illness", 40 | "Vacation" 41 | ], 42 | "pattern": "^(.*)$" 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Chapter09/VacationRequestLogicApp/VacationRequestLogicApp/Payloads/VacationRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "EmployeeId": "111111", 3 | "RequestDate": "2019-06-06", 4 | "VacationType": "Illness" 5 | } -------------------------------------------------------------------------------- /Chapter09/VacationRequestLogicApp/VacationRequestLogicApp/Payloads/VacationRequestRow.json: -------------------------------------------------------------------------------- 1 | { 2 | "PartitionKey": "111111", 3 | "EmployeeId": "111111", 4 | "RowKey": "2019-06-06", 5 | "RequestDate": "2019-06-06", 6 | "VacationType": "Illness" 7 | } -------------------------------------------------------------------------------- /Chapter09/VacationRequestLogicApp/VacationRequestLogicApp/VacationRequestLogicApp.deployproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | Release 10 | AnyCPU 11 | 12 | 13 | 14 | 03b563eb-0c87-45a0-8f60-d4612a841582 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | False 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Chapter09/VacationRequestLogicApp/VacationRequestLogicApp/bin/Debug/staging/VacationRequestLogicApp/LogicApp.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "logicAppName": { 6 | "value": "VacationRequestLogicApp" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /Chapter11/EventGridSolution/EventGridSolution.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29201.188 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventHandlerFunction", "EventHandlerFunction\EventHandlerFunction.csproj", "{1E42D032-73A3-4650-BEC0-68BE3AC89EAB}" 7 | EndProject 8 | Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "ResourceGroupTemplate", "ResourceGroupTemplate\ResourceGroupTemplate.deployproj", "{AA0D100A-547F-4976-B802-85A0F4DFF01E}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {1E42D032-73A3-4650-BEC0-68BE3AC89EAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {1E42D032-73A3-4650-BEC0-68BE3AC89EAB}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {1E42D032-73A3-4650-BEC0-68BE3AC89EAB}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {1E42D032-73A3-4650-BEC0-68BE3AC89EAB}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AA0D100A-547F-4976-B802-85A0F4DFF01E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AA0D100A-547F-4976-B802-85A0F4DFF01E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AA0D100A-547F-4976-B802-85A0F4DFF01E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AA0D100A-547F-4976-B802-85A0F4DFF01E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {1D3C89C1-8768-4854-B327-348A0C7512F9} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Chapter11/EventGridSolution/EventHandlerFunction/EventGridFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.Http; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Newtonsoft.Json; 10 | using Microsoft.Azure.WebJobs.Extensions.EventGrid; 11 | using Microsoft.Azure.EventGrid.Models; 12 | 13 | namespace EventHandlerFunction 14 | { 15 | public static class EventGridFunctions 16 | { 17 | [FunctionName("EventHandlerFunction")] 18 | public static void EventGridHandler([EventGridTrigger]EventGridEvent eventGridEvent, ILogger log) 19 | { 20 | log.LogInformation(eventGridEvent.EventType); 21 | log.LogInformation(eventGridEvent.Data.ToString()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Chapter11/EventGridSolution/EventHandlerFunction/EventHandlerFunction.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | PreserveNewest 13 | 14 | 15 | PreserveNewest 16 | Never 17 | 18 | 19 | -------------------------------------------------------------------------------- /Chapter11/EventGridSolution/EventHandlerFunction/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /Chapter11/EventGridSolution/ResourceGroupTemplate/ResourceGroupTemplate.deployproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | Release 10 | AnyCPU 11 | 12 | 13 | 14 | aa0d100a-547f-4976-b802-85a0f4dff01e 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | False 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Chapter11/EventGridSolution/ResourceGroupTemplate/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "sites_eventhandlerfuncapp_name": { 6 | "value": "eventgriddemofuncapp" 7 | }, 8 | "components_eventhandlerfuncapp_name": { 9 | "value": "eventgriddemoappinsight" 10 | }, 11 | "storageAccounts_eventsourcestorage_name": { 12 | "value": "eventgriddemostore" 13 | }, 14 | "storageAccounts_eventhandlerfuncappstore_name": { 15 | "value": "eventgriddemocodestore" 16 | }, 17 | "serverfarms_WestEuropePlan_externalid": { 18 | "value": "eventgriddemoplan" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Mastering Azure Serverless Computing 5 | 6 | Mastering Azure Serverless Computing 7 | 8 | This is the code repository for [Mastering Azure Serverless Computing ](https://www.packtpub.com/cloud-networking/mastering-azure-serverless-computing?utm_source=github&utm_medium=repository&utm_campaign=9781789951226), published by Packt. 9 | 10 | **A practical guide to building and deploying enterprise-grade serverless applications using Azure Functions** 11 | 12 | ## What is this book about? 13 | Application development has evolved from traditional monolithic app development to using serverless options and microservices. This book is designed to guide you through using Microsoft's Azure Functions to process data, integrate systems, and build simple APIs and microservices. 14 | 15 | 16 | This book covers the following exciting features: 17 | * Create and deploy advanced Azure Functions 18 | * Learn to extend the runtime of Azure Functions 19 | * Orchestrate your logic through code or a visual workflow 20 | * Add caching, security, routing, and filtering to your APIs 21 | * Use serverless technologies in real-world scenarios 22 | 23 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1789951224) today! 24 | 25 | https://www.packtpub.com/ 26 | 27 | ## Instructions and Navigations 28 | All of the code is organized into folders. For example, Chapter02. 29 | 30 | The code will look like the following: 31 | ``` 32 | public static class SimpleExample 33 | { 34 | [FunctionName("QueueTrigger")] 35 | public static void Run( 36 | [QueueTrigger("inputQueue")] string inItem, 37 | [Queue("outputQueue")] out string outItem, 38 | ILogger log) 39 | { 40 | log.LogInformation($"C# function processed: {inItem}"); 41 | } 42 | } 43 | ``` 44 | 45 | **Following is what you need for this book:** 46 | This book is designed for cloud administrators, architects, and developers interested in building scalable systems and deploying serverless applications with Azure Functions. Prior knowledge of core Microsoft Azure services and Azure Functions is necessary to understand the topics covered in this book. 47 | 48 | With the following software and hardware list you can run all code files present in the book (Chapter 1-12). 49 | ### Software and Hardware List 50 | | Chapter | Software required | OS required | 51 | | -------- | ------------------------------------ | ----------------------------------- | 52 | | 1,2,3,6,7 | Azure Functions Core Tool | Windows, Mac OS X, and Linux (Any) | 53 | | 1,2,3,6,7 | Visual Studio 2019 Community | Windows, Mac OS X | 54 | | 1,2,3 | Visual Studio Code | Windows, Mac OS X, and Linux (Any) | 55 | | 7 | Docker Desktop | Windows, Mac OS X, and Linux (Any) | 56 | 57 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://static.packt-cdn.com/downloads/9781789951226_ColorImages.pdf). 58 | 59 | ### Related products 60 | * Azure Serverless Computing Cookbook - Second Edition [[Packt]](https://www.packtpub.com/virtualization-and-cloud/azure-serverless-computing-cookbook-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781789615265) [[Amazon]](https://www.amazon.com/dp/1789615267) 61 | 62 | ## Get to Know the Author 63 | **Lorenzo Barbieri** 64 | specializes in cloud-native applications and application modernization on Azure and Office 365, Windows and cross-platform applications, Visual Studio, and DevOps, and likes to talk with people and communities about technology, food, and funny things. 65 | He is a speaker, a trainer, and a public speaking coach. He has helped many students, developers, and other professionals, as well as many of his colleagues, to improve their stage presence with a view to delivering exceptional presentations. 66 | Lorenzo works for Microsoft, in the One Commercial Partner Technical Organization, helping partners, developers, communities, and customers across Western Europe, supporting software development on Microsoft and OSS technologies. 67 | 68 | **Massimo Bonanni** 69 | specializes in cloud application development and, in particular, in Azure compute technologies. Over the last 3 years, he has worked with important Italian and European customers to implement distributed applications using Service Fabric and microservices architecture. 70 | Massimo is an Azure technical trainer in Microsoft and his goal is to help customers utilize their Azure skills to achieve more and leverage the power of Azure in their solutions. He is also a technical speaker at national and international conferences, a Microsoft Certified Trainer, a former MVP (for 6 years in Visual Studio and Development Technologies and Windows Development), an Intel Software Innovator, and an Intel Black Belt. 71 | 72 | ### Suggestions and Feedback 73 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 74 | 75 | 76 | ### Download a free PDF 77 | 78 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
79 |

https://packt.link/free-ebook/9781789951226

--------------------------------------------------------------------------------