Brancher une API Azure Functions

Brancher une API Azure Functions

Brancher une API Azure Functions

Cet article fait partie de la série Azure Static Web Apps avec Blazor.

Un exemple complet et fonctionnel est disponible ici : mongeon/code-examples · azure-swa-blazor/03-api-functions.

Jusqu’ici, notre app Blazor WASM est purement statique. C’est suffisant pour bien des projets, mais tôt ou tard vous allez avoir besoin d’un backend : appeler une API tierce sans exposer de clés, accéder à une base de données, ou faire du traitement que vous ne voulez pas exécuter dans le navigateur.

Azure Static Web Apps supporte deux approches pour ça : les managed functions (incluses dans le tier gratuit) et le linked backend (tier Standard). On va se concentrer sur les managed functions, parce que c’est la plus simple et la plus courante.

L’idée générale

SWA agit comme un reverse proxy. Toutes les requêtes vers /api/* sont automatiquement redirigées vers votre Azure Functions, de façon transparente. Côté Blazor, vous appelez /api/products comme si c’était un endpoint sur le même serveur. Pas de CORS à configurer, pas d’URL externe à gérer.

C’est ce détail qui rend l’intégration aussi agréable. Votre frontend et votre backend partagent le même domaine, donc le navigateur traite les appels API comme des requêtes same-origin.

Créer le projet Azure Functions

On va ajouter un projet Azure Functions dans la même solution que notre projet Blazor. On utilise le modèle isolated worker avec .NET :

# À la racine de votre solution
mkdir Api
cd Api
func init --worker-runtime dotnet-isolated --target-framework net10.0

Ça va créer un projet Functions avec le modèle isolated worker. Si vous n’avez pas les Azure Functions Core Tools installées, c’est le moment de le faire avec npm install -g azure-functions-core-tools@4.

Ensuite, on crée une fonction HTTP :

func new --name GetProducts --template "HTTP trigger"

Modifiez la fonction générée pour retourner quelque chose d’utile. Voici un exemple simple :

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using System.Net;

namespace Api;

public class GetProducts
{
    [Function("GetProducts")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "products")] HttpRequestData req)
    {
        // Données simulées : en pratique, vous les récupéreriez depuis une base de données
        var products = new[]
        {
            new { Id = 1, Name = "Widget", Price = 9.99 },
            new { Id = 2, Name = "Gadget", Price = 24.99 },
            new { Id = 3, Name = "Thingamajig", Price = 14.99 }
        };

        var response = req.CreateResponse(HttpStatusCode.OK);
        await response.WriteAsJsonAsync(products);
        return response;
    }
}

Quelques points importants. Le AuthorizationLevel.Anonymous est nécessaire parce que SWA gère l’authentification en amont. Le Route = "products" fait que l’endpoint sera disponible à /api/products.

La structure de votre solution devrait maintenant ressembler à ça :

Client/
  Pages/
  wwwroot/
    staticwebapp.config.json
  Program.cs
  Client.csproj
Api/
  GetProducts.cs
  Program.cs
  host.json
  local.settings.json
  Api.csproj

Appeler l’API depuis Blazor

Côté Blazor, on utilise HttpClient pour appeler notre API. Dans Program.cs du projet Client, le HttpClient est déjà configuré avec la bonne base address :

builder.Services.AddScoped(sp =>
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

Ensuite, dans un composant Razor :

@page "/products"
@inject HttpClient Http

<h3>Products</h3>

@if (products == null)
{
    <p>Loading...</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Name</th>
                <th>Price</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in products)
            {
                <tr>
                    <td>@product.Name</td>
                    <td>@product.Price.ToString("C")</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private Product[]? products;

    protected override async Task OnInitializedAsync()
    {
        // L'appel se fait vers /api/products, même domaine
        products = await Http.GetFromJsonAsync<Product[]>("api/products");
    }

    private class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public decimal Price { get; set; }
    }
}

L’URL relative api/products fonctionne parce que le BaseAddress du HttpClient pointe déjà vers le bon domaine, que ce soit en local ou déployé.

Le développement local

C’est ici que ça peut accrocher. En développement, votre app Blazor tourne sur un port (disons localhost:5000) et Azure Functions tourne sur un autre (disons localhost:7071). Les appels vers /api/products ne vont pas aboutir parce qu’il n’y a pas de proxy.

La solution la plus simple pour le développement local est de configurer le HttpClient pour pointer vers le port de Functions en développement. Dans Program.cs :

var apiBaseAddress = builder.Configuration["ApiBaseAddress"]
    ?? builder.HostEnvironment.BaseAddress;

builder.Services.AddScoped(sp =>
    new HttpClient { BaseAddress = new Uri(apiBaseAddress) });

Et dans wwwroot/appsettings.Development.json :

{
  "ApiBaseAddress": "http://localhost:7071"
}

En production, ApiBaseAddress n’est pas défini, donc le HttpClient utilise le BaseAddress par défaut. L’autre option, c’est d’utiliser le CLI swa pour simuler le proxy localement. On va couvrir ça en détail dans l’article 5 de la série.

Mettre à jour le workflow GitHub Actions

Pour que SWA sache qu’il y a une API à déployer, il faut ajouter le api_location dans le workflow :

- name: Build And Deploy
  uses: Azure/static-web-apps-deploy@v1
  with:
    azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
    repo_token: ${{ secrets.GITHUB_TOKEN }}
    action: "upload"
    app_location: "Client"
    api_location: "Api"
    output_location: "wwwroot"

Le api_location: "Api" dit au workflow de compiler et déployer le projet Functions qui se trouve dans le dossier Api. Après le déploiement, vos endpoints /api/ sont automatiquement disponibles sous le même domaine que votre app Blazor.

Managed functions vs linked backend

Les managed functions sont déployées et gérées directement par SWA. C’est ce qu’on utilise dans cet article. Elles ont quelques limites : les requêtes sont limitées à 45 secondes, et les seuls triggers supportés sont HTTP. Pas de timer triggers, pas de queue triggers.

Le linked backend permet de connecter une Azure Functions app existante (ou même un App Service, un Container App, etc.) à votre SWA. Ça demande le tier Standard et la configuration se fait dans le portail Azure plutôt que dans le workflow. Quand vous linkez un backend, mettez api_location à une chaîne vide ("") dans votre workflow pour éviter les conflits.

Pour la plupart des projets, les managed functions sont amplement suffisantes. Si vous avez besoin de triggers non-HTTP ou de requêtes longues, passez au linked backend.

Les secrets et variables d’environnement

Vos Azure Functions vont probablement avoir besoin de clés API ou de connection strings. Ne les mettez pas dans le code. Utilisez les application settings dans le portail Azure : Static Web Apps > votre app > Environment variables.

Côté code, vous y accédez comme des variables d’environnement normales :

var connectionString = Environment.GetEnvironmentVariable("DATABASE_CONNECTION_STRING");

En local, mettez ces valeurs dans Api/local.settings.json (qui est dans le .gitignore par défaut). Le fichier par défaut ressemble à ceci :

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
  }
}

Vous ajouteriez vos propres secrets au besoin, par exemple "DATABASE_CONNECTION_STRING": "votre-connection-string-locale" dans la section Values.

Dans le prochain article, on va explorer l’authentification intégrée de SWA : les fournisseurs built-in, le endpoint /.auth/, et comment récupérer l’utilisateur connecté dans Blazor et dans l’API.

Articles de la série

  1. C’est quoi une Azure Static Web App?
  2. Le fichier staticwebapp.config.json
  3. Brancher une API Azure Functions (Cet article)
  4. L’authentification intégrée de SWA
  5. Développer localement avec le CLI swa
  6. Les preview environments sur les PR

Bon déploiement, et si vos appels API retournent du HTML, vérifiez votre staticwebapp.config.json.


Cet article a été rédigé avec l’aide de l’IA et révisé par moi.


Suggestions de lecture :