├── .devcontainer
└── static-content-hosting
│ └── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github
└── dependabot.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.MD
├── async-request-reply
├── README.md
├── deploy.bicep
└── src
│ ├── AsyncOperationStatusChecker.cs
│ ├── AsyncProcessingBackgroundWorker.cs
│ ├── AsyncProcessingWorkAcceptor.cs
│ ├── CloudBlockBlobExtensions.cs
│ ├── CustomerPOCO.cs
│ ├── Program.cs
│ ├── asyncpattern.csproj
│ ├── asyncpattern.sln
│ ├── host.json
│ └── local.settings.json
├── choreography
├── README.md
├── choreography-example.drawio
└── choreography-example.png
├── claim-check
├── .gitattributes
├── .gitignore
├── README.md
└── code-samples
│ ├── all-samples.sln
│ ├── sample-1
│ ├── FunctionConsumer1
│ │ ├── FunctionConsumer1.cs
│ │ ├── FunctionConsumer1.csproj
│ │ ├── ISampleBlobDataMover.cs
│ │ ├── Program.cs
│ │ ├── SampleBlobDataMover.cs
│ │ ├── host.json
│ │ └── local.settings.json.template
│ ├── bicep
│ │ └── main.bicep
│ ├── images
│ │ ├── sample-1-diagram.drawio
│ │ └── sample-1-diagram.png
│ ├── readme.md
│ └── sample-1.sln
│ ├── sample-2
│ ├── ClientConsumer2
│ │ ├── ClientConsumer2.csproj
│ │ ├── EventHubsConsumer.cs
│ │ ├── Program.cs
│ │ ├── SampleBlobDataMover.cs
│ │ ├── SampleSettingsValidator.cs
│ │ └── appsettings.json.template
│ ├── bicep
│ │ └── main.bicep
│ ├── images
│ │ ├── sample-2-diagram.drawio
│ │ └── sample-2-diagram.png
│ ├── readme.md
│ └── sample-2.sln
│ ├── sample-3
│ ├── FunctionConsumer3
│ │ ├── FunctionConsumer3.cs
│ │ ├── FunctionConsumer3.csproj
│ │ ├── ISampleBlobDataMover.cs
│ │ ├── Program.cs
│ │ ├── SampleBlobDataMover.cs
│ │ ├── host.json
│ │ └── local.settings.json.template
│ ├── bicep
│ │ └── main.bicep
│ ├── images
│ │ ├── sample-3-diagram.drawio
│ │ └── sample-3-diagram.png
│ ├── readme.md
│ └── sample-3.sln
│ └── sample-4
│ ├── ClientProducer4
│ ├── ClientProducer4.csproj
│ ├── ISampleBlobDataMover.cs
│ ├── ISampleKafkaProducer.cs
│ ├── Program.cs
│ ├── SampleBlobDataMover.cs
│ ├── SampleKafkaProducer.cs
│ ├── SampleSettingsValidator.cs
│ ├── SampleTokenCredentialHelper.cs
│ └── appsettings.json.template
│ ├── FunctionConsumer4
│ ├── FunctionConsumer4.cs
│ ├── FunctionConsumer4.csproj
│ ├── ISampleBlobDataMover.cs
│ ├── Program.cs
│ ├── SampleBlobDataMover.cs
│ ├── host.json
│ └── local.settings.json.template
│ ├── bicep
│ └── main.bicep
│ ├── images
│ ├── sample-4-diagram.drawio
│ └── sample-4-diagram.png
│ ├── readme.md
│ └── sample-4.sln
├── leader-election
├── DistributedMutex
│ ├── BlobDistributedMutex.cs
│ ├── BlobLeaseManager.cs
│ └── DistributedMutex.csproj
├── LeaderElection.sln
├── LeaderElectionConsoleWorker
│ ├── LeaderElectionConsoleWorker.csproj
│ ├── Program.cs
│ └── app.config
└── Readme.md
├── pipes-and-filters
├── .devcontainer
│ └── devcontainer.json
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── ImageProcessingPipeline
│ ├── .gitignore
│ ├── ImageProcessingPipeline.csproj
│ ├── ImageProcessingPipeline.sln
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── PublishFinal.cs
│ ├── Resize.cs
│ ├── Watermark.cs
│ ├── host.json
│ ├── local.settings.template.json
│ └── resources
│ │ └── watermark.png
├── README.md
├── bicep
│ └── main.bicep
├── images
│ └── clouds.png
└── pipes-and-filters.drawio
├── priority-queue
├── PriorityQueue.sln
├── PriorityQueueConsumerHigh
│ ├── .gitignore
│ ├── PriorityQueueConsumerHigh.csproj
│ ├── PriorityQueueConsumerHighFn.cs
│ ├── Properties
│ │ ├── serviceDependencies.json
│ │ └── serviceDependencies.local.json
│ ├── host.json
│ └── local.settings.json
├── PriorityQueueConsumerLow
│ ├── .gitignore
│ ├── PriorityQueueConsumerLow.csproj
│ ├── PriorityQueueConsumerLowFn.cs
│ ├── Properties
│ │ ├── serviceDependencies.json
│ │ └── serviceDependencies.local.json
│ ├── host.json
│ └── local.settings.json
├── PriorityQueueSender
│ ├── .gitignore
│ ├── Priority.cs
│ ├── PriorityQueueSender.csproj
│ ├── PriorityQueueSenderFn.cs
│ ├── Properties
│ │ ├── serviceDependencies.json
│ │ └── serviceDependencies.local.json
│ ├── host.json
│ └── local.settings.json
└── Readme.md
├── sharding
├── README.md
├── sharding-example.drawio
└── sharding-example.png
├── static-content-hosting
├── README.md
├── bicep
│ └── main.bicep
├── src
│ ├── index.template.html
│ └── static
│ │ ├── 404.html
│ │ └── style.css
├── static-content-hosting-pattern.drawio
└── static-content-hosting-pattern.png
└── valet-key
├── .gitignore
├── .vscode
└── extensions.json
├── README.md
├── ValetKey.Client
├── Program.cs
├── ValetKey.Client.csproj
└── appsettings.json
├── ValetKey.Web
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── FileServices.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── ValetKey.Web.csproj
├── host.json
└── local.settings.template.json
├── bicep
└── main.bicep
├── valet-key-example.drawio
├── valet-key-example.png
└── valet-key.sln
/.devcontainer/static-content-hosting/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
3 | {
4 | "name": "static-content-hosting-devcontainer",
5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
7 | "features": {
8 | "ghcr.io/devcontainers/features/azure-cli:1": {
9 | "version": "latest"
10 | }
11 | },
12 | "customizations": {
13 | "vscode": {
14 | "extensions": [
15 | "ms-vscode.azurecli",
16 | "ms-azuretools.vscode-bicep"
17 | ]
18 | }
19 | }
20 |
21 | // Features to add to the dev container. More info: https://containers.dev/features.
22 | // "features": {},
23 |
24 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
25 | // "forwardPorts": [5000, 5001],
26 | // "portsAttributes": {
27 | // "5001": {
28 | // "protocol": "https"
29 | // }
30 | // }
31 |
32 | // Use 'postCreateCommand' to run commands after the container is created.
33 | // "postCreateCommand": "dotnet restore",
34 |
35 | // Configure tool-specific properties.
36 | // "customizations": {},
37 |
38 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
39 | // "remoteUser": "root"
40 | }
41 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | insert_final_newline = true
7 |
8 | # 4 space indentation
9 | indent_style = space
10 | indent_size = 4
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "nuget" # See documentation for possible values
9 | directories:
10 | - "/async-request-reply/src"
11 | - "/claim-check/code-samples"
12 | - "/leader-election"
13 | - "/pipes-and-filters/ImageProcessingPipeline"
14 | - "/priority-queue"
15 | - "/valet-key/ValetKey.*"
16 | schedule:
17 | interval: "weekly"
18 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Cloud Design Patterns
2 |
3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [Contributor License Agreements](https://cla.opensource.microsoft.com).
4 |
5 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
6 |
7 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
8 |
9 | - [Code of Conduct](#coc)
10 | - [Issues and Bugs](#issue)
11 | - [Feature Requests](#feature)
12 | - [Submission Guidelines](#submit)
13 |
14 | ## Code of Conduct
15 |
16 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
17 |
18 | ## Found an Issue?
19 |
20 | If you find a bug in the source code or a mistake in the documentation, you can help us by [submitting an issue](#submit-issue) to the GitHub repository. Even better, you can [submit a Pull Request](#submit-pr) with a fix.
21 |
22 | ## Want a Feature?
23 |
24 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub repository. If you would like to *implement* a new feature, please submit an issue with a proposal for your work first, to be sure that we can use it.
25 |
26 | - **Small features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
27 |
28 | ## Submission Guidelines
29 |
30 | ### Submitting an Issue
31 |
32 | Before you submit an issue, search the archive, maybe your question was already answered.
33 |
34 | If your issue appears to be a bug, and hasn't been reported, open a new issue.
35 | Help us to maximize the effort we can spend fixing issues and adding new
36 | features, by not reporting duplicate issues. Providing the following information will increase the chances of your issue being dealt with quickly:
37 |
38 | - **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
39 | - **Version** - what version is affected (e.g. 0.1.2)
40 | - **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you
41 | - **Browsers and Operating System** - is this a problem with all browsers?
42 | - **Reproduce the Error** - provide a live example or a unambiguous set of steps
43 | - **Related Issues** - has a similar issue been reported before?
44 | - **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
45 | causing the problem (line of code or commit)
46 |
47 | You can file new issues by providing the above information this repository's [issues list](https://github.com/mspnp/cloud-design-patterns/issues/new).
48 |
49 | ### Submitting a Pull Request (PR)
50 |
51 | Before you submit your Pull Request (PR) consider the following guidelines:
52 |
53 | * Search the repository's [pull requests](https://github.com/mspnp/cloud-design-patterns/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate effort.
54 |
55 | - Make your changes in a new git fork:
56 | - Commit your changes using a descriptive commit message
57 | - Push your fork to GitHub:
58 | - In GitHub, create a pull request
59 | - If we suggest changes then:
60 | - Make the required updates.
61 | - Rebase your fork and force push to your GitHub repository (this will update your Pull Request):
62 |
63 | ```shell
64 | git rebase main -i
65 | git push -f
66 | ```
67 |
68 | That's it! Thank you for your contribution!
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Cloud Design Patterns
2 |
3 | Copyright (c) Microsoft Corporation
4 |
5 | All rights reserved.
6 |
7 | MIT License
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy of
10 | this software and associated documentation files (the ""Software""), to deal in
11 | the Software without restriction, including without limitation the rights to
12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
13 | the Software, and to permit persons to whom the Software is furnished to do so,
14 | subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in all
17 | copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
21 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
22 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
23 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cloud Design Patterns
2 |
3 | This repository contains companion code for the article series found in the [Cloud Design Patterns](https://aka.ms/cloud-design-patterns) series in Azure Architecture Center.
4 |
5 | ## Patterns demonstrated
6 |
7 | | Pattern definition | Sample code |
8 | | :----------------- | :---------- |
9 | | [Asynchronous Request-Reply](https://learn.microsoft.com/azure/architecture/patterns/async-request-reply) | ./async-request-reply/ |
10 | | [Choreography](https://learn.microsoft.com/azure/architecture/patterns/choreography) | ./choreography/ |
11 | | [Claim Check](https://learn.microsoft.com/azure/architecture/patterns/claim-check) | ./claim-check/ |
12 | | [Deployment Stamps](https://learn.microsoft.com/azure/architecture/patterns/deployment-stamp) | [mspnp/solution-architectures#deployment-stamp](https://github.com/mspnp/solution-architectures/tree/master/apps/deployment-stamp)$^{*}$ |
13 | | [Geode](https://learn.microsoft.com/azure/architecture/patterns/geodes) | [mspnp/geode-pattern-accelerator](https://github.com/mspnp/geode-pattern-accelerator)$^{*}$ |
14 | | [Health Endpoint Monitoring](https://learn.microsoft.com/azure/architecture/patterns/health-endpoint-monitoring) | [dotnet/AspNetCore.Docs#health-checks](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/host-and-deploy/health-checks/samples/7.x/HealthChecksSample)$^{*}$ |
15 | | [Leader Election](https://learn.microsoft.com/azure/architecture/patterns/leader-election) | ./leader-election/ |
16 | | [Pipes & Filters](https://learn.microsoft.com/azure/architecture/patterns/pipes-and-filters) | ./pipes-and-filters/ |
17 | | [Priority Queue](https://learn.microsoft.com/azure/architecture/patterns/priority-queue) | ./priority-queue/ |
18 | | [Rate Limiting](https://learn.microsoft.com/azure/architecture/patterns/rate-limiting-pattern) | Go: [mspnp/go-batcher](https://github.com/mspnp/go-batcher)\*
Java: [Azure-Samples/java-rate-limiting-pattern-sample](https://github.com/Azure-Samples/java-rate-limiting-pattern-sample)\* |
19 | | [Saga](https://learn.microsoft.com/azure/architecture/reference-architectures/saga/saga) | [Azure-Samples/saga-orchestration-serverless](https://github.com/Azure-Samples/saga-orchestration-serverless)$^{*}$ |
20 | | [Sharding](https://learn.microsoft.com/azure/architecture/patterns/sharding) | ./sharding/ |
21 | | [Static Content Hosting](https://learn.microsoft.com/azure/architecture/patterns/static-content-hosting) | ./static-content-hosting/ |
22 | | [Valet Key](https://learn.microsoft.com/en-us/azure/architecture/patterns/valet-key) | ./valet-key/ |
23 |
24 | > Items donated with $^{*}$ are part of the Azure Architecture Center series, but the sample code is hosted in a different repository.
25 |
26 | ## Contributions
27 |
28 | Please see our [Contributor guide](./CONTRIBUTING.md).
29 |
30 | ## Microsoft Open Source Code of Conduct
31 |
32 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
33 |
34 | Resources:
35 |
36 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
37 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
38 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
39 |
40 | With :heart: from Azure patterns & practices, [Azure Architecture Center](https://azure.com/architecture).
41 |
42 |
--------------------------------------------------------------------------------
/SECURITY.MD:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), and [Xamarin](https://github.com/xamarin).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/async-request-reply/src/AsyncOperationStatusChecker.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs;
2 | using Azure.Storage.Blobs.Specialized;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Azure.Functions.Worker;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace Asyncpattern
9 | {
10 | public class AsyncOperationStatusChecker(ILogger _logger)
11 | {
12 | [Function("AsyncOperationStatusChecker")]
13 | public async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "RequestStatus/{thisGUID}")] HttpRequest req,
14 | [BlobInput("data/{thisGUID}.blobdata", Connection = "DataStorage")] BlockBlobClient inputBlob, string thisGUID)
15 | {
16 | OnCompleteEnum OnComplete = Enum.Parse(req.Query["OnComplete"].FirstOrDefault() ?? "Redirect");
17 | OnPendingEnum OnPending = Enum.Parse(req.Query["OnPending"].FirstOrDefault() ?? "OK");
18 |
19 | _logger.LogInformation($"C# HTTP trigger function processed a request for status on {thisGUID} - OnComplete {OnComplete} - OnPending {OnPending}");
20 |
21 | // ** Check to see if the blob is present **
22 | if (await inputBlob.ExistsAsync())
23 | {
24 | // ** If it's present, depending on the value of the optional "OnComplete" parameter choose what to do. **
25 | return await OnCompleted(OnComplete, inputBlob, thisGUID);
26 | }
27 | else
28 | {
29 | // ** If it's NOT present, then we need to back off, so depending on the value of the optional "OnPending" parameter choose what to do. **
30 | string rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{thisGUID}";
31 |
32 | switch (OnPending)
33 | {
34 | case OnPendingEnum.OK:
35 | {
36 | // Return an HTTP 200 status code with the
37 | return new OkObjectResult(new { status = "In progress", Location = rqs });
38 | }
39 |
40 | case OnPendingEnum.Synchronous:
41 | {
42 | // Back off and retry. Time out if the backoff period hits one minute
43 | int backoff = 250;
44 |
45 | while (!await inputBlob.ExistsAsync() && backoff < 64000)
46 | {
47 | _logger.LogInformation($"Synchronous mode {thisGUID}.blob - retrying in {backoff} ms");
48 | backoff = backoff * 2;
49 | await Task.Delay(backoff);
50 | }
51 |
52 | if (await inputBlob.ExistsAsync())
53 | {
54 | _logger.LogInformation($"Synchronous Redirect mode {thisGUID}.blob - completed after {backoff} ms");
55 | return await OnCompleted(OnComplete, inputBlob, thisGUID);
56 | }
57 | else
58 | {
59 | _logger.LogInformation($"Synchronous mode {thisGUID}.blob - NOT FOUND after timeout {backoff} ms");
60 | return new NotFoundResult();
61 | }
62 | }
63 |
64 | default:
65 | {
66 | throw new InvalidOperationException($"Unexpected value: {OnPending}");
67 | }
68 | }
69 | }
70 | }
71 | private async Task OnCompleted(OnCompleteEnum OnComplete, BlockBlobClient inputBlob, string thisGUID)
72 | {
73 | switch (OnComplete)
74 | {
75 | case OnCompleteEnum.Redirect:
76 |
77 | {
78 | // The typical way to generate a SAS token in code requires the storage account key.
79 | //If you need to use “Managed Identity” to control access to your storage accounts in code, which is something I highly recommend wherever possible as this is a security best practice.
80 | // In this scenario, you won't have a storage account key, so you'll need to find another way to generate the shared access signatures.
81 | // To do that, we need to use an approach called user delegation SAS . By using a user delegation SAS, we can sign the signature with the Microsoft Entra ID credentials instead of the storage account key.
82 | BlobServiceClient blobServiceClient = inputBlob.GetParentBlobContainerClient().GetParentBlobServiceClient();
83 | var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(7));
84 | // Redirect to the SAS URI to blob storage
85 | return new RedirectResult(inputBlob.GenerateSASURI(userDelegationKey));
86 | }
87 |
88 | case OnCompleteEnum.Stream:
89 | {
90 | // Download the file and return it directly to the caller.
91 | // For larger files, use a stream to minimize RAM usage.
92 | return new OkObjectResult(await inputBlob.DownloadContentAsync());
93 | }
94 |
95 | default:
96 | {
97 | throw new InvalidOperationException($"Unexpected value: {OnComplete}");
98 | }
99 | }
100 | }
101 | }
102 |
103 | public enum OnCompleteEnum
104 | {
105 |
106 | Redirect,
107 | Stream
108 | }
109 |
110 | public enum OnPendingEnum
111 | {
112 |
113 | OK,
114 | Synchronous
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/async-request-reply/src/AsyncProcessingBackgroundWorker.cs:
--------------------------------------------------------------------------------
1 | using Azure.Messaging.ServiceBus;
2 | using Azure.Storage.Blobs;
3 | using Microsoft.Azure.Functions.Worker;
4 |
5 | namespace Asyncpattern
6 | {
7 | public class AsyncProcessingBackgroundWorker(BlobContainerClient _blobContainerClient)
8 | {
9 | [Function(nameof(AsyncProcessingBackgroundWorker))]
10 | public async Task Run([ServiceBusTrigger("outqueue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message)
11 | {
12 | //Perform an actual action against the blob data source for the async readers to be able to check against.
13 | // This is where your actual service worker processing will be performed
14 |
15 | var requestGuid = message.ApplicationProperties["RequestGUID"].ToString();
16 | string blobName = $"{requestGuid}.blobdata";
17 |
18 | await _blobContainerClient.CreateIfNotExistsAsync();
19 |
20 | var blobClient = _blobContainerClient.GetBlobClient(blobName);
21 | using (MemoryStream memoryStream = new MemoryStream())
22 | using (StreamWriter writer = new StreamWriter(memoryStream))
23 | {
24 | writer.Write(message.Body.ToString());
25 | writer.Flush();
26 | memoryStream.Position = 0;
27 |
28 | await blobClient.UploadAsync(memoryStream, overwrite: true);
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/async-request-reply/src/AsyncProcessingWorkAcceptor.cs:
--------------------------------------------------------------------------------
1 | using Azure.Messaging.ServiceBus;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.AspNetCore.Mvc;
4 | using Microsoft.Azure.Functions.Worker;
5 | using Microsoft.Extensions.Logging;
6 | using Newtonsoft.Json;
7 | using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute;
8 |
9 | namespace Asyncpattern
10 | {
11 | public class AsyncProcessingWorkAcceptor(ILogger _logger, ServiceBusClient _serviceBusClient)
12 | {
13 | [Function("AsyncProcessingWorkAcceptor")]
14 | public async Task RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, [FromBody] CustomerPOCO customer)
15 | {
16 | if (string.IsNullOrEmpty(customer.id) || string.IsNullOrEmpty(customer.customername))
17 | {
18 | return new BadRequestResult();
19 | }
20 |
21 | var reqid = Guid.NewGuid().ToString();
22 |
23 | var rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{reqid}";
24 |
25 | var messagePayload = JsonConvert.SerializeObject(customer);
26 | var message = new ServiceBusMessage(messagePayload);
27 | message.ApplicationProperties.Add("RequestGUID", reqid);
28 | message.ApplicationProperties.Add("RequestSubmittedAt", DateTime.Now);
29 | message.ApplicationProperties.Add("RequestStatusURL", rqs);
30 | var sender = _serviceBusClient.CreateSender("outqueue");
31 |
32 | await sender.SendMessageAsync(message);
33 | return new AcceptedResult(rqs, $"Request Accepted for Processing{Environment.NewLine}ProxyStatus: {rqs}");
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/async-request-reply/src/CloudBlockBlobExtensions.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs;
2 | using Azure.Storage.Blobs.Models;
3 | using Azure.Storage.Blobs.Specialized;
4 | using Azure.Storage.Sas;
5 |
6 | namespace Asyncpattern
7 | {
8 | public static class CloudBlockBlobExtensions
9 | {
10 | public static string GenerateSASURI(this BlockBlobClient inputBlob, UserDelegationKey userDelegationKey)
11 | {
12 | BlobServiceClient blobServiceClient = inputBlob.GetParentBlobContainerClient().GetParentBlobServiceClient();
13 |
14 | BlobSasBuilder blobSasBuilder = new BlobSasBuilder()
15 | {
16 | BlobContainerName = inputBlob.BlobContainerName,
17 | BlobName = inputBlob.Name,
18 | Resource = "b",
19 | StartsOn = DateTimeOffset.UtcNow,
20 | ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(10)
21 | };
22 | blobSasBuilder.SetPermissions(BlobSasPermissions.Read);
23 |
24 | var blobUriBuilder = new BlobUriBuilder(inputBlob.Uri)
25 | {
26 | Sas = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName)
27 | };
28 |
29 | //Generate the shared access signature on the blob, setting the constraints directly on the signature.
30 | Uri sasUri = blobUriBuilder.ToUri();
31 |
32 | //Return the URI string for the container, including the SAS token.
33 | return sasUri.ToString();
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/async-request-reply/src/CustomerPOCO.cs:
--------------------------------------------------------------------------------
1 | namespace Asyncpattern
2 | {
3 | public class CustomerPOCO
4 | {
5 | public string id { get; set; }
6 | public string customername { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/async-request-reply/src/Program.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Messaging.ServiceBus;
3 | using Azure.Storage.Blobs;
4 | using Microsoft.Azure.Functions.Worker;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Hosting;
7 |
8 | var host = new HostBuilder()
9 | .ConfigureFunctionsWebApplication()
10 | .ConfigureServices(services =>
11 | {
12 | services.AddApplicationInsightsTelemetryWorkerService();
13 | services.ConfigureFunctionsApplicationInsights();
14 |
15 | // blobServiceClient using Managed Identity
16 | var storageAccountUriString = Environment.GetEnvironmentVariable("DataStorage__blobServiceUri");
17 | var storageContainerEndpoint = storageAccountUriString + "/data";
18 |
19 | // Get a credential and create a client object for the blob container.
20 | BlobContainerClient containerClient = new BlobContainerClient(new Uri(storageContainerEndpoint),new DefaultAzureCredential());
21 | services.AddSingleton(containerClient);
22 |
23 | // ServiceBusClient using Managed Identity
24 | var fullyQualifiedNamespace = Environment.GetEnvironmentVariable("ServiceBusConnection__fullyQualifiedNamespace");
25 | var serviceBusClient = new ServiceBusClient(fullyQualifiedNamespace, new DefaultAzureCredential());
26 | services.AddSingleton(serviceBusClient);
27 | })
28 | .Build();
29 |
30 | host.Run();
31 |
--------------------------------------------------------------------------------
/async-request-reply/src/asyncpattern.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 | PreserveNewest
29 | Never
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/async-request-reply/src/asyncpattern.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34902.65
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Asyncpattern", "Asyncpattern.csproj", "{97782A87-CCF4-4C3B-A0B4-3424A446B50F}"
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 | {97782A87-CCF4-4C3B-A0B4-3424A446B50F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {97782A87-CCF4-4C3B-A0B4-3424A446B50F}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {97782A87-CCF4-4C3B-A0B4-3424A446B50F}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {97782A87-CCF4-4C3B-A0B4-3424A446B50F}.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 = {AD2DBADC-2860-4112-A8E3-AF7EF7B68E61}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/async-request-reply/src/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | },
9 | "enableLiveMetricsFilters": true
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/async-request-reply/src/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
6 | "ServiceBusConnection__fullyQualifiedNamespace": "",
7 | "DataStorage__blobServiceUri": ""
8 | }
9 | }
--------------------------------------------------------------------------------
/choreography/README.md:
--------------------------------------------------------------------------------
1 | # Choregraphy pattern example
2 |
3 | The [Choreography cloud design pattern](https://learn.microsoft.com/azure/architecture/patterns/choreography) does not have code associated with it. You'll find an example of this pattern in the Example section of that article.
4 |
5 | The scenario presented is how code running in an Azure Container Apps can use a message broker to exchange messages without central coordination to process a business transaction that requires ordered handling of unbounded sequences of related messages. The scenario uses a [Drone Delivery app](https://github.com/mspnp/microservices-reference-implementation).
6 |
7 | 
8 |
9 | ## Related documentation
10 |
11 | - [Choose between Azure messaging services](https://learn.microsoft.com/azure/service-bus-messaging/compare-messaging-services)
12 | - Use asynchronous distributed messaging through the [publisher-subscriber pattern](https://learn.microsoft.com/azure/architecture/patterns/publisher-subscriber)
13 | - Managing consistency issues implementing [Saga](https://learn.microsoft.com/azure/architecture/reference-architectures/saga/saga)
14 |
15 | ## Contributions
16 |
17 | Please see our [contributor guide](../CONTRIBUTING.md).
18 |
19 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact with any additional questions or comments.
20 |
21 | With :heart: from Microsoft Patterns & Practices, [Azure Architecture Center](https://aka.ms/architecture).
22 |
--------------------------------------------------------------------------------
/choreography/choreography-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/choreography/choreography-example.png
--------------------------------------------------------------------------------
/claim-check/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sh text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/claim-check/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | azcli-execution.log
3 | .vscode/
4 | .vs/
5 | local.settings.json
6 | App.config
7 | obj/
--------------------------------------------------------------------------------
/claim-check/README.md:
--------------------------------------------------------------------------------
1 | # Claim-check cloud pattern examples
2 |
3 | This folder contains several examples illustrating the use of the [Claim-Check design pattern](https://learn.microsoft.com/azure/architecture/patterns/claim-check) with various Azure services.
4 |
5 | > All the examples in this folder use [`DefaultAzureCredential`](https://learn.microsoft.com/dotnet/azure/sdk/authentication/#defaultazurecredential) to authenticate when accessing Azure resources. The Bicep script on each sample assigns the necessary RBAC (Role-Based Access Control) permissions to an account principal, which is provided as a parameter, during deployment.
6 |
7 | ## [Automatic claim check token generation, Azure Queue Storage as messaging system](./code-samples/sample-1/)
8 |
9 | > Technologies used: Azure Blob Storage, Azure Event Grid, Azure Functions, Azure Blob Queue, .NET
10 |
11 | This example uses Azure Blob Storage to store the payload, but any service that supports [Event Grid](https://azure.microsoft.com/services/event-grid/) integration can be used. A client application uploads the payload to Azure Blob Store and Event Grid automatically generates an event, with a reference to the blob, that can be used as a claim check token. The event is forwarded to an Azure Queue Storage queue from where it can be retrieved by the consumer sample app.
12 |
13 | This approach allows a client application to poll the queue, get the message, extract the reference to the payload blob, and use it to download the payload directly from Azure Blob Storage. Azure Functions can also consume the Event Grid message directly.
14 |
15 | ## [Automatic claim check token generation, Event Hubs as messaging system](./code-samples/sample-2/)
16 |
17 | > Technologies used: Azure Blob Storage, Azure Event Grid, Azure Event Hubs, .NET
18 |
19 | Like in the previous example, a reference message is automatically generated by Event Grid when a payload is uploaded to Azure Blob Storage. In this sample, Event Hubs is used as the messaging system.
20 |
21 | ## [Automatic claim check token generation, Service Bus as messaging system](./code-samples/sample-3/)
22 |
23 | > Technologies used: Azure Blob Storage, Azure Event Grid, Azure Service Bus, Azure Functions, .NET
24 |
25 | Like in the previous two examples, a reference message is automatically generated by Event Grid when a payload is uploaded to Azure Blob Storage. In this sample, Service Bus is used as the messaging system.
26 |
27 | ## [Manual claim check token generation, Kafka API-enabled Event Hubs as messaging system](./code-samples/sample-4/)
28 |
29 | > Technologies used: Azure Blob Storage, Azure Event Hubs with Kafka Api, Azure Functions, .NET
30 |
31 | In this example the client application uploads the payload to Azure Blob Storage and manually generates the message to be used as a claim check token, which is sent via Event Hubs.
32 |
33 | This example uses [Event Hubs with Kafka enabled](https://learn.microsoft.com/azure/event-hubs/event-hubs-create-kafka-enabled), to demonstrate the ease of using other Azure services like Azure Blob Storage, Azure functions etc. with a different messaging protocol like Kafka.
34 |
35 | ## Contributions
36 |
37 | Please see our [Contributor guide](../CONTRIBUTING.md).
38 |
39 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact with any additional questions or comments.
40 |
41 | With :heart: from Azure patterns & practices, [Azure Architecture Center](https://azure.com/architecture).
--------------------------------------------------------------------------------
/claim-check/code-samples/all-samples.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34723.18
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionConsumer3", "sample-3\FunctionConsumer3\FunctionConsumer3.csproj", "{67AC55F4-7647-466F-8A3E-83E2878347AF}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample-3", "sample-3", "{81F731CC-6F42-4816-BDD4-0FA541895909}"
9 | ProjectSection(SolutionItems) = preProject
10 | sample-3\readme.md = sample-3\readme.md
11 | EndProjectSection
12 | EndProject
13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample-1", "sample-1", "{F1A6CB79-1241-4B93-A011-4A8051228840}"
14 | ProjectSection(SolutionItems) = preProject
15 | sample-1\readme.md = sample-1\readme.md
16 | EndProjectSection
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample-2", "sample-2", "{873C40EF-AF5B-4B46-8BDE-DEA007079812}"
19 | ProjectSection(SolutionItems) = preProject
20 | sample-2\readme.md = sample-2\readme.md
21 | EndProjectSection
22 | EndProject
23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample-4", "sample-4", "{EA1E4C36-059D-41E8-89DF-389985CF5FBD}"
24 | ProjectSection(SolutionItems) = preProject
25 | sample-4\readme.md = sample-4\readme.md
26 | EndProjectSection
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionConsumer1", "sample-1\FunctionConsumer1\FunctionConsumer1.csproj", "{6022107B-519D-4B28-937E-DFC348933E3F}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionConsumer4", "sample-4\FunctionConsumer4\FunctionConsumer4.csproj", "{5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}"
31 | EndProject
32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientProducer4", "sample-4\ClientProducer4\ClientProducer4.csproj", "{CA4D4A68-55B4-496F-B976-D3464076FC62}"
33 | EndProject
34 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4C721EF7-6533-46D2-9BA5-A3639D38B715}"
35 | ProjectSection(SolutionItems) = preProject
36 | ..\README.md = ..\README.md
37 | EndProjectSection
38 | EndProject
39 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientConsumer2", "sample-2\ClientConsumer2\ClientConsumer2.csproj", "{8FD964BC-7351-443C-AC9D-573F821FCF2D}"
40 | EndProject
41 | Global
42 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
43 | Debug|Any CPU = Debug|Any CPU
44 | Release|Any CPU = Release|Any CPU
45 | EndGlobalSection
46 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
47 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {6022107B-519D-4B28-937E-DFC348933E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
52 | {6022107B-519D-4B28-937E-DFC348933E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
53 | {6022107B-519D-4B28-937E-DFC348933E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {6022107B-519D-4B28-937E-DFC348933E3F}.Release|Any CPU.Build.0 = Release|Any CPU
55 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
57 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
58 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Release|Any CPU.Build.0 = Release|Any CPU
59 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.Release|Any CPU.ActiveCfg = Release|Any CPU
62 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.Release|Any CPU.Build.0 = Release|Any CPU
63 | {8FD964BC-7351-443C-AC9D-573F821FCF2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
64 | {8FD964BC-7351-443C-AC9D-573F821FCF2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
65 | {8FD964BC-7351-443C-AC9D-573F821FCF2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
66 | {8FD964BC-7351-443C-AC9D-573F821FCF2D}.Release|Any CPU.Build.0 = Release|Any CPU
67 | EndGlobalSection
68 | GlobalSection(SolutionProperties) = preSolution
69 | HideSolutionNode = FALSE
70 | EndGlobalSection
71 | GlobalSection(NestedProjects) = preSolution
72 | {67AC55F4-7647-466F-8A3E-83E2878347AF} = {81F731CC-6F42-4816-BDD4-0FA541895909}
73 | {6022107B-519D-4B28-937E-DFC348933E3F} = {F1A6CB79-1241-4B93-A011-4A8051228840}
74 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA} = {EA1E4C36-059D-41E8-89DF-389985CF5FBD}
75 | {CA4D4A68-55B4-496F-B976-D3464076FC62} = {EA1E4C36-059D-41E8-89DF-389985CF5FBD}
76 | {8FD964BC-7351-443C-AC9D-573F821FCF2D} = {873C40EF-AF5B-4B46-8BDE-DEA007079812}
77 | EndGlobalSection
78 | GlobalSection(ExtensibilityGlobals) = postSolution
79 | SolutionGuid = {1B1FEA88-54B9-477B-927B-F3542A3AA1B6}
80 | EndGlobalSection
81 | EndGlobal
82 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/FunctionConsumer1/FunctionConsumer1.cs:
--------------------------------------------------------------------------------
1 |
2 | using Azure.Storage.Queues.Models;
3 | using Microsoft.Azure.Functions.Worker;
4 | using Microsoft.Extensions.Logging;
5 | using System.Text;
6 | using System.Text.Json;
7 |
8 | namespace Pnp.Samples.ClaimCheckPattern
9 | {
10 | ///
11 | /// Sample function illustrating the processing of Storage Queue messages containing claim-check style events forwarded by Event Grid
12 | ///
13 | public class FunctionConsumer1(ILoggerFactory loggerFactory, ISampleBlobDataMover sampleBlobDataMover)
14 | {
15 | readonly ILogger _logger = loggerFactory.CreateLogger();
16 | readonly ISampleBlobDataMover _downloader = sampleBlobDataMover;
17 |
18 | ///
19 | /// Function that processes Storage Queue messages auto-generated by Event Grid when files are uploaded to a storage blob container.
20 | ///
21 | [Function(nameof(FunctionConsumer1))]
22 | public async Task RunAsync(
23 | [QueueTrigger("%StorageQueue%", Connection = "StorageQueueConnectionString")] QueueMessage receivedMessage
24 | )
25 | {
26 | _logger.LogInformation("Message Queue message received: {MessageId}", receivedMessage.MessageId);
27 |
28 | var messageText = Encoding.UTF8.GetString(receivedMessage.Body);
29 | var jsonMessage = JsonDocument.Parse(messageText).RootElement;
30 | var payloadUri = new Uri(jsonMessage.GetProperty("data").GetProperty("url").GetString()!);
31 | _logger.LogInformation("Message received. Payload Url: {Uri}", payloadUri);
32 |
33 | // download the payload
34 | var payload = await _downloader.DownloadAsync(payloadUri);
35 | _logger.LogInformation("Payload content (Url {Uri)}\n {Payload}", payloadUri, payload);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/FunctionConsumer1/FunctionConsumer1.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 | PreserveNewest
29 |
30 |
31 | Never
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/FunctionConsumer1/ISampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Pnp.Samples.ClaimCheckPattern
3 | {
4 | public interface ISampleBlobDataMover
5 | {
6 | Task DownloadAsync(Uri blobUri, bool deleteAfter = true);
7 | Task UploadAsync(string blobUri, string containerName, string content);
8 | }
9 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/FunctionConsumer1/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Functions.Worker;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 | using Pnp.Samples.ClaimCheckPattern;
5 |
6 | var host = new HostBuilder()
7 | .ConfigureFunctionsWebApplication()
8 | .ConfigureServices(services =>
9 | {
10 | services.AddApplicationInsightsTelemetryWorkerService();
11 | services.ConfigureFunctionsApplicationInsights();
12 | services.AddScoped();
13 | })
14 | .Build();
15 |
16 | host.Run();
17 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/FunctionConsumer1/SampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Storage.Blobs;
3 | using Azure.Storage.Blobs.Specialized;
4 | using Microsoft.Extensions.Logging;
5 | using System.Text;
6 |
7 | namespace Pnp.Samples.ClaimCheckPattern
8 | {
9 | ///
10 | /// A sample Storage Blob uploader/downloader. Uses Azure Entra ID for authentication.
11 | ///
12 | public class SampleBlobDataMover(ILoggerFactory loggerFactory) : ISampleBlobDataMover
13 | {
14 | readonly ILogger _logger = loggerFactory.CreateLogger();
15 |
16 | ///
17 | /// Downloads the referenced payload from Azure Blobs and returns the content.
18 | /// Optionally deletes the blob after download
19 | ///
20 | public async Task DownloadAsync(Uri blobUri, bool deleteAfter = true)
21 | {
22 | var blobClient = new BlockBlobClient(blobUri, new DefaultAzureCredential());
23 | if (!await blobClient.ExistsAsync())
24 | {
25 | _logger.LogError("Blob {BlobUri} does not exist.", blobUri.AbsoluteUri);
26 | return string.Empty;
27 | }
28 | var response = await blobClient.DownloadContentAsync();
29 | if (deleteAfter)
30 | {
31 | await blobClient.DeleteIfExistsAsync();
32 | }
33 | return Encoding.UTF8.GetString(response.Value.Content);
34 | }
35 |
36 | ///
37 | /// Upload sample text content to Azure Blob Storage and returns Url to the newly created blob
38 | ///
39 | public async Task UploadAsync(string blobUri, string containerName, string content)
40 | {
41 | var serviceClient = new BlobServiceClient(new Uri(blobUri), new DefaultAzureCredential());
42 |
43 | var containerClient = serviceClient.GetBlobContainerClient(containerName);
44 | await containerClient.CreateIfNotExistsAsync();
45 |
46 | var blobClient = containerClient.GetBlobClient(Guid.NewGuid().ToString());
47 | await blobClient.UploadAsync(new BinaryData(content), overwrite: true);
48 | _logger.LogInformation("Uploaded content to {Uri}", blobClient.Uri.AbsoluteUri);
49 | return blobClient.Uri.AbsoluteUri;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/FunctionConsumer1/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/FunctionConsumer1/local.settings.json.template:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
6 |
7 | "AzureFunctionsJobHost__logging__applicationInsights__samplingSettings__isEnabled": "false",
8 |
9 | "StorageQueueConnectionString:serviceUri": "https://{STORAGE_ACCOUNT_NAME}.queue.core.windows.net",
10 | "StorageQueue": "claimcheckqueue"
11 | }
12 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/bicep/main.bicep:
--------------------------------------------------------------------------------
1 | targetScope = 'resourceGroup'
2 |
3 | @minLength(5)
4 | @description('Location of the resources. Defaults to resource group location.')
5 | param location string = resourceGroup().location
6 |
7 | @minLength(36)
8 | @description('The guid of the principal running the valet key generation code. In Azure this would be replaced with the managed identity of the Azure Function, when running locally it will be your user.')
9 | param principalId string
10 |
11 | @minLength(3)
12 | @maxLength(5)
13 | @description('The globally unique prefix naming resources.')
14 | param namePrefix string
15 |
16 | /*** EXISTING RESOURCES ***/
17 |
18 | @description('Built-in Azure RBAC role that is applied to a Storage account to grant "Storage Blob Data Contributor" privileges.')
19 | resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
20 | name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
21 | scope: subscription()
22 | }
23 |
24 | @description('Built-in Azure RBAC role that is applied to a Storage account to grant "Storage Queue Data Contributor" privileges.')
25 | resource storageQueueDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
26 | name: '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
27 | scope: subscription()
28 | }
29 |
30 | /*** NEW RESOURCES ***/
31 |
32 | @description('The Azure Storage account which will be where authorized clients upload large blobs to. The Azure Function will hand out scoped, time-limited SaS tokens for this blobs in this account.')
33 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
34 | name: 'st${namePrefix}cc'
35 | location: location
36 | sku: {
37 | name: 'Standard_LRS'
38 | }
39 | kind: 'StorageV2'
40 | properties: {
41 | accessTier: 'Hot'
42 | allowBlobPublicAccess: false
43 | allowCrossTenantReplication: false
44 | allowSharedKeyAccess: false // Only allowed access by Managed Identity
45 | isLocalUserEnabled: false
46 | isHnsEnabled: false
47 | isNfsV3Enabled: false
48 | isSftpEnabled: false
49 | largeFileSharesState: 'Disabled'
50 | minimumTlsVersion: 'TLS1_2'
51 | publicNetworkAccess: 'Enabled' // Enabled to test the sample code from a local machine
52 | supportsHttpsTrafficOnly: true
53 | defaultToOAuthAuthentication: true
54 | allowedCopyScope: 'PrivateLink'
55 | networkAcls: {
56 | defaultAction: 'Allow' // For this sample, public Internet access is expected
57 | bypass: 'None'
58 | virtualNetworkRules: []
59 | ipRules: []
60 | }
61 | }
62 |
63 | resource blobContainers 'blobServices' = {
64 | name: 'default'
65 |
66 | @description('The blob container to serve as the large payloads data store.')
67 | resource payloadsContainer 'containers' = {
68 | name: 'payloads'
69 | }
70 | }
71 |
72 | resource QueueServices 'queueServices' = {
73 | name: 'default'
74 |
75 | @description('The queue to serve as the message queue for the sample.')
76 | resource claimcheckqueue 'queues' = {
77 | name: 'claimcheckqueue'
78 | }
79 | }
80 | }
81 |
82 | @description('Set permissions to give the user principal access to Storage Blob from the sample applications')
83 | resource blobContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
84 | name: guid(storageAccount::blobContainers::payloadsContainer.id, storageBlobDataContributorRole.id, principalId)
85 | scope: storageAccount::blobContainers::payloadsContainer
86 | properties: {
87 | principalId: principalId
88 | roleDefinitionId: storageBlobDataContributorRole.id
89 | principalType: 'User' // 'ServicePrincipal' if this was a managed identity
90 | description: 'Allows this Microsoft Entra principal to access blobs in this storage blob container.'
91 | }
92 | }
93 |
94 | @description('Set permissions to give the user principal access to Storage Queues from the sample applications')
95 | resource queueContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
96 | name: guid(storageAccount::QueueServices::claimcheckqueue.id, storageQueueDataContributorRole.id, principalId)
97 | scope: storageAccount::QueueServices::claimcheckqueue
98 | properties: {
99 | principalId: principalId
100 | roleDefinitionId: storageQueueDataContributorRole.id
101 | principalType: 'User' // 'ServicePrincipal' if this was a managed identity
102 | description: 'Allows this Microsoft Entra principal to access messages in this storage queue.'
103 | }
104 | }
105 |
106 | @description('Event Grid system topic to subscribe to blob created events.')
107 | resource eventGridStorageBlobTopic 'Microsoft.EventGrid/systemTopics@2023-12-15-preview' = {
108 | name: '${storageAccount.name}${guid(namePrefix, 'storage')}'
109 | location: location
110 | identity: {
111 | type: 'SystemAssigned'
112 | }
113 | properties: {
114 | source: storageAccount.id
115 | topicType: 'microsoft.storage.storageaccounts'
116 | }
117 | }
118 |
119 | @description('Event Grid system topic subscription to queue blob created events.')
120 | resource eventGridBlobCreatedQueueSubscription 'Microsoft.EventGrid/systemTopics/eventSubscriptions@2023-12-15-preview' = {
121 | parent: eventGridStorageBlobTopic
122 | name: 'storagequeue'
123 | properties: {
124 | destination: {
125 | properties: {
126 | resourceId: storageAccount.id
127 | queueName: 'claimcheckqueue'
128 | }
129 | endpointType: 'StorageQueue'
130 | }
131 | filter: {
132 | includedEventTypes: [
133 | 'Microsoft.Storage.BlobCreated'
134 | ]
135 | }
136 | eventDeliverySchema: 'EventGridSchema'
137 | }
138 | }
139 |
140 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/images/sample-1-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/claim-check/code-samples/sample-1/images/sample-1-diagram.png
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/readme.md:
--------------------------------------------------------------------------------
1 | # Sample 1: Automatic claim check token generation with Event Grid, Azure Queue Storage as messaging system
2 |
3 | ## Technologies used: Azure Blob Storage, Azure Event Grid, Azure Functions, Azure Queue Storage, .NET 9.0
4 |
5 | This example uses Azure Blob Storage to store the payload, but any service that supports [Event Grid](https://azure.microsoft.com/services/event-grid/) integration can be used. A client application uploads the payload to Azure Blob Store and Event Grid automatically generates an event, with a reference to the blob, that can be used as a claim check token. The event is forwarded to an Azure Queue Storage queue from where it can be retrieved by the consumer sample app.
6 |
7 | This approach allows a client application to poll the queue, get the message, extract the reference to the payload blob, and use it to download the payload directly from Azure Blob Storage. Azure Functions can also consume the Event Grid message directly.
8 |
9 | > This example uses [`DefaultAzureCredential`](https://learn.microsoft.com/dotnet/azure/sdk/authentication/#defaultazurecredential) for authentication while accessing Azure resources. the user principal must be provided as a parameter to the included Bicep script. The Bicep script is responsible for assigning the necessary RBAC (Role-Based Access Control) permissions for accessing the various Azure resources. While the principal can be the account associated with the interactive user, there are alternative [configurations](https://learn.microsoft.com/dotnet/azure/sdk/authentication/?tabs=command-line#exploring-the-sequence-of-defaultazurecredential-authentication-methods) available.
10 |
11 | 
12 |
13 | 1. The payload uploaded to Azure Blob Storage.
14 | 1. A Blob Created event is generated by Event Grid.
15 | 1. Event Grid forwards the event, containing a reference to the payload blob, to Azure Queue Storage.
16 | 1. An Azure Function connects to the queue and reads messages as they arrive.
17 | 1. The Function extracts the reference to the payload blob from the message and downloads the blob directly from storage.
18 |
19 | ## :rocket: Deployment guide
20 |
21 | Install the prerequisites and follow the steps to deploy and run the examples.
22 |
23 | ### Prerequisites
24 |
25 | - Permission to create a new resource group and resources in an [Azure subscription](https://azure.com/free)
26 | - Unix-like shell. Also available in:
27 | - [Azure Cloud Shell](https://shell.azure.com/)
28 | - [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/windows/wsl/install)
29 | - [Git](https://git-scm.com/downloads)
30 | - [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)
31 | - [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools)
32 | - [Azurite](/azure/storage/common/storage-use-azurite)
33 | - [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli)
34 | - Optionally, an IDE, like [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Visual Studio Code](https://code.visualstudio.com/).
35 |
36 | ### Steps
37 |
38 | 1. Clone this repository to your workstation and navigate to the working directory.
39 |
40 | ```shell
41 | git clone https://github.com/mspnp/cloud-design-patterns
42 | cd cloud-design-patterns/claim-check/code-samples/sample-1
43 | ```
44 |
45 | 1. Log into Azure and create an empty resource group.
46 |
47 | ```azurecli
48 | az login
49 | az account set -s
50 |
51 | NAME_PREFIX=
52 | LOCATION=eastus2
53 | RESOURCE_GROUP_NAME="rg-${NAME_PREFIX}-${LOCATION}"
54 |
55 | az group create -n "${RESOURCE_GROUP_NAME}" -l ${LOCATION}
56 | ```
57 |
58 | 1. Deploy the supporting Azure resources.
59 |
60 | ```azurecli
61 | CURRENT_USER_OBJECT_ID=$(az ad signed-in-user show -o tsv --query id)
62 |
63 | # This could take a few minutes
64 | az deployment group create -n deploy-claim-check -f bicep/main.bicep -g "${RESOURCE_GROUP_NAME}" -p namePrefix=$NAME_PREFIX principalId=$CURRENT_USER_OBJECT_ID
65 | ```
66 |
67 | 1. Configure the sample consumer to use the created Azure resources.
68 |
69 | ```shell
70 | sed "s/{STORAGE_ACCOUNT_NAME}/st${NAME_PREFIX}cc/g" FunctionConsumer1/local.settings.json.template > FunctionConsumer1/local.settings.json
71 | ```
72 |
73 | 1. [Run Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite#run-azurite) blob storage emulation service.
74 |
75 | > The local storage emulator is required as an Azure Storage account is a required "backing resource" for Azure Functions.
76 |
77 | 1. Launch the consumer sample application to receive and process claim check messages from Azure Queue Storage.
78 |
79 | The message consumer sample application for this scenario is implemented as an Azure Function, showcasing the serveless approach. Run the sample application to connect to the the queue and process messages as they arrive.
80 |
81 | ```bash
82 | cd sample-1\FunctionConsumer1
83 | func start
84 | ```
85 |
86 | > Please note: For demo purposes, the sample consumer application will write the payload content to the the screen. Keep that in mind before you try sending really large payloads.
87 |
88 | ### :checkered_flag: Try it out
89 |
90 | To generate a claim check message you just have to drop a file in the created Azure Storage account. You can use Azure **Storage browser** to do that. [Refer this to know how to upload blobs to a container using Storage Explorer](https://learn.microsoft.com/azure/storage/blobs/quickstart-storage-explorer#upload-blobs-to-the-container).
91 |
92 | ### :broom: Clean up
93 |
94 | Remove the resource group that you created when you are done with this sample.
95 |
96 | ```azurecli
97 | az group delete -n "${RESOURCE_GROUP_NAME}" -y
98 | ```
99 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-1/sample-1.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34723.18
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionConsumer1", "FunctionConsumer1\FunctionConsumer1.csproj", "{6022107B-519D-4B28-937E-DFC348933E3F}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5260176F-A29C-479D-AEB7-876E233403EE}"
9 | ProjectSection(SolutionItems) = preProject
10 | readme.md = readme.md
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 | {6022107B-519D-4B28-937E-DFC348933E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {6022107B-519D-4B28-937E-DFC348933E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {6022107B-519D-4B28-937E-DFC348933E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {6022107B-519D-4B28-937E-DFC348933E3F}.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 = {1B1FEA88-54B9-477B-927B-F3542A3AA1B6}
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/ClientConsumer2/ClientConsumer2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | enable
7 | enable
8 | Pnp.Samples.ClaimCheckPattern
9 | latest
10 |
11 |
12 |
13 |
14 | PreserveNewest
15 |
16 |
17 |
18 |
19 | Never
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/ClientConsumer2/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Extensions.Logging;
3 | using Pnp.Samples.ClaimCheckPattern;
4 |
5 | var configuration = new ConfigurationBuilder()
6 | .AddJsonFile("appsettings.json", false, false).Build();
7 |
8 | using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
9 |
10 | Console.WriteLine("Begin sample 2 for Claim Check pattern");
11 | Console.WriteLine("Uses the Event Hubs EventProcessorClient to receive claim-check messages, auto-generated with event-grid when a blob is uploaded to Storage Blob");
12 | Console.WriteLine("Initializing...");
13 | Console.WriteLine();
14 |
15 | configuration.ThrowIfMissingSettings([
16 | "AppSettings:EventHubsFullyQualifiedNamespace",
17 | "AppSettings:EventHubName",
18 | "AppSettings:EventProcessorStorageBlobUrl",
19 | "AppSettings:EventProcessorStorageContainer"
20 | ]);
21 |
22 | Console.WriteLine("Receiving messages...");
23 | var eventHubsConsumer = new EventHubsConsumer(configuration, loggerFactory);
24 | var cts = new CancellationTokenSource();
25 | var task = eventHubsConsumer.StartAsync(cts.Token);
26 |
27 | Console.WriteLine("Press any key to terminate the application...");
28 | Console.ReadKey(true);
29 | cts.Cancel();
30 |
31 | Console.WriteLine("Exiting...");
32 | await task;
33 | await eventHubsConsumer.StopAsync();
34 | Console.WriteLine("Done.");
35 |
36 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/ClientConsumer2/SampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Storage.Blobs;
3 | using Azure.Storage.Blobs.Specialized;
4 | using Microsoft.Extensions.Logging;
5 | using System.Text;
6 |
7 | namespace Pnp.Samples.ClaimCheckPattern
8 | {
9 | ///
10 | /// A sample Storage Blob uploader/downloader. Uses Azure Entra ID for authentication.
11 | ///
12 | public class SampleBlobDataMover(ILoggerFactory loggerFactory)
13 | {
14 | readonly ILogger _logger = loggerFactory.CreateLogger();
15 |
16 | ///
17 | /// Downloads the referenced payload from Azure Blobs and returns the content.
18 | /// Optionally deletes the blob after download
19 | ///
20 | public async Task DownloadAsync(Uri blobUri, bool deleteAfter = true)
21 | {
22 | var blobClient = new BlockBlobClient(blobUri, new DefaultAzureCredential());
23 | if (!await blobClient.ExistsAsync())
24 | {
25 | _logger.LogError("Blob {BlobUri} does not exist.", blobUri.AbsoluteUri);
26 | return string.Empty;
27 | }
28 | var response = await blobClient.DownloadContentAsync();
29 | if (deleteAfter)
30 | {
31 | await blobClient.DeleteIfExistsAsync();
32 | }
33 | return Encoding.UTF8.GetString(response.Value.Content);
34 | }
35 |
36 | ///
37 | /// Upload sample text content to Azure Blob Storage and returns Url to the newly created blob
38 | ///
39 | public async Task UploadAsync(string blobUri, string containerName, string content)
40 | {
41 | var serviceClient = new BlobServiceClient(new Uri(blobUri), new DefaultAzureCredential());
42 |
43 | var containerClient = serviceClient.GetBlobContainerClient(containerName);
44 | await containerClient.CreateIfNotExistsAsync();
45 |
46 | var blobClient = containerClient.GetBlobClient(Guid.NewGuid().ToString());
47 | await blobClient.UploadAsync(new BinaryData(content), overwrite: true);
48 | _logger.LogInformation("Uploaded content to {Uri}", blobClient.Uri.AbsoluteUri);
49 | return blobClient.Uri.AbsoluteUri;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/ClientConsumer2/SampleSettingsValidator.cs:
--------------------------------------------------------------------------------
1 | namespace Microsoft.Extensions.Configuration;
2 |
3 | public static class SampleSettingsValidator
4 | {
5 | ///
6 | /// Ensure the required app settings are present
7 | ///
8 | ///
9 | ///
10 | public static void ThrowIfMissingSettings(this IConfiguration configuration, List settings)
11 | {
12 | if (settings.Any(option => string.IsNullOrWhiteSpace(configuration.GetSection(option).Value)))
13 | {
14 | throw new ApplicationException($"Configure options {string.Join(", ", settings)} in appsettings.json and run again.");
15 | }
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/ClientConsumer2/appsettings.json.template:
--------------------------------------------------------------------------------
1 | {
2 | "AppSettings": {
3 | "EventHubsFullyQualifiedNamespace": "{EVENT_HUBS_NAMESPACE}.servicebus.windows.net",
4 | "EventHubName": "evh-claimcheck",
5 |
6 | "EventProcessorStorageBlobUrl": "https://{EVENT_PROCESSOR_STORAGE_ACCOUNT_NAME}.blob.core.windows.net",
7 | "EventProcessorStorageContainer": "eventprocessor"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/images/sample-2-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/claim-check/code-samples/sample-2/images/sample-2-diagram.png
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/readme.md:
--------------------------------------------------------------------------------
1 | # Sample 2: Automatic claim check token generation with Event Grid, Event Hubs as messaging system
2 |
3 | ## Technologies used: Azure Blob Storage, Azure Event Grid, Azure Event Hub, .NET 9.0
4 |
5 | Like in Sample 1, a reference message is automatically generated by [Event Grid](https://azure.microsoft.com/services/event-grid/) when the payload is uploaded to Azure Blob Storage. In this sample, [Event Hubs](https://learn.microsoft.com/azure/event-hubs) is used as the messaging system.
6 |
7 | Using Event Hubs enable integration with a existing Event Hub-based solutions and to take advantage of its unique capabilities, like schema registry support and Apache Kafka compatibility. And Event Hub can be configured to [automatically archive](https://learn.microsoft.com/azure/event-hubs/event-hubs-capture-overview) received messages to make then available as an Avro files for integration with tools like Apache Spark, Stream Analytics, and Azure Data Factory.
8 |
9 | > This example uses [`DefaultAzureCredential`](https://learn.microsoft.com/dotnet/azure/sdk/authentication/#defaultazurecredential) for authentication while accessing Azure resources. the user principal must be provided as a parameter to the included Bicep script. The Bicep script is responsible for assigning the necessary RBAC (Role-Based Access Control) permissions for accessing the various Azure resources. While the principal can be the account associated with the interactive user, there are alternative [configurations](https://learn.microsoft.com/dotnet/azure/sdk/authentication/?tabs=command-line#exploring-the-sequence-of-defaultazurecredential-authentication-methods) available.
10 |
11 | 
12 |
13 | 1. The payload uploaded to Azure Blob Storage.
14 | 1. A Blob Created event is generated by Event Grid.
15 | 1. Event Grid forwards the event, containing a reference to the payload blob, to an Azure Event Hub.
16 | 1. An net console program connects to the Event Hub and reads messages as they arrive.
17 | 1. The net console program extracts the reference to the payload blob from the message and downloads the blob directly from storage.
18 |
19 | ## :rocket: Deployment guide
20 |
21 | Install the prerequisites and follow the steps to deploy and run the examples.
22 |
23 | ### Prerequisites
24 |
25 | - Permission to create a new resource group and resources in an [Azure subscription](https://azure.com/free)
26 | - Unix-like shell. Also available in:
27 | - [Azure Cloud Shell](https://shell.azure.com/)
28 | - [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/windows/wsl/install)
29 | - [Git](https://git-scm.com/downloads)
30 | - [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)
31 | - [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli)
32 | - Optionally, an IDE, like [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Visual Studio Code](https://code.visualstudio.com/).
33 |
34 | ### Steps
35 |
36 | 1. Clone this repository to your workstation and navigate to the working directory.
37 |
38 | ```bash
39 | git clone https://github.com/mspnp/cloud-design-patterns
40 | cd cloud-design-patterns/claim-check/code-samples/sample-2
41 | ```
42 |
43 | 2. Log into Azure and create an empty resource group.
44 |
45 | ```bash
46 | az login
47 | az account set -s
48 |
49 | NAME_PREFIX=""
50 | LOCATION=eastus2
51 | RESOURCE_GROUP_NAME="rg-${NAME_PREFIX}-${LOCATION}"
52 |
53 | az group create -n "${RESOURCE_GROUP_NAME}" -l ${LOCATION}
54 | ```
55 |
56 | 3. Deploy the supporting Azure resources.
57 |
58 | ```bash
59 | CURRENT_USER_OBJECT_ID=$(az ad signed-in-user show -o tsv --query id)
60 |
61 | # This could take a few minutes
62 | az deployment group create -n deploy-claim-check -f bicep/main.bicep -g "${RESOURCE_GROUP_NAME}" -p namePrefix=$NAME_PREFIX principalId=$CURRENT_USER_OBJECT_ID
63 | ```
64 |
65 | 4. Configure the sample consumer to use the created Azure resources.
66 |
67 | ```bash
68 | sed "s/{EVENT_HUBS_NAMESPACE}/evhns-${NAME_PREFIX}/g" ClientConsumer2/appsettings.json.template >ClientConsumer2/appsettings.json
69 | sed -i "s/{EVENT_PROCESSOR_STORAGE_ACCOUNT_NAME}/st${NAME_PREFIX}ehub/g" ClientConsumer2/appsettings.json
70 | ```
71 |
72 | 5. Launch the consumer sample application to receive and process claim check messages from Event Hubs.
73 |
74 | The message consumer sample application for this scenario is implemented as a Command Line Interface (CLI) application. Run the sample application to connect to Event Hubs and process messages as they arrive.
75 |
76 | ```bash
77 | dotnet run --project ClientConsumer2
78 | ```
79 |
80 | > Please note: For demo purposes, the sample application will write the payload content to the the screen. Keep that in mind before you try sending really large payloads.
81 |
82 | ### :checkered_flag: Try it out
83 |
84 | To generate a claim check message you just have to drop a file in the created Azure Storage account. You can use Azure **Storage browser** to do that. [Refer this to know how to upload blobs to a container using Storage Explorer](https://learn.microsoft.com/azure/storage/blobs/quickstart-storage-explorer#upload-blobs-to-the-container).
85 |
86 | ### :broom: Clean up
87 |
88 | Remove the resource group that you created when you are done with this sample.
89 |
90 | ```bash
91 | az group delete -n "${RESOURCE_GROUP_NAME}" -y
92 | ```
93 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-2/sample-2.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34723.18
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientConsumer2", "ClientConsumer2\ClientConsumer2.csproj", "{325326E4-0909-4B3E-97E6-283900C84DF6}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC97FAC0-E4D1-43FF-869F-FB079BA5CB68}"
9 | ProjectSection(SolutionItems) = preProject
10 | readme.md = readme.md
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 | {325326E4-0909-4B3E-97E6-283900C84DF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {325326E4-0909-4B3E-97E6-283900C84DF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {325326E4-0909-4B3E-97E6-283900C84DF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {325326E4-0909-4B3E-97E6-283900C84DF6}.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 = {1B1FEA88-54B9-477B-927B-F3542A3AA1B6}
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/FunctionConsumer3/FunctionConsumer3.cs:
--------------------------------------------------------------------------------
1 | using Azure.Messaging.ServiceBus;
2 | using Microsoft.Azure.Functions.Worker;
3 | using Microsoft.Extensions.Logging;
4 | using System.Text;
5 | using System.Text.Json;
6 |
7 | namespace Pnp.Samples.ClaimCheckPattern
8 | {
9 | ///
10 | /// Sample function illustrating the processing of Service Bus messages containing claim-check style events forwarded by Event Grid
11 | ///
12 | public class FunctionConsumer3(ILoggerFactory loggerFactory, ISampleBlobDataMover sampleBlobDataMover)
13 | {
14 | readonly ILogger _logger = loggerFactory.CreateLogger();
15 | readonly ISampleBlobDataMover _downloader = sampleBlobDataMover;
16 |
17 | ///
18 | /// Function that processes Service Bus events auto-generated using Event Grid when files are uploaded to a storage blob container.
19 | ///
20 | [Function(nameof(FunctionConsumer3))]
21 | public async Task RunAsync(
22 | [ServiceBusTrigger("%ServiceBusQueue%", Connection = "ServiceBusConnection", IsBatched = false, AutoCompleteMessages = true)]
23 | ServiceBusReceivedMessage receivedMessage
24 | )
25 | {
26 | _logger.LogInformation("Service Bus message received: {MessageId}", receivedMessage.MessageId);
27 |
28 | var messageText = Encoding.UTF8.GetString(receivedMessage.Body);
29 | var jsonMessage = JsonDocument.Parse(messageText).RootElement;
30 | var payloadUri = new Uri(jsonMessage.GetProperty("data").GetProperty("url").GetString()!);
31 | _logger.LogInformation("Message received. Payload Url: {Uri}", payloadUri);
32 |
33 | // download the payload
34 | var payload = await _downloader.DownloadAsync(payloadUri);
35 | _logger.LogInformation("Payload content\n {Payload}", payload);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/FunctionConsumer3/FunctionConsumer3.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/FunctionConsumer3/ISampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Pnp.Samples.ClaimCheckPattern
3 | {
4 | public interface ISampleBlobDataMover
5 | {
6 | Task DownloadAsync(Uri blobUri, bool deleteAfter = true);
7 | Task UploadAsync(string blobUri, string containerName, string content);
8 | }
9 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/FunctionConsumer3/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Functions.Worker;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 | using Pnp.Samples.ClaimCheckPattern;
5 |
6 | var host = new HostBuilder()
7 | .ConfigureFunctionsWebApplication()
8 | .ConfigureServices(services =>
9 | {
10 | services.AddApplicationInsightsTelemetryWorkerService();
11 | services.ConfigureFunctionsApplicationInsights();
12 | services.AddScoped();
13 | })
14 | .Build();
15 |
16 | host.Run();
17 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/FunctionConsumer3/SampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Storage.Blobs;
3 | using Azure.Storage.Blobs.Specialized;
4 | using Microsoft.Extensions.Logging;
5 | using System.Text;
6 |
7 | namespace Pnp.Samples.ClaimCheckPattern
8 | {
9 | ///
10 | /// A sample Storage Blob uploader/downloader. Uses Azure Entra ID for authentication.
11 | ///
12 | public class SampleBlobDataMover(ILoggerFactory loggerFactory) : ISampleBlobDataMover
13 | {
14 | readonly ILogger _logger = loggerFactory.CreateLogger();
15 |
16 | ///
17 | /// Downloads the referenced payload from Azure Blobs and returns the content.
18 | /// Optionally deletes the blob after download
19 | ///
20 | public async Task DownloadAsync(Uri blobUri, bool deleteAfter = true)
21 | {
22 | var blobClient = new BlockBlobClient(blobUri, new DefaultAzureCredential());
23 | if (!await blobClient.ExistsAsync())
24 | {
25 | _logger.LogError("Blob {BlobUri} does not exist.", blobUri.AbsoluteUri);
26 | return string.Empty;
27 | }
28 | var response = await blobClient.DownloadContentAsync();
29 | if (deleteAfter)
30 | {
31 | await blobClient.DeleteIfExistsAsync();
32 | }
33 | return Encoding.UTF8.GetString(response.Value.Content);
34 | }
35 |
36 | ///
37 | /// Upload sample text content to Azure Blob Storage and returns Url to the newly created blob
38 | ///
39 | public async Task UploadAsync(string blobUri, string containerName, string content)
40 | {
41 | var serviceClient = new BlobServiceClient(new Uri(blobUri), new DefaultAzureCredential());
42 |
43 | var containerClient = serviceClient.GetBlobContainerClient(containerName);
44 | await containerClient.CreateIfNotExistsAsync();
45 |
46 | var blobClient = containerClient.GetBlobClient(Guid.NewGuid().ToString());
47 | await blobClient.UploadAsync(new BinaryData(content), overwrite: true);
48 | _logger.LogInformation("Uploaded content to {Uri}", blobClient.Uri.AbsoluteUri);
49 | return blobClient.Uri.AbsoluteUri;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/FunctionConsumer3/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/FunctionConsumer3/local.settings.json.template:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
6 |
7 | "AzureFunctionsJobHost__logging__applicationInsights__samplingSettings__isEnabled": "false",
8 |
9 | "ServiceBusConnection:fullyQualifiedNamespace": "{SERVICE_BUS_NAMESPACE}.servicebus.windows.net",
10 | "ServiceBusQueue": "esbq-claimcheck"
11 | }
12 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/images/sample-3-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/claim-check/code-samples/sample-3/images/sample-3-diagram.png
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-3/sample-3.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34723.18
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionConsumer3", "FunctionConsumer3\FunctionConsumer3.csproj", "{67AC55F4-7647-466F-8A3E-83E2878347AF}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D8D14A53-FEC2-4CA6-BB0D-E916D6AAAE69}"
9 | ProjectSection(SolutionItems) = preProject
10 | readme.md = readme.md
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 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {67AC55F4-7647-466F-8A3E-83E2878347AF}.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 = {1B1FEA88-54B9-477B-927B-F3542A3AA1B6}
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/ClientProducer4.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 | enable
6 | enable
7 | Pnp.Samples.ClaimCheckPattern
8 | latest
9 |
10 |
11 |
12 |
13 | PreserveNewest
14 |
15 |
16 |
17 |
18 | Never
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/ISampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Pnp.Samples.ClaimCheckPattern
3 | {
4 | public interface ISampleBlobDataMover
5 | {
6 | Task DownloadAsync(Uri blobUri, bool deleteAfter = true);
7 | Task UploadAsync(string blobUri, string containerName, string content);
8 | }
9 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/ISampleKafkaProducer.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Pnp.Samples.ClaimCheckPattern
3 | {
4 | public interface ISampleKafkaProducer
5 | {
6 | Task SendMessageAsync(string message);
7 | }
8 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Extensions.Logging;
3 | using Pnp.Samples.ClaimCheckPattern;
4 | using System.Text.Json;
5 |
6 | Console.WriteLine("Begin sample 4 for Claim Check pattern");
7 | Console.WriteLine("Uploads a sample file to Storage Blob and sends a claim-check message via Event Hubs using the Kafka Api");
8 | Console.WriteLine("Initializing...");
9 | Console.WriteLine();
10 |
11 | var configuration = new ConfigurationBuilder()
12 | .AddJsonFile("appsettings.json", false, false).Build();
13 | using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
14 |
15 | configuration.ThrowIfMissingSettings([
16 | "AppSettings:eventHubEndpoint",
17 | "AppSettings:eventHubFqdn",
18 | "AppSettings:eventHubName",
19 | "AppSettings:storageBlobUri",
20 | "AppSettings:payloadContainerName"
21 | ]);
22 |
23 | Console.WriteLine("Uploading sample file to Azure Blob Storage...");
24 | var blobMover = new SampleBlobDataMover(loggerFactory);
25 | var newBlobUri = await blobMover.UploadAsync(
26 | configuration.GetSection("AppSettings:StorageBlobUri").Value!,
27 | configuration.GetSection("AppSettings:PayloadContainerName").Value!,
28 | "Hello, World! This is a sample text file to illustrate using the claim check cloud pattern");
29 | Console.WriteLine();
30 |
31 | Console.WriteLine("Sending Claim Check message...");
32 | var kafkaWorker = new SampleKafkaProducer(configuration);
33 |
34 | await kafkaWorker.SendMessageAsync(JsonSerializer.Serialize(new { payloadUri = newBlobUri }));
35 | Console.WriteLine();
36 |
37 | Console.WriteLine("Done.");
38 | Console.WriteLine("Press any key to exit the sample application.");
39 | Console.ReadKey();
40 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/SampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Storage.Blobs;
3 | using Azure.Storage.Blobs.Specialized;
4 | using Microsoft.Extensions.Logging;
5 | using System.Text;
6 |
7 | namespace Pnp.Samples.ClaimCheckPattern
8 | {
9 | ///
10 | /// A sample Storage Blob uploader/downloader. Uses Azure Entra ID for authentication.
11 | ///
12 | public class SampleBlobDataMover(ILoggerFactory loggerFactory) : ISampleBlobDataMover
13 | {
14 | readonly ILogger _logger = loggerFactory.CreateLogger();
15 |
16 | ///
17 | /// Downloads the referenced payload from Azure Blobs and returns the content.
18 | /// Optionally deletes the blob after download
19 | ///
20 | public async Task DownloadAsync(Uri blobUri, bool deleteAfter = true)
21 | {
22 | var blobClient = new BlockBlobClient(blobUri, new DefaultAzureCredential());
23 | if (!await blobClient.ExistsAsync())
24 | {
25 | _logger.LogError("Blob {BlobUri} does not exist.", blobUri.AbsoluteUri);
26 | return string.Empty;
27 | }
28 | var response = await blobClient.DownloadContentAsync();
29 | if (deleteAfter)
30 | {
31 | await blobClient.DeleteIfExistsAsync();
32 | }
33 | return Encoding.UTF8.GetString(response.Value.Content);
34 | }
35 |
36 | ///
37 | /// Upload sample text content to Azure Blob Storage and returns Url to the newly created blob
38 | ///
39 | public async Task UploadAsync(string blobUri, string containerName, string content)
40 | {
41 | var serviceClient = new BlobServiceClient(new Uri(blobUri), new DefaultAzureCredential());
42 |
43 | var containerClient = serviceClient.GetBlobContainerClient(containerName);
44 | await containerClient.CreateIfNotExistsAsync();
45 |
46 | var blobClient = containerClient.GetBlobClient(Guid.NewGuid().ToString());
47 | await blobClient.UploadAsync(new BinaryData(content), overwrite: true);
48 | _logger.LogInformation("Uploaded content to {Uri}", blobClient.Uri.AbsoluteUri);
49 | return blobClient.Uri.AbsoluteUri;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/SampleKafkaProducer.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Confluent.Kafka;
3 | using Microsoft.Extensions.Configuration;
4 |
5 | namespace Pnp.Samples.ClaimCheckPattern
6 | {
7 | ///
8 | /// Implements the Kafka producer to send messages to a Kafka-enabled Event Hub namespace
9 | /// Uses Azure Entra ID to authenticate.
10 | ///
11 | public class SampleKafkaProducer(IConfiguration configuration) : ISampleKafkaProducer
12 | {
13 | readonly string _eventHubEndpoint = configuration.GetSection("AppSettings:eventHubEndpoint").Value!;
14 | readonly string _kafkaFqdn = configuration.GetSection("AppSettings:eventHubFqdn").Value!;
15 | readonly string _topic = configuration.GetSection("AppSettings:eventHubName").Value!;
16 |
17 | public async Task SendMessageAsync(string message)
18 | {
19 | try
20 | {
21 | var config = new ProducerConfig
22 | {
23 | BootstrapServers = _kafkaFqdn,
24 | SecurityProtocol = SecurityProtocol.SaslSsl,
25 | SaslMechanism = SaslMechanism.OAuthBearer,
26 | //Debug = "security,broker,protocol", //Uncomment for librdkafka debugging information
27 | };
28 |
29 | using var producer = new ProducerBuilder(config)
30 | .SetOAuthBearerTokenRefreshHandler(async (client, cfg) =>
31 | {
32 | var credential = new DefaultAzureCredential();
33 | var accessToken = await credential.GetTokenAsync(new Azure.Core.TokenRequestContext([_eventHubEndpoint]));
34 | client.OAuthBearerSetToken(accessToken.Token, accessToken.ExpiresOn.ToUnixTimeMilliseconds(), credential.GetUserPrincipal());
35 | })
36 | .Build();
37 | try
38 | {
39 | var deliveryResult = await producer.ProduceAsync(_topic, new Message { Value = message });
40 | Console.WriteLine($"Delivered '{deliveryResult.Value}' to '{deliveryResult.TopicPartitionOffset}'");
41 | }
42 | catch (ProduceException e)
43 | {
44 | Console.WriteLine($"Delivery failed: {e.Error.Reason}");
45 | }
46 | }
47 | catch (Exception e)
48 | {
49 | Console.WriteLine("!!! Exception Occurred - {0}", e.Message);
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/SampleSettingsValidator.cs:
--------------------------------------------------------------------------------
1 | namespace Microsoft.Extensions.Configuration;
2 |
3 | public static class SampleSettingsValidator
4 | {
5 | ///
6 | /// Ensure the required app settings are present
7 | ///
8 | ///
9 | ///
10 | public static void ThrowIfMissingSettings(this IConfiguration configuration, List settings)
11 | {
12 | if (settings.Any(option => string.IsNullOrWhiteSpace(configuration.GetSection(option).Value)))
13 | {
14 | throw new ApplicationException($"Configure options {string.Join(", ", settings)} in appsettings.json and run again.");
15 | }
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/SampleTokenCredentialHelper.cs:
--------------------------------------------------------------------------------
1 | using Azure.Core;
2 | using System.IdentityModel.Tokens.Jwt;
3 |
4 | namespace Azure.Identity;
5 |
6 | internal static class SampleTokenCredentialHelper
7 | {
8 |
9 | ///
10 | /// Extract the user principal from the token credential.
11 | ///
12 | public static string GetUserPrincipal(this DefaultAzureCredential azureCredential)
13 | {
14 | const string DefaultEntraIdCredentialContext = "https://management.azure.com/.default";
15 |
16 | var token = azureCredential.GetToken(new TokenRequestContext([DefaultEntraIdCredentialContext]), CancellationToken.None);
17 | var handler = new JwtSecurityTokenHandler();
18 | var jwtSecurityToken = handler.ReadJwtToken(token.Token);
19 | return jwtSecurityToken.Subject;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/ClientProducer4/appsettings.json.template:
--------------------------------------------------------------------------------
1 | {
2 | "AppSettings": {
3 | "EventHubEndpoint": "https://{EVENT_HUBS_NAMESPACE}.servicebus.windows.net",
4 | "EventHubFqdn": "{EVENT_HUBS_NAMESPACE}.servicebus.windows.net:9093",
5 | "EventHubName": "evh-claimcheck",
6 |
7 | "StorageBlobUri": "https://{STORAGE_ACCOUNT_NAME}.blob.core.windows.net",
8 | "PayloadContainerName": "payloads"
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/FunctionConsumer4/FunctionConsumer4.cs:
--------------------------------------------------------------------------------
1 | using Azure.Messaging.EventHubs;
2 | using Microsoft.Azure.Functions.Worker;
3 | using Microsoft.Extensions.Logging;
4 | using System.Text;
5 | using System.Text.Json;
6 |
7 | namespace Pnp.Samples.ClaimCheckPattern
8 | {
9 | ///
10 | /// Sample function illustrating the processing of Event Hub events containing claim-check style events.
11 | ///
12 | public class FunctionConsumer4(ILoggerFactory loggerFactory, ISampleBlobDataMover sampleBlobDataMover)
13 | {
14 | readonly ILogger _logger = loggerFactory.CreateLogger();
15 | readonly ISampleBlobDataMover _downloader = sampleBlobDataMover;
16 |
17 | [Function(nameof(FunctionConsumer4))]
18 | public async Task RunAsync(
19 | [EventHubTrigger("%EventHubName%", Connection = "EventHubConnectionString", IsBatched = false)] EventData receivedMessage
20 | )
21 | {
22 | _logger.LogInformation("EventHub message received: {SequenceNumber}", receivedMessage.SequenceNumber);
23 |
24 | // retrieve the paylod Uri from the message
25 | var jsonMessage = JsonDocument.Parse(Encoding.UTF8.GetString(receivedMessage.EventBody)).RootElement;
26 | var payloadUri = new Uri(jsonMessage.GetProperty("payloadUri").GetString()!);
27 | _logger.LogInformation("Message received. Payload Url: {Uri}", payloadUri);
28 |
29 | // download the payload
30 | var payload = await _downloader.DownloadAsync(payloadUri);
31 | _logger.LogInformation("Payload content (Url {Uri)}\n {Payload}", payloadUri, payload);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/FunctionConsumer4/FunctionConsumer4.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 | PreserveNewest
29 |
30 |
31 | Never
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/FunctionConsumer4/ISampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Pnp.Samples.ClaimCheckPattern
3 | {
4 | public interface ISampleBlobDataMover
5 | {
6 | Task DownloadAsync(Uri blobUri, bool deleteAfter = true);
7 | Task UploadAsync(string blobUri, string containerName, string content);
8 | }
9 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/FunctionConsumer4/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Functions.Worker;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 | using Pnp.Samples.ClaimCheckPattern;
5 |
6 | var host = new HostBuilder()
7 | .ConfigureFunctionsWebApplication()
8 | .ConfigureServices(services =>
9 | {
10 | services.AddApplicationInsightsTelemetryWorkerService();
11 | services.ConfigureFunctionsApplicationInsights();
12 | services.AddScoped();
13 | })
14 | .Build();
15 |
16 | host.Run();
17 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/FunctionConsumer4/SampleBlobDataMover.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Azure.Storage.Blobs;
3 | using Azure.Storage.Blobs.Specialized;
4 | using Microsoft.Extensions.Logging;
5 | using System.Text;
6 |
7 | namespace Pnp.Samples.ClaimCheckPattern
8 | {
9 | ///
10 | /// A sample Storage Blob uploader/downloader. Uses Azure Entra ID for authentication.
11 | ///
12 | public class SampleBlobDataMover(ILoggerFactory loggerFactory) : ISampleBlobDataMover
13 | {
14 | readonly ILogger _logger = loggerFactory.CreateLogger();
15 |
16 | ///
17 | /// Downloads the referenced payload from Azure Blobs and returns the content.
18 | /// Optionally deletes the blob after download
19 | ///
20 | public async Task DownloadAsync(Uri blobUri, bool deleteAfter = true)
21 | {
22 | var blobClient = new BlockBlobClient(blobUri, new DefaultAzureCredential());
23 | if (!await blobClient.ExistsAsync())
24 | {
25 | _logger.LogError("Blob {BlobUri} does not exist.", blobUri.AbsoluteUri);
26 | return string.Empty;
27 | }
28 | var response = await blobClient.DownloadContentAsync();
29 | if (deleteAfter)
30 | {
31 | await blobClient.DeleteIfExistsAsync();
32 | }
33 | return Encoding.UTF8.GetString(response.Value.Content);
34 | }
35 |
36 | ///
37 | /// Upload sample text content to Azure Blob Storage and returns Url to the newly created blob
38 | ///
39 | public async Task UploadAsync(string blobUri, string containerName, string content)
40 | {
41 | var serviceClient = new BlobServiceClient(new Uri(blobUri), new DefaultAzureCredential());
42 |
43 | var containerClient = serviceClient.GetBlobContainerClient(containerName);
44 | await containerClient.CreateIfNotExistsAsync();
45 |
46 | var blobClient = containerClient.GetBlobClient(Guid.NewGuid().ToString());
47 | await blobClient.UploadAsync(new BinaryData(content), overwrite: true);
48 | _logger.LogInformation("Uploaded content to {Uri}", blobClient.Uri.AbsoluteUri);
49 | return blobClient.Uri.AbsoluteUri;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/FunctionConsumer4/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "excludedTypes": "Request",
7 | "isEnabled": true
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/FunctionConsumer4/local.settings.json.template:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
6 | "UseDevelopmentStorage": true,
7 | "AzureFunctionsJobHost__logging__applicationInsights__samplingSettings__isEnabled": "false",
8 |
9 | "EventHubConnectionString:fullyQualifiedNamespace": "{EVENT_HUBS_NAMESPACE}.servicebus.windows.net",
10 | "EventHubName": "evh-claimcheck"
11 | }
12 | }
13 |
14 |
15 |
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/images/sample-4-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/claim-check/code-samples/sample-4/images/sample-4-diagram.png
--------------------------------------------------------------------------------
/claim-check/code-samples/sample-4/sample-4.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34723.18
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionConsumer4", "FunctionConsumer4\FunctionConsumer4.csproj", "{5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientProducer4", "ClientProducer4\ClientProducer4.csproj", "{CA4D4A68-55B4-496F-B976-D3464076FC62}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D704D686-1E69-4DF7-B224-B4E5BBF9773B}"
11 | ProjectSection(SolutionItems) = preProject
12 | readme.md = readme.md
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 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {5A7E5D39-BF09-4A3B-A7A1-7084A5359BAA}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {CA4D4A68-55B4-496F-B976-D3464076FC62}.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 = {1B1FEA88-54B9-477B-927B-F3542A3AA1B6}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/leader-election/DistributedMutex/BlobLeaseManager.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 | namespace DistributedMutex
4 | {
5 | using System;
6 | using System.Diagnostics;
7 | using System.Net;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Azure;
11 | using Azure.Identity;
12 | using Azure.Storage.Blobs;
13 | using Azure.Storage.Blobs.Specialized;
14 |
15 | public struct BlobSettings
16 | {
17 | public readonly string Container;
18 | public readonly string BlobName;
19 | public BlobServiceClient BlobServiceClient;
20 |
21 | public BlobSettings(string blobUri, string container, string blobName)
22 | {
23 | var blobClientOptions = new BlobClientOptions();
24 | blobClientOptions.Retry.Delay = TimeSpan.FromSeconds(5);
25 | blobClientOptions.Retry.MaxRetries = 3;
26 |
27 | BlobServiceClient = new BlobServiceClient(new Uri(blobUri), new DefaultAzureCredential(), blobClientOptions);
28 | Container = container;
29 | BlobName = blobName;
30 | }
31 | }
32 |
33 | ///
34 | /// Wrapper around a Windows Azure Blob Lease
35 | ///
36 | internal class BlobLeaseManager
37 | {
38 | private readonly BlobContainerClient leaseContainerClient;
39 | private readonly PageBlobClient leaseBlobClient;
40 |
41 | public BlobLeaseManager(BlobSettings settings)
42 | : this(settings.BlobServiceClient, settings.Container, settings.BlobName)
43 | {
44 | }
45 |
46 | public BlobLeaseManager(BlobServiceClient blobServiceClient, string leaseContainerName, string leaseBlobName)
47 | {
48 | leaseContainerClient = blobServiceClient.GetBlobContainerClient(leaseContainerName);
49 | leaseBlobClient = leaseContainerClient.GetPageBlobClient(leaseBlobName);
50 | leaseContainerClient.CreateIfNotExists();
51 | leaseBlobClient.CreateIfNotExists(512);
52 | }
53 |
54 | public void ReleaseLease(string leaseId)
55 | {
56 | try
57 | {
58 | var leaseClient = leaseBlobClient.GetBlobLeaseClient(leaseId);
59 | leaseClient.Release();
60 | }
61 | catch (RequestFailedException e)
62 | {
63 | // Lease will eventually be released.
64 | Trace.TraceError(e.ErrorCode);
65 | }
66 | }
67 |
68 | public async Task AcquireLeaseAsync(CancellationToken token)
69 | {
70 | bool blobNotFound = false;
71 | try
72 | {
73 | var leaseClient = leaseBlobClient.GetBlobLeaseClient();
74 | var lease = await leaseClient.AcquireAsync(TimeSpan.FromSeconds(15), null, token);
75 | return lease.Value.LeaseId;
76 | }
77 | catch (RequestFailedException storageException)
78 | {
79 | Trace.TraceError(storageException.ErrorCode);
80 |
81 | var status = storageException.Status;
82 | if (status == (int) HttpStatusCode.NotFound)
83 | {
84 | blobNotFound = true;
85 | }
86 |
87 | if (status == (int)HttpStatusCode.Conflict)
88 | {
89 | return null;
90 | }
91 | }
92 | catch (Exception e)
93 | {
94 | // If the storage account is unavailable or we fail for any other reason we still want to keep retrying
95 | Trace.TraceError(e.Message);
96 | return null;
97 | }
98 |
99 | if (blobNotFound)
100 | {
101 | await CreateBlobAsync(token);
102 | return await AcquireLeaseAsync(token);
103 | }
104 |
105 | return null;
106 | }
107 |
108 | public async Task RenewLeaseAsync(string leaseId, CancellationToken token)
109 | {
110 | try
111 | {
112 | var leaseClient = leaseBlobClient.GetBlobLeaseClient(leaseId);
113 | await leaseClient.RenewAsync(cancellationToken: token);
114 | return true;
115 | }
116 | catch (RequestFailedException storageException)
117 | {
118 | Trace.TraceError(storageException.ErrorCode);
119 |
120 | return false;
121 | }
122 | }
123 |
124 | private async Task CreateBlobAsync(CancellationToken token)
125 | {
126 | await leaseContainerClient.CreateIfNotExistsAsync(cancellationToken: token);
127 | await leaseBlobClient.CreateIfNotExistsAsync(0, cancellationToken: token);
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/leader-election/DistributedMutex/DistributedMutex.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/leader-election/LeaderElection.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistributedMutex", "DistributedMutex\DistributedMutex.csproj", "{75FCC9BC-48D0-4D86-871E-087386F8B602}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LeaderElectionConsoleWorker", "LeaderElectionConsoleWorker\LeaderElectionConsoleWorker.csproj", "{164AD311-21A1-401D-AF26-E2DE1CD68675}"
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(SolutionProperties) = preSolution
16 | HideSolutionNode = FALSE
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {75FCC9BC-48D0-4D86-871E-087386F8B602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {75FCC9BC-48D0-4D86-871E-087386F8B602}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {75FCC9BC-48D0-4D86-871E-087386F8B602}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {75FCC9BC-48D0-4D86-871E-087386F8B602}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {164AD311-21A1-401D-AF26-E2DE1CD68675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {164AD311-21A1-401D-AF26-E2DE1CD68675}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {164AD311-21A1-401D-AF26-E2DE1CD68675}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {164AD311-21A1-401D-AF26-E2DE1CD68675}.Release|Any CPU.Build.0 = Release|Any CPU
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/leader-election/LeaderElectionConsoleWorker/LeaderElectionConsoleWorker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | DistributedMutex
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/leader-election/LeaderElectionConsoleWorker/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace LeaderElectionConsoleWorker
5 | {
6 | using System;
7 | using System.Configuration;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using DistributedMutex;
11 |
12 | class Program
13 | {
14 | static async Task Main(string[] args)
15 | {
16 | // Create a new shared cancellation token source
17 | CancellationTokenSource source = new CancellationTokenSource();
18 | CancellationToken token = source.Token;
19 |
20 | // Get the connection string from app settings
21 | var storageUri = ConfigurationManager.AppSettings["StorageUri"];
22 | if (string.IsNullOrEmpty(storageUri))
23 | {
24 | Console.Error.WriteLine("A connection string must be set in the app.config file.");
25 | return;
26 | }
27 | // Create a BlobSettings object with the connection string and the name of the blob to use for the lease
28 | BlobSettings blobSettings = new BlobSettings(
29 | storageUri,
30 | "leases",
31 | "leader");
32 |
33 | // Get the current process ID for output
34 | var pid = System.Diagnostics.Process.GetCurrentProcess().Id;
35 |
36 | // Start an async task that will wait for a keypress and cancel the token when a key is pressed
37 | var uiTask = Task.Run(async () =>
38 | {
39 | while (!token.IsCancellationRequested)
40 | {
41 | if (Console.KeyAvailable)
42 | {
43 | Console.ReadKey(true);
44 | Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] Requesting shutdown.");
45 | source.Cancel();
46 | }
47 | await Task.Delay(500);
48 | }
49 | Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] This process ({pid}) is shutting down.");
50 |
51 | });
52 |
53 | // Create a new BlobDistributedMutex object with the BlobSettings object and a task
54 | // to run when the lease is acquired, and an action to run when the lease is not acquired.
55 | BlobDistributedMutex distributedMutex = new BlobDistributedMutex(
56 | blobSettings,
57 | async (CancellationToken token) =>
58 | {
59 | while (!token.IsCancellationRequested)
60 | {
61 | Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] This process ({pid}) is currently the leader. Press any key to exit.");
62 | await Task.Delay(15000);
63 | }
64 | }, () => {
65 | Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] This process ({pid}) could not acquire lease. Retrying in 20 seconds. Press any key to exit.");
66 | });
67 |
68 | // Wait for completion of the DistributedMutex and the UI task before exiting
69 | await distributedMutex.RunTaskWhenMutexAcquired(token);
70 | await uiTask;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/leader-election/LeaderElectionConsoleWorker/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pipes-and-filters/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
3 | {
4 | "name": "C# (.NET)",
5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6 | "image": "mcr.microsoft.com/devcontainers/dotnet:8.0-bookworm",
7 | "features": {
8 | "ghcr.io/devcontainers/features/azure-cli:1": {
9 | "version": "latest"
10 | }
11 | },
12 | "customizations": {
13 | "vscode": {
14 | "extensions": [
15 | "ms-dotnettools.csdevkit",
16 | "ms-azuretools.vscode-azurefunctions",
17 | "ms-vscode.azurecli",
18 | "ms-azuretools.vscode-bicep",
19 | "DavidAnson.vscode-markdownlint"
20 | ]
21 | }
22 | }
23 |
24 | // Features to add to the dev container. More info: https://containers.dev/features.
25 | // "features": {},
26 |
27 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
28 | // "forwardPorts": [5000, 5001],
29 | // "portsAttributes": {
30 | // "5001": {
31 | // "protocol": "https"
32 | // }
33 | // }
34 |
35 | // Use 'postCreateCommand' to run commands after the container is created.
36 | // "postCreateCommand": "dotnet restore",
37 |
38 | // Configure tool-specific properties.
39 | // "customizations": {},
40 |
41 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
42 | // "remoteUser": "root"
43 | }
44 |
--------------------------------------------------------------------------------
/pipes-and-filters/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-dotnettools.csdevkit",
4 | "ms-azuretools.vscode-azurefunctions",
5 | "ms-vscode.azurecli",
6 | "ms-azuretools.vscode-bicep",
7 | "DavidAnson.vscode-markdownlint"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/pipes-and-filters/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Attach to .NET Functions",
6 | "type": "coreclr",
7 | "request": "attach",
8 | "processId": "${command:azureFunctions.pickProcess}"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/pipes-and-filters/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "azureFunctions.deploySubpath": "ImageProcessingPipeline/bin/Release/net8.0/publish",
3 | "azureFunctions.projectLanguage": "C#",
4 | "azureFunctions.projectRuntime": "~4",
5 | "debug.internalConsoleOptions": "neverOpen",
6 | "azureFunctions.preDeployTask": "publish (functions)"
7 | }
8 |
--------------------------------------------------------------------------------
/pipes-and-filters/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "clean (functions)",
6 | "command": "dotnet",
7 | "args": [
8 | "clean",
9 | "/property:GenerateFullPaths=true",
10 | "/consoleloggerparameters:NoSummary"
11 | ],
12 | "type": "process",
13 | "problemMatcher": "$msCompile",
14 | "options": {
15 | "cwd": "${workspaceFolder}/ImageProcessingPipeline"
16 | }
17 | },
18 | {
19 | "label": "build (functions)",
20 | "command": "dotnet",
21 | "args": [
22 | "build",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "type": "process",
27 | "dependsOn": "clean (functions)",
28 | "group": {
29 | "kind": "build",
30 | "isDefault": true
31 | },
32 | "problemMatcher": "$msCompile",
33 | "options": {
34 | "cwd": "${workspaceFolder}/ImageProcessingPipeline"
35 | }
36 | },
37 | {
38 | "label": "clean release (functions)",
39 | "command": "dotnet",
40 | "args": [
41 | "clean",
42 | "--configuration",
43 | "Release",
44 | "/property:GenerateFullPaths=true",
45 | "/consoleloggerparameters:NoSummary"
46 | ],
47 | "type": "process",
48 | "problemMatcher": "$msCompile",
49 | "options": {
50 | "cwd": "${workspaceFolder}/ImageProcessingPipeline"
51 | }
52 | },
53 | {
54 | "label": "publish (functions)",
55 | "command": "dotnet",
56 | "args": [
57 | "publish",
58 | "--configuration",
59 | "Release",
60 | "/property:GenerateFullPaths=true",
61 | "/consoleloggerparameters:NoSummary"
62 | ],
63 | "type": "process",
64 | "dependsOn": "clean release (functions)",
65 | "problemMatcher": "$msCompile",
66 | "options": {
67 | "cwd": "${workspaceFolder}/ImageProcessingPipeline"
68 | }
69 | },
70 | {
71 | "type": "func",
72 | "dependsOn": "build (functions)",
73 | "options": {
74 | "cwd": "${workspaceFolder}/ImageProcessingPipeline/bin/Debug/net8.0"
75 | },
76 | "command": "host start",
77 | "isBackground": true,
78 | "problemMatcher": "$func-dotnet-watch"
79 | }
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/.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
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/ImageProcessingPipeline.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 | Never
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Always
35 |
36 |
37 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/ImageProcessingPipeline.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.002.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageProcessingPipeline", "ImageProcessingPipeline.csproj", "{9E2EFA90-60A5-409A-960F-3C2A5A184264}"
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 | {9E2EFA90-60A5-409A-960F-3C2A5A184264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {9E2EFA90-60A5-409A-960F-3C2A5A184264}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {9E2EFA90-60A5-409A-960F-3C2A5A184264}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {9E2EFA90-60A5-409A-960F-3C2A5A184264}.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 = {B7A172BA-FA5D-45FD-B091-E7F55399D039}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/Program.cs:
--------------------------------------------------------------------------------
1 | using Azure.Identity;
2 | using Microsoft.Extensions.Azure;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.FileProviders;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | var host = new HostBuilder()
8 | .ConfigureFunctionsWorkerDefaults()
9 | .ConfigureServices((hostContext, services) =>
10 | {
11 | services.AddAzureClients(c =>
12 | {
13 | c.UseCredential(new DefaultAzureCredential());
14 | c.AddBlobServiceClient(hostContext.Configuration.GetSection("output")).WithName("processed");
15 | });
16 | services.AddSingleton(new ManifestEmbeddedFileProvider(typeof(Program).Assembly));
17 | })
18 | .Build();
19 |
20 | host.Run();
21 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ImageProcessingPipeline": {
4 | "commandName": "Project",
5 | "commandLineArgs": "--port 7253",
6 | "launchBrowser": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/PublishFinal.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs;
2 | using Azure.Storage.Blobs.Models;
3 | using Azure.Storage.Blobs.Specialized;
4 | using Microsoft.Azure.Functions.Worker;
5 | using Microsoft.Extensions.Azure;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace ImageProcessingPipeline
9 | {
10 | public class PublishFinal(ILogger logger, IAzureClientFactory blobClientFactory)
11 | {
12 | private readonly ILogger _logger = logger;
13 | private readonly BlobContainerClient _destinationContainerClient = blobClientFactory.CreateClient("processed").GetBlobContainerClient("processed");
14 |
15 | [Function(nameof(PublishFinal))]
16 | public async Task RunAsync(
17 | [QueueTrigger("pipe-yhrb", Connection = "pipe")] string imageFilePath,
18 | [BlobInput("{QueueTrigger}", Connection = "pipe")] BlockBlobClient imageBlob,
19 | CancellationToken cancellationToken)
20 | {
21 | _logger.LogDebug("Starting copying {source} into {destination}.", imageBlob.Uri, _destinationContainerClient.Uri);
22 |
23 | // Copy blob and delete orginal
24 | var newBlobClient = _destinationContainerClient.GetBlobClient(imageBlob.Name);
25 | await newBlobClient.UploadAsync(await imageBlob.OpenReadAsync(null, cancellationToken), overwrite: true, cancellationToken);
26 | await imageBlob.DeleteAsync(DeleteSnapshotsOption.None, null, cancellationToken);
27 |
28 | _logger.LogInformation("Copied {source} into {destination} and deleted original.", imageBlob.Uri, _destinationContainerClient.Uri);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/Resize.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs.Models;
2 | using Azure.Storage.Blobs.Specialized;
3 | using Microsoft.Azure.Functions.Worker;
4 | using Microsoft.Extensions.Logging;
5 | using SixLabors.ImageSharp;
6 | using SixLabors.ImageSharp.Processing;
7 |
8 | namespace ImageProcessingPipeline
9 | {
10 |
11 | public class Resize(ILogger logger)
12 | {
13 | private readonly ILogger _logger = logger;
14 |
15 | [Function(nameof(Resize))]
16 | [QueueOutput("pipe-fjur", Connection = "pipe")]
17 | public async Task RunAsync(
18 | [QueueTrigger("pipe-xfty", Connection = "pipe")] string imageFilePath,
19 | [BlobInput("{QueueTrigger}", Connection = "pipe")] BlockBlobClient imageBlob,
20 | CancellationToken cancellationToken)
21 | {
22 | _logger.LogInformation("Processing image {uri} for resizing.", imageBlob.Uri);
23 |
24 | // Download image and resize it
25 | using BlobDownloadStreamingResult imageBlobContents = await imageBlob.DownloadStreamingAsync(null, cancellationToken);
26 | var image = await Image.LoadAsync(imageBlobContents.Content, cancellationToken);
27 | image.Mutate(i =>
28 | {
29 | i.Resize(new ResizeOptions
30 | {
31 | Mode = ResizeMode.Max,
32 | Size = new Size(600, 600)
33 | });
34 | });
35 |
36 | // Write modified image back to storage
37 | _logger.LogDebug("Writing resized image back to storage: {uri}.", imageBlob.Uri);
38 | using (var blobStream = await imageBlob.OpenWriteAsync(overwrite: true, null, cancellationToken))
39 | {
40 | await image.SaveAsync(blobStream, image.Metadata.DecodedImageFormat!, cancellationToken);
41 | }
42 |
43 | _logger.LogInformation("Image resizing done. Adding image \"{filePath}\" into the next pipe.", imageFilePath);
44 | return imageFilePath;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/Watermark.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs.Models;
2 | using Azure.Storage.Blobs.Specialized;
3 | using Microsoft.Azure.Functions.Worker;
4 | using Microsoft.Extensions.FileProviders;
5 | using Microsoft.Extensions.Logging;
6 | using SixLabors.ImageSharp;
7 | using SixLabors.ImageSharp.Processing;
8 |
9 | namespace ImageProcessingPipeline
10 | {
11 | public class Watermark(ILogger logger, IFileProvider files)
12 | {
13 | private readonly ILogger _logger = logger;
14 | private readonly IFileProvider _files = files;
15 |
16 | [Function(nameof(Watermark))]
17 | [QueueOutput("pipe-yhrb", Connection = "pipe")]
18 | public async Task RunAsync(
19 | [QueueTrigger("pipe-fjur", Connection = "pipe")] string imageFilePath,
20 | [BlobInput("{QueueTrigger}", Connection = "pipe")] BlockBlobClient imageBlob,
21 | CancellationToken cancellationToken)
22 | {
23 | _logger.LogInformation("Processing image {uri} for watermarking.", imageBlob.Uri);
24 |
25 | // Download image and watermark it
26 | using BlobDownloadStreamingResult imageBlobContents = await imageBlob.DownloadStreamingAsync(null, cancellationToken);
27 | var image = await Image.LoadAsync(imageBlobContents.Content, cancellationToken);
28 |
29 | var resources = _files.GetDirectoryContents("/");
30 | using var watermarkStream = resources.First(resource=>resource.Name.Equals("resources/watermark.png")).CreateReadStream();
31 | var watermarkImage = await Image.LoadAsync(watermarkStream, cancellationToken);
32 |
33 | image.Mutate(i =>
34 | {
35 | i.DrawImage(watermarkImage, new Point((image.Width - watermarkImage.Width) / 2, (image.Height - watermarkImage.Height) / 2), 0.5f);
36 | });
37 |
38 | // Write modified image back to storage
39 | _logger.LogDebug("Writing watermarked image back to storage: {uri}.", imageBlob.Uri);
40 | using (var blobStream = await imageBlob.OpenWriteAsync(overwrite: true, null, cancellationToken))
41 | {
42 | await image.SaveAsync(blobStream, image.Metadata.DecodedImageFormat!, cancellationToken);
43 | }
44 |
45 | _logger.LogInformation("Watermarking done. Adding image \"{filePath}\" into the next pipe.", imageFilePath);
46 | return imageFilePath;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "enableDependencyTracking": true,
6 | "enableLiveMetrics": true,
7 | "enablePerformanceCountersCollection": true,
8 | "samplingSettings": {
9 | "isEnabled": true,
10 | "excludedTypes": "Request"
11 | },
12 | "enableLiveMetricsFilters": true
13 | },
14 | "logLevel": {
15 | "default": "Warning",
16 | "Host.Aggregator": "Information",
17 | "Host.Results": "Information",
18 | "Function": "Information",
19 | "Azure.Storage.Blobs": "Critical",
20 | "Azure.Core": "Critical",
21 | "Azure.Identity": "Critical"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/local.settings.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
5 | "AzureWebJobsStorage__accountName": "STORAGE_ACCOUNT_NAME",
6 | "pipe__accountName": "STORAGE_ACCOUNT_NAME",
7 | "output__serviceUri": "https://STORAGE_ACCOUNT_NAME.blob.core.windows.net"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/pipes-and-filters/ImageProcessingPipeline/resources/watermark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/pipes-and-filters/ImageProcessingPipeline/resources/watermark.png
--------------------------------------------------------------------------------
/pipes-and-filters/bicep/main.bicep:
--------------------------------------------------------------------------------
1 | targetScope = 'resourceGroup'
2 |
3 | @minLength(3)
4 | @description('The name of the storage account. Must be globally unique.')
5 | param storageAccountName string
6 |
7 | @minLength(36)
8 | @description('The Object ID (GUID) the user your Azure CLI session is logged in as.')
9 | param userObjectId string
10 |
11 | @minLength(5)
12 | @description('Location of the resources. Defaults to resource group location')
13 | param location string = resourceGroup().location
14 |
15 | /*** EXISTING RESOURCES ***/
16 |
17 | @description('Built-in Azure RBAC role that is applied to a Storage account to grant "Storage Blob Data Contributor" privileges. Granted to the user provided in the paramters.')
18 | resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
19 | name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
20 | scope: subscription()
21 | }
22 |
23 | @description('Built-in Azure RBAC role that is applied to a Storage account to grant "Storage Queue Data Contributor" privileges. Granted to the user provided in the paramters.')
24 | resource storageQueueDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
25 | name: '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
26 | scope: subscription()
27 | }
28 |
29 | /*** NEW RESOURCES ***/
30 |
31 | @description('The Azure Storage account which will contain the pipes (queues) and the images to be sent through the filters (Azure Functions).')
32 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
33 | name: storageAccountName
34 | location: location
35 | sku: {
36 | name: 'Standard_LRS'
37 | }
38 | kind: 'StorageV2'
39 | properties: {
40 | accessTier: 'Hot'
41 | allowBlobPublicAccess: true
42 | allowCrossTenantReplication: false
43 | allowSharedKeyAccess: false // This storage account is configured to only work with Microsoft Entra ID authentication
44 | isLocalUserEnabled: false
45 | isHnsEnabled: false
46 | isNfsV3Enabled: false
47 | isSftpEnabled: false
48 | largeFileSharesState: 'Disabled'
49 | minimumTlsVersion: 'TLS1_2'
50 | publicNetworkAccess: 'Enabled' // This sample does not use private networking, but could be configured to use Private Link connections if fully deploy to Azure
51 | supportsHttpsTrafficOnly: true
52 | }
53 |
54 | resource blobContainers 'blobServices' = {
55 | name: 'default'
56 |
57 | resource images 'containers' = {
58 | name: 'images'
59 | }
60 |
61 | resource processed 'containers' = {
62 | name: 'processed'
63 | }
64 | }
65 |
66 | resource queueContainers 'queueServices' = {
67 | name: 'default'
68 |
69 | resource pipexfty 'queues' = {
70 | name: 'pipe-xfty'
71 | }
72 | }
73 | }
74 |
75 | @description('Grant Storage Blob Data Contributor to the user.')
76 | resource blobContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
77 | name: guid(userObjectId, storageAccount.id, storageBlobDataContributorRole.id)
78 | scope: storageAccount
79 | properties: {
80 | principalId: userObjectId
81 | roleDefinitionId: storageBlobDataContributorRole.id
82 | principalType: 'User'
83 | }
84 | }
85 |
86 | @description('Grant Storage Queue Data Contributor to the user.')
87 | resource queueContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
88 | name: guid(userObjectId, storageAccount.id, storageQueueDataContributorRole.id)
89 | scope: storageAccount
90 | properties: {
91 | principalId: userObjectId
92 | roleDefinitionId: storageQueueDataContributorRole.id
93 | principalType: 'User'
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/pipes-and-filters/images/clouds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/pipes-and-filters/images/clouds.png
--------------------------------------------------------------------------------
/priority-queue/PriorityQueue.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31205.134
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5EDEDBDF-7C69-489E-A9E0-AB1E5B2AD481}"
7 | ProjectSection(SolutionItems) = preProject
8 | Readme.md = Readme.md
9 | EndProjectSection
10 | EndProject
11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PriorityQueueConsumerHigh", "PriorityQueueConsumerHigh\PriorityQueueConsumerHigh.csproj", "{17291F2C-2F43-4910-A1C5-A81B4C05CB7E}"
12 | EndProject
13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PriorityQueueConsumerLow", "PriorityQueueConsumerLow\PriorityQueueConsumerLow.csproj", "{3FEC510A-6AE0-4A49-BB3E-B337814F7F43}"
14 | EndProject
15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PriorityQueueSender", "PriorityQueueSender\PriorityQueueSender.csproj", "{8A281D7D-4481-4333-92DD-271F7A78AA6E}"
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Release|Any CPU = Release|Any CPU
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {17291F2C-2F43-4910-A1C5-A81B4C05CB7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {17291F2C-2F43-4910-A1C5-A81B4C05CB7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {17291F2C-2F43-4910-A1C5-A81B4C05CB7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {17291F2C-2F43-4910-A1C5-A81B4C05CB7E}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {3FEC510A-6AE0-4A49-BB3E-B337814F7F43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {3FEC510A-6AE0-4A49-BB3E-B337814F7F43}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {3FEC510A-6AE0-4A49-BB3E-B337814F7F43}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {3FEC510A-6AE0-4A49-BB3E-B337814F7F43}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {8A281D7D-4481-4333-92DD-271F7A78AA6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {8A281D7D-4481-4333-92DD-271F7A78AA6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {8A281D7D-4481-4333-92DD-271F7A78AA6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {8A281D7D-4481-4333-92DD-271F7A78AA6E}.Release|Any CPU.Build.0 = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(SolutionProperties) = preSolution
37 | HideSolutionNode = FALSE
38 | EndGlobalSection
39 | GlobalSection(ExtensibilityGlobals) = postSolution
40 | SolutionGuid = {E162A7B2-849B-4840-B9D9-D0E10FBE0814}
41 | EndGlobalSection
42 | EndGlobal
43 |
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerHigh/.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
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | v4
5 |
6 |
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | PreserveNewest
16 |
17 |
18 | PreserveNewest
19 | Never
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHighFn.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.WebJobs;
2 | using Microsoft.Extensions.Logging;
3 |
4 | namespace PriorityQueueConsumerHigh
5 | {
6 | public static class PriorityQueueConsumerHighFn
7 | {
8 | [FunctionName("HighPriorityQueueConsumerFunction")]
9 | public static void Run([ServiceBusTrigger("messages", "highPriority", Connection = "ServiceBusConnection")]string highPriorityMessage, ILogger log)
10 | {
11 | log.LogInformation($"C# ServiceBus topic trigger function processed message: {highPriorityMessage}");
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerHigh/Properties/serviceDependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights"
5 | },
6 | "storage1": {
7 | "type": "storage",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerHigh/Properties/serviceDependencies.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights.sdk"
5 | },
6 | "storage1": {
7 | "type": "storage.emulator",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerHigh/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerHigh/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet",
6 | "ServiceBusConnection__fullyQualifiedNamespace": ".servicebus.windows.net"
7 | }
8 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerLow/.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
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | v4
5 | 736bb6a2-68b4-463b-a8fb-3a90cba7cd4f
6 |
7 |
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PreserveNewest
17 |
18 |
19 | PreserveNewest
20 | Never
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLowFn.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.WebJobs;
2 | using Microsoft.Extensions.Logging;
3 |
4 | namespace PriorityQueueConsumerLow
5 | {
6 | public static class PriorityQueueConsumerLowFn
7 | {
8 | [FunctionName("LowPriorityQueueConsumerFunction")]
9 | public static void Run([ServiceBusTrigger("messages", "lowPriority", Connection = "ServiceBusConnection")]string lowPriorityMessage, ILogger log)
10 | {
11 | log.LogInformation($"C# ServiceBus topic trigger function processed message: {lowPriorityMessage}");
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerLow/Properties/serviceDependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights"
5 | },
6 | "storage1": {
7 | "type": "storage",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerLow/Properties/serviceDependencies.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights.sdk"
5 | },
6 | "storage1": {
7 | "type": "storage.emulator",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerLow/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueConsumerLow/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet",
6 | "ServiceBusConnection__fullyQualifiedNamespace": ".servicebus.windows.net"
7 | }
8 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/.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
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/Priority.cs:
--------------------------------------------------------------------------------
1 | namespace PriorityQueueSender
2 | {
3 | public static class Priority
4 | {
5 | public static readonly string High = "highpriority";
6 |
7 | public static readonly string Low = "lowpriority";
8 | }
9 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | v4
5 |
6 |
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | PreserveNewest
16 |
17 |
18 | PreserveNewest
19 | Never
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/PriorityQueueSenderFn.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Azure.Messaging.ServiceBus;
4 | using Microsoft.Azure.WebJobs;
5 |
6 | namespace PriorityQueueSender
7 | {
8 | public static class PriorityQueueSenderFn
9 | {
10 | [FunctionName("PriorityQueueSenderFunction")]
11 | public static async Task Run(
12 | [TimerTrigger("0,30 * * * * *")] TimerInfo myTimer,
13 | [ServiceBus("messages", Connection = "ServiceBusConnection")] IAsyncCollector collector )
14 | {
15 | for (int i = 0; i < 10; i++)
16 | {
17 | var messageId = Guid.NewGuid().ToString();
18 | var lpMessage = new ServiceBusMessage() { MessageId = messageId };
19 | lpMessage.ApplicationProperties["Priority"] = Priority.Low;
20 | lpMessage.Body = BinaryData.FromString($"Low priority message with Id: {messageId}");
21 | await collector.AddAsync(lpMessage);
22 |
23 | messageId = Guid.NewGuid().ToString();
24 | var hpMessage = new ServiceBusMessage() { MessageId = messageId };
25 | hpMessage.ApplicationProperties["Priority"] = Priority.High;
26 | hpMessage.Body = BinaryData.FromString($"High priority message with Id: {messageId}");
27 | await collector.AddAsync(hpMessage);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/Properties/serviceDependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights"
5 | },
6 | "storage1": {
7 | "type": "storage",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/Properties/serviceDependencies.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights.sdk"
5 | },
6 | "storage1": {
7 | "type": "storage.emulator",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/priority-queue/PriorityQueueSender/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet",
6 | "ServiceBusConnection__fullyQualifiedNamespace": "mgransb1.servicebus.windows.net"
7 | }
8 | }
--------------------------------------------------------------------------------
/sharding/README.md:
--------------------------------------------------------------------------------
1 | # Sharding pattern example
2 |
3 | The [Sharding cloud design pattern](https://learn.microsoft.com/azure/architecture/patterns/sharding) does not have code associated with it. You'll find an example of this pattern in the [Example section](https://learn.microsoft.com/azure/architecture/patterns/sharding#example) of that article.
4 |
5 | The scenario presented is how code running in an Azure Web App can use a *lookup* against a managed shard list database to select the correct Azure SQL Database instance for queries. The scenario uses a book cataloging system, with the shard being based off of the books' ISBNs.
6 |
7 | 
8 |
9 | ## Related documentation
10 |
11 | - [Scaling out with Azure SQL Database](https://learn.microsoft.com/azure/azure-sql/database/elastic-scale-introduction)
12 | - [Sharding models in Azure CosmosDB for PostgreSQL](https://learn.microsoft.com/azure/cosmos-db/postgresql/concepts-sharding-models)
13 |
14 | ## Contributions
15 |
16 | Please see our [Contributor guide](../CONTRIBUTING.md).
17 |
18 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact with any additional questions or comments.
19 |
20 | With :heart: from Azure patterns & practices, [Azure Architecture Center](https://azure.com/architecture).
21 |
--------------------------------------------------------------------------------
/sharding/sharding-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/sharding/sharding-example.png
--------------------------------------------------------------------------------
/static-content-hosting/bicep/main.bicep:
--------------------------------------------------------------------------------
1 | targetScope = 'resourceGroup'
2 |
3 | @minLength(5)
4 | @description('Location of the resources. Defaults to resource group location.')
5 | param location string = resourceGroup().location
6 |
7 | @minLength(5)
8 | @description('The globally unique name for the storage account.')
9 | param storageAccountName string
10 |
11 | @minLength(5)
12 | @description('The user assignee object id to be granted with Storage Blob Data Contributor permissions.')
13 | param assigneeObjectId string
14 |
15 |
16 | /*** EXISTING SUBSCRIPTION RESOURCES ***/
17 |
18 | resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview' existing = {
19 | name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
20 | scope: subscription()
21 | }
22 |
23 | /*** EXISTING RESOURCES ***/
24 |
25 | /*** NEW RESOURCES ***/
26 |
27 | @description('The Azure Storage account with static website support enabled and where operations upload static content to.')
28 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
29 | name: storageAccountName
30 | location: location
31 | sku: {
32 | name: 'Standard_LRS'
33 | }
34 | kind: 'StorageV2'
35 | properties: {
36 | accessTier: 'Hot'
37 | allowBlobPublicAccess: false
38 | allowCrossTenantReplication: false
39 | allowSharedKeyAccess: false // This storage account is not configured to allow SaS tokens, since all content in this static content hosting example is public. For content that need to be protected, this field could be enabled.
40 | isLocalUserEnabled: false
41 | isHnsEnabled: false
42 | isNfsV3Enabled: false
43 | isSftpEnabled: false
44 | largeFileSharesState: 'Disabled'
45 | minimumTlsVersion: 'TLS1_2'
46 | publicNetworkAccess: 'Enabled' // In a static content hosting scenario, typically clients are not hosted in your virtual network. However if they were, then you could disable this. In this sample, you'll be accessing this from your workstation.
47 | supportsHttpsTrafficOnly: true
48 | defaultToOAuthAuthentication: true
49 | allowedCopyScope: 'PrivateLink'
50 | networkAcls: {
51 | defaultAction: 'Allow' // For this sample, public Internet access is expected since content is delivered to anonymous client
52 | bypass: 'None'
53 | virtualNetworkRules: []
54 | ipRules: []
55 | }
56 | }
57 | }
58 |
59 | resource storageAccountUserStorageBlobDataContributorRole_roleAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
60 | scope: storageAccount
61 | name: guid(storageAccount.id, storageBlobDataContributorRole.id, assigneeObjectId)
62 | properties: {
63 | roleDefinitionId: storageBlobDataContributorRole.id
64 | description: 'Allows cluster identity to join the nodepool vmss resources to this subnet.'
65 | principalId: assigneeObjectId
66 | principalType: 'User'
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/static-content-hosting/src/index.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Welcome to contoso.com
8 |
9 |
--------------------------------------------------------------------------------
/static-content-hosting/src/static/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Page Not Found
7 |
8 |
--------------------------------------------------------------------------------
/static-content-hosting/src/static/style.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | color: blue;
3 | text-align: center;
4 | }
--------------------------------------------------------------------------------
/static-content-hosting/static-content-hosting-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/static-content-hosting/static-content-hosting-pattern.png
--------------------------------------------------------------------------------
/valet-key/.gitignore:
--------------------------------------------------------------------------------
1 | # Azurite
2 | __blobstorage__/
3 | __azurite*
4 | .mono/
5 |
--------------------------------------------------------------------------------
/valet-key/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-azuretools.vscode-azurefunctions",
4 | "ms-dotnettools.csharp",
5 | "azurite.azurite",
6 | "ms-azuretools.vscode-bicep"
7 | ]
8 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Client/Program.cs:
--------------------------------------------------------------------------------
1 | namespace ValetKey.Client
2 | {
3 | using Azure;
4 | using Azure.Storage.Blobs;
5 | using Microsoft.Extensions.Configuration;
6 | using System.Net.Http.Json;
7 |
8 | public class Program
9 | {
10 | private static readonly HttpClient httpClient = new();
11 |
12 | public static async Task Main()
13 | {
14 | Console.WriteLine("Press any key to run the sample...");
15 | Console.ReadKey();
16 |
17 | var configuration = new ConfigurationBuilder()
18 | .AddJsonFile("appsettings.json", false, false).Build();
19 |
20 | // Get the valet key endpoint from config
21 | var tokenServiceEndpoint = configuration.GetSection("AppSettings:ServiceEndpointUrl").Value;
22 | if (string.IsNullOrWhiteSpace(tokenServiceEndpoint)) throw new InvalidOperationException("Configure AppSettings:ServiceEndpointUrl and run again.");
23 |
24 | // Get the valet key
25 | var blobSas = await GetBlobSasAsync(new Uri(tokenServiceEndpoint));
26 |
27 | var sasUri = new UriBuilder(blobSas!.BlobUri!)
28 | {
29 | Query = blobSas.Signature
30 | };
31 |
32 | var blob = new BlobClient(sasUri.Uri);
33 | try
34 | {
35 | using (var stream = await GetFileToUploadAsync(10))
36 | {
37 | await blob.UploadAsync(stream);
38 | }
39 |
40 | Console.WriteLine("Blob uploaded successful: {0}", blob.Name);
41 | }
42 | catch (RequestFailedException e)
43 | {
44 | // Check for a 403 (Forbidden) error. If the SAS is invalid,
45 | // Azure Storage returns this error.
46 | if (e.Status == 403)
47 | {
48 | Console.WriteLine("Write operation failed for SAS {0}", sasUri);
49 | Console.WriteLine("Additional error information: " + e.Message);
50 | Console.WriteLine();
51 | }
52 | else
53 | {
54 | Console.WriteLine(e.Message);
55 | throw;
56 | }
57 | }
58 |
59 | Console.WriteLine();
60 | Console.WriteLine("Done. Press any key to exit...");
61 | Console.ReadKey();
62 | }
63 |
64 | private static async Task GetBlobSasAsync(Uri tokenUri)
65 | {
66 | return await httpClient.GetFromJsonAsync(tokenUri);
67 | }
68 |
69 | ///
70 | /// Create a sample file containing random bytes of data
71 | ///
72 | private static async Task GetFileToUploadAsync(int sizeMb)
73 | {
74 | var stream = new MemoryStream();
75 |
76 | var rnd = new Random();
77 | var buffer = new byte[1024 * 1024];
78 |
79 | for (int i = 0; i < sizeMb; i++)
80 | {
81 | rnd.NextBytes(buffer);
82 | await stream.WriteAsync(buffer.AsMemory(0, buffer.Length));
83 | }
84 |
85 | stream.Position = 0;
86 |
87 | return stream;
88 | }
89 |
90 | public class StorageEntitySas
91 | {
92 | public Uri? BlobUri { get; set; }
93 | public string? Signature { get; set; }
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Client/ValetKey.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PreserveNewest
17 | true
18 | PreserveNewest
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/valet-key/ValetKey.Client/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSettings": {
3 | "ServiceEndpointUrl": "http://localhost:7071/api/file-services/access"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/.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 | # Azurite
14 | __blobstorage__/
15 | __azurite*
16 | .mono/
17 |
18 | # User-specific files (MonoDevelop/Xamarin Studio)
19 | *.userprefs
20 |
21 | # Build results
22 | [Dd]ebug/
23 | [Dd]ebugPublic/
24 | [Rr]elease/
25 | [Rr]eleases/
26 | x64/
27 | x86/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 |
33 | # Visual Studio 2015 cache/options directory
34 | .vs/
35 | # Uncomment if you have tasks that create the project's static files in wwwroot
36 | #wwwroot/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # DNX
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 |
56 | *_i.c
57 | *_p.c
58 | *_i.h
59 | *.ilk
60 | *.meta
61 | *.obj
62 | *.pch
63 | *.pdb
64 | *.pgc
65 | *.pgd
66 | *.rsp
67 | *.sbr
68 | *.tlb
69 | *.tli
70 | *.tlh
71 | *.tmp
72 | *.tmp_proj
73 | *.log
74 | *.vspscc
75 | *.vssscc
76 | .builds
77 | *.pidb
78 | *.svclog
79 | *.scc
80 |
81 | # Chutzpah Test files
82 | _Chutzpah*
83 |
84 | # Visual C++ cache files
85 | ipch/
86 | *.aps
87 | *.ncb
88 | *.opendb
89 | *.opensdf
90 | *.sdf
91 | *.cachefile
92 | *.VC.db
93 | *.VC.VC.opendb
94 |
95 | # Visual Studio profiler
96 | *.psess
97 | *.vsp
98 | *.vspx
99 | *.sap
100 |
101 | # TFS 2012 Local Workspace
102 | $tf/
103 |
104 | # Guidance Automation Toolkit
105 | *.gpState
106 |
107 | # ReSharper is a .NET coding add-in
108 | _ReSharper*/
109 | *.[Rr]e[Ss]harper
110 | *.DotSettings.user
111 |
112 | # JustCode is a .NET coding add-in
113 | .JustCode
114 |
115 | # TeamCity is a build add-in
116 | _TeamCity*
117 |
118 | # DotCover is a Code Coverage Tool
119 | *.dotCover
120 |
121 | # NCrunch
122 | _NCrunch_*
123 | .*crunch*.local.xml
124 | nCrunchTemp_*
125 |
126 | # MightyMoose
127 | *.mm.*
128 | AutoTest.Net/
129 |
130 | # Web workbench (sass)
131 | .sass-cache/
132 |
133 | # Installshield output folder
134 | [Ee]xpress/
135 |
136 | # DocProject is a documentation generator add-in
137 | DocProject/buildhelp/
138 | DocProject/Help/*.HxT
139 | DocProject/Help/*.HxC
140 | DocProject/Help/*.hhc
141 | DocProject/Help/*.hhk
142 | DocProject/Help/*.hhp
143 | DocProject/Help/Html2
144 | DocProject/Help/html
145 |
146 | # Click-Once directory
147 | publish/
148 |
149 | # Publish Web Output
150 | *.[Pp]ublish.xml
151 | *.azurePubxml
152 | # TODO: Comment the next line if you want to checkin your web deploy settings
153 | # but database connection strings (with potential passwords) will be unencrypted
154 | #*.pubxml
155 | *.publishproj
156 |
157 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
158 | # checkin your Azure Web App publish settings, but sensitive information contained
159 | # in these scripts will be unencrypted
160 | PublishScripts/
161 |
162 | # NuGet Packages
163 | *.nupkg
164 | # The packages folder can be ignored because of Package Restore
165 | **/packages/*
166 | # except build/, which is used as an MSBuild target.
167 | !**/packages/build/
168 | # Uncomment if necessary however generally it will be regenerated when needed
169 | #!**/packages/repositories.config
170 | # NuGet v3's project.json files produces more ignoreable files
171 | *.nuget.props
172 | *.nuget.targets
173 |
174 | # Microsoft Azure Build Output
175 | csx/
176 | *.build.csdef
177 |
178 | # Microsoft Azure Emulator
179 | ecf/
180 | rcf/
181 |
182 | # Windows Store app package directories and files
183 | AppPackages/
184 | BundleArtifacts/
185 | Package.StoreAssociation.xml
186 | _pkginfo.txt
187 |
188 | # Visual Studio cache files
189 | # files ending in .cache can be ignored
190 | *.[Cc]ache
191 | # but keep track of directories ending in .cache
192 | !*.[Cc]ache/
193 |
194 | # Others
195 | ClientBin/
196 | ~$*
197 | *~
198 | *.dbmdl
199 | *.dbproj.schemaview
200 | *.jfm
201 | *.pfx
202 | *.publishsettings
203 | node_modules/
204 | orleans.codegen.cs
205 |
206 | # Since there are multiple workflows, uncomment next line to ignore bower_components
207 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
208 | #bower_components/
209 |
210 | # RIA/Silverlight projects
211 | Generated_Code/
212 |
213 | # Backup & report files from converting an old project file
214 | # to a newer Visual Studio version. Backup files are not needed,
215 | # because we have git ;-)
216 | _UpgradeReport_Files/
217 | Backup*/
218 | UpgradeLog*.XML
219 | UpgradeLog*.htm
220 |
221 | # SQL Server files
222 | *.mdf
223 | *.ldf
224 |
225 | # Business Intelligence projects
226 | *.rdl.data
227 | *.bim.layout
228 | *.bim_*.settings
229 |
230 | # Microsoft Fakes
231 | FakesAssemblies/
232 |
233 | # GhostDoc plugin setting file
234 | *.GhostDoc.xml
235 |
236 | # Node.js Tools for Visual Studio
237 | .ntvs_analysis.dat
238 |
239 | # Visual Studio 6 build log
240 | *.plg
241 |
242 | # Visual Studio 6 workspace options file
243 | *.opt
244 |
245 | # Visual Studio LightSwitch build output
246 | **/*.HTMLClient/GeneratedArtifacts
247 | **/*.DesktopClient/GeneratedArtifacts
248 | **/*.DesktopClient/ModelManifest.xml
249 | **/*.Server/GeneratedArtifacts
250 | **/*.Server/ModelManifest.xml
251 | _Pvt_Extensions
252 |
253 | # Paket dependency manager
254 | .paket/paket.exe
255 | paket-files/
256 |
257 | # FAKE - F# Make
258 | .fake/
259 |
260 | # JetBrains Rider
261 | .idea/
262 | *.sln.iml
263 |
264 | # CodeRush
265 | .cr/
266 |
267 | # Python Tools for Visual Studio (PTVS)
268 | __pycache__/
269 | *.pyc
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-azuretools.vscode-azurefunctions",
4 | "ms-dotnettools.csharp",
5 | "azurite.azurite",
6 | ]
7 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Attach to .NET Functions",
6 | "type": "coreclr",
7 | "request": "attach",
8 | "processId": "${command:azureFunctions.pickProcess}"
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "azureFunctions.deploySubpath": "bin/Release/net8.0/publish",
3 | "azureFunctions.projectLanguage": "C#",
4 | "azureFunctions.projectRuntime": "~4",
5 | "debug.internalConsoleOptions": "neverOpen",
6 | "azureFunctions.preDeployTask": "publish (functions)"
7 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "clean (functions)",
6 | "command": "dotnet",
7 | "args": [
8 | "clean",
9 | "/property:GenerateFullPaths=true",
10 | "/consoleloggerparameters:NoSummary"
11 | ],
12 | "type": "process",
13 | "problemMatcher": "$msCompile"
14 | },
15 | {
16 | "label": "build (functions)",
17 | "command": "dotnet",
18 | "args": [
19 | "build",
20 | "/property:GenerateFullPaths=true",
21 | "/consoleloggerparameters:NoSummary"
22 | ],
23 | "type": "process",
24 | "dependsOn": "clean (functions)",
25 | "group": {
26 | "kind": "build",
27 | "isDefault": true
28 | },
29 | "problemMatcher": "$msCompile"
30 | },
31 | {
32 | "label": "clean release (functions)",
33 | "command": "dotnet",
34 | "args": [
35 | "clean",
36 | "--configuration",
37 | "Release",
38 | "/property:GenerateFullPaths=true",
39 | "/consoleloggerparameters:NoSummary"
40 | ],
41 | "type": "process",
42 | "problemMatcher": "$msCompile"
43 | },
44 | {
45 | "label": "publish (functions)",
46 | "command": "dotnet",
47 | "args": [
48 | "publish",
49 | "--configuration",
50 | "Release",
51 | "/property:GenerateFullPaths=true",
52 | "/consoleloggerparameters:NoSummary"
53 | ],
54 | "type": "process",
55 | "dependsOn": "clean release (functions)",
56 | "problemMatcher": "$msCompile"
57 | },
58 | {
59 | "type": "func",
60 | "dependsOn": "build (functions)",
61 | "options": {
62 | "cwd": "${workspaceFolder}/bin/Debug/net8.0"
63 | },
64 | "command": "host start",
65 | "isBackground": true,
66 | "problemMatcher": "$func-dotnet-watch"
67 | }
68 | ]
69 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/FileServices.cs:
--------------------------------------------------------------------------------
1 | using Azure.Storage.Blobs;
2 | using Azure.Storage.Blobs.Specialized;
3 | using Azure.Storage.Sas;
4 | using Microsoft.Azure.Functions.Worker;
5 | using Microsoft.Azure.Functions.Worker.Http;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace ValetKey.Web
9 | {
10 | public class FileServices(ILoggerFactory loggerFactory)
11 | {
12 | private readonly ILogger _logger = loggerFactory.CreateLogger();
13 |
14 | // WARNING: This route would normally require its own AuthZ so that you are handing out valet keys
15 | // to only authorized clients. For example, using App Service authentication integrated with
16 | // the IdP requirements aligned with your clients.
17 | [Function(nameof(FileServices))]
18 | public async Task RunAsync(
19 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "file-services/access")] HttpRequestData req,
20 | [BlobInput("uploads", Connection = "UploadStorage")] BlobContainerClient blobContainerClient,
21 | CancellationToken cancellationToken
22 | )
23 | {
24 | _logger.LogInformation("Processing new request for a valet key.");
25 |
26 | return await GetSharedAccessReferenceForUploadAsync(blobContainerClient, Guid.NewGuid().ToString(), cancellationToken);
27 | }
28 |
29 | ///
30 | /// Return an access key that allows the caller to upload a file to this specific destination for defined period of time (~three minutes).
31 | ///
32 | private async Task GetSharedAccessReferenceForUploadAsync(BlobContainerClient blobContainerClient, string blobName, CancellationToken cancellationToken)
33 | {
34 | var blobServiceClient = blobContainerClient.GetParentBlobServiceClient();
35 | var blobClient = blobContainerClient.GetBlockBlobClient(blobName);
36 |
37 | var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow.AddMinutes(-3),
38 | DateTimeOffset.UtcNow.AddMinutes(3), cancellationToken);
39 |
40 | // Limit the scope of this SaS token to the following:
41 | // - The specific blob
42 | // - Create permissions only
43 | // - In the next ~three minutes
44 | // - Over HTTPs
45 | var blobSasBuilder = new BlobSasBuilder
46 | {
47 | BlobContainerName = blobContainerClient.Name,
48 | BlobName = blobClient.Name,
49 | Resource = "b",
50 | StartsOn = DateTimeOffset.UtcNow.AddMinutes(-3),
51 | ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(3),
52 | Protocol = SasProtocol.Https
53 | };
54 | blobSasBuilder.SetPermissions(BlobSasPermissions.Create);
55 |
56 | var sas = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName).ToString();
57 |
58 | _logger.LogInformation("Generated user delegated SaS token for {uri} that expires at {expiresOn}.", blobClient.Uri, blobSasBuilder.ExpiresOn);
59 |
60 | return new StorageEntitySas
61 | {
62 | BlobUri = blobClient.Uri,
63 | Signature = sas
64 | };
65 | }
66 |
67 | public class StorageEntitySas
68 | {
69 | public Uri? BlobUri { get; internal set; }
70 | public string? Signature { get; internal set; }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Functions.Worker;
2 | using Microsoft.Extensions.Hosting;
3 |
4 | var host = new HostBuilder()
5 | .ConfigureFunctionsWorkerDefaults(appBuilder =>
6 | {
7 | appBuilder.ConfigureBlobStorageExtension();
8 | })
9 | .Build();
10 |
11 | await host.RunAsync();
12 |
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ValetKey_Web": {
4 | "commandName": "Project",
5 | "commandLineArgs": "--port 7071",
6 | "launchBrowser": false
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/ValetKey.Web.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | PreserveNewest
18 |
19 |
20 | PreserveNewest
21 | Never
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | },
9 | "enableLiveMetricsFilters": true
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/valet-key/ValetKey.Web/local.settings.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
6 | "UploadStorage__blobServiceUri": "https://STORAGE_ACCOUNT_NAME.blob.core.windows.net"
7 | }
8 | }
--------------------------------------------------------------------------------
/valet-key/bicep/main.bicep:
--------------------------------------------------------------------------------
1 | targetScope = 'resourceGroup'
2 |
3 | @minLength(5)
4 | @description('Location of the resources. Defaults to resource group location.')
5 | param location string = resourceGroup().location
6 |
7 | @minLength(36)
8 | @description('The guid of the principal running the valet key generation code. In Azure this would be replaced with the managed identity of the Azure Function, when running locally it will be your user.')
9 | param principalId string
10 |
11 | @minLength(5)
12 | @description('The globally unique name for the storage account.')
13 | param storageAccountName string
14 |
15 | /*** EXISTING RESOURCES ***/
16 |
17 | @description('Built-in Azure RBAC role that is applied to a Storage account to grant "Storage Blob Data Contributor" privileges. Used by the managed identity of the valet key Azure Function as for being able to delegate permissions to create blobs.')
18 | resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
19 | name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
20 | scope: subscription()
21 | }
22 |
23 | @description('Built-in Azure RBAC role that is applied to a Storage account to grant "Storage Blob Delegator" privileges. Used by the managed identity of the valet key Azure Function to manage generate SaS tokens.')
24 | resource storageBlobDelegatorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
25 | name: 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a'
26 | scope: subscription()
27 | }
28 |
29 | /*** NEW RESOURCES ***/
30 |
31 | @description('Workload logs.')
32 | resource workloadLogs 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
33 | name: 'la-logs'
34 | location: location
35 | properties: {
36 | publicNetworkAccessForIngestion: 'Enabled'
37 | publicNetworkAccessForQuery: 'Enabled'
38 | sku: {
39 | name: 'PerGB2018'
40 | }
41 | retentionInDays: 30
42 | features: {
43 | enableLogAccessUsingOnlyResourcePermissions: true
44 | }
45 | workspaceCapping: {
46 | dailyQuotaGb: -1
47 | }
48 | }
49 | }
50 |
51 | @description('The Azure Storage account which will be where authorized clients upload large blobs to. The Azure Function will hand out scoped, time-limited SaS tokens for this blobs in this account.')
52 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
53 | name: storageAccountName
54 | location: location
55 | sku: {
56 | name: 'Standard_LRS'
57 | }
58 | kind: 'StorageV2'
59 | properties: {
60 | accessTier: 'Hot'
61 | allowBlobPublicAccess: false
62 | allowCrossTenantReplication: false
63 | allowSharedKeyAccess: false // Only managed identity allowed, we needed to change the way to generate SAS token using UserDelegationKey
64 | isLocalUserEnabled: false
65 | isHnsEnabled: false
66 | isNfsV3Enabled: false
67 | isSftpEnabled: false
68 | largeFileSharesState: 'Disabled'
69 | minimumTlsVersion: 'TLS1_2'
70 | publicNetworkAccess: 'Enabled' // In a valet key scenario, typically clients are not hosted in your virtual network. However if they were, then you could disable this. In this sample, you'll be accessing this from your workstation.
71 | supportsHttpsTrafficOnly: true
72 | defaultToOAuthAuthentication: true
73 | allowedCopyScope: 'PrivateLink'
74 | sasPolicy: {
75 | expirationAction: 'Log'
76 | sasExpirationPeriod: '00.00:10:00' // Log the creation of SaS tokens over 10 minutes long
77 | }
78 | keyPolicy: {
79 | keyExpirationPeriodInDays: 10 // Storage account key isn't used, require agressive rotation
80 | }
81 | networkAcls: {
82 | defaultAction: 'Allow' // For this sample, public Internet access is expected
83 | bypass: 'None'
84 | virtualNetworkRules: []
85 | ipRules: []
86 | }
87 | }
88 |
89 | resource blobContainers 'blobServices' = {
90 | name: 'default'
91 |
92 | @description('The blob container that SaS tokens will be generated for.')
93 | resource uploadsContainer 'containers' = {
94 | name: 'uploads'
95 | }
96 | }
97 | }
98 |
99 | @description('Enable access logs on blob storage.')
100 | resource azureStorageBlobAccessLogs 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
101 | name: 'default'
102 | scope: storageAccount::blobContainers
103 | properties: {
104 | logs: [
105 | {
106 | categoryGroup: 'allLogs'
107 | enabled: true
108 | }
109 | ]
110 | workspaceId: workloadLogs.id
111 | }
112 | }
113 |
114 | @description('SaS tokens are created at the account level, allow our identified principal the permissions necessary to create those.')
115 | resource blobUploadStorageDelegator 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
116 | name: guid(storageAccount.id, storageBlobDelegatorRole.id, principalId)
117 | scope: storageAccount
118 | properties: {
119 | principalId: principalId
120 | roleDefinitionId: storageBlobDelegatorRole.id
121 | principalType: 'User' // 'ServicePrincipal' if this was a managed identity
122 | description: 'Allows this Microsoft Entra principal to create Entra ID-signed SaS tokens for this storage account.'
123 | }
124 | }
125 |
126 | @description('User delegation requires the user doing the delegating to SaS to also have the permissions being delgated. So scoping a Data Contributor to the container. In this scenario, technically this principal only needs permissions to create blobs.')
127 | resource blobContributorUploadStorage 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
128 | name: guid(storageAccount::blobContainers::uploadsContainer.id, storageBlobDataContributorRole.id, principalId)
129 | scope: storageAccount::blobContainers::uploadsContainer
130 | properties: {
131 | principalId: principalId
132 | roleDefinitionId: storageBlobDataContributorRole.id
133 | principalType: 'User' // 'ServicePrincipal' if this was a managed identity
134 | description: 'Allows this Microsoft Entra principal to manage blobs in this storage container.'
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/valet-key/valet-key-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mspnp/cloud-design-patterns/ec4c70c30df8012150ac4fe02486a7508bf7ed3a/valet-key/valet-key-example.png
--------------------------------------------------------------------------------
/valet-key/valet-key.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.5.2.0
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValetKey.Client", "ValetKey.Client\ValetKey.Client.csproj", "{E04AE64E-BF28-F956-5423-989F1F266BD9}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValetKey.Web", "ValetKey.Web\ValetKey.Web.csproj", "{576BFECC-F315-A089-0A93-BF9D5845EB04}"
8 | EndProject
9 | Global
10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
11 | Debug|Any CPU = Debug|Any CPU
12 | Release|Any CPU = Release|Any CPU
13 | EndGlobalSection
14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
15 | {E04AE64E-BF28-F956-5423-989F1F266BD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
16 | {E04AE64E-BF28-F956-5423-989F1F266BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
17 | {E04AE64E-BF28-F956-5423-989F1F266BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
18 | {E04AE64E-BF28-F956-5423-989F1F266BD9}.Release|Any CPU.Build.0 = Release|Any CPU
19 | {576BFECC-F315-A089-0A93-BF9D5845EB04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {576BFECC-F315-A089-0A93-BF9D5845EB04}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {576BFECC-F315-A089-0A93-BF9D5845EB04}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {576BFECC-F315-A089-0A93-BF9D5845EB04}.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 = {FE8353B1-C363-4C0D-8F2D-64ECAA853FDD}
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------