Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Ebben a rövid útmutatóban megtudhatja, hogyan használhatja a TypeSpec-et RESTful API-alkalmazások tervezésére, létrehozására és implementálására. A TypeSpec egy nyílt forráskódú nyelv a felhőszolgáltatás API-k leírásához, és több platform ügyfél- és kiszolgálókódját hozza létre. A rövid útmutatót követve megtanulhatja, hogyan határozhatja meg egyszer az API-szerződést, és hogyan hozhat létre konzisztens implementációkat, segítve a karbantartható és jól dokumentált API-szolgáltatások kiépítését.
A TypeSpec kódgenerátorával egy ASP.NET Core-kiszolgálót hozhat létre útválasztással és Swagger felhasználói felülettel, csatlakoztathatja azt az Azure Cosmos DB-hez a megőrzése érdekében, és üzembe helyezheti az Azure Container Appsben.
A TypeSpec API-fejlesztésben betöltött szerepének kontextusáról lásd a TypeSpec áttekintését.
Befejezési idő: 20–25 perc
Ebben a gyors kezdő útmutatóban a következőket teheti meg:
- Az API definiálása a TypeSpec használatával
- API-kiszolgálóalkalmazás létrehozása
- Az Azure Cosmos DB integrálása állandó tároláshoz
- Az API helyi futtatása és tesztelése
- Üzembe helyezés az Azure Container Appsben
Prerequisites
- Aktív Azure-fiók. Ha nincs fiókja, hozzon létre ingyenes fiókot .
- .NET 9 SDK
- Node.js LTS – a TypeSpec parancssori felülethez és a csomagkezelőhöz szükséges.
- Visual Studio Code a következő bővítményekkel:
- TypeSpec kiterjesztés
- Nem kötelező: Üzembe helyezés az Azure Developer CLI-vel
Fejlesztés a TypeSpec használatával
A TypeSpec nyelvfüggetlen módon határozza meg az API-t, és létrehozza az API-kiszolgálót és az ügyfélkódtárat több platformhoz. Ez a funkció a következő funkciókat teszi lehetővé:
- API-szerződésed határozd meg egyszer
- Konzisztens kiszolgáló- és ügyfélkód létrehozása
- Az API-infrastruktúra helyett az üzleti logika implementálására összpontosítson
A TypeSpec API-szolgáltatáskezelést biztosít:
- API-definíció nyelve
- Kiszolgálóoldali útválasztási köztes szoftver API-hoz
- Ügyfélkódtárak az API használatához
Ügyfélkéréseket és kiszolgálóintegrációkat biztosít:
- Üzleti logika implementálása köztes szoftverben, például azure-szolgáltatások adatbázisokhoz, tároláshoz és üzenetkezeléshez
- Kiszolgáló üzemeltetése az API-hoz (helyileg vagy az Azure-ban)
- Üzembe helyezési szkriptek megismételhető erőforrás-előkészítéshez és üzembe helyezéshez
Új TypeSpec-alkalmazás létrehozása
Hozzon létre egy új mappát az API-kiszolgáló és a TypeSpec fájlok tárolásához.
mkdir my_typespec_quickstart cd my_typespec_quickstartTelepítse a TypeSpec fordítót globálisan:
npm install -g @typespec/compilerEllenőrizze, hogy a TypeSpec megfelelően van-e telepítve:
tsp --versionA TypeSpec projekt inicializálása:
tsp initVálaszoljon a következő kérdésekre a megadott válaszokkal:
- Inicializál egy új projektet? Y
- Kiválaszt egy projektsablont? Általános REST API
- Projektnév megadása: Widgetek
- Milyen emittereket szeretne használni?
- OpenAPI 3.1-dokumentum
- C# kiszolgálói csonkok
A TypeSpec emitterek olyan kódtárak, amelyek különböző TypeSpec fordító API-kat használnak a TypeSpec fordítási folyamatának tükrözésére és összetevők létrehozására.
A folytatás előtt várja meg, amíg az inicializálás befejeződik.
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/startFordítsa le a projektet:
tsp compile .Jótanács
Az iteratív fejlesztéshez használja a watch üzemmódot a fájlmódosítások automatikus újrafordítására:
tsp compile . --watch. A létrehozott kiszolgálót és sémát naprakészen tartja, amint módosítjamain.tsp.A TypeSpec létrehozza az alapértelmezett projektet a következő két külön mappában
./tsp-output:- séma
- kiszolgáló
Nyissa meg a
./tsp-output/schema/openapi.yamlfájlt. Figyelje meg, hogy a néhány sor./main.tsptöbb mint 200 sornyi OpenApi-specifikációt hozott létre Önnek.Nyissa meg a
./tsp-output/server/aspnetmappát. Figyelje meg, hogy az állványozott .NET-fájlok a következők:-
./generated/operations/IWidgets.csA Widgets metódusok felületét határozza meg. -
./generated/controllers/WidgetsController.csimplementálja a widgetek metódusaiba való integrációt. Itt helyezzük el az üzleti logikát. -
./generated/modelsA Widget API modelljeit határozza meg.
-
TypeSpec emitterek konfigurálása
Az API-kiszolgáló generációjának konfigurálásához használja a TypeSpec fájlokat.
Nyissa meg a
tspconfig.yamlmeglévő konfigurációt, és cserélje le a következő YAML-re: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"Ez a konfiguráció megjósol több olyan változást, amelyre szükségünk van egy teljes mértékben generált .NET API-kiszolgálóhoz.
-
emit-mocks: Hozza létre a kiszolgálóhoz szükséges összes projektfájlt. -
use-swaggerui: Integrálja a Swagger felhasználói felületét, hogy böngészőbarát módon használhassa az API-t. -
emitter-output-dir: Állítsa be a kimeneti könyvtárat a kiszolgáló- és az OpenApi-specifikáció generációjához is. - Hozzon létre mindent a
./server.
-
A projekt újrafordítása:
tsp compile .Váltás az új
/serverkönyvtárra:cd serverHozzon létre egy alapértelmezett fejlesztői tanúsítványt, ha még nem rendelkezik ilyen tanúsítvánnyal:
dotnet dev-certs httpsFuttassa a projektet:
dotnet runVárja meg, amíg az értesítés megnyílik a böngészőben.
Nyissa meg a böngészőt, és adja hozzá a Swagger felhasználói felületi útvonalát.
/swaggerAz alapértelmezett TypeSpec API és a kiszolgáló is működik.
Az alkalmazásfájl-struktúra ismertetése
A létrehozott kiszolgáló projektstruktúrája tartalmazza a .NET vezérlőalapú API-kiszolgálót, a projekt létrehozásához szükséges .NET-fájlokat és az Azure-integráció közbenső szoftverét.
├── appsettings.Development.json
├── appsettings.json
├── docs
├── generated
├── mocks
├── Program.cs
├── Properties
├── README.md
├── ServiceProject.csproj
└── wwwroot
-
Adja hozzá az üzleti logikát: ebben a példában kezdje a
./server/mocks/Widget.csfájllal. A generáltWidget.cssablon metódusokat biztosít. -
Frissítse a kiszolgálót: adjon hozzá minden adott kiszolgálókonfigurációt a kiszolgálóhoz
./program.cs. -
Használja az OpenApi specifikációt: a TypeSpec létrehozta a OpenApi3.json fájlt a
./server/wwwrootfájlba, és elérhetővé tette a Swagger UI-ben a fejlesztés közben. Ez egy felhasználói felületet biztosít a specifikációhoz. Az API-t anélkül használhatja, hogy olyan kérési mechanizmust kell megadnia, mint a REST-ügyfél vagy a webes előtér.
Adatmegőrzés módosítása az Azure Cosmos DB no-sql-ra
Most, hogy az alapszintű Widget API-kiszolgáló működik, frissítse a kiszolgálót az Azure Cosmos DB-vel való együttműködésre egy állandó adattárhoz.
A címtárban adja hozzá az
./serverAzure Cosmos DB-t a projekthez:dotnet add package Microsoft.Azure.CosmosAdja hozzá az Azure Identity-kódtárat a hitelesítéshez az Azure-ban:
dotnet add package Azure.IdentityFrissítse a
./server/ServiceProject.csprojCosmos DB integrációs beállításait:<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>- Az EnableSdkContainerSupport lehetővé teszi a .NET SDK beépített tároló buildtámogatásának (dotnet publish ––container) használatát Dockerfile írása nélkül.
- A Newtonsoft.Json hozzáadja azt a Json .NET szerializálót, amelyet a Cosmos DB SDK a .NET-objektumok JSON-ra és JSON-ból való konvertálására használ.
Hozzon létre egy új regisztrációs fájlt a
./azure/CosmosDbRegistrationCosmos DB-regisztráció kezeléséhez: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"); } } } }Figyelje meg a végpont környezeti változót:
var cosmosEndpoint = builder.Configuration["Configuration:AzureCosmosDb:Endpoint"];Hozzon létre egy új Widget-osztályt,
./azure/WidgetsCosmos.cshogy üzleti logikát biztosítson az Azure Cosmos DB-vel való integrációhoz az állandó tárolóhoz.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"); } } } }Hozza létre a fájlt az
./server/services/CosmosDbInitializer.csAzure-ban való hitelesítéshez: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; } } }Frissítse a
./server/program.cs-t, hogy a Cosmos DB-t használja, és engedélyezze a Swagger UI használatát egy éles környezetben. Másolás a teljes fájlba:// 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();A projekt létrehozása:
dotnet buildA projekt most a Cosmos DB-integrációval épül fel. Hozzuk létre az üzembehelyezési szkripteket az Azure-erőforrások létrehozásához és a projekt üzembe helyezéséhez.
Üzembehelyezési infrastruktúra létrehozása
Hozza létre a megismételhető üzembe helyezéshez szükséges fájlokat az Azure Developer CLI-vel és a Bicep-sablonokkal.
A TypeSpec projekt gyökerénél hozzon létre egy
azure.yamlüzembehelyezési definíciós fájlt, és illessze be a következő forrást:# 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: githubFigyelje meg, hogy ez a konfiguráció a létrehozott projekthelyre (
./server) hivatkozik. Győződjön meg arról, hogy a./tspconfig.yamlegyezik a./azure.yaml-ben megadott helyszínnel.A TypeSpec projekt gyökerénél hozzon létre egy könyvtárat
./infra.Hozzon létre egy
./infra/main.bicepparamfájlt, és másolja az alábbiakba az üzembe helyezéshez szükséges paraméterek meghatározásához:using './main.bicep' param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'dev') param location = readEnvironmentVariable('AZURE_LOCATION', 'eastus2') param deploymentUserPrincipalId = readEnvironmentVariable('AZURE_PRINCIPAL_ID', '')Ez a paramlista biztosítja az üzembe helyezéshez szükséges minimális paramétereket.
Hozzon létre egy
./infra/main.bicepfájlt, és másolja az alábbiakba a kiépítéshez és üzembe helyezéshez szükséges Azure-erőforrások meghatározásához: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.loginServerA kimeneti változók lehetővé teszik a kiépített felhőerőforrások használatát a helyi fejlesztéssel.
A containerAppsApp címke a serviceName változót (a
apiértékre állítva a fájl tetején) és aapi-et, amit a./azure.yaml-ben adtak meg, használja. Ez a kapcsolat tájékoztatja az Azure Developer CLI-t, hogy hol helyezze üzembe a .NET-projektet az Azure Container Apps üzemeltetési erőforrásában....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..
Projektstruktúra
A végső projektstruktúra tartalmazza a TypeSpec API-fájlokat, a Express.js kiszolgálót és az Azure üzembehelyezési fájljait:
├── infra
├── tsp-output
├── .gitignore
├── .azure.yaml
├── Dockerfile
├── main.tsp
├── package-lock.json
├── package.json
├── tspconfig.yaml
| Area | Fájlok/könyvtárak |
|---|---|
| TypeSpec |
main.tsp, tspconfig.yaml |
| Express.js kiszolgáló |
./tsp-output/server/(beleértve a létrehozott fájlokat, például controllers/: , models/ServiceProject.csproj) |
| Azure Developer CLI üzembe helyezése |
./azure.yaml,./infra/ |
Alkalmazás üzembe helyezése az Azure-ban
Ezt az alkalmazást az Azure Container Apps használatával telepítheti az Azure-ban:
Hitelesítés az Azure Developer CLI-ben:
azd auth loginÜzembe helyezés az Azure Container Appsben az Azure Developer CLI használatával:
azd up
Alkalmazás használata böngészőben
Az üzembe helyezést követően a következőt teheti:
- Nyissa meg a Swagger felhasználói felületet az API teszteléséhez itt:
/swagger. - Az egyes API-k Kipróbálás funkciójával widgeteket hozhat létre, olvashat, frissíthet és törölhet az API-n keresztül.
Az alkalmazás növelése
Most, hogy a teljes folyamat teljes körűen működik, folytassa az API elkészítését:
- Ismerje meg a TypeSpec nyelvet, hogy további API-kat és API-rétegfunkciókat adhasson hozzá a
./main.tsp. - Adjon hozzá további emittereket , és konfigurálja a paramétereket a
./tspconfig.yaml. - Ahogy további funkciókat ad hozzá a TypeSpec-fájlokhoz, a kiszolgálóprojekt forráskódjával támogatja ezeket a módosításokat.
- Továbbra is használjon jelszó nélküli hitelesítést az Azure Identity használatával.
Erőforrások tisztítása
Ha végzett ezzel a rövid útmutatóval, eltávolíthatja az Azure-erőforrásokat:
azd down
Vagy törölje az erőforráscsoportot közvetlenül az Azure Portalról.
Hibaelhárítás
A .NET 9 követelmény nem teljesül
Hiba:Build error: This project requires .NET 9. You have <version> installed.
Solution:
- Ellenőrizze a .NET-verziót:
dotnet --version. - Telepítse a .NET 9-et a dot.net oldalról.
HTTPS-tanúsítványhiba
Hiba:System.IO.IOException: The certificate generation failed futtatáskor dotnet dev-certs https
Solution:
- Megbízható a meglévő tanúsítvány:
dotnet dev-certs https --trust. - Ha ez nem sikerül, tisztítsa meg és hozza létre újra a következőt:
dotnet dev-certs https --clean, majddotnet dev-certs https. - Windows rendszeren győződjön meg arról, hogy rendszergazdaként futtatja a terminált.
@typespec/http-server-csharp emitter-ütközés
Hiba:Emitter error: Dependency conflict futtatáskor tsp compile .
Solution:
Ellenőrizze, hogy
tspconfig.yamltartalmazza mindkét emittert aemit:alatt.emit: - "@typespec/openapi3" - "@typespec/http-server-csharp"Gyorsítótár törlése:
npm cache clean --force, majd próbálkozzon újra:tsp compile ..
A Cosmos DB hitelesítése sikertelen
Hiba:Azure.Identity.AuthenticationFailedException Vagy Connection string/key not set
Solution:
- Győződjön meg arról, hogy a környezeti változó be van állítva:
echo $AZURE_COSMOS_ENDPOINT(macOS/Linux) vagyecho %AZURE_COSMOS_ENDPOINT%(Windows). - Helyi fejlesztéshez állítsa be az értéket a
Program.csvagy egy.envfájlban. - A termelési célra ellenőrizze, hogy a Tárolóalkalmazás titkos kulcsa konfigurálva van (lásd az "Üzembe helyezési infrastruktúra létrehozása" szakaszt).
A Swagger felhasználói felülete a 404-et mutatja
Hiba: A böngésző a következőt mutatja 404 Not Found/swagger
Solution:
Ellenőrizze a Swagger-végpont konfigurációját a következő helyen
Program.cs:c.SwaggerEndpoint("/openapi.yaml", "TypeSpec Generated OpenAPI Docs"); c.RoutePrefix = "swagger";Győződjön meg arról
openapi.yaml, hogy awwwroot/címtárban van.Újraépítés és újraindítás:
dotnet clean && dotnet build && dotnet run.