Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans ce guide de démarrage rapide, vous allez apprendre à utiliser TypeSpec pour concevoir, générer et implémenter une application API RESTful. TypeSpec est un langage open source pour décrire les API de service cloud et génère du code client et serveur pour plusieurs plateformes. En suivant ce guide de démarrage rapide, vous allez apprendre à définir votre contrat d’API une fois et à générer des implémentations cohérentes, ce qui vous aide à créer des services d’API plus faciles à gérer et bien documentés.
Dans ce guide de démarrage rapide, vous :
- Définir votre API à l’aide de TypeSpec
- Créer une application de serveur d’API
- Intégrer Azure Cosmos DB pour le stockage persistant
- Exécuter et tester votre API localement
- Déployer sur Azure Container Apps
Prerequisites
- Un compte Azure actif. Créez un compte gratuitement si vous n’en avez pas.
- SDK .NET 9
- Node.js LTS installé sur votre système.
- Visual Studio Code avec les extensions suivantes :
- Extension TypeSpec
- Facultatif : Déploiement avec Azure Developer CLI
Développement avec TypeSpec
TypeSpec définit votre API de manière indépendante du langage et génère le serveur d’API et la bibliothèque cliente pour plusieurs plateformes. Cette fonctionnalité vous permet de :
- Définir votre contrat d’API une seule fois
- Générer du code client et serveur cohérent
- Concentrez-vous sur l’implémentation de la logique métier plutôt que sur l’infrastructure d’API
TypeSpec fournit la gestion des services d’API :
- Langage de définition d’API
- Middleware de routage côté serveur pour l’API
- Bibliothèques clientes pour l’utilisation de l’API
Vous fournissez des demandes clientes et des intégrations de serveur :
- Implémenter une logique métier dans un intergiciel ( middleware) tel que les services Azure pour les bases de données, le stockage et la messagerie
- Serveur d’hébergement pour votre API (localement ou dans Azure)
- Scripts de déploiement pour l’approvisionnement et le déploiement reproductibles
Créer une application TypeSpec
Créez un dossier pour contenir le serveur d’API et les fichiers TypeSpec.
mkdir my_typespec_quickstart cd my_typespec_quickstartInstallez globalement le compilateur TypeSpec :
npm install -g @typespec/compilerVérifiez que TypeSpec est installé correctement :
tsp --versionInitialisez le projet TypeSpec :
tsp initRépondez aux questions ci-dessous avec les réponses fournies.
- Initialiser un nouveau projet ici ? Y
- Sélectionnez un modèle de projet ? API REST générique
- Entrez un nom de projet : Widgets
- Quels émetteurs voulez-vous utiliser ?
- Document OpenAPI 3.1
- Stubs de serveur C#
Les émetteurs TypeSpec sont des bibliothèques qui utilisent différentes API du compilateur TypeSpec pour réfléchir au processus de compilation TypeSpec et générer des artefacts.
Attendez que l’initialisation se termine avant de continuer.
Run tsp compile . to build the project. Please review the following messages from emitters: @typespec/http-server-csharp: Generated ASP.Net services require dotnet 9: https://dotnet.microsoft.com/download Create an ASP.Net service project for your TypeSpec: > npx hscs-scaffold . --use-swaggerui --overwrite More information on getting started: https://aka.ms/tsp/hscs/startCompilez le projet :
tsp compile .TypeSpec génère le projet par défaut dans
./tsp-output, créant deux dossiers distincts :- schema
- server
Ouvrez le fichier
./tsp-output/schema/openapi.yaml. Notez que les quelques lignes dans./main.tspont généré plus de 200 lignes de spécification OpenApi pour vous.Ouvrez le dossier
./tsp-output/server/aspnet. Notez que les fichiers .NET scaffoldés incluent :-
./generated/operations/IWidgets.csdéfinit l’interface pour les méthodes Widgets. -
./generated/controllers/WidgetsController.csimplémente l’intégration aux méthodes Widgets. C’est là que vous placez votre logique métier. -
./generated/modelsdéfinit les modèles de l’API Widget.
-
Configurer des émetteurs TypeSpec
Utilisez les fichiers TypeSpec pour configurer la génération du serveur d’API.
Ouvrez le
tsconfig.yamlet remplacez la configuration existante par le YAML suivant :emit: - "@typespec/openapi3" - "@typespec/http-server-csharp" options: "@typespec/openapi3": emitter-output-dir: "{cwd}/server/wwwroot" openapi-versions: - 3.1.0 "@typespec/http-server-csharp": emitter-output-dir: "{cwd}/server/" use-swaggerui: true overwrite: true emit-mocks: "mocks-and-project-files"Cette configuration projette plusieurs modifications dont nous avons besoin pour un serveur d’API .NET entièrement généré :
-
emit-mocks: Créez tous les fichiers projet nécessaires pour le serveur. -
use-swaggerui: Intégrez l’interface utilisateur Swagger afin de pouvoir utiliser l’API de manière conviviale pour le navigateur. -
emitter-output-dir: définissez le répertoire de sortie pour la génération de serveur et la génération de spécification OpenApi. - Générez tout en
./server.
-
Recompilez le projet :
tsp compile .Passez au nouveau
/serverrépertoire :cd serverCréez un certificat de développeur par défaut si vous n’en avez pas déjà :
dotnet dev-certs httpsExécutez le projet :
dotnet runAttendez que la notification s’ouvre dans le navigateur.
Ouvrez le navigateur et ajoutez l’itinéraire de l’interface utilisateur Swagger.
/swaggerL’API TypeSpec et le serveur par défaut fonctionnent tous les deux.
Comprendre la structure des fichiers d’application
La structure du projet pour le serveur généré inclut le serveur d’API basé sur le contrôleur .NET, les fichiers .NET pour la génération du projet et l’intergiciel pour votre intégration Azure.
├── appsettings.Development.json
├── appsettings.json
├── docs
├── generated
├── mocks
├── Program.cs
├── Properties
├── README.md
├── ServiceProject.csproj
└── wwwroot
-
Ajoutez votre logique métier : dans cet exemple, commencez par le
./server/mocks/Widget.csfichier. LeWidget.csgénéré fournit des méthodes standard. -
Mettez à jour le serveur : ajoutez des configurations de serveur spécifiques à
./program.cs. -
Utilisez la spécification OpenApi : le TypeSpec a généré le fichier OpenApi3.json dans le fichier
./server/wwwrootet l’a rendu disponible pour Swagger UI pendant le développement. Cela fournit une interface utilisateur pour votre spécification. Vous pouvez interagir avec votre API sans avoir à fournir un mécanisme de requête tel qu’un client REST ou un serveur frontal web.
Modifier la persistance vers Azure Cosmos DB no-sql
Maintenant que le serveur d’API widget de base fonctionne, mettez à jour le serveur pour qu’il fonctionne avec Azure Cosmos DB pour un magasin de données persistant.
Dans le
./serverrépertoire, ajoutez Azure Cosmos DB au projet :dotnet add package Microsoft.Azure.CosmosAjoutez la bibliothèque d’identités Azure pour vous authentifier auprès d’Azure :
dotnet add package Azure.IdentityMettez à jour les paramètres d’intégration
./server/ServiceProject.csprojde Cosmos DB :<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> ... existing settings ... <EnableSdkContainerSupport>true</EnableSdkContainerSupport> </PropertyGroup> <ItemGroup> ... existing settings ... <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> </ItemGroup> </Project>- EnableSdkContainerSupport vous permet d’utiliser la prise en charge intégrée de la création de conteneurs du Kit de développement logiciel (SDK) .NET (dotnet publish --container) sans écrire de Dockerfile.
- Newtonsoft.Json ajoute le sérialiseur Json .NET utilisé par le Kit de développement logiciel (SDK) Cosmos DB pour convertir vos objets .NET vers et à partir de JSON.
Créez un nouveau fichier
./azure/CosmosDbRegistrationd'enregistrement pour gérer l'enregistrement Cosmos DB :using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Configuration; using System; using System.Threading.Tasks; using Azure.Identity; using DemoService; namespace WidgetService.Service { /// <summary> /// Registration class for Azure Cosmos DB services and implementations /// </summary> public static class CosmosDbRegistration { /// <summary> /// Registers the Cosmos DB client and related services for dependency injection /// </summary> /// <param name="builder">The web application builder</param> public static void RegisterCosmosServices(this WebApplicationBuilder builder) { // Register the HttpContextAccessor for accessing the HTTP context builder.Services.AddHttpContextAccessor(); // Get configuration settings var cosmosEndpoint = builder.Configuration["Configuration:AzureCosmosDb:Endpoint"]; // Validate configuration ValidateCosmosDbConfiguration(cosmosEndpoint); // Configure Cosmos DB client options var cosmosClientOptions = new CosmosClientOptions { SerializerOptions = new CosmosSerializationOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase }, ConnectionMode = ConnectionMode.Direct }; builder.Services.AddSingleton(serviceProvider => { var credential = new DefaultAzureCredential(); // Create Cosmos client with token credential authentication return new CosmosClient(cosmosEndpoint, credential, cosmosClientOptions); }); // Initialize Cosmos DB if needed builder.Services.AddHostedService<CosmosDbInitializer>(); // Register WidgetsCosmos implementation of IWidgets builder.Services.AddScoped<IWidgets, WidgetsCosmos>(); } /// <summary> /// Validates the Cosmos DB configuration settings /// </summary> /// <param name="cosmosEndpoint">The Cosmos DB endpoint</param> /// <exception cref="ArgumentException">Thrown when configuration is invalid</exception> private static void ValidateCosmosDbConfiguration(string cosmosEndpoint) { if (string.IsNullOrEmpty(cosmosEndpoint)) { throw new ArgumentException("Cosmos DB Endpoint must be specified in configuration"); } } } }Notez la variable d’environnement pour le point de terminaison :
var cosmosEndpoint = builder.Configuration["Configuration:AzureCosmosDb:Endpoint"];Créez une classe
./azure/WidgetsCosmos.csWidget pour fournir une logique métier à intégrer à Azure Cosmos DB pour votre magasin persistant.using System; using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Linq; // Use generated models and operations using DemoService; namespace WidgetService.Service { /// <summary> /// Implementation of the IWidgets interface that uses Azure Cosmos DB for persistence /// </summary> public class WidgetsCosmos : IWidgets { private readonly CosmosClient _cosmosClient; private readonly ILogger<WidgetsCosmos> _logger; private readonly IHttpContextAccessor _httpContextAccessor; private readonly string _databaseName = "WidgetDb"; private readonly string _containerName = "Widgets"; /// <summary> /// Initializes a new instance of the WidgetsCosmos class. /// </summary> /// <param name="cosmosClient">The Cosmos DB client instance</param> /// <param name="logger">Logger for diagnostic information</param> /// <param name="httpContextAccessor">Accessor for the HTTP context</param> public WidgetsCosmos( CosmosClient cosmosClient, ILogger<WidgetsCosmos> logger, IHttpContextAccessor httpContextAccessor) { _cosmosClient = cosmosClient; _logger = logger; _httpContextAccessor = httpContextAccessor; } /// <summary> /// Gets a reference to the Cosmos DB container for widgets /// </summary> private Container WidgetsContainer => _cosmosClient.GetContainer(_databaseName, _containerName); /// <summary> /// Lists all widgets in the database /// </summary> /// <returns>Array of Widget objects</returns> public async Task<WidgetList> ListAsync() { try { var queryDefinition = new QueryDefinition("SELECT * FROM c"); var widgets = new List<Widget>(); using var iterator = WidgetsContainer.GetItemQueryIterator<Widget>(queryDefinition); while (iterator.HasMoreResults) { var response = await iterator.ReadNextAsync(); widgets.AddRange(response.ToList()); } // Create and return a WidgetList instead of Widget[] return new WidgetList { Items = widgets.ToArray() }; } catch (Exception ex) { _logger.LogError(ex, "Error listing widgets from Cosmos DB"); throw new Error(500, "Failed to retrieve widgets from database"); } } /// <summary> /// Retrieves a specific widget by ID /// </summary> /// <param name="id">The ID of the widget to retrieve</param> /// <returns>The retrieved Widget</returns> public async Task<Widget> ReadAsync(string id) { try { var response = await WidgetsContainer.ReadItemAsync<Widget>( id, new PartitionKey(id)); return response.Resource; } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { _logger.LogWarning("Widget with ID {WidgetId} not found", id); throw new Error(404, $"Widget with ID '{id}' not found"); } catch (Exception ex) { _logger.LogError(ex, "Error reading widget {WidgetId} from Cosmos DB", id); throw new Error(500, "Failed to retrieve widget from database"); } } /// <summary> /// Creates a new widget from the provided Widget object /// </summary> /// <param name="body">The Widget object to store in the database</param> /// <returns>The created Widget</returns> public async Task<Widget> CreateAsync(Widget body) { try { // Validate the Widget if (body == null) { throw new Error(400, "Widget data cannot be null"); } if (string.IsNullOrEmpty(body.Id)) { throw new Error(400, "Widget must have an Id"); } if (body.Color != "red" && body.Color != "blue") { throw new Error(400, "Color must be 'red' or 'blue'"); } // Save the widget to Cosmos DB var response = await WidgetsContainer.CreateItemAsync( body, new PartitionKey(body.Id)); _logger.LogInformation("Created widget with ID {WidgetId}", body.Id); return response.Resource; } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.Conflict) { _logger.LogError(ex, "Widget with ID {WidgetId} already exists", body.Id); throw new Error(409, $"Widget with ID '{body.Id}' already exists"); } catch (Exception ex) when (!(ex is Error)) { _logger.LogError(ex, "Error creating widget in Cosmos DB"); throw new Error(500, "Failed to create widget in database"); } } /// <summary> /// Updates an existing widget with properties specified in the patch document /// </summary> /// <param name="id">The ID of the widget to update</param> /// <param name="body">The WidgetMergePatchUpdate object containing properties to update</param> /// <returns>The updated Widget</returns> public async Task<Widget> UpdateAsync(string id, TypeSpec.Http.WidgetMergePatchUpdate body) { try { // Validate input parameters if (body == null) { throw new Error(400, "Update data cannot be null"); } if (body.Color != null && body.Color != "red" && body.Color != "blue") { throw new Error(400, "Color must be 'red' or 'blue'"); } // First check if the item exists Widget existingWidget; try { var response = await WidgetsContainer.ReadItemAsync<Widget>( id, new PartitionKey(id)); existingWidget = response.Resource; } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { _logger.LogWarning("Widget with ID {WidgetId} not found for update", id); throw new Error(404, $"Widget with ID '{id}' not found"); } // Apply the patch updates only where properties are provided bool hasChanges = false; if (body.Weight.HasValue) { existingWidget.Weight = body.Weight.Value; hasChanges = true; } if (body.Color != null) { existingWidget.Color = body.Color; hasChanges = true; } // Only perform the update if changes were made if (hasChanges) { // Use ReplaceItemAsync for the update var updateResponse = await WidgetsContainer.ReplaceItemAsync( existingWidget, id, new PartitionKey(id)); _logger.LogInformation("Updated widget with ID {WidgetId}", id); return updateResponse.Resource; } // If no changes, return the existing widget _logger.LogInformation("No changes to apply for widget with ID {WidgetId}", id); return existingWidget; } catch (Error) { // Rethrow Error exceptions throw; } catch (Exception ex) { _logger.LogError(ex, "Error updating widget {WidgetId} in Cosmos DB", id); throw new Error(500, "Failed to update widget in database"); } } /// <summary> /// Deletes a widget by its ID /// </summary> /// <param name="id">The ID of the widget to delete</param> public async Task DeleteAsync(string id) { try { await WidgetsContainer.DeleteItemAsync<Widget>(id, new PartitionKey(id)); _logger.LogInformation("Deleted widget with ID {WidgetId}", id); } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { _logger.LogWarning("Widget with ID {WidgetId} not found for deletion", id); throw new Error(404, $"Widget with ID '{id}' not found"); } catch (Exception ex) { _logger.LogError(ex, "Error deleting widget {WidgetId} from Cosmos DB", id); throw new Error(500, "Failed to delete widget from database"); } } /// <summary> /// Analyzes a widget by ID and returns a simplified analysis result /// </summary> /// <param name="id">The ID of the widget to analyze</param> /// <returns>An AnalyzeResult containing the analysis of the widget</returns> public async Task<AnalyzeResult> AnalyzeAsync(string id) { try { // First retrieve the widget from the database Widget widget; try { var response = await WidgetsContainer.ReadItemAsync<Widget>( id, new PartitionKey(id)); widget = response.Resource; } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { _logger.LogWarning("Widget with ID {WidgetId} not found for analysis", id); throw new Error(404, $"Widget with ID '{id}' not found"); } // Create the analysis result var result = new AnalyzeResult { Id = widget.Id, Analysis = $"Weight: {widget.Weight}, Color: {widget.Color}" }; _logger.LogInformation("Analyzed widget with ID {WidgetId}", id); return result; } catch (Error) { // Rethrow Error exceptions throw; } catch (Exception ex) { _logger.LogError(ex, "Error analyzing widget {WidgetId} from Cosmos DB", id); throw new Error(500, "Failed to analyze widget from database"); } } } }Créez le fichier pour l’authentification
./server/services/CosmosDbInitializer.csauprès d’Azure :using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace WidgetService.Service { /// <summary> /// Hosted service that initializes Cosmos DB resources on application startup /// </summary> public class CosmosDbInitializer : IHostedService { private readonly CosmosClient _cosmosClient; private readonly ILogger<CosmosDbInitializer> _logger; private readonly IConfiguration _configuration; private readonly string _databaseName; private readonly string _containerName = "Widgets"; public CosmosDbInitializer(CosmosClient cosmosClient, ILogger<CosmosDbInitializer> logger, IConfiguration configuration) { _cosmosClient = cosmosClient; _logger = logger; _configuration = configuration; _databaseName = _configuration["CosmosDb:DatabaseName"] ?? "WidgetDb"; } public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Ensuring Cosmos DB database and container exist..."); try { // Create database if it doesn't exist var databaseResponse = await _cosmosClient.CreateDatabaseIfNotExistsAsync( _databaseName, cancellationToken: cancellationToken); _logger.LogInformation("Database {DatabaseName} status: {Status}", _databaseName, databaseResponse.StatusCode == System.Net.HttpStatusCode.Created ? "Created" : "Already exists"); // Create container if it doesn't exist (using id as partition key) var containerResponse = await databaseResponse.Database.CreateContainerIfNotExistsAsync( new ContainerProperties { Id = _containerName, PartitionKeyPath = "/id" }, throughput: 400, // Minimum RU/s cancellationToken: cancellationToken); _logger.LogInformation("Container {ContainerName} status: {Status}", _containerName, containerResponse.StatusCode == System.Net.HttpStatusCode.Created ? "Created" : "Already exists"); } catch (Exception ex) { _logger.LogError(ex, "Error initializing Cosmos DB"); throw; } } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } }Mettez à jour
./server/program.cspour utiliser Cosmos DB et autorisez l’interface utilisateur Swagger dans un déploiement de production. Copiez dans l’intégralité du fichier :// Generated by @typespec/http-server-csharp // <auto-generated /> #nullable enable using TypeSpec.Helpers; using WidgetService.Service; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(options => { options.Filters.Add<HttpServiceExceptionFilter>(); }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // Replace original registration with the Cosmos DB one CosmosDbRegistration.RegisterCosmosServices(builder); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } // Swagger UI is always available app.UseSwagger(); app.UseSwaggerUI(c => { c.DocumentTitle = "TypeSpec Generated OpenAPI Viewer"; c.SwaggerEndpoint("/openapi.yaml", "TypeSpec Generated OpenAPI Docs"); c.RoutePrefix = "swagger"; }); app.UseHttpsRedirection(); app.UseStaticFiles(); app.Use(async (context, next) => { context.Request.EnableBuffering(); await next(); }); app.MapGet("/openapi.yaml", async (HttpContext context) => { var externalFilePath = "wwwroot/openapi.yaml"; if (!File.Exists(externalFilePath)) { context.Response.StatusCode = StatusCodes.Status404NotFound; await context.Response.WriteAsync("OpenAPI spec not found."); return; } context.Response.ContentType = "application/json"; await context.Response.SendFileAsync(externalFilePath); }); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();Construisez le projet :
dotnet buildLe projet s’appuie désormais sur l’intégration de Cosmos DB. Nous allons créer les scripts de déploiement pour créer les ressources Azure et déployer le projet.
Créer une infrastructure de déploiement
Créez les fichiers nécessaires pour disposer d’un déploiement reproductible avec azure Developer CLI et des modèles Bicep.
À la racine du projet TypeSpec, créez un fichier de définition de déploiement
azure.yamlet collez le texte source suivant :# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json name: azure-typespec-scaffold-dotnet metadata: template: azd-init@1.14.0 services: api: project: ./server host: containerapp language: dotnet pipeline: provider: githubNotez que cette configuration fait référence à l’emplacement du projet généré (
./server). Vérifiez que./tspconfig.yamlcorrespond à l’emplacement spécifié dans./azure.yaml.À la racine du projet TypeSpec, créez un
./infrarépertoire.Créez un
./infra/main.bicepparamfichier et copiez-le dans ce qui suit pour définir les paramètres dont nous avons besoin pour le déploiement :using './main.bicep' param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'dev') param location = readEnvironmentVariable('AZURE_LOCATION', 'eastus2') param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID', '')Cette liste de paramètres contient les paramètres minimaux nécessaires pour ce déploiement.
Créez un
./infra/main.bicepfichier et copiez-le dans ce qui suit pour définir les ressources Azure pour l’approvisionnement et le déploiement :metadata description = 'Bicep template for deploying a GitHub App using Azure Container Apps and Azure Container Registry.' targetScope = 'resourceGroup' param serviceName string = 'api' var databaseName = 'WidgetDb' var containerName = 'Widgets' @minLength(1) @maxLength(64) @description('Name of the environment that can be used as part of naming resource convention') param environmentName string @minLength(1) @description('Primary location for all resources') param location string @description('Id of the principal to assign database and application roles.') param deploymentUserPrincipalId string = '' var resourceToken = toLower(uniqueString(resourceGroup().id, environmentName, location)) var tags = { 'azd-env-name': environmentName repo: 'https://github.com/typespec' } module managedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = { name: 'user-assigned-identity' params: { name: 'identity-${resourceToken}' location: location tags: tags } } module cosmosDb 'br/public:avm/res/document-db/database-account:0.8.1' = { name: 'cosmos-db-account' params: { name: 'cosmos-db-nosql-${resourceToken}' location: location locations: [ { failoverPriority: 0 locationName: location isZoneRedundant: false } ] tags: tags disableKeyBasedMetadataWriteAccess: true disableLocalAuth: true networkRestrictions: { publicNetworkAccess: 'Enabled' ipRules: [] virtualNetworkRules: [] } capabilitiesToAdd: [ 'EnableServerless' ] sqlRoleDefinitions: [ { name: 'nosql-data-plane-contributor' dataAction: [ 'Microsoft.DocumentDB/databaseAccounts/readMetadata' 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' ] } ] sqlRoleAssignmentsPrincipalIds: union( [ managedIdentity.outputs.principalId ], !empty(deploymentUserPrincipalId) ? [deploymentUserPrincipalId] : [] ) sqlDatabases: [ { name: databaseName containers: [ { name: containerName paths: [ '/id' ] } ] } ] } } module containerRegistry 'br/public:avm/res/container-registry/registry:0.5.1' = { name: 'container-registry' params: { name: 'containerreg${resourceToken}' location: location tags: tags acrAdminUserEnabled: false anonymousPullEnabled: true publicNetworkAccess: 'Enabled' acrSku: 'Standard' } } var containerRegistryRole = subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', '8311e382-0749-4cb8-b61a-304f252e45ec' ) module registryUserAssignment 'br/public:avm/ptn/authorization/resource-role-assignment:0.1.1' = if (!empty(deploymentUserPrincipalId)) { name: 'container-registry-role-assignment-push-user' params: { principalId: deploymentUserPrincipalId resourceId: containerRegistry.outputs.resourceId roleDefinitionId: containerRegistryRole } } module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.7.0' = { name: 'log-analytics-workspace' params: { name: 'log-analytics-${resourceToken}' location: location tags: tags } } module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.8.0' = { name: 'container-apps-env' params: { name: 'container-env-${resourceToken}' location: location tags: tags logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.resourceId zoneRedundant: false } } module containerAppsApp 'br/public:avm/res/app/container-app:0.9.0' = { name: 'container-apps-app' params: { name: 'container-app-${resourceToken}' environmentResourceId: containerAppsEnvironment.outputs.resourceId location: location tags: union(tags, { 'azd-service-name': serviceName }) ingressTargetPort: 8080 ingressExternal: true ingressTransport: 'auto' stickySessionsAffinity: 'sticky' scaleMaxReplicas: 1 scaleMinReplicas: 1 corsPolicy: { allowCredentials: true allowedOrigins: [ '*' ] } managedIdentities: { systemAssigned: false userAssignedResourceIds: [ managedIdentity.outputs.resourceId ] } secrets: { secureList: [ { name: 'azure-cosmos-db-nosql-endpoint' value: cosmosDb.outputs.endpoint } { name: 'user-assigned-managed-identity-client-id' value: managedIdentity.outputs.clientId } ] } containers: [ { image: 'mcr.microsoft.com/dotnet/samples:aspnetapp-9.0' name: serviceName resources: { cpu: '0.25' memory: '.5Gi' } env: [ { name: 'CONFIGURATION__AZURECOSMOSDB__ENDPOINT' secretRef: 'azure-cosmos-db-nosql-endpoint' } { name: 'AZURE_CLIENT_ID' secretRef: 'user-assigned-managed-identity-client-id' } ] } ] } } output CONFIGURATION__AZURECOSMOSDB__ENDPOINT string = cosmosDb.outputs.endpoint output CONFIGURATION__AZURECOSMOSDB__DATABASENAME string = databaseName output CONFIGURATION__AZURECOSMOSDB__CONTAINERNAME string = containerName output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.outputs.loginServerLes variables de sortie vous permettent d’utiliser les ressources cloud approvisionnées avec votre développement local.
La balise containerAppsApp utilise la variable serviceName (définie
apien haut du fichier) et leapispécifié dans./azure.yaml. Cette connexion indique à Azure Developer CLI où déployer le projet .NET sur la ressource d’hébergement Azure Container Apps....bicep... module containerAppsApp 'br/public:avm/res/app/container-app:0.9.0' = { name: 'container-apps-app' params: { name: 'container-app-${resourceToken}' environmentResourceId: containerAppsEnvironment.outputs.resourceId location: location tags: union(tags, { 'azd-service-name': serviceName }) <--------- `API` ...bicep..
Structure du projet
La structure finale du projet inclut les fichiers API TypeSpec, le serveur Express.js et les fichiers de déploiement Azure :
├── infra
├── tsp-output
├── .gitignore
├── .azure.yaml
├── Dockerfile
├── main.tsp
├── package-lock.json
├── package.json
├── tspconfig.yaml
| Area | Fichiers/répertoires |
|---|---|
| TypeSpec |
main.tsp, tspconfig.yaml |
| serveurExpress.js |
./tsp-output/server/ (inclut des fichiers générés tels que controllers/, models/, ServiceProject.csproj) |
| Déploiement d’Azure Developer CLI |
./azure.yaml,./infra/ |
Déployer une application sur Azure
Vous pouvez déployer cette application sur Azure à l’aide d’Azure Container Apps :
Authentifiez-vous auprès de l’interface CLI du développeur Azure :
azd auth loginDéployez sur Azure Container Apps à l’aide d’Azure Developer CLI :
azd up
Utiliser l’application dans le navigateur
Une fois déployé, vous pouvez :
- Accédez à l’interface utilisateur Swagger pour tester votre API à l’adresse
/swagger. - Utilisez la fonctionnalité Try it now sur chaque API pour créer, lire, mettre à jour et supprimer des widgets via l’API.
Développer votre application
Maintenant que vous disposez de l’intégralité du processus de bout en bout, continuez à générer votre API :
- En savoir plus sur le langage TypeSpec pour ajouter d’autres API et fonctionnalités de couche API dans le
./main.tsp. - Ajoutez des émetteurs supplémentaires et configurez leurs paramètres dans le
./tspconfig.yaml. - Lorsque vous ajoutez d’autres fonctionnalités dans vos fichiers TypeSpec, prenez en charge ces modifications avec le code source dans le projet de serveur.
- Continuez à utiliser l’authentification sans mot de passe avec Azure Identity.
Nettoyer les ressources
Lorsque vous avez terminé avec ce guide de démarrage rapide, vous pouvez supprimer les ressources Azure :
azd down
Ou supprimez le groupe de ressources directement à partir du portail Azure.