├── .devcontainer
├── Dockerfile
├── devcontainer.json
└── first-run-notice.txt
├── .github
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── azure.yaml
├── http.sln
├── http
├── .gitignore
├── Program.cs
├── Properties
│ ├── launchSettings.json
│ ├── serviceDependencies.json
│ └── serviceDependencies.local.json
├── host.json
├── http.csproj
├── httpGetFunction.cs
├── httpPostBodyFunction.cs
├── test.http
└── testdata.json
└── infra
├── abbreviations.json
├── app
├── api.bicep
├── rbac.bicep
├── storage-PrivateEndpoint.bicep
└── vnet.bicep
├── main.bicep
└── main.parameters.json
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/vscode/devcontainers/universal:latest
2 |
3 | # Copy custom first notice message.
4 | COPY first-run-notice.txt /tmp/staging/
5 | RUN sudo mv -f /tmp/staging/first-run-notice.txt /usr/local/etc/vscode-dev-containers/ \
6 | && sudo rm -rf /tmp/staging
7 |
8 | # Install PowerShell 7.x
9 | RUN sudo apt-get update \
10 | && sudo apt-get install -y wget apt-transport-https software-properties-common \
11 | && wget -q https://packages.microsoft.com/config/ubuntu/$(. /etc/os-release && echo $VERSION_ID)/packages-microsoft-prod.deb \
12 | && sudo dpkg -i packages-microsoft-prod.deb \
13 | && sudo apt-get update \
14 | && sudo apt-get install -y powershell
15 |
16 | # Install Azure Functions Core Tools
17 | RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \
18 | && sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \
19 | && sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-$(lsb_release -cs)-prod $(lsb_release -cs) main" > /etc/apt/sources.list.d/dotnetdev.list' \
20 | && sudo apt-get update \
21 | && sudo apt-get install -y azure-functions-core-tools-4
22 |
23 | # Install Azure Developer CLI
24 | RUN curl -fsSL https://aka.ms/install-azd.sh | bash
25 |
26 | # Install mechanical-markdown for quickstart validations
27 | RUN pip install mechanical-markdown
28 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Functions Quickstarts Codespace",
3 | "dockerFile": "Dockerfile",
4 | "features": {
5 | "azure-cli": "latest"
6 | },
7 | "customizations": {
8 | "vscode": {
9 | "extensions": [
10 | "ms-azuretools.vscode-bicep",
11 | "ms-azuretools.vscode-docker",
12 | "ms-azuretools.vscode-azurefunctions",
13 | "GitHub.copilot",
14 | "humao.rest-client"
15 | ]
16 | }
17 | },
18 | "mounts": [
19 | // Mount docker-in-docker library volume
20 | "source=codespaces-linux-var-lib-docker,target=/var/lib/docker,type=volume"
21 | ],
22 | // Always run image-defined docker-init.sh to enable docker-in-docker
23 | "overrideCommand": false,
24 | "remoteUser": "codespace",
25 | "runArgs": [
26 | // Enable ptrace-based debugging for Go in container
27 | "--cap-add=SYS_PTRACE",
28 | "--security-opt",
29 | "seccomp=unconfined",
30 |
31 | // Enable docker-in-docker configuration
32 | "--init",
33 | "--privileged"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/.devcontainer/first-run-notice.txt:
--------------------------------------------------------------------------------
1 | 👋 Welcome to the Functions Codespace! You are on the Functions Quickstarts image.
2 | It includes everything needed to run through our tutorials and quickstart applications.
3 |
4 | 📚 Functions docs can be found at: https://learn.microsoft.com/en-us/azure/azure-functions/
5 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
4 | > Please provide us with the following information:
5 | > ---------------------------------------------------------------
6 |
7 | ### This issue is for a: (mark with an `x`)
8 | ```
9 | - [ ] bug report -> please search issues before submitting
10 | - [ ] feature request
11 | - [ ] documentation issue or request
12 | - [ ] regression (a behavior that used to work and stopped in a new release)
13 | ```
14 |
15 | ### Minimal steps to reproduce
16 | >
17 |
18 | ### Any log messages given by the failure
19 | >
20 |
21 | ### Expected/desired behavior
22 | >
23 |
24 | ### OS and Version?
25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?)
26 |
27 | ### Versions
28 | >
29 |
30 | ### Mention any other details that might be useful
31 |
32 | > ---------------------------------------------------------------
33 | > Thanks! We'll be in touch soon.
34 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Purpose
2 |
3 | * ...
4 |
5 | ## Does this introduce a breaking change?
6 |
7 | ```
8 | [ ] Yes
9 | [ ] No
10 | ```
11 |
12 | ## Pull Request Type
13 | What kind of change does this Pull Request introduce?
14 |
15 |
16 | ```
17 | [ ] Bugfix
18 | [ ] Feature
19 | [ ] Code style update (formatting, local variables)
20 | [ ] Refactoring (no functional changes, no api changes)
21 | [ ] Documentation content changes
22 | [ ] Other... Please describe:
23 | ```
24 |
25 | ## How to Test
26 | * Get the code
27 |
28 | ```
29 | git clone [repo-address]
30 | cd [repo-name]
31 | git checkout [branch-name]
32 | npm install
33 | ```
34 |
35 | * Test the code
36 |
37 | ```
38 | ```
39 |
40 | ## What to Check
41 | Verify that the following are valid
42 | * ...
43 |
44 | ## Other Information
45 |
--------------------------------------------------------------------------------
/.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 | # Azurite config files
8 | __azurite*
9 | __blobstorage*
10 | __queue*
11 | __table*
12 |
13 | # User-specific files
14 | *.suo
15 | *.user
16 | *.userosscache
17 | *.sln.docstates
18 |
19 | # User-specific files (MonoDevelop/Xamarin Studio)
20 | *.userprefs
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 |
34 | # Visual Studio 2015 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # MSTest test Results
40 | [Tt]est[Rr]esult*/
41 | [Bb]uild[Ll]og.*
42 |
43 | # NUNIT
44 | *.VisualState.xml
45 | TestResult.xml
46 |
47 | # Build Results of an ATL Project
48 | [Dd]ebugPS/
49 | [Rr]eleasePS/
50 | dlldata.c
51 |
52 | # DNX
53 | project.lock.json
54 | project.fragment.lock.json
55 | artifacts/
56 |
57 | *_i.c
58 | *_p.c
59 | *_i.h
60 | *.ilk
61 | *.meta
62 | *.obj
63 | *.pch
64 | *.pdb
65 | *.pgc
66 | *.pgd
67 | *.rsp
68 | *.sbr
69 | *.tlb
70 | *.tli
71 | *.tlh
72 | *.tmp
73 | *.tmp_proj
74 | *.log
75 | *.vspscc
76 | *.vssscc
77 | .builds
78 | *.pidb
79 | *.svclog
80 | *.scc
81 |
82 | # Chutzpah Test files
83 | _Chutzpah*
84 |
85 | # Visual C++ cache files
86 | ipch/
87 | *.aps
88 | *.ncb
89 | *.opendb
90 | *.opensdf
91 | *.sdf
92 | *.cachefile
93 | *.VC.db
94 | *.VC.VC.opendb
95 |
96 | # Visual Studio profiler
97 | *.psess
98 | *.vsp
99 | *.vspx
100 | *.sap
101 |
102 | # TFS 2012 Local Workspace
103 | $tf/
104 |
105 | # Guidance Automation Toolkit
106 | *.gpState
107 |
108 | # ReSharper is a .NET coding add-in
109 | _ReSharper*/
110 | *.[Rr]e[Ss]harper
111 | *.DotSettings.user
112 |
113 | # JustCode is a .NET coding add-in
114 | .JustCode
115 |
116 | # TeamCity is a build add-in
117 | _TeamCity*
118 |
119 | # DotCover is a Code Coverage Tool
120 | *.dotCover
121 |
122 | # NCrunch
123 | _NCrunch_*
124 | .*crunch*.local.xml
125 | nCrunchTemp_*
126 |
127 | # MightyMoose
128 | *.mm.*
129 | AutoTest.Net/
130 |
131 | # Web workbench (sass)
132 | .sass-cache/
133 |
134 | # Installshield output folder
135 | [Ee]xpress/
136 |
137 | # DocProject is a documentation generator add-in
138 | DocProject/buildhelp/
139 | DocProject/Help/*.HxT
140 | DocProject/Help/*.HxC
141 | DocProject/Help/*.hhc
142 | DocProject/Help/*.hhk
143 | DocProject/Help/*.hhp
144 | DocProject/Help/Html2
145 | DocProject/Help/html
146 |
147 | # Click-Once directory
148 | publish/
149 |
150 | # Publish Web Output
151 | *.[Pp]ublish.xml
152 | *.azurePubxml
153 | # TODO: Comment the next line if you want to checkin your web deploy settings
154 | # but database connection strings (with potential passwords) will be unencrypted
155 | #*.pubxml
156 | *.publishproj
157 |
158 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
159 | # checkin your Azure Web App publish settings, but sensitive information contained
160 | # in these scripts will be unencrypted
161 | PublishScripts/
162 |
163 | # NuGet Packages
164 | *.nupkg
165 | # The packages folder can be ignored because of Package Restore
166 | **/packages/*
167 | # except build/, which is used as an MSBuild target.
168 | !**/packages/build/
169 | # Uncomment if necessary however generally it will be regenerated when needed
170 | #!**/packages/repositories.config
171 | # NuGet v3's project.json files produces more ignoreable files
172 | *.nuget.props
173 | *.nuget.targets
174 |
175 | # Microsoft Azure Build Output
176 | csx/
177 | *.build.csdef
178 |
179 | # Microsoft Azure Emulator
180 | ecf/
181 | rcf/
182 |
183 | # Windows Store app package directories and files
184 | AppPackages/
185 | BundleArtifacts/
186 | Package.StoreAssociation.xml
187 | _pkginfo.txt
188 |
189 | # Visual Studio cache files
190 | # files ending in .cache can be ignored
191 | *.[Cc]ache
192 | # but keep track of directories ending in .cache
193 | !*.[Cc]ache/
194 |
195 | # Others
196 | ClientBin/
197 | ~$*
198 | *~
199 | *.dbmdl
200 | *.dbproj.schemaview
201 | *.jfm
202 | *.pfx
203 | *.publishsettings
204 | node_modules/
205 | orleans.codegen.cs
206 |
207 | # Since there are multiple workflows, uncomment next line to ignore bower_components
208 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
209 | #bower_components/
210 |
211 | # RIA/Silverlight projects
212 | Generated_Code/
213 |
214 | # Backup & report files from converting an old project file
215 | # to a newer Visual Studio version. Backup files are not needed,
216 | # because we have git ;-)
217 | _UpgradeReport_Files/
218 | Backup*/
219 | UpgradeLog*.XML
220 | UpgradeLog*.htm
221 |
222 | # SQL Server files
223 | *.mdf
224 | *.ldf
225 |
226 | # Business Intelligence projects
227 | *.rdl.data
228 | *.bim.layout
229 | *.bim_*.settings
230 |
231 | # Microsoft Fakes
232 | FakesAssemblies/
233 |
234 | # GhostDoc plugin setting file
235 | *.GhostDoc.xml
236 |
237 | # Node.js Tools for Visual Studio
238 | .ntvs_analysis.dat
239 |
240 | # Visual Studio 6 build log
241 | *.plg
242 |
243 | # Visual Studio 6 workspace options file
244 | *.opt
245 |
246 | # Visual Studio LightSwitch build output
247 | **/*.HTMLClient/GeneratedArtifacts
248 | **/*.DesktopClient/GeneratedArtifacts
249 | **/*.DesktopClient/ModelManifest.xml
250 | **/*.Server/GeneratedArtifacts
251 | **/*.Server/ModelManifest.xml
252 | _Pvt_Extensions
253 |
254 | # Paket dependency manager
255 | .paket/paket.exe
256 | paket-files/
257 |
258 | # FAKE - F# Make
259 | .fake/
260 |
261 | # JetBrains Rider
262 | .idea/
263 | *.sln.iml
264 |
265 | # CodeRush
266 | .cr/
267 |
268 | # Python Tools for Visual Studio (PTVS)
269 | __pycache__/
270 | *.pyc
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-azuretools.vscode-azurefunctions",
4 | "ms-dotnettools.csharp"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "azureFunctions.deploySubpath": "http/bin/Release/net8.0/publish",
3 | "azureFunctions.projectLanguage": "C#",
4 | "azureFunctions.projectRuntime": "~4",
5 | "debug.internalConsoleOptions": "neverOpen",
6 | "azureFunctions.preDeployTask": "publish (functions)"
7 | }
--------------------------------------------------------------------------------
/.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}/http"
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}/http"
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}/http"
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}/http"
68 | }
69 | },
70 | {
71 | "type": "func",
72 | "dependsOn": "build (functions)",
73 | "options": {
74 | "cwd": "${workspaceFolder}/http/bin/Debug/net8.0"
75 | },
76 | "command": "host start",
77 | "isBackground": true,
78 | "problemMatcher": "$func-dotnet-watch"
79 | }
80 | ]
81 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [project-title] Changelog
2 |
3 |
4 | # x.y.z (yyyy-mm-dd)
5 |
6 | *Features*
7 | * ...
8 |
9 | *Bug Fixes*
10 | * ...
11 |
12 | *Breaking Changes*
13 | * ...
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to [project-title]
2 |
3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
6 |
7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
9 | provided by the bot. You will only need to do this once across all repos using our CLA.
10 |
11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
14 |
15 | - [Code of Conduct](#coc)
16 | - [Issues and Bugs](#issue)
17 | - [Feature Requests](#feature)
18 | - [Submission Guidelines](#submit)
19 |
20 | ## Code of Conduct
21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
22 |
23 | ## Found an Issue?
24 | If you find a bug in the source code or a mistake in the documentation, you can help us by
25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can
26 | [submit a Pull Request](#submit-pr) with a fix.
27 |
28 | ## Want a Feature?
29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub
30 | Repository. If you would like to *implement* a new feature, please submit an issue with
31 | a proposal for your work first, to be sure that we can use it.
32 |
33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
34 |
35 | ## Submission Guidelines
36 |
37 | ### Submitting an Issue
38 | Before you submit an issue, search the archive, maybe your question was already answered.
39 |
40 | If your issue appears to be a bug, and hasn't been reported, open a new issue.
41 | Help us to maximize the effort we can spend fixing issues and adding new
42 | features, by not reporting duplicate issues. Providing the following information will increase the
43 | chances of your issue being dealt with quickly:
44 |
45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
46 | * **Version** - what version is affected (e.g. 0.1.2)
47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you
48 | * **Browsers and Operating System** - is this a problem with all browsers?
49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps
50 | * **Related Issues** - has a similar issue been reported before?
51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
52 | causing the problem (line of code or commit)
53 |
54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new].
55 |
56 | ### Submitting a Pull Request (PR)
57 | Before you submit your Pull Request (PR) consider the following guidelines:
58 |
59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR
60 | that relates to your submission. You don't want to duplicate effort.
61 |
62 | * Make your changes in a new git fork:
63 |
64 | * Commit your changes using a descriptive commit message
65 | * Push your fork to GitHub:
66 | * In GitHub, create a pull request
67 | * If we suggest changes then:
68 | * Make the required updates.
69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request):
70 |
71 | ```shell
72 | git rebase master -i
73 | git push -f
74 | ```
75 |
76 | That's it! Thank you for your contribution!
77 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
17 |
18 | # Azure Functions C# HTTP Trigger using Azure Developer CLI
19 |
20 | This template repository contains an HTTP trigger reference sample for functions written in C# (isolated process mode) and deployed to Azure using the Azure Developer CLI (`azd`). The sample uses managed identity and a virtual network to make sure deployment is secure by default. You can opt out of a VNet being used in the sample by setting VNET_ENABLED to false in the parameters.
21 |
22 | This source code supports the article [Quickstart: Create and deploy functions to Azure Functions using the Azure Developer CLI](https://learn.microsoft.com/azure/azure-functions/create-first-function-azure-developer-cli?pivots=programming-language-dotnet).
23 |
24 | This project is designed to run on your local computer. You can also use GitHub Codespaces:
25 |
26 | [](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=836901178)
27 |
28 | This codespace is already configured with the required tools to complete this tutorial using either `azd` or Visual Studio Code. If you're working a codespace, skip down to [Prepare your local environment](#prepare-your-local-environment).
29 |
30 | ## Prerequisites
31 |
32 | + [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
33 | + [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?pivots=programming-language-csharp#install-the-azure-functions-core-tools)
34 | + To use Visual Studio to run and debug locally:
35 | + [Visual Studio 2022](https://visualstudio.microsoft.com/vs/).
36 | + Make sure to select the **Azure development** workload during installation.
37 | + To use Visual Studio Code to run and debug locally:
38 | + [Visual Studio Code](https://code.visualstudio.com/)
39 | + [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
40 |
41 | ## Initialize the local project
42 |
43 | You can initialize a project from this `azd` template in one of these ways:
44 |
45 | + Use this `azd init` command from an empty local (root) folder:
46 |
47 | ```shell
48 | azd init --template functions-quickstart-dotnet-azd
49 | ```
50 |
51 | Supply an environment name, such as `flexquickstart` when prompted. In `azd`, the environment is used to maintain a unique deployment context for your app.
52 |
53 | + Clone the GitHub template repository locally using the `git clone` command:
54 |
55 | ```shell
56 | git clone https://github.com/Azure-Samples/functions-quickstart-dotnet-azd.git
57 | cd functions-quickstart-dotnet-azd
58 | ```
59 |
60 | You can also clone the repository from your own fork in GitHub.
61 |
62 | ## Prepare your local environment
63 |
64 | Navigate to the `http` app folder and create a file in that folder named _local.settings.json_ that contains this JSON data:
65 |
66 | ```json
67 | {
68 | "IsEncrypted": false,
69 | "Values": {
70 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
71 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
72 | }
73 | }
74 | ```
75 |
76 | ## Run your app from the terminal
77 |
78 | 1. From the `http` folder, run this command to start the Functions host locally:
79 |
80 | ```shell
81 | func start
82 | ```
83 |
84 | 1. From your HTTP test tool in a new terminal (or from your browser), call the HTTP GET endpoint:
85 |
86 | 1. Test the HTTP POST trigger with a payload using your favorite secure HTTP test tool.
87 |
88 | **Cmd\bash**
89 |
90 | This example runs from the `http` folder and uses the `curl` tool with payload data from the [`testdata.json`](./http/testdata.json) project file:
91 |
92 | ```shell
93 | curl -i http://localhost:7071/api/httppost -H "Content-Type: text/json" -d @testdata.json
94 | ```
95 |
96 | **PowerShell**
97 |
98 | You can also use this `Invoke-RestMethod` cmdlet in PowerShell from the `http` folder:
99 |
100 | ```powershell
101 | Invoke-RestMethod -Uri http://localhost:7071/api/httppost -Method Post -ContentType "application/json" -InFile "testdata.json"
102 | ```
103 |
104 | 1. When you're done, press Ctrl+C in the terminal window to stop the `func.exe` host process.
105 |
106 | ## Run your app using Visual Studio Code
107 |
108 | 1. Open the `http` app folder in a new terminal.
109 | 1. Run the `code .` code command to open the project in Visual Studio Code.
110 | 1. In the command palette (F1), type `Azurite: Start`, which enables debugging without warnings.
111 | 1. Press **Run/Debug (F5)** to run in the debugger. Select **Debug anyway** if prompted about local emulator not running.
112 | 1. Send GET and POST requests to the `httpget` and `httppost` endpoints respectively using your HTTP test tool (or browser for `httpget`). If you have the [RestClient](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension installed, you can execute requests directly from the [`test.http`](./http/test.http) project file.
113 |
114 | ## Run your app using Visual Studio
115 |
116 | 1. Open the `http.sln` solution file in Visual Studio.
117 | 1. Press **Run/F5** to run in the debugger. Make a note of the `localhost` URL endpoints, including the port, which might not be `7071`.
118 | 1. Open the [`test.http`](./http/test.http) project file, update the port on the `localhost` URL (if needed), and then use the built-in HTTP client to call the `httpget` and `httppost` endpoints.
119 |
120 | ## Source Code
121 |
122 | The function code for the `httpget` and `httppost` endpoints are defined in [`httpGetFunction.cs`](./http/httpGetFunction.cs) and [`httpPostBodyFunction.cs`](./http/httpPostBodyFunction.cs), respectively. The `Function` attribute applied to the async `Run` method sets the name of the function endpoint.
123 |
124 | This code shows an HTTP GET (webhook):
125 |
126 | ```csharp
127 | [Function("httpget")]
128 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")]
129 | HttpRequest req,
130 | string name)
131 | {
132 | var returnValue = string.IsNullOrEmpty(name)
133 | ? "Hello, World."
134 | : $"Hello, {name}.";
135 |
136 | _logger.LogInformation($"C# HTTP trigger function processed a request for {returnValue}.");
137 |
138 | return new OkObjectResult(returnValue);
139 | }
140 | ```
141 |
142 | This code shows the HTTP POST that received a JSON formatted `person` object in the request body and returns a message using the values in the payload:
143 |
144 | ```csharp
145 | [Function("httppost")]
146 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
147 | [FromBody] Person person)
148 | {
149 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for url {req.Body}");
150 |
151 | if (string.IsNullOrEmpty(person.Name) | string.IsNullOrEmpty(person.Age.ToString()) | person.Age == 0)
152 | {
153 | _logger.LogInformation("C# HTTP POST trigger function processed a request with no name/age provided.");
154 | return new BadRequestObjectResult("Please provide both name and age in the request body.");
155 | }
156 |
157 | var returnValue = $"Hello, {person.Name}! You are {person.Age} years old.";
158 |
159 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for {person.Name} who is {person.Age} years old.");
160 | return new OkObjectResult(returnValue);
161 | }
162 | ```
163 |
164 | ## Deploy to Azure
165 |
166 | Run this command to provision the function app, with any required Azure resources, and deploy your code:
167 |
168 | ```shell
169 | azd up
170 | ```
171 |
172 | By default, this sample prompts to enable a virtual network for enhanced security. If you want to deploy without a virtual network without prompting, you can configure `VNET_ENABLED` to `false` before running `azd up`:
173 |
174 | ```bash
175 | azd env set VNET_ENABLED false
176 | azd up
177 | ```
178 |
179 | You're prompted to supply these required deployment parameters:
180 |
181 | | Parameter | Description |
182 | | ---- | ---- |
183 | | _Environment name_ | An environment that's used to maintain a unique deployment context for your app. You won't be prompted if you created the local project using `azd init`.|
184 | | _Azure subscription_ | Subscription in which your resources are created.|
185 | | _Azure location_ | Azure region in which to create the resource group that contains the new Azure resources. Only regions that currently support the Flex Consumption plan are shown.|
186 |
187 | After publish completes successfully, `azd` provides you with the URL endpoints of your new functions, but without the function key values required to access the endpoints. To learn how to obtain these same endpoints along with the required function keys, see [Invoke the function on Azure](https://learn.microsoft.com/azure/azure-functions/create-first-function-azure-developer-cli?pivots=programming-language-dotnet#invoke-the-function-on-azure) in the companion article [Quickstart: Create and deploy functions to Azure Functions using the Azure Developer CLI](https://learn.microsoft.com/azure/azure-functions/create-first-function-azure-developer-cli?pivots=programming-language-dotnet).
188 |
189 | ## Redeploy your code
190 |
191 | You can run the `azd up` command as many times as you need to both provision your Azure resources and deploy code updates to your function app.
192 |
193 | >[!NOTE]
194 | >Deployed code files are always overwritten by the latest deployment package.
195 |
196 | ## Clean up resources
197 |
198 | When you're done working with your function app and related resources, you can use this command to delete the function app and its related resources from Azure and avoid incurring any further costs:
199 |
200 | ```shell
201 | azd down
202 | ```
203 |
--------------------------------------------------------------------------------
/azure.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
2 |
3 | name: starter-dotnet8-flex-func
4 | metadata:
5 | template: functions-quickstart-dotnet-azd@1.1.0
6 | services:
7 | api:
8 | project: ./http/
9 | language: dotnet
10 | host: function
11 |
--------------------------------------------------------------------------------
/http.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.35027.167
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "http", "http\http.csproj", "{32C6DAE7-2329-47AB-8551-2A9EF0353C9C}"
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 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.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 = {F5C50B53-B73E-4C2E-AF58-F034B65DEFAD}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/http/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 |
334 | # Tabs Studio
335 | *.tss
336 |
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 |
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 |
346 | # OpenCover UI analysis results
347 | OpenCover/
348 |
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 |
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 |
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 |
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 |
361 | # Local History for Visual Studio
362 | .localhistory/
363 |
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 |
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 |
387 | # Local History for Visual Studio Code
388 | .history/
389 |
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 |
397 | # JetBrains Rider
398 | *.sln.iml
399 |
--------------------------------------------------------------------------------
/http/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Functions.Worker;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 |
5 | var host = new HostBuilder()
6 | .ConfigureFunctionsWebApplication()
7 | .ConfigureServices(services =>
8 | {
9 | services.AddApplicationInsightsTelemetryWorkerService();
10 | services.ConfigureFunctionsApplicationInsights();
11 | })
12 | .Build();
13 |
14 | host.Run();
15 |
--------------------------------------------------------------------------------
/http/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "FunctionHttp": {
4 | "commandName": "Project",
5 | "commandLineArgs": "--port 7129",
6 | "launchBrowser": false
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/http/Properties/serviceDependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "appInsights1": {
4 | "type": "appInsights"
5 | },
6 | "storage1": {
7 | "type": "storage",
8 | "connectionId": "AzureWebJobsStorage"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/http/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 | }
--------------------------------------------------------------------------------
/http/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 | }
--------------------------------------------------------------------------------
/http/http.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | v4
5 | Exe
6 | enable
7 | enable
8 | 09bd123b-3401-4507-b92c-0b283b95b537
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 |
--------------------------------------------------------------------------------
/http/httpGetFunction.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.Azure.Functions.Worker;
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace Company.Function
7 | {
8 | public class httpGetFunction
9 | {
10 | private readonly ILogger _logger;
11 |
12 | public httpGetFunction(ILoggerFactory loggerFactory)
13 | {
14 | _logger = loggerFactory.CreateLogger();
15 | }
16 |
17 | [Function("httpget")]
18 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")]
19 | HttpRequest req,
20 | string name)
21 | {
22 | var returnValue = string.IsNullOrEmpty(name)
23 | ? "Hello, World."
24 | : $"Hello, {name}.";
25 |
26 | _logger.LogInformation($"C# HTTP trigger function processed a request for {returnValue}.");
27 |
28 | return new OkObjectResult(returnValue);
29 | }
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/http/httpPostBodyFunction.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.AspNetCore.Mvc;
4 | using Microsoft.Azure.Functions.Worker;
5 | using Microsoft.Extensions.Logging;
6 | using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute;
7 |
8 | namespace Company.Function
9 | {
10 | public class HttpPostBody
11 | {
12 | private readonly ILogger _logger;
13 |
14 | public HttpPostBody(ILoggerFactory loggerFactory)
15 | {
16 | _logger = loggerFactory.CreateLogger();
17 | }
18 |
19 | [Function("httppost")]
20 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
21 | [FromBody] Person person)
22 | {
23 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for url {req.Body}");
24 |
25 | if (string.IsNullOrEmpty(person.Name) | string.IsNullOrEmpty(person.Age.ToString()) | person.Age == 0)
26 | {
27 | _logger.LogInformation("C# HTTP POST trigger function processed a request with no name/age provided.");
28 | return new BadRequestObjectResult("Please provide both name and age in the request body.");
29 | }
30 |
31 | var returnValue = $"Hello, {person.Name}! You are {person.Age} years old.";
32 |
33 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for {person.Name} who is {person.Age} years old.");
34 | return new OkObjectResult(returnValue);
35 | }
36 | }
37 | public record Person([property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("age")] int Age);
38 | }
--------------------------------------------------------------------------------
/http/test.http:
--------------------------------------------------------------------------------
1 | # Tip: You might need to modify the port value on the
2 | # localhost URL when debugging in Visual Studio.
3 |
4 | GET http://localhost:7071/api/httpget HTTP/1.1
5 |
6 | ###
7 |
8 | GET http://localhost:7071/api/httpget?name=World HTTP/1.1
9 |
10 | ###
11 |
12 | POST http://localhost:7071/api/httppost HTTP/1.1
13 | content-type: application/json
14 |
15 | {
16 | "name": "Awesome Developer",
17 | "age": 25
18 | }
19 |
--------------------------------------------------------------------------------
/http/testdata.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Awesome Developer",
3 | "age": 25
4 | }
--------------------------------------------------------------------------------
/infra/abbreviations.json:
--------------------------------------------------------------------------------
1 | {
2 | "analysisServicesServers": "as",
3 | "apiManagementService": "apim-",
4 | "appConfigurationConfigurationStores": "appcs-",
5 | "appManagedEnvironments": "cae-",
6 | "appContainerApps": "ca-",
7 | "authorizationPolicyDefinitions": "policy-",
8 | "automationAutomationAccounts": "aa-",
9 | "blueprintBlueprints": "bp-",
10 | "blueprintBlueprintsArtifacts": "bpa-",
11 | "cacheRedis": "redis-",
12 | "cdnProfiles": "cdnp-",
13 | "cdnProfilesEndpoints": "cdne-",
14 | "cognitiveServicesAccounts": "cog-",
15 | "cognitiveServicesFormRecognizer": "cog-fr-",
16 | "cognitiveServicesTextAnalytics": "cog-ta-",
17 | "computeAvailabilitySets": "avail-",
18 | "computeCloudServices": "cld-",
19 | "computeDiskEncryptionSets": "des",
20 | "computeDisks": "disk",
21 | "computeDisksOs": "osdisk",
22 | "computeGalleries": "gal",
23 | "computeSnapshots": "snap-",
24 | "computeVirtualMachines": "vm",
25 | "computeVirtualMachineScaleSets": "vmss-",
26 | "containerInstanceContainerGroups": "ci",
27 | "containerRegistryRegistries": "cr",
28 | "containerServiceManagedClusters": "aks-",
29 | "databricksWorkspaces": "dbw-",
30 | "dataFactoryFactories": "adf-",
31 | "dataLakeAnalyticsAccounts": "dla",
32 | "dataLakeStoreAccounts": "dls",
33 | "dataMigrationServices": "dms-",
34 | "dBforMySQLServers": "mysql-",
35 | "dBforPostgreSQLServers": "psql-",
36 | "devicesIotHubs": "iot-",
37 | "devicesProvisioningServices": "provs-",
38 | "devicesProvisioningServicesCertificates": "pcert-",
39 | "documentDBDatabaseAccounts": "cosmos-",
40 | "eventGridDomains": "evgd-",
41 | "eventGridDomainsTopics": "evgt-",
42 | "eventGridEventSubscriptions": "evgs-",
43 | "eventHubNamespaces": "evhns-",
44 | "eventHubNamespacesEventHubs": "evh-",
45 | "hdInsightClustersHadoop": "hadoop-",
46 | "hdInsightClustersHbase": "hbase-",
47 | "hdInsightClustersKafka": "kafka-",
48 | "hdInsightClustersMl": "mls-",
49 | "hdInsightClustersSpark": "spark-",
50 | "hdInsightClustersStorm": "storm-",
51 | "hybridComputeMachines": "arcs-",
52 | "insightsActionGroups": "ag-",
53 | "insightsComponents": "appi-",
54 | "keyVaultVaults": "kv-",
55 | "kubernetesConnectedClusters": "arck",
56 | "kustoClusters": "dec",
57 | "kustoClustersDatabases": "dedb",
58 | "logicIntegrationAccounts": "ia-",
59 | "logicWorkflows": "logic-",
60 | "machineLearningServicesWorkspaces": "mlw-",
61 | "managedIdentityUserAssignedIdentities": "id-",
62 | "managementManagementGroups": "mg-",
63 | "migrateAssessmentProjects": "migr-",
64 | "networkApplicationGateways": "agw-",
65 | "networkApplicationSecurityGroups": "asg-",
66 | "networkAzureFirewalls": "afw-",
67 | "networkBastionHosts": "bas-",
68 | "networkConnections": "con-",
69 | "networkDnsZones": "dnsz-",
70 | "networkExpressRouteCircuits": "erc-",
71 | "networkFirewallPolicies": "afwp-",
72 | "networkFirewallPoliciesWebApplication": "waf",
73 | "networkFirewallPoliciesRuleGroups": "wafrg",
74 | "networkFrontDoors": "fd-",
75 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-",
76 | "networkLoadBalancersExternal": "lbe-",
77 | "networkLoadBalancersInternal": "lbi-",
78 | "networkLoadBalancersInboundNatRules": "rule-",
79 | "networkLocalNetworkGateways": "lgw-",
80 | "networkNatGateways": "ng-",
81 | "networkNetworkInterfaces": "nic-",
82 | "networkNetworkSecurityGroups": "nsg-",
83 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-",
84 | "networkNetworkWatchers": "nw-",
85 | "networkPrivateDnsZones": "pdnsz-",
86 | "networkPrivateLinkServices": "pl-",
87 | "networkPublicIPAddresses": "pip-",
88 | "networkPublicIPPrefixes": "ippre-",
89 | "networkRouteFilters": "rf-",
90 | "networkRouteTables": "rt-",
91 | "networkRouteTablesRoutes": "udr-",
92 | "networkTrafficManagerProfiles": "traf-",
93 | "networkVirtualNetworkGateways": "vgw-",
94 | "networkVirtualNetworks": "vnet-",
95 | "networkVirtualNetworksSubnets": "snet-",
96 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-",
97 | "networkVirtualWans": "vwan-",
98 | "networkVpnGateways": "vpng-",
99 | "networkVpnGatewaysVpnConnections": "vcn-",
100 | "networkVpnGatewaysVpnSites": "vst-",
101 | "notificationHubsNamespaces": "ntfns-",
102 | "notificationHubsNamespacesNotificationHubs": "ntf-",
103 | "operationalInsightsWorkspaces": "log-",
104 | "portalDashboards": "dash-",
105 | "powerBIDedicatedCapacities": "pbi-",
106 | "purviewAccounts": "pview-",
107 | "recoveryServicesVaults": "rsv-",
108 | "resourcesResourceGroups": "rg-",
109 | "searchSearchServices": "srch-",
110 | "serviceBusNamespaces": "sb-",
111 | "serviceBusNamespacesQueues": "sbq-",
112 | "serviceBusNamespacesTopics": "sbt-",
113 | "serviceEndPointPolicies": "se-",
114 | "serviceFabricClusters": "sf-",
115 | "signalRServiceSignalR": "sigr",
116 | "sqlManagedInstances": "sqlmi-",
117 | "sqlServers": "sql-",
118 | "sqlServersDataWarehouse": "sqldw-",
119 | "sqlServersDatabases": "sqldb-",
120 | "sqlServersDatabasesStretch": "sqlstrdb-",
121 | "storageStorageAccounts": "st",
122 | "storageStorageAccountsVm": "stvm",
123 | "storSimpleManagers": "ssimp",
124 | "streamAnalyticsCluster": "asa-",
125 | "synapseWorkspaces": "syn",
126 | "synapseWorkspacesAnalyticsWorkspaces": "synw",
127 | "synapseWorkspacesSqlPoolsDedicated": "syndp",
128 | "synapseWorkspacesSqlPoolsSpark": "synsp",
129 | "timeSeriesInsightsEnvironments": "tsi-",
130 | "webServerFarms": "plan-",
131 | "webSitesAppService": "app-",
132 | "webSitesAppServiceEnvironment": "ase-",
133 | "webSitesFunctions": "func-",
134 | "webStaticSites": "stapp-"
135 | }
--------------------------------------------------------------------------------
/infra/app/api.bicep:
--------------------------------------------------------------------------------
1 | param name string
2 | @description('Primary location for all resources & Flex Consumption Function App')
3 | param location string = resourceGroup().location
4 | param tags object = {}
5 | param applicationInsightsName string = ''
6 | param appServicePlanId string
7 | param appSettings object = {}
8 | param runtimeName string
9 | param runtimeVersion string
10 | param serviceName string = 'api'
11 | param storageAccountName string
12 | param deploymentStorageContainerName string
13 | param virtualNetworkSubnetId string = ''
14 | param instanceMemoryMB int = 2048
15 | param maximumInstanceCount int = 100
16 | param identityId string = ''
17 | param identityClientId string = ''
18 | param enableBlob bool = true
19 | param enableQueue bool = false
20 | param enableTable bool = false
21 | param enableFile bool = false
22 |
23 | @allowed(['SystemAssigned', 'UserAssigned'])
24 | param identityType string = 'UserAssigned'
25 |
26 | var applicationInsightsIdentity = 'ClientId=${identityClientId};Authorization=AAD'
27 | var kind = 'functionapp,linux'
28 |
29 | // Create base application settings
30 | var baseAppSettings = {
31 | // Only include required credential settings unconditionally
32 | AzureWebJobsStorage__credential: 'managedidentity'
33 | AzureWebJobsStorage__clientId: identityClientId
34 |
35 | // Application Insights settings are always included
36 | APPLICATIONINSIGHTS_AUTHENTICATION_STRING: applicationInsightsIdentity
37 | APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString
38 | }
39 |
40 | // Dynamically build storage endpoint settings based on feature flags
41 | var blobSettings = enableBlob ? { AzureWebJobsStorage__blobServiceUri: stg.properties.primaryEndpoints.blob } : {}
42 | var queueSettings = enableQueue ? { AzureWebJobsStorage__queueServiceUri: stg.properties.primaryEndpoints.queue } : {}
43 | var tableSettings = enableTable ? { AzureWebJobsStorage__tableServiceUri: stg.properties.primaryEndpoints.table } : {}
44 | var fileSettings = enableFile ? { AzureWebJobsStorage__fileServiceUri: stg.properties.primaryEndpoints.file } : {}
45 |
46 | // Merge all app settings
47 | var allAppSettings = union(
48 | appSettings,
49 | blobSettings,
50 | queueSettings,
51 | tableSettings,
52 | fileSettings,
53 | baseAppSettings
54 | )
55 |
56 | resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' existing = {
57 | name: storageAccountName
58 | }
59 |
60 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) {
61 | name: applicationInsightsName
62 | }
63 |
64 | // Create a Flex Consumption Function App to host the API
65 | module api 'br/public:avm/res/web/site:0.15.1' = {
66 | name: '${serviceName}-flex-consumption'
67 | params: {
68 | kind: kind
69 | name: name
70 | location: location
71 | tags: union(tags, { 'azd-service-name': serviceName })
72 | serverFarmResourceId: appServicePlanId
73 | managedIdentities: {
74 | systemAssigned: identityType == 'SystemAssigned'
75 | userAssignedResourceIds: [
76 | '${identityId}'
77 | ]
78 | }
79 | functionAppConfig: {
80 | deployment: {
81 | storage: {
82 | type: 'blobContainer'
83 | value: '${stg.properties.primaryEndpoints.blob}${deploymentStorageContainerName}'
84 | authentication: {
85 | type: identityType == 'SystemAssigned' ? 'SystemAssignedIdentity' : 'UserAssignedIdentity'
86 | userAssignedIdentityResourceId: identityType == 'UserAssigned' ? identityId : ''
87 | }
88 | }
89 | }
90 | scaleAndConcurrency: {
91 | instanceMemoryMB: instanceMemoryMB
92 | maximumInstanceCount: maximumInstanceCount
93 | }
94 | runtime: {
95 | name: runtimeName
96 | version: runtimeVersion
97 | }
98 | }
99 | siteConfig: {
100 | alwaysOn: false
101 | }
102 | virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : null
103 | appSettingsKeyValuePairs: allAppSettings
104 | }
105 | }
106 |
107 | output SERVICE_API_NAME string = api.outputs.name
108 | // Ensure output is always string, handle potential null from module output if SystemAssigned is not used
109 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = identityType == 'SystemAssigned' ? api.outputs.?systemAssignedMIPrincipalId ?? '' : ''
110 |
--------------------------------------------------------------------------------
/infra/app/rbac.bicep:
--------------------------------------------------------------------------------
1 | param storageAccountName string
2 | param appInsightsName string
3 | param managedIdentityPrincipalId string // Principal ID for the Managed Identity
4 | param userIdentityPrincipalId string = '' // Principal ID for the User Identity
5 | param allowUserIdentityPrincipal bool = false // Flag to enable user identity role assignments
6 | param enableBlob bool = true
7 | param enableQueue bool = false
8 | param enableTable bool = false
9 |
10 | // Define Role Definition IDs internally
11 | var storageRoleDefinitionId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' //Storage Blob Data Owner role
12 | var queueRoleDefinitionId = '974c5e8b-45b9-4653-ba55-5f855dd0fb88' // Storage Queue Data Contributor role
13 | var tableRoleDefinitionId = '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3' // Storage Table Data Contributor role
14 | var monitoringRoleDefinitionId = '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher role ID
15 |
16 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing = {
17 | name: storageAccountName
18 | }
19 |
20 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = {
21 | name: appInsightsName
22 | }
23 |
24 | // Role assignment for Storage Account (Blob) - Managed Identity
25 | resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableBlob) {
26 | name: guid(storageAccount.id, managedIdentityPrincipalId, storageRoleDefinitionId) // Use managed identity ID
27 | scope: storageAccount
28 | properties: {
29 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageRoleDefinitionId)
30 | principalId: managedIdentityPrincipalId // Use managed identity ID
31 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal
32 | }
33 | }
34 |
35 | // Role assignment for Storage Account (Blob) - User Identity
36 | resource storageRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableBlob && allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) {
37 | name: guid(storageAccount.id, userIdentityPrincipalId, storageRoleDefinitionId)
38 | scope: storageAccount
39 | properties: {
40 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageRoleDefinitionId)
41 | principalId: userIdentityPrincipalId // Use user identity ID
42 | principalType: 'User' // User Identity is a User Principal
43 | }
44 | }
45 |
46 | // Role assignment for Storage Account (Queue) - Managed Identity
47 | resource queueRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableQueue) {
48 | name: guid(storageAccount.id, managedIdentityPrincipalId, queueRoleDefinitionId) // Use managed identity ID
49 | scope: storageAccount
50 | properties: {
51 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', queueRoleDefinitionId)
52 | principalId: managedIdentityPrincipalId // Use managed identity ID
53 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal
54 | }
55 | }
56 |
57 | // Role assignment for Storage Account (Queue) - User Identity
58 | resource queueRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableQueue && allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) {
59 | name: guid(storageAccount.id, userIdentityPrincipalId, queueRoleDefinitionId)
60 | scope: storageAccount
61 | properties: {
62 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', queueRoleDefinitionId)
63 | principalId: userIdentityPrincipalId // Use user identity ID
64 | principalType: 'User' // User Identity is a User Principal
65 | }
66 | }
67 |
68 | // Role assignment for Storage Account (Table) - Managed Identity
69 | resource tableRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableTable) {
70 | name: guid(storageAccount.id, managedIdentityPrincipalId, tableRoleDefinitionId) // Use managed identity ID
71 | scope: storageAccount
72 | properties: {
73 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', tableRoleDefinitionId)
74 | principalId: managedIdentityPrincipalId // Use managed identity ID
75 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal
76 | }
77 | }
78 |
79 | // Role assignment for Storage Account (Table) - User Identity
80 | resource tableRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableTable && allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) {
81 | name: guid(storageAccount.id, userIdentityPrincipalId, tableRoleDefinitionId)
82 | scope: storageAccount
83 | properties: {
84 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', tableRoleDefinitionId)
85 | principalId: userIdentityPrincipalId // Use user identity ID
86 | principalType: 'User' // User Identity is a User Principal
87 | }
88 | }
89 |
90 | // Role assignment for Application Insights - Managed Identity
91 | resource appInsightsRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
92 | name: guid(applicationInsights.id, managedIdentityPrincipalId, monitoringRoleDefinitionId) // Use managed identity ID
93 | scope: applicationInsights
94 | properties: {
95 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', monitoringRoleDefinitionId)
96 | principalId: managedIdentityPrincipalId // Use managed identity ID
97 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal
98 | }
99 | }
100 |
101 | // Role assignment for Application Insights - User Identity
102 | resource appInsightsRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) {
103 | name: guid(applicationInsights.id, userIdentityPrincipalId, monitoringRoleDefinitionId)
104 | scope: applicationInsights
105 | properties: {
106 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', monitoringRoleDefinitionId)
107 | principalId: userIdentityPrincipalId // Use user identity ID
108 | principalType: 'User' // User Identity is a User Principal
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/infra/app/storage-PrivateEndpoint.bicep:
--------------------------------------------------------------------------------
1 | param virtualNetworkName string
2 | param subnetName string
3 | @description('Specifies the storage account resource name')
4 | param resourceName string
5 | param location string = resourceGroup().location
6 | param tags object = {}
7 | param enableBlob bool = true
8 | param enableQueue bool = false
9 | param enableTable bool = false
10 |
11 | resource vnet 'Microsoft.Network/virtualNetworks@2021-08-01' existing = {
12 | name: virtualNetworkName
13 | }
14 |
15 | resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = {
16 | name: resourceName
17 | }
18 |
19 | // Storage DNS zone names
20 | var blobPrivateDNSZoneName = 'privatelink.blob.${environment().suffixes.storage}'
21 | var queuePrivateDNSZoneName = 'privatelink.queue.${environment().suffixes.storage}'
22 | var tablePrivateDNSZoneName = 'privatelink.table.${environment().suffixes.storage}'
23 |
24 | // AVM module for Blob Private Endpoint with private DNS zone
25 | module blobPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (enableBlob) {
26 | name: 'blob-private-endpoint-deployment'
27 | params: {
28 | name: 'blob-private-endpoint'
29 | location: location
30 | tags: tags
31 | subnetResourceId: '${vnet.id}/subnets/${subnetName}'
32 | privateLinkServiceConnections: [
33 | {
34 | name: 'blobPrivateLinkConnection'
35 | properties: {
36 | privateLinkServiceId: storageAccount.id
37 | groupIds: [
38 | 'blob'
39 | ]
40 | }
41 | }
42 | ]
43 | customDnsConfigs: []
44 | // Creates private DNS zone and links
45 | privateDnsZoneGroup: {
46 | name: 'blobPrivateDnsZoneGroup'
47 | privateDnsZoneGroupConfigs: [
48 | {
49 | name: 'storageBlobARecord'
50 | privateDnsZoneResourceId: enableBlob ? privateDnsZoneBlobDeployment.outputs.resourceId : ''
51 | }
52 | ]
53 | }
54 | }
55 | }
56 |
57 | // AVM module for Queue Private Endpoint with private DNS zone
58 | module queuePrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (enableQueue) {
59 | name: 'queue-private-endpoint-deployment'
60 | params: {
61 | name: 'queue-private-endpoint'
62 | location: location
63 | tags: tags
64 | subnetResourceId: '${vnet.id}/subnets/${subnetName}'
65 | privateLinkServiceConnections: [
66 | {
67 | name: 'queuePrivateLinkConnection'
68 | properties: {
69 | privateLinkServiceId: storageAccount.id
70 | groupIds: [
71 | 'queue'
72 | ]
73 | }
74 | }
75 | ]
76 | customDnsConfigs: []
77 | // Creates private DNS zone and links
78 | privateDnsZoneGroup: {
79 | name: 'queuePrivateDnsZoneGroup'
80 | privateDnsZoneGroupConfigs: [
81 | {
82 | name: 'storageQueueARecord'
83 | privateDnsZoneResourceId: enableQueue ? privateDnsZoneQueueDeployment.outputs.resourceId : ''
84 | }
85 | ]
86 | }
87 | }
88 | }
89 |
90 | // AVM module for Table Private Endpoint with private DNS zone
91 | module tablePrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (enableTable) {
92 | name: 'table-private-endpoint-deployment'
93 | params: {
94 | name: 'table-private-endpoint'
95 | location: location
96 | tags: tags
97 | subnetResourceId: '${vnet.id}/subnets/${subnetName}'
98 | privateLinkServiceConnections: [
99 | {
100 | name: 'tablePrivateLinkConnection'
101 | properties: {
102 | privateLinkServiceId: storageAccount.id
103 | groupIds: [
104 | 'table'
105 | ]
106 | }
107 | }
108 | ]
109 | customDnsConfigs: []
110 | // Creates private DNS zone and links
111 | privateDnsZoneGroup: {
112 | name: 'tablePrivateDnsZoneGroup'
113 | privateDnsZoneGroupConfigs: [
114 | {
115 | name: 'storageTableARecord'
116 | privateDnsZoneResourceId: enableTable ? privateDnsZoneTableDeployment.outputs.resourceId : ''
117 | }
118 | ]
119 | }
120 | }
121 | }
122 |
123 | // AVM module for Blob Private DNS Zone
124 | module privateDnsZoneBlobDeployment 'br/public:avm/res/network/private-dns-zone:0.7.1' = if (enableBlob) {
125 | name: 'blob-private-dns-zone-deployment'
126 | params: {
127 | name: blobPrivateDNSZoneName
128 | location: 'global'
129 | tags: tags
130 | virtualNetworkLinks: [
131 | {
132 | name: '${resourceName}-blob-link-${take(toLower(uniqueString(resourceName, virtualNetworkName)), 4)}'
133 | virtualNetworkResourceId: vnet.id
134 | registrationEnabled: false
135 | location: 'global'
136 | tags: tags
137 | }
138 | ]
139 | }
140 | }
141 |
142 | // AVM module for Queue Private DNS Zone
143 | module privateDnsZoneQueueDeployment 'br/public:avm/res/network/private-dns-zone:0.7.1' = if (enableQueue) {
144 | name: 'queue-private-dns-zone-deployment'
145 | params: {
146 | name: queuePrivateDNSZoneName
147 | location: 'global'
148 | tags: tags
149 | virtualNetworkLinks: [
150 | {
151 | name: '${resourceName}-queue-link-${take(toLower(uniqueString(resourceName, virtualNetworkName)), 4)}'
152 | virtualNetworkResourceId: vnet.id
153 | registrationEnabled: false
154 | location: 'global'
155 | tags: tags
156 | }
157 | ]
158 | }
159 | }
160 |
161 | // AVM module for Table Private DNS Zone
162 | module privateDnsZoneTableDeployment 'br/public:avm/res/network/private-dns-zone:0.7.1' = if (enableTable) {
163 | name: 'table-private-dns-zone-deployment'
164 | params: {
165 | name: tablePrivateDNSZoneName
166 | location: 'global'
167 | tags: tags
168 | virtualNetworkLinks: [
169 | {
170 | name: '${resourceName}-table-link-${take(toLower(uniqueString(resourceName, virtualNetworkName)), 4)}'
171 | virtualNetworkResourceId: vnet.id
172 | registrationEnabled: false
173 | location: 'global'
174 | tags: tags
175 | }
176 | ]
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/infra/app/vnet.bicep:
--------------------------------------------------------------------------------
1 | @description('Specifies the name of the virtual network.')
2 | param vNetName string
3 |
4 | @description('Specifies the location.')
5 | param location string = resourceGroup().location
6 |
7 | @description('Specifies the name of the subnet for the Service Bus private endpoint.')
8 | param peSubnetName string = 'private-endpoints-subnet'
9 |
10 | @description('Specifies the name of the subnet for Function App virtual network integration.')
11 | param appSubnetName string = 'app'
12 |
13 | param tags object = {}
14 |
15 | // Migrated to use AVM module instead of direct resource declaration
16 | module virtualNetwork 'br/public:avm/res/network/virtual-network:0.6.1' = {
17 | name: 'vnet-deployment'
18 | params: {
19 | // Required parameters
20 | name: vNetName
21 | addressPrefixes: [
22 | '10.0.0.0/16'
23 | ]
24 | // Non-required parameters
25 | location: location
26 | tags: tags
27 | subnets: [
28 | {
29 | name: peSubnetName
30 | addressPrefix: '10.0.1.0/24'
31 | privateEndpointNetworkPolicies: 'Disabled'
32 | privateLinkServiceNetworkPolicies: 'Enabled'
33 | }
34 | {
35 | name: appSubnetName
36 | addressPrefix: '10.0.2.0/24'
37 | privateEndpointNetworkPolicies: 'Disabled'
38 | privateLinkServiceNetworkPolicies: 'Enabled'
39 | delegation: 'Microsoft.App/environments'
40 | }
41 | ]
42 | }
43 | }
44 |
45 | output peSubnetName string = peSubnetName
46 | output peSubnetID string = '${virtualNetwork.outputs.resourceId}/subnets/${peSubnetName}'
47 | output appSubnetName string = appSubnetName
48 | output appSubnetID string = '${virtualNetwork.outputs.resourceId}/subnets/${appSubnetName}'
49 |
--------------------------------------------------------------------------------
/infra/main.bicep:
--------------------------------------------------------------------------------
1 | targetScope = 'subscription'
2 |
3 | @minLength(1)
4 | @maxLength(64)
5 | @description('Name of the the environment which is used to generate a short unique hash used in all resources.')
6 | param environmentName string
7 |
8 | @minLength(1)
9 | @description('Primary location for all resources & Flex Consumption Function App')
10 | @allowed([
11 | 'australiaeast'
12 | 'australiasoutheast'
13 | 'brazilsouth'
14 | 'canadacentral'
15 | 'centralindia'
16 | 'centralus'
17 | 'eastasia'
18 | 'eastus'
19 | 'eastus2'
20 | 'eastus2euap'
21 | 'francecentral'
22 | 'germanywestcentral'
23 | 'italynorth'
24 | 'japaneast'
25 | 'koreacentral'
26 | 'northcentralus'
27 | 'northeurope'
28 | 'norwayeast'
29 | 'southafricanorth'
30 | 'southcentralus'
31 | 'southeastasia'
32 | 'southindia'
33 | 'spaincentral'
34 | 'swedencentral'
35 | 'uaenorth'
36 | 'uksouth'
37 | 'ukwest'
38 | 'westcentralus'
39 | 'westeurope'
40 | 'westus'
41 | 'westus2'
42 | 'westus3'
43 | ])
44 | @metadata({
45 | azd: {
46 | type: 'location'
47 | }
48 | })
49 | param location string
50 | param vnetEnabled bool
51 | param apiServiceName string = ''
52 | param apiUserAssignedIdentityName string = ''
53 | param applicationInsightsName string = ''
54 | param appServicePlanName string = ''
55 | param logAnalyticsName string = ''
56 | param resourceGroupName string = ''
57 | param storageAccountName string = ''
58 | param vNetName string = ''
59 | @description('Id of the user identity to be used for testing and debugging. This is not required in production. Leave empty if not needed.')
60 | param principalId string = deployer().objectId
61 |
62 | var abbrs = loadJsonContent('./abbreviations.json')
63 | var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
64 | var tags = { 'azd-env-name': environmentName }
65 | var functionAppName = !empty(apiServiceName) ? apiServiceName : '${abbrs.webSitesFunctions}api-${resourceToken}'
66 | var deploymentStorageContainerName = 'app-package-${take(functionAppName, 32)}-${take(toLower(uniqueString(functionAppName, resourceToken)), 7)}'
67 |
68 | // Organize resources in a resource group
69 | resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
70 | name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'
71 | location: location
72 | tags: tags
73 | }
74 |
75 | // User assigned managed identity to be used by the function app to reach storage and other dependencies
76 | // Assign specific roles to this identity in the RBAC module
77 | module apiUserAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = {
78 | name: 'apiUserAssignedIdentity'
79 | scope: rg
80 | params: {
81 | location: location
82 | tags: tags
83 | name: !empty(apiUserAssignedIdentityName) ? apiUserAssignedIdentityName : '${abbrs.managedIdentityUserAssignedIdentities}api-${resourceToken}'
84 | }
85 | }
86 |
87 | // Create an App Service Plan to group applications under the same payment plan and SKU
88 | module appServicePlan 'br/public:avm/res/web/serverfarm:0.1.1' = {
89 | name: 'appserviceplan'
90 | scope: rg
91 | params: {
92 | name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}'
93 | sku: {
94 | name: 'FC1'
95 | tier: 'FlexConsumption'
96 | }
97 | reserved: true
98 | location: location
99 | tags: tags
100 | }
101 | }
102 |
103 | module api './app/api.bicep' = {
104 | name: 'api'
105 | scope: rg
106 | params: {
107 | name: functionAppName
108 | location: location
109 | tags: tags
110 | applicationInsightsName: monitoring.outputs.name
111 | appServicePlanId: appServicePlan.outputs.resourceId
112 | runtimeName: 'dotnet-isolated'
113 | runtimeVersion: '8.0'
114 | storageAccountName: storage.outputs.name
115 | enableBlob: storageEndpointConfig.enableBlob
116 | enableQueue: storageEndpointConfig.enableQueue
117 | enableTable: storageEndpointConfig.enableTable
118 | deploymentStorageContainerName: deploymentStorageContainerName
119 | identityId: apiUserAssignedIdentity.outputs.resourceId
120 | identityClientId: apiUserAssignedIdentity.outputs.clientId
121 | appSettings: {
122 | }
123 | virtualNetworkSubnetId: vnetEnabled ? serviceVirtualNetwork.outputs.appSubnetID : ''
124 | }
125 | }
126 |
127 | // Backing storage for Azure functions backend API
128 | module storage 'br/public:avm/res/storage/storage-account:0.8.3' = {
129 | name: 'storage'
130 | scope: rg
131 | params: {
132 | name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}'
133 | allowBlobPublicAccess: false
134 | allowSharedKeyAccess: false // Disable local authentication methods as per policy
135 | dnsEndpointType: 'Standard'
136 | publicNetworkAccess: vnetEnabled ? 'Disabled' : 'Enabled'
137 | networkAcls: vnetEnabled ? {
138 | defaultAction: 'Deny'
139 | bypass: 'None'
140 | } : {
141 | defaultAction: 'Allow'
142 | bypass: 'AzureServices'
143 | }
144 | blobServices: {
145 | containers: [{name: deploymentStorageContainerName}]
146 | }
147 | minimumTlsVersion: 'TLS1_2' // Enforcing TLS 1.2 for better security
148 | location: location
149 | tags: tags
150 | }
151 | }
152 |
153 | // Define the configuration object locally to pass to the modules
154 | var storageEndpointConfig = {
155 | enableBlob: true // Required for AzureWebJobsStorage, .zip deployment, Event Hubs trigger and Timer trigger checkpointing
156 | enableQueue: false // Required for Durable Functions and MCP trigger
157 | enableTable: false // Required for Durable Functions and OpenAI triggers and bindings
158 | enableFiles: false // Not required, used in legacy scenarios
159 | allowUserIdentityPrincipal: true // Allow interactive user identity to access for testing and debugging
160 | }
161 |
162 | // Consolidated Role Assignments
163 | module rbac 'app/rbac.bicep' = {
164 | name: 'rbacAssignments'
165 | scope: rg
166 | params: {
167 | storageAccountName: storage.outputs.name
168 | appInsightsName: monitoring.outputs.name
169 | managedIdentityPrincipalId: apiUserAssignedIdentity.outputs.principalId
170 | userIdentityPrincipalId: principalId
171 | enableBlob: storageEndpointConfig.enableBlob
172 | enableQueue: storageEndpointConfig.enableQueue
173 | enableTable: storageEndpointConfig.enableTable
174 | allowUserIdentityPrincipal: storageEndpointConfig.allowUserIdentityPrincipal
175 | }
176 | }
177 |
178 | // Virtual Network & private endpoint to blob storage
179 | module serviceVirtualNetwork 'app/vnet.bicep' = if (vnetEnabled) {
180 | name: 'serviceVirtualNetwork'
181 | scope: rg
182 | params: {
183 | location: location
184 | tags: tags
185 | vNetName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}'
186 | }
187 | }
188 |
189 | module storagePrivateEndpoint 'app/storage-PrivateEndpoint.bicep' = if (vnetEnabled) {
190 | name: 'servicePrivateEndpoint'
191 | scope: rg
192 | params: {
193 | location: location
194 | tags: tags
195 | virtualNetworkName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}'
196 | subnetName: vnetEnabled ? serviceVirtualNetwork.outputs.peSubnetName : '' // Keep conditional check for safety, though module won't run if !vnetEnabled
197 | resourceName: storage.outputs.name
198 | enableBlob: storageEndpointConfig.enableBlob
199 | enableQueue: storageEndpointConfig.enableQueue
200 | enableTable: storageEndpointConfig.enableTable
201 | }
202 | }
203 |
204 | // Monitor application with Azure Monitor - Log Analytics and Application Insights
205 | module logAnalytics 'br/public:avm/res/operational-insights/workspace:0.11.1' = {
206 | name: '${uniqueString(deployment().name, location)}-loganalytics'
207 | scope: rg
208 | params: {
209 | name: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}'
210 | location: location
211 | tags: tags
212 | dataRetention: 30
213 | }
214 | }
215 |
216 | module monitoring 'br/public:avm/res/insights/component:0.6.0' = {
217 | name: '${uniqueString(deployment().name, location)}-appinsights'
218 | scope: rg
219 | params: {
220 | name: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}'
221 | location: location
222 | tags: tags
223 | workspaceResourceId: logAnalytics.outputs.resourceId
224 | disableLocalAuth: true
225 | }
226 | }
227 |
228 | // App outputs
229 | output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.name
230 | output AZURE_LOCATION string = location
231 | output AZURE_TENANT_ID string = tenant().tenantId
232 | output SERVICE_API_NAME string = api.outputs.SERVICE_API_NAME
233 | output AZURE_FUNCTION_NAME string = api.outputs.SERVICE_API_NAME
234 |
--------------------------------------------------------------------------------
/infra/main.parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "environmentName": {
6 | "value": "${AZURE_ENV_NAME}"
7 | },
8 | "location": {
9 | "value": "${AZURE_LOCATION}"
10 | },
11 | "principalId": {
12 | "value": "${AZURE_PRINCIPAL_ID}"
13 | },
14 | "vnetEnabled": {
15 | "value": "${VNET_ENABLED}"
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------