Skip to content

Commit 316e023

Browse files
authored
Updating dotnet class-based sample to .NET 8 and Flex Consumption (#291)
* Updating sample to .NET 8 and Flex Consumption * Adding metadata so it can be indexed by Azure Samples * Updating readme and index with more auth details * Moving the Flex Consumption mention down to the function section
1 parent d65942a commit 316e023

13 files changed

+191
-8
lines changed

samples/DotnetIsolated-ClassBased/DotnetIsolated-ClassBased.csproj

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net6.0</TargetFramework>
3+
<TargetFramework>net8.0</TargetFramework>
44
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
55
<OutputType>Exe</OutputType>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<RootNamespace>DotnetIsolated_ClassBased</RootNamespace>
99
</PropertyGroup>
1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
12-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
13-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" />
14-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.SignalRService" Version="1.13.0" />
11+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
12+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
13+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" />
14+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.SignalRService" Version="1.14.0" />
1515
</ItemGroup>
1616
<ItemGroup>
1717
<None Update="host.json">

samples/DotnetIsolated-ClassBased/Functions.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Net;
23
using Microsoft.Azure.Functions.Worker;
34
using Microsoft.Azure.Functions.Worker.Http;
@@ -21,7 +22,9 @@ public Functions(IServiceProvider serviceProvider, ILogger<Functions> logger) :
2122
public HttpResponseData GetWebPage([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req)
2223
{
2324
var response = req.CreateResponse(HttpStatusCode.OK);
24-
response.WriteString(File.ReadAllText("content/index.html"));
25+
string home = Environment.GetEnvironmentVariable("HOME");
26+
string htmlFilePath = Path.Combine(home, "site", "wwwroot", "content", "index.html");
27+
response.WriteString(File.ReadAllText(htmlFilePath));
2528
response.Headers.Add("Content-Type", "text/html");
2629
return response;
2730
}
+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
---
2+
description: This is a chatroom sample that demonstrates bidirectional message pushing between Azure SignalR Service and Azure Functions in a serverless scenario using the Flex Consumption hosting plan and .NET.
3+
page_type: sample
4+
products:
5+
- azure-functions
6+
- azure-signalr-service
7+
- azure
8+
urlFragment: bidirectional-chatroom-sample-charp
9+
languages:
10+
- csharp
11+
---
12+
# Azure function bidirectional chatroom sample
13+
14+
This is a chatroom sample that demonstrates bidirectional message pushing between Azure SignalR Service and Azure Functions in a serverless scenario. It leverages the [**upstream**](https://docs.microsoft.com/azure/azure-signalr/concept-upstream) provided by Azure SignalR Service that features proxying messages from client to upstream endpoints in serverless scenario. Azure Functions with SignalR trigger binding allows you to write code to receive and push messages in several languages, including JavaScript, Python, C#, etc.
15+
16+
- [Prerequisites](#prerequisites)
17+
- [Run sample in Azure](#run-sample-in-azure)
18+
- [Create Azure SignalR Service](#create-azure-signalr-service)
19+
- [Deploy project to Azure Function](#deploy-project-to-azure-function)
20+
- [Use a chat sample website to test end to end](#use-a-chat-sample-website-to-test-end-to-end)
21+
- [Use Key Vault secret reference](#use-key-vault-secret-reference)
22+
- [Enable AAD Token on upstream](#enable-aad-token-on-upstream)
23+
24+
<a name="prerequisites"></a>
25+
26+
## Prerequisites
27+
28+
The following are required to build this tutorial.
29+
* [.NET SDK](https://dotnet.microsoft.com/download) (Version 8.0, required for Functions extensions)
30+
* [Azure Functions Core Tools](https://docs.microsoft.com/azure/azure-functions/functions-run-local?tabs=windows%2Ccsharp%2Cbash#install-the-azure-functions-core-tools) (Version 4.0.5907 or newer)
31+
* [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) (Version 2.63.0 or newer)
32+
33+
<a name="run-sample-in-azure"></a>
34+
35+
## Run sample in Azure
36+
37+
You will create an Azure SignalR Service and an Azure Function app to host the sample. And you will launch chatroom locally but connecting to Azure SignalR Service and Azure Function.
38+
39+
### Create Azure SignalR Service
40+
41+
1. Create Azure SignalR Service using `az cli`
42+
43+
```bash
44+
resourceGroup=myResourceGroup
45+
signalrName=mySignalRName
46+
region=eastus
47+
48+
# Create a resource group.
49+
az group create --name $resourceGroup --location $region
50+
51+
az signalr create -n $signalrName -g $resourceGroup --service-mode Serverless --sku Standard_S1
52+
# Get connection string for later use.
53+
connectionString=$(az signalr key list -n $signalrName -g $resourceGroup --query primaryConnectionString -o tsv)
54+
```
55+
56+
For more details about creating Azure SignalR Service, see the [tutorial](https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-quickstart-azure-functions-javascript#create-an-azure-signalr-service-instance).
57+
58+
### Deploy and configure project to Azure Function
59+
60+
1. Deploy with Azure Functions Core Tools
61+
1. [Install Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Ccsharp%2Cbash#install-the-azure-functions-core-tools)
62+
2. [Create a Flex Consumption Azure Function App](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to?tabs=azure-cli%2Cvs-code-publish&pivots=programming-language-csharp) (code snippet shown below). This sample can also be used with other Azure Functions hosting plans.
63+
64+
```bash
65+
#!/bin/bash
66+
67+
# Function app and storage account names must be unique.
68+
storageName=mystorageaccount
69+
functionAppName=myserverlessfunc
70+
71+
# Create an Azure storage account in the resource group.
72+
az storage account create \
73+
--name $storageName \
74+
--location $region \
75+
--resource-group $resourceGroup \
76+
--sku Standard_LRS
77+
78+
# Create a serverless function app in the resource group.
79+
az functionapp create \
80+
--name $functionAppName \
81+
--storage-account $storageName \
82+
--flexconsumption-location $region \
83+
--resource-group $resourceGroup \
84+
--runtime dotnet-isolated
85+
```
86+
3. Update application settings
87+
88+
```bash
89+
az functionapp config appsettings set --resource-group $resourceGroup --name $functionAppName --setting AzureSignalRConnectionString=$connectionString
90+
```
91+
92+
4. Publish the sample to the Azure Function you created before.
93+
94+
```bash
95+
cd <root>/samples/DotnetIsolated-ClassBased
96+
// If prompted function app version, use --force
97+
func azure functionapp publish $functionAppName --dotnet-isolated
98+
```
99+
100+
2. Update Azure SignalR Service Upstream settings
101+
102+
Open the Azure Portal and nevigate to the Function App created before. Find `signalr_extension` key in the **App keys** blade.
103+
104+
![Overview with auth](imgs/getkeys.png)
105+
106+
Copy the `signalr_extensions` value and use Azure Portal to set the upstream setting.
107+
- In the *Upstream URL Pattern*, fill in the `<function-url>/runtime/webhooks/signalr?code=<signalr_extension-key>`
108+
> [!NOTE]
109+
> The `signalr_extensions` code is required by Azure Function but the trigger does not only use this code but also Azure SignalR Service connection string to validate requests. If you're very serious about the code, use KeyVault secret reference feature to save the code. See [Use Key Vault secret reference](#use-keyvault-secret-reference).
110+
111+
![Upstream](imgs/upstream-portal.png)
112+
113+
### Use a chat sample website to test end to end
114+
115+
1. Use browser to visit `<function-app-url>/api/index` for the web page of the demo.
116+
117+
2. Try send messages by entering them into the main chat box.
118+
![Chatroom](imgs/chatroom-noauth.png)
119+
120+
## Use Key Vault secret reference
121+
122+
The url of upstream is not encrypted at rest. If you have any sensitive information, you can use Key Vault to save this sensitive information. Basically, you can enable managed identity of Azure SignalR Service and then grant a read permission on a Key Vault instance and use Key Vault reference instead of plaintext in `Upstream URL Pattern`.
123+
124+
The following steps demonstrate how to use Key Vault secret reference to save `signalr_extensions`.
125+
126+
1. Enable managed identity.
127+
128+
1. Enable managed identity with system assigned identity.
129+
130+
Open portal and navigate to **Identity**, and switch to **System assigned** page. Switch **Status** to **On**.
131+
132+
![SystemAssignedIdentity](imgs/system-assigned-identity.png)
133+
134+
2. Create a Key Vault instance.
135+
136+
```bash
137+
az keyvault create --name "<your-unique-keyvault-name>" --resource-group "myResourceGroup" --location "EastUS"
138+
```
139+
140+
3. Save `signalr_extensions` to secret.
141+
142+
```bash
143+
az keyvault secret set --name "signalrkey" --vault-name "<your-unique-keyvault-name>" --value "<signalr_extension_code_copied_from_azure_function>"
144+
```
145+
146+
4. Grant **Secret Read** permission to the Key Vault.
147+
148+
```bash
149+
az keyvault set-policy --name "<your-unique-keyvault-name>" --object-id "<object-id-shown-in-system-assigned-identity>" --secret-permissions get
150+
```
151+
152+
5. Get the secret identity of the secret.
153+
154+
```bash
155+
az keyvault secret show --name "signalrkey" --vault-name "<your-unique-keyvault-name>" --query id -o tsv
156+
```
157+
158+
6. Update **Upstream URL Pattern** with Key Vault reference. You need to follow the syntax `{@Microsoft.KeyVault(SecretUri=<secret-identity>)}`. As shown below:
159+
160+
![KeyVaultReference](imgs/key-vault-reference.png)
161+
162+
## Enable AAD Token on upstream
163+
164+
You can set **ManagedIdentity** as the **Auth** setting in upstream. After that, SignalR Service will set an AAD Token into the `Authorization` for each upstream request.
165+
166+
1. Make sure you have enabled managed identity.
167+
168+
2. Click the asterisk in *Hub Rules* and a new page pops out as shown below.
169+
![Upstream details](imgs/upstream-details-portal.png)
170+
171+
3. Select *Use Managed Identity* under *Upstream Authentication* and choose the system identity created earlier for the SignalR service.
172+
173+
4. Use browser to visit `<function-app-url>/api/index` for the web page of the demo
174+
175+
5. Try send messages by entering them into the main chat box. You can verify the `Authorization` has set from the `with Authorization: true`
176+
![Chatroom](imgs/chatroom.png)

samples/DotnetIsolated-ClassBased/content/index.html

+6-2
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ <h3>Serverless chat</h3>
9999
</div>
100100

101101
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
102-
<script src="https://cdn.jsdelivr.net/npm/@aspnet/[email protected].3/dist/browser/signalr.js"></script>
102+
<script src="https://cdn.jsdelivr.net/npm/@aspnet/[email protected].27/dist/browser/signalr.js"></script>
103103
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
104104
<script src="https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.js"></script>
105105
<script src="https://cdn.jsdelivr.net/npm/[email protected]/enc-base64.js"></script>
@@ -203,9 +203,13 @@ <h3>Serverless chat</h3>
203203
};
204204
function onNewConnection(message) {
205205
data.myConnectionId = message.ConnectionId;
206+
authEnabled = false;
207+
if (message.Authentication) {
208+
authEnabled = true;
209+
}
206210
newConnectionMessage = {
207211
id: counter++,
208-
text: `${message.ConnectionId} has connected.`
212+
text: `${message.ConnectionId} has connected, with Authorization: ${authEnabled.toString()}`
209213
};
210214
data.messages.unshift(newConnectionMessage);
211215
}
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)