Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I den här snabbstarten får du lära dig hur du använder TypeSpec för att utforma, generera och implementera ett RESTful API-program. TypeSpec är ett språk med öppen källkod för att beskriva API:er för molntjänster och genererar klient- och serverkod för flera plattformar. Genom att följa den här snabbstarten får du lära dig hur du definierar ditt API-kontrakt en gång och genererar konsekventa implementeringar, vilket hjälper dig att skapa mer underhållsbara och väldokumenterade API-tjänster.
I den här snabbstarten kommer du att göra följande:
- Definiera ditt API med TypeSpec
- Skapa ett API-serverprogram
- Integrera Azure Cosmos DB för beständig lagring
- Köra och testa ditt API lokalt
- Distribuera till Azure Container Apps
Prerequisites
- Ett aktivt Azure-konto. Skapa ett konto kostnadsfritt om du inte har något.
- .NET 9 SDK
- Node.js LTS installerat på systemet.
- Visual Studio Code med följande tillägg:
- TypeSpec extension
- Valfritt: Distribution med Azure Developer CLI
Utveckla med TypeSpec
TypeSpec definierar ditt API på ett språkagnostiskt sätt och genererar API-servern och klientbiblioteket för flera plattformar. Med den här funktionen kan du:
- Definiera ditt API-kontrakt en gång
- Generera konsekvent server- och klientkod
- Fokusera på att implementera affärslogik i stället för API-infrastruktur
TypeSpec tillhandahåller API-tjänsthantering:
- API-definitionsspråk
- Mellanprogram för routning på serversidan för API
- Klientbibliotek för användning av API
Du tillhandahåller klientbegäranden och serverintegreringar:
- Implementera affärslogik i mellanprogram som Azure-tjänster för databaser, lagring och meddelanden
- Värdserver för ditt API (lokalt eller i Azure)
- Distributionsskript för upprepningsbar etablering och distribution
Skapa ett nytt TypeSpec-program
- Skapa en ny mapp för att lagra API-servern och TypeSpec-filerna. - mkdir my_typespec_quickstart cd my_typespec_quickstart
- Installera TypeSpec-kompilatorn globalt: - npm install -g @typespec/compiler
- Kontrollera att TypeSpec är korrekt installerat: - tsp --version
- Initiera TypeSpec-projektet: - tsp init
- Besvara följande frågor med de svar som tillhandahålls: - Initiera ett nytt projekt här? Y
- Välj en projektmall Allmänt REST-API
- Ange ett projektnamn: Widgetar
- Vilka sändare vill du använda?
- OpenAPI 3.1-dokument
- C#-serverstubbar
 
 - TypeSpec-utsändare är bibliotek som använder olika TypeSpec-kompilator-API:er för att reflektera över TypeSpec-kompileringsprocessen och generera artefakter. 
- Vänta tills initieringen har slutförts innan du fortsätter. - 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/start
- Kompilera projektet: - tsp compile .
- TypeSpec genererar standardprojektet i - ./tsp-outputoch skapar två separata mappar:- schema
- server
 
- Öppna - ./tsp-output/schema/openapi.yaml-filen. Observera att de få raderna i- ./main.tspgenererade över 200 rader med OpenApi-specifikation åt dig.
- Öppna mappen - ./tsp-output/server/aspnet. Observera att de strukturerade .NET-filerna innehåller:- 
              ./generated/operations/IWidgets.csdefinierar gränssnittet för Widgets-metoderna.
- 
              ./generated/controllers/WidgetsController.csimplementerar integreringen med Widgets-metoderna. Det är här du lägger din affärslogik.
- 
              ./generated/modelsdefinierar modellerna för Widget-API:et.
 
- 
              
Konfigurera TypeSpec-utsändare
Använd TypeSpec-filerna för att konfigurera API-servergenereringen.
- Öppna - tsconfig.yamloch ersätt den befintliga konfigurationen med följande YAML:- 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"- Den här konfigurationen projicerar flera ändringar som vi behöver för en fullständigt genererad .NET API-server: - 
              emit-mocks: Skapa alla projektfiler som behövs för servern.
- 
              use-swaggerui: Integrera Swagger-användargränssnittet så att du kan använda API:et på ett webbläsarvänligt sätt.
- 
              emitter-output-dir: Ange utdatakatalogen för både servergenerering och OpenApi-specifikationsgenerering.
- Generera allt till ./server.
 
- 
              
- Kompilera om projektet: - tsp compile .
- Ändra till den nya - /serverkatalogen:- cd server
- Skapa ett standardcertifikat för utvecklare om du inte redan har ett: - dotnet dev-certs https
- Kör projektet: - dotnet run- Vänta tills meddelandet har öppnats i webbläsaren. 
- Öppna webbläsaren och lägg till Swagger UI-rutten, - /swagger.
- Standard-TypeSpec-API:et och servern fungerar båda. 
Förstå programfilstrukturen
Projektstrukturen för den genererade servern innehåller den .NET-styrenhetsbaserade API-servern, .NET-filerna för att skapa projektet och mellanprogrammet för din Azure-integrering.
├── appsettings.Development.json
├── appsettings.json
├── docs
├── generated
├── mocks
├── Program.cs
├── Properties
├── README.md
├── ServiceProject.csproj
└── wwwroot
- 
              Lägg till din affärslogik: i det här exemplet börjar du med ./server/mocks/Widget.csfilen. Den genereradeWidget.cstillhandahåller standardmetoder.
- 
              Uppdatera servern: lägg till specifika serverkonfigurationer i ./program.cs.
- 
              Använd OpenApi-specifikationen: TypeSpec genererade OpenApi3.json-filen till ./server/wwwroot-filen och gjorde den tillgänglig för Swagger UI under utvecklingen. Detta ger ett användargränssnitt för din specifikation. Du kan interagera med ditt API utan att behöva tillhandahålla en mekanism för begäran, till exempel en REST-klient eller en webbklientdel.
Ändra beständighet till Azure Cosmos DB no-sql
Nu när den grundläggande Widget API-servern fungerar uppdaterar du servern så att den fungerar med Azure Cosmos DB för ett beständigt datalager.
- ./serverLägg till Azure Cosmos DB i projektet i katalogen:- dotnet add package Microsoft.Azure.Cosmos
- Lägg till Azure Identity-biblioteket för att autentisera till Azure: - dotnet add package Azure.Identity
- Uppdatera integreringsinställningarna - ./server/ServiceProject.csprojför 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>- Med EnableSdkContainerSupport kan du använda .NET SDK:s inbyggda stöd för containerbygge (dotnet publish – container) utan att skriva en Dockerfile.
- Newtonsoft.Json lägger till Json .NET-serialiseraren som Cosmos DB SDK använder för att konvertera .NET-objekt till och från JSON.
 
- Skapa en ny registreringsfil - ./azure/CosmosDbRegistrationför att hantera Cosmos DB-registreringen:- 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"); } } } }- Observera miljövariabeln för slutpunkten: - var cosmosEndpoint = builder.Configuration["Configuration:AzureCosmosDb:Endpoint"];
- Skapa en ny Widget-klass, - ./azure/WidgetsCosmos.cs, för att tillhandahålla affärslogik som integreras med Azure Cosmos DB för din permanenta lagringslösning.- 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"); } } } }
- Skapa filen för att autentisera - ./server/services/CosmosDbInitializer.cstill 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; } } }
- Uppdatera - ./server/program.csför att använda Cosmos DB och tillåt att Swagger UI används i en produktion. Kopiera i hela filen:- // 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();
- Bygg projektet: - dotnet build- Projektet byggs nu med Cosmos DB-integrering. Nu ska vi skapa distributionsskripten för att skapa Azure-resurserna och distribuera projektet. 
Skapa distributionsinfrastruktur
Skapa de filer som behövs för att ha en upprepningsbar distribution med Azure Developer CLI - och Bicep-mallar.
- I roten för TypeSpec-projektet skapar du en - azure.yamldistributionsdefinitionsfil och klistrar in följande källa:- # 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: github- Observera att den här konfigurationen refererar till den genererade projektplatsen ( - ./server). Kontrollera att- ./tspconfig.yamlmatchar den plats som anges i- ./azure.yaml.
- Skapa en - ./infrakatalog i roten i TypeSpec-projektet.
- Skapa en - ./infra/main.bicepparamfil och kopiera i följande för att definiera de parametrar vi behöver för distributionen:- using './main.bicep' param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'dev') param location = readEnvironmentVariable('AZURE_LOCATION', 'eastus2') param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID', '')- Den här paramlistan innehåller de minimiparametrar som behövs för den här distributionen. 
- Skapa en - ./infra/main.bicepfil och kopiera i följande för att definiera Azure-resurser för etablering och distribution:- 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.loginServer- Med utdatavariablerna kan du använda de etablerade molnresurserna med din lokala utveckling. 
- ContainerAppsApp-taggen använder variabeln serviceName (anges till - apiöverst i filen) och angiven- apii- ./azure.yaml. Den här anslutningen talar om för Azure Developer CLI var .NET-projektet ska distribueras till Azure Container Apps-värdresursen.- ...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..
Project structure
Den slutliga projektstrukturen innehåller TypeSpec API-filerna, Express.js-servern och Azure-distributionsfilerna:
├── infra
├── tsp-output
├── .gitignore
├── .azure.yaml
├── Dockerfile
├── main.tsp
├── package-lock.json
├── package.json
├── tspconfig.yaml
| Area | Files/Directories | 
|---|---|
| TypeSpec | main.tsp,tspconfig.yaml | 
| Express.js server | ./tsp-output/server/(innehåller genererade filer somcontrollers/,models/,ServiceProject.csproj) | 
| Azure Developer CLI-distribution | ./azure.yaml,./infra/ | 
Distribuera programmet till Azure
Du kan distribuera det här programmet till Azure med Hjälp av Azure Container Apps:
- Autentisera till Azure Developer CLI: - azd auth login
- Distribuera till Azure Container Apps med hjälp av Azure Developer CLI: - azd up
Använda programmet i webbläsaren
När du har distribuerat kan du:
- Få åtkomst till Swagger-användargränssnittet för att testa ditt API på /swagger.
- Använd funktionen Prova nu på varje API för att skapa, läsa, uppdatera och ta bort widgetar via API:et.
Utveckla din applikation
Nu när hela processen från slutpunkt till slutpunkt fungerar fortsätter du att skapa ditt API:
- Läs mer om TypeSpec-språket för att lägga till fler API:er och API-lagerfunktioner i ./main.tsp.
- Lägg till ytterligare utsändare och konfigurera deras parametrar i ./tspconfig.yaml.
- När du lägger till fler funktioner i TypeSpec-filerna stöder du dessa ändringar med källkod i serverprojektet.
- Fortsätt att använda lösenordslös autentisering med Azure Identity.
Rensa resurser
När du är klar med den här snabbstarten kan du ta bort Azure-resurserna:
azd down
Eller ta bort resursgruppen direkt från Azure-portalen.
