Đơn Hóa Phát Triển .NET với Aspire

Aspire là một công cụ mới giúp đơn giản hóa việc phát triển ứng dụng .NET bằng cách quản lý các phụ thuộc và cấu hình, cho phép nhà phát triển tập trung vào việc xây dựng tính năng thay vì vật lộn với việc thiết lập và các vấn đề về môi trường.

Theo tài liệu chính thức, .NET Aspire cung cấp các công cụ, mẫu và gói để giúp bạn xây dựng các ứng dụng có khả năng quan sát sẵn sàng sản xuất. Được cung cấp thông qua các gói NuGet, .NET Aspire đơn giản hóa các thách thức phổ biến trong phát triển ứng dụng hiện đại.

Vài tuần trước, tôi đã tham gia AspiriFridays nơi Damian Edwards, David Fowler, Maddy Montaquila và tôi thêm .NET Aspire vào ứng dụng JosephGuadagno.NET Broadcasting. Hãy cùng xem xét Aspire có thể giúp bạn đơn giản hóa quy trình phát triển .NET như thế nào.

Lưu ý: Bài viết blog này được cập nhật vào ngày 2 tháng 8 để phản ánh phiên bản Aspire 9.4.0

Bắt đầu với Aspire

Để bắt đầu với Aspire, bạn cần một số điều kiện tiên quyết:

Lưu ý: .NET Aspiration cũng hoạt động với GitHub Codespaces hoặc Dev Containers.

Nếu bạn muốn sử dụng các mẫu dự án Aspire, bạn có thể cài đặt chúng bằng lệnh sau:

dotnet new install Aspire.ProjectTemplates --force

Lưu ý: Các mẫu này được cập nhật thường xuyên, vì vậy bạn có thể muốn chạy lệnh này định kỳ để đảm bảo bạn có các mẫu mới nhất.

Tôi khuyên bạn nên cài đặt công cụ CLI Aspire, cung cấp một tập hợp các lệnh để giúp bạn quản lý các dự án Aspire. Bạn có thể cài đặt công cụ CLI bằng cách chạy lệnh sau:

dotnet tool install -g Aspire.Cli --prerelease

Bây giờ chúng ta đã sẵn sàng để “Aspirify” dự án của mình!

Dự án

Tôi có một dự án mẫu tôi sử dụng cho các buổi thuyết trình và demo. Đó là một ứng dụng web ASP.NET Core đơn giản cho phép người dùng thêm, chỉnh sửa, xóa và liệt kê các liên hệ. Dự án được thiết kế để là một ví dụ đơn giản về cách xây dựng ứng dụng web sử dụng các công nghệ .NET, và nó phục vụ như một điểm khởi đầu tuyệt vời để học .NET.

Dự án được xây dựng sử dụng .NET 9 và tận dụng nhiều công nghệ khác nhau chẳng hạn như:

Bạn có thể tìm thấy dự án contacts-noauth trên GitHub. Nếu bạn muốn theo dõi, bạn có thể clone repository và mở nó trong IDE hoặc trình soạn thảo mã ưa thích của bạn.

git clone https://github.com/jguadagno/contacts-noauth.git

Thêm Aspire vào dự án

Các mẫu dự án Aspire được thiết kế để giúp bạn nhanh chóng thiết lập một dự án mới với các phụ thuộc và cấu hình cần thiết để xây dựng ứng dụng .NET. Chúng cung cấp một điểm khởi đầu cho ứng dụng của bạn, cho phép bạn tập trung vào việc xây dựng tính năng thay vì vật lộn với việc thiết lập và các vấn đề về môi trường. Có hai dự án được tạo khi bạn thêm Aspire vào giải pháp của bạn:

  • App Host: Dự án này hoạt động như điểm nhập cho ứng dụng Aspire của bạn. Nó chứa tệp AppHost.cs, nơi bạn cấu hình ứng dụng và các dịch vụ của nó.
  • Service Defaults: Dự án này thiết lập các dịch vụ mặc định cho ứng dụng Aspire của bạn, chẳng hạn như telemetry, logging, health checks và hơn thế nữa. Nó chứa tệp Extensions.cs thiết lập các dịch vụ này.

Tùy thuộc vào IDE bạn đang sử dụng, bạn có thể thêm Aspire vào dự án của bạn theo những cách khác nhau. Dưới đây là các bước cho Visual Studio, Visual Studio Code và JetBrains Rider.

Visual Studio

Visual Studio cung cấp một cách đơn giản để thêm Aspire vào dự án của bạn bằng cách sử dụng các mẫu dự án tích hợp và theo ý kiến của tôi, đây là cách dễ nhất để bắt đầu với Aspire.

Trong Visual Studio, trong Solution Explorer, nhấp chuột phải vào dự án Contacts.Api và chọn “Add” > “.NET Aspire Orchestration Support”. Điều này sẽ mở hộp thoại “Add .NET Aspire Orchestration Support”.

Mặc định là tốt, vì vậy bạn có thể nhấp “Add” để tạo dự án Aspire. Điều này sẽ tạo hai dự án mới trong giải pháp của bạn có tên là Contacts.AppHost và Contacts.ServiceDefaults sau đó thêm các tham chiếu cần thiết vào dự án Contacts.Api.

Khi các dự án được tạo, bạn sẽ thấy chúng trong Solution Explorer. Dự án Contacts.AppHost chứa tệp AppHost.cs, nơi bạn cấu hình ứng dụng và các dịch vụ của nó. Dự án Contacts.ServiceDefaults chứa tệp Extensions.cs, thiết lập các dịch vụ mặc định cho ứng dụng Aspire của bạn. Visual Studio cũng thêm hai cấu hình chạy mới vào giải pháp, một cho HTTP và một cho HTTPS. Bạn có thể sử dụng các cấu hình này để chạy ứng dụng Aspire của bạn. Ngoài ra, Visual Studio sẽ hiển thị trang chào mừng Aspire cung cấp cái nhìn tổng quan về các dự án Aspire và cách sử dụng chúng.

Visual Studio Code

Với giải pháp được mở trong Visual Studio Code, bạn có thể thêm Aspire vào dự án bằng cách mở terminal và chạy lệnh sau:

dotnet new aspire-host -o Contacts.AppHost

Sau đó thêm dự án app host vào giải pháp:

dotnet sln add .\Contacts.sln add .\Contacts.AppHost\Contacts.AppHost.csproj

Bây giờ, hãy thêm dự án Service Defaults vào giải pháp. Trong terminal, chạy lệnh sau:

dotnet new aspire-servicedefaults -o Contacts.ServiceDefaults

Sau đó thêm dự án service defaults vào giải pháp:

dotnet sln add .\Contacts.sln add .\Contacts.ServiceDefaults\Contacts.ServiceDefaults.csproj

Bạn sẽ cần thêm tham chiếu dự án Contacts.ServiceDefaults vào cả dự án Contacts.Api và Contacts.WebUi. Bạn có thể làm điều này bằng cách chạy các lệnh sau trong terminal:

dotnet add .\Contacts.Api\Contacts.Api.csproj reference .\Contacts.ServiceDefaults\Contacts.ServiceDefaults.csproj
dotnet add .\Contacts.WebUi\Contacts.WebUi.csproj reference .\Contacts.ServiceDefaults\Contacts.ServiceDefaults.csproj

Bây giờ chúng ta nên có các dự án Aspire được thêm vào giải pháp. Bạn có thể xác minh điều này bằng cách mở Solution Explorer trong Visual Studio Code và kiểm tra xem các dự án Contacts.AppHost và Contacts.ServiceDefaults có được liệt kê hay không. Nếu vậy, chúng ta đã sẵn sàng để chạy ứng dụng.

JetBrains Rider

Với giải pháp được mở trong JetBrains Rider, bạn có thể thêm Aspire vào dự án bằng cách nhấp chuột phải vào giải pháp trong Solution Explorer và chọn “Add” > “New Project…”. Điều này sẽ mở hộp thoại “New Project”.

  • Chọn Aspire từ các mẫu dự án ở phía bên trái.
  • Nhập một tên cho dự án, chẳng hạn như Contacts.AppHost.
  • Chọn loại App Host.
  • Nhấp “Create” để tạo dự án.

Điều này sẽ tạo phần đầu tiên của việc “Aspirify” dự án của bạn. Bạn sẽ thấy một dự án mới trong Solution Explorer với tên bạn đã chỉ định.

Bây giờ, chúng ta cần thêm dự án Service Default vào giải pháp.

Nhấp chuột phải vào giải pháp trong Solution Explorer và chọn “Add” > “New Project…”. Điều này sẽ mở hộp thoại “New Project”.

  • Chọn Aspire từ các mẫu dự án ở phía bên trái.
  • Nhập một tên cho dự án, chẳng hạn như Contacts.ServiceDefaults.
  • Chọn loại Service Defaults.
  • Nhấp “Create” để tạo dự án.

Bây giờ bạn sẽ thấy một dự án mới trong Solution Explorer với tên bạn đã chỉ định. Trong dự án đó, bạn sẽ tìm thấy tệp Extensions.cs, nơi Aspire cấu hình một số dịch vụ mặc định.

Bạn cũng sẽ nhận thấy rằng các mẫu Aspire đã thêm hai tệp cấu hình mới vào giải pháp:

  • Contacts.AppHost: http
  • Contacts.AppHost: https

Bạn sẽ sử dụng một trong các cấu hình này để chạy ứng dụng Aspire của bạn. Cấu hình http là để chạy ứng dụng không có HTTPS, trong khi cấu hình https là để chạy ứng dụng có HTTPS.

Thêm Tài nguyên (Dự án)

Bây giờ chúng ta đã có các dự án Aspire được thêm vào giải pháp, chúng ta có thể bắt đầu thêm các tài nguyên của ứng dụng. Trong trường hợp này, chúng ta sẽ thêm hỗ trợ cho tải ứng dụng web, API và các thành phần cơ sở dữ liệu.

Lưu ý: Phần còn lại của bài viết này sẽ hướng dẫn bạn thêm các thành phần vào dự án. Đối với điều này, không quan trọng bạn đang sử dụng IDE nào, vì các bước giống nhau trên Visual Studio, Visual Studio Code và JetBrains Rider.

Tất cả điều này được thực hiện bằng cách thêm các thành phần vào tệp AppHost.cs trong dự án Contacts.AppHost. Bạn có thể coi điều này giống như một tệp Startup.cs hoặc Program.cs trong ứng dụng ASP.NET Core truyền thống. Đây là nơi chúng ta kết hợp các mảnh của ứng dụng với nhau và thiết lập bất kỳ phụ thuộc nào giữa chúng.

Hãy bắt đầu bằng cách mở tệp AppHost.cs trong dự án Contacts.AppHost.

Thêm Dự án Web Api

Tùy thuộc vào IDE bạn đang sử dụng, bạn sẽ thấy một phiên bản hơi khác của tệp AppHost.cs. Nếu bạn đang sử dụng Visual Studio và bạn đã thêm các dự án Aspire bằng cách sử dụng tùy chọn “Add .NET Aspire Orchestration Support”, bạn sẽ thấy một tệp AppHost.cs cơ bản trông như thế này:

var builder = DistributedApplication.CreateBuilder(args);

builder.AddProject<Projects.Contacts_Api>("contacts-api");

builder.Build().Run();

Nếu bạn đang sử dụng Visual Studio Code hoặc JetBrains Rider, bạn sẽ thấy một tệp AppHost.cs chung hơn trông như thế này:

var builder = DistributedApplication.CreateBuilder(args);

builder.Build().Run();

Đây là sự khác biệt duy nhất giữa hai phiên bản của tệp AppHost.cs. Phiên bản Visual Studio có một dòng thêm dự án Contacts.Api và tham chiếu đến ứng dụng, trong khi các phiên bản Visual Studio Code và JetBrains Rider thì không.

Hãy giải thích điều gì đã xảy ra ở đây. Phương thức DistributedApplication.CreateBuilder(args), dòng 1, tạo một phiên bản DistributedApplicationBuilder mới, được sử dụng để cấu hình ứng dụng. Phương thức AddProject<Projects.Contacts_Api>(“contacts-api”), dòng 3, thêm dự án Contacts.Api vào ứng dụng, với tên là contacts-api, cho phép nó được chạy như một phần của ứng dụng Aspire. Sau đó phương thức builder.Build().Run(), dòng 5, xây dựng ứng dụng và chạy nó.

Nếu bạn đang sử dụng Visual Studio Code hoặc JetBrains Rider, bạn sẽ cần thêm dự án Contacts.Api như một tham chiếu vào dự án Contacts.Api, sau đó, thêm dự án vào ứng dụng Aspire. Bạn có thể làm điều này bằng cách thêm dòng sau vào tệp AppHost.cs:

builder.AddProject<Projects.Contacts_Api>("contacts-api");

Lưu ý: Trường name, trong trường hợp này là contacts-api, được sử dụng để xác định dự án trong ứng dụng Aspire. Bạn có thể sử dụng bất kỳ tên nào bạn thích, nhưng được khuyến nghị sử dụng một tên mô tả và độc đáo trong ứng dụng của bạn.

Đến thời điểm này, bạn nên có dự án Contacts.Api được thêm vào ứng dụng đã được Aspirify. Bạn có thể xác minh điều này bằng cách chạy ứng dụng bằng cách sử dụng cấu hình chạy http hoặc https trong Visual Studio hoặc Contacts.AppHost: http(s) trong Rider.

Lưu ý: Nếu bạn đang chạy dự án mẫu này với phiên bản aspire 9.3.1-preview.1.25305.6, có một lỗi ngăn ứng dụng chạy. Bạn có thể khắc phục điều này bằng cách chuyển dòng builder.AddServiceDefaults(); đến ngay sau dòng var builder = WebApplication.CreateBuilder(args); trong tệp Program.cs của dự án Contacts.Api, dòng 20. Sau đó xóa dòng builder.MapDefaultEndpoints(); trong tệp Program.cs, và thêm dòng sau ngay sau dòng var app = builder.Build();, khoảng dòng 28.

Phần try của tệp Program.cs nên trông như thế này:

try
{
    var builder = WebApplication.CreateBuilder(args);
    builder.AddServiceDefaults();

    builder.Logging.ClearProviders();
    builder.Host.UseNLog();
    
    ConfigureServices(builder.Services);

    var app = builder.Build();

    app.MapDefaultEndpoints();

    if (builder.Environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    ConfigureMiddleware(app);
    app.Run();
}

Tại thời điểm bài viết này, nó là một vấn đề tôi đã gửi cho đội ngũ Aspire.

Bây giờ bạn nên có thể chạy ứng dụng, và bảng điều khiển Aspire nên có thể truy cập tại một URL như https://localhost:17004 hoặc http://localhost:15122.

Lưu ý:: Các số cổng có thể thay đổi tùy thuộc vào cấu hình của bạn, vì vậy hãy kiểm tra cấu hình chạy trong Visual Studio hoặc Rider để xem URL chính xác.

Sau khi chạy thành công, bạn sẽ thấy bảng điều khiển Aspire, cung cấp cái nhìn tổng quan về ứng dụng và các thành phần của nó. Bảng điều khiển sẽ hiển thị dự án contacts-api như một dịch vụ đang chạy.

Tổng quan Bảng điều khiển

Bảng điều khiển Aspire cung cấp cái nhìn tổng quan toàn diện về ứng dụng của bạn, bao gồm:

  1. Danh sách các dịch vụ đang chạy
  2. Đầu ra console từ ứng dụng
  3. Thông tin logging có cấu trúc
  4. Thông tin tracing
  5. Metrics và health checks

Chúng ta sẽ khám phá bảng điều khiển chi tiết hơn trong một bài viết trong tương lai, nhưng bây giờ bạn có thể thấy dự án contacts-api đang chạy và có thể truy cập. Nếu bạn nhấp vào liên kết URLs trong bảng điều khiển, Api sẽ mở trong một tab mới, và bạn có thể thấy Swagger UI cho API.

Thêm dự án Sql Server

Bây giờ chúng ta đã có dự án Contacts.Api được thêm vào ứng dụng Aspire, hãy “nói” với Aspire về cơ sở dữ liệu SQL Server mà API sẽ sử dụng.

Quay lại tệp AppHost.cs trong dự án Contacts.AppHost. Chúng ta sẽ thêm dòng sau vào builder sau dòng AddProject cho dự án Contacts.Api:

var sql = builder.AddSqlServer("SqlServer")   
    .WithLifetime(ContainerLifetime.Persistent);

Dòng này thêm cơ sở dữ liệu SQL Server vào ứng dụng Aspire, với tên SqlServer. Phương thức WithLifetime(ContainerLifetime.Persistent) chỉ định rằng cơ sở dữ liệu nên là persistent, có nghĩa là nó sẽ không tạo lại cơ sở dữ liệu mỗi khi ứng dụng được chạy. Điều này hữu ích cho mục đích phát triển và thử nghiệm, vì nó cho phép bạn giữ dữ liệu trong cơ sở dữ liệu giữa các lần chạy.

Bạn có thể thấy đường gạch dưới màu đỏ dưới phương thức AddSqlServer, cho thấy phương thức không được nhận diện. Điều này là vì bạn cần thêm một tham chiếu đến gói Aspire.Hosting.SqlServer trong dự án của bạn. Bạn có thể làm điều này bằng cách chạy lệnh sau trong terminal:

dotnet add package Aspire.Hosting.SqlServer

Hoặc thêm nó thông qua NuGet Package Manager trong Visual Studio hoặc Rider.

Bây giờ bạn có thể chạy ứng dụng lại, và bảng điều khiển Aspire nên hiển thị dự án SqlServer như một dịch vụ đang chạy. Lần đầu tiên bạn chạy ứng dụng, nó sẽ mất vài phút để xây dựng hình ảnh container cho cơ sở dữ liệu SQL Server, vì vậy hãy kiên nhẫn. Khi hình ảnh được xây dựng, ứng dụng sẽ bắt đầu, và bạn nên thấy dự án SqlServer trong bảng điều khiển Aspire. Tuy nhiên, chúng ta vẫn chưa làm xong. Cơ sở dữ liệu sẽ trống. Vì vậy chúng ta sẽ cần thêm một schema vào nó, và tùy chọn, một số dữ liệu seed.

Chạy dự án lại. Đảm bảo bạn có Docker hoặc Podman đang chạy, và container SQL Server sẽ được tạo.

Tùy thuộc vào tốc độ bạn xem bảng điều khiển Aspire, bạn có thể thấy SqlServer là unhealthy, đó là vì container/server vẫn đang được xây dựng. Khi container được xây dựng, dự án SqlServer sẽ được đánh dấu là healthy và bạn có thể bắt đầu sử dụng nó.

Nếu bạn đang sử dụng Sqlite làm cơ sở dữ liệu của bạn, bạn có thể thêm dòng sau vào tệp AppHost.cs thay vì:

var sqlite = builder.AddSqlite("Sqlite");

Điều này sẽ yêu cầu gói CommunityToolkit.Aspire.Hosting.Sqlite, mà bạn có thể thêm bằng cách chạy lệnh sau trong terminal:

dotnet add package CommunityToolkit.Aspire.Hosting.Sqlite

Nếu bạn đang sử dụng Sql Server, hãy đợi đến khi dự án SqlServer được đánh dấu là healthy trong bảng điều khiển Aspire trước khi bạn tiếp tục. Nếu bạn đang sử dụng Sqlite, bạn có thể tiếp tục mà không cần đợi cơ sở dữ liệu được tạo.

Dừng dự án nếu nó vẫn đang chạy, và hãy thêm schema cơ sở dữ liệu vào dự án.

Tạo Schema Cơ sở dữ liệu

Để tạo schema cơ sở dữ liệu cho mẫu này, chúng ta thêm một số mã vào tệp AppHost.cs trong dự án Contacts.AppHost sẽ chạy các script tạo cơ sở dữ liệu là một phần của dự án này.

Lưu ý: Việc có các script để tạo schema cơ sở dữ liệu không chỉ là một phương pháp tốt nhất, mà nó cũng cho phép các nhà phát triển khác dễ dàng thiết lập schema cơ sở dữ liệu trong môi trường phát triển cục bộ của họ và không cần một server cơ sở dữ liệu “phát triển” trung tâm.

Nếu bạn đã clone repository này để theo dõi, bạn sẽ tìm thấy các script tạo cơ sở dữ liệu trong thư mục db-scripts ở gốc của dự án. Có một thư mục cho mỗi loại cơ sở dữ liệu, chẳng hạn như sqlserver và sqlite. Mỗi thư mục chứa hai tệp create-data.sql và create-tables.sql. Tệp create-data.sql chứa các lệnh SQL để chèn dữ liệu seed vào cơ sở dữ liệu, trong khi tệp create-tables.sql chứa các lệnh SQL để tạo schema cơ sở dữ liệu. Cũng có một tệp bổ sung cho cơ sở dữ liệu SQL Server có tên create-database-and-user.sql, chứa các lệnh SQL để tạo cơ sở dữ liệu và người dùng cho cơ sở dữ liệu SQL Server.

Quay lại tệp AppHost.cs trong dự án Contacts.AppHost. Chúng ta sẽ cần tạo cơ sở dữ liệu dựa trên các script này. Chúng ta sẽ thêm mã sau vào tệp AppHost.cs, ngay sau dòng var sql:

Đối với SQL Server, thêm mã sau:

var path = builder.AppHostDirectory;
var sqlText = string.Concat(
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlserver\create-database-and-user.sql")), 
    " ",
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlserver\create-tables.sql")),
    " ",    
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlserver\create-data.sql")));

var db = sql.AddDatabase("Contacts")
    .WithCreationScript(sqlText);

Lưu ý:: Đối với cơ sở dữ liệu SQL Server, Aspiration sẽ báo cáo nó unhealthy vì cơ sở dữ liệu chưa được tạo ra. Một khi cơ sở dữ liệu được tạo, nó sẽ được đánh dấu là healthy.

Lưu ý:: Nếu bạn đang sử dụng script của riêng mình, hãy đảm bảo rằng các script được đặt theo đúng thứ tự, và mỗi script kết thúc bằng một dòng mới/carriage return hoặc một dòng trống. Điều này quan trọng vì Aspiration sẽ thực thi các script theo thứ tự chúng được cung cấp, và nếu không có dòng mới ở cuối script, nó có thể không được thực thi chính xác.

Đối với Sqlite, thêm mã sau:

var path = builder.AppHostDirectory;
var sqlText = string.Concat(
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlite\create-tables.sql")),
    " ",    
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlite\create-data.sql")));

var db = sql.AddDatabase("Contacts")
    .WithCreationScript(sqlText);

Bây giờ khi bạn chạy ứng dụng, Aspiration sẽ thực thi các script tạo cơ sở dữ liệu và tạo schema cơ sở dữ liệu. Bạn có thể xác minh rằng cơ sở dữ liệu được tạo bằng cách kiểm tra bảng điều khiển Aspiration, nơi bạn nên thấy cơ sở dữ liệu Contacts được liệt kê dưới dự án SqlServer hoặc Sqlite, tùy thuộc vào cơ sở dữ liệu bạn đang sử dụng. Đến thời điểm này, bạn thậm chí có thể kết nối với cơ sở dữ liệu bằng cách sử dụng một công cụ quản lý cơ sở dữ liệu để xem schema cơ sở dữ liệu và dữ liệu seed đã được thêm vào.

Để Contacts.Api Sử dụng Cơ sở dữ liệu Quản lý bởi Aspiration

Bây giờ vấn đề tạo đối tượng/dự án quan trọng. Vì dự án Contacts.Api phụ thuộc vào cơ sở dữ liệu, chúng ta cần đảm bảo rằng dự án Contacts.Api biết về cơ sở dữ liệu mà chúng ta vừa tạo. Chúng ta có thể làm điều này bằng cách thêm một tham chiếu đến cơ sở dữ liệu trong tệp AppHost.cs. Điều này sẽ yêu cầu chúng ta chuyển dòng AddProject cho dự án Contacts.Api đến ngay sau dòng var db, để cơ sở dữ liệu được tạo trước khi dự án API được thêm vào. Tệp AppHost.cs nên trông như thế này:

var builder = DistributedApplication.CreateBuilder(args);

// Comments out the following 2 lines if you are using Sqlite
var sql = builder.AddSqlServer("SqlServer")   
    .WithLifetime(ContainerLifetime.Persistent);

// Uncomment out is using Sqlite
// var sql = builder.AddSqlite("Sqlite");

var path = builder.AppHostDirectory;
var sqlText = string.Concat(
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlserver\create-database-and-user.sql")), 
    " ",
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlserver\create-tables.sql")),
    " ",    
    File.ReadAllText(Path.Combine(path, @"..\..\db-scripts\sqlserver\create-data.sql")));

var db = sql.AddDatabase("Contacts")
    .WithCreationScript(sqlText);

builder.AddProject<Projects.Contacts_Api>("contacts-api");

Bây giờ chúng ta cần cập nhật cấu hình của dự án Contacts.Api để sử dụng cơ sở dữ liệu được quản lý bởi Aspiration. Điều này được thực hiện bằng cách gọi chuỗi phương thức WithEnvironment trên phương thức AddProject cho dự án Contacts.Api. Điều này sẽ truyền chuỗi kết nối cơ sở dữ liệu đến dự án API, cho phép nó kết nối với cơ sở dữ liệu.

builder.AddProject<Projects.Contacts_Api>("contacts-api")
    .WithEnvironment("ConnectionStrings__ContactsDatabaseSqlServer", db)
    .WaitFor(db);

hoặc cho Sqlite:

builder.AddProject<Projects.Contacts_Api>("contacts-api")
    .WithEnvironment("ConnectionStrings__ContactsDatabaseSqlite", db)
    .WaitFor(db);

Lưu ý:: Lưu ý dấu gạch dưới kép (__ ) trong tên biến môi trường. Đây là một quy ước được sử dụng bởi .NET để tách các phần của khóa cấu hình. Trong trường hợp này, nó tách phần Settings khỏi khóa ContactsDatabaseSqlServer hoặc ContactsDatabaseSqlite. Bạn có thể thấy các cài đặt trong tệp appsettings.json trong dự án Contacts.Api, nơi chuỗi kết nối được định nghĩa.

Phương thức WaitFor() nói với Aspiration phải đợi cơ sở dữ liệu được tạo trước khi bắt đầu dự án Contacts.Api. Điều này quan trọng vì dự án API cần kết nối với cơ sở dữ liệu, và nếu cơ sở dữ liệu chưa được tạo, nó sẽ không thể bắt đầu.

Chạy ứng dụng lại, và bạn nên thấy dự án Contacts.Api hiện được đánh dấu là healthy trong bảng điều khiển Aspiration. Bạn cũng có thể xác minh rằng API có thể kết nối với cơ sở dữ liệu bằng cách kiểm tra nhật ký trong bảng điều khiển Aspiration.

Thêm dự án Web App

Bây giờ chúng ta đã có dự án Contacts.Api được thêm vào ứng dụng Aspiration, hãy thêm dự án Contacts.WebUi. Dự án này là một ứng dụng web ASP.NET Core đơn giản cho phép người dùng tương tác với API.

Như bạn có thể đoán, chúng ta sẽ thêm dự án Contacts.WebUi vào tệp AppHost.cs trong dự án Contacts.AppHost.

Vì dự án Web UI phụ thuộc vào dự án API, chúng ta cần đảm bảo rằng dự án API được tạo trước khi dự án Web UI, sau đó chúng ta cần lấy một tham chiếu đến nó.

Quay lại tệp AppHost.cs trong dự án Contacts.AppHost, tìm dòng thêm dự án Contacts.Api.

builder.AddProject<Projects.Contacts_Api>("contacts-api")
    .WithEnvironment("ConnectionStrings__ContactsDatabaseSqlServer", db);

Thay đổi dòng này để thêm một biến tên api để giữ tham chiếu đến dự án API:

var api = builder.AddProject<Projects.Contacts_Api>("contacts-api")
    .WithEnvironment("ConnectionStrings__ContactsDatabaseSqlServer", db);

Bây giờ, chúng ta có thể thêm dự án Contacts.WebUi vào ứng dụng Aspiration. Thêm dòng sau sau dòng dự án Contacts.Api:

builder.AddProject<Projects.Contacts_WebUi>("contacts-web-ui");

Tuy nhiên, chúng ta cần đảm bảo rằng dự án Web UI biết về dự án API, vì vậy chúng ta cần thêm một tham chiếu đến dự án API trong dự án Contacts.WebUi. Chúng ta có thể làm điều này bằng cách gọi chuỗi phương thức WithEnvironment trên phương thức AddProject cho dự án Contacts.WebUi.

builder.AddProject<Projects.Contacts_WebUi>("contacts-web-ui")
    .WithReference(api)
    .WaitFor(api);

Điều này đảm bảo rằng dự án ContactsApi bắt đầu trước dự án Contacts.WebUi, và rằng dự án Web UI biết về dự án API. Phương thức WithReference(api) nói với Aspiration truyền tham chiếu dự án API đến dự án Web UI, cho phép nó kết nối với API. Phương thức WaitFor(api) nói với Aspiration phải đợi dự án API được tạo trước khi bắt đầu dự án Web UI.

Bây giờ dự án Contacts.WebUi này yêu cầu một URL đến các API mà Contacts.Api có. Có một cài đặt trong tệp appsettings.json trong dự án Contacts.WebUi chỉ định URL của API. Chúng ta có thể đặt URL này thành URL của dự án API bằng cách sử dụng một quy ước mà Aspiration cung cấp. Mở tệp appsettings.Development.json trong dự án Contacts.WebUi, và tìm cài đặt ApiRootUrl. Sau đó cập nhật giá trị thành https://contacts-api. Tên contacts-api nên khớp với bất kỳ tên nào bạn đã sử dụng khi thêm dự án Contacts.Api. Framework Aspiration sẽ tự động giải quyết điều này thành URL của dự án API khi ứng dụng được chạy.

Bây giờ khi bạn chạy ứng dụng, Aspiration sẽ bắt đầu dự án Contacts.WebUi và truyền URL của dự án API đến nó. Bạn có thể xác minh rằng dự án Web UI có thể kết nối với API bằng cách nhấp vào dòng **Contacts** trong dự án Web Ui.

Tuy nhiên, bạn có thể nhận thấy rằng dự án Web UI không thể kết nối với dự án API. Điều này là vì dự án Web UI đang cố gắng kết nối với dự án API bằng URL https://contacts-api, nhưng dự án Contacts.WebUi không biết cách “kết nối” đến điểm cuối đó vì chúng ta đã không kết nối một số cấu hình Aspiration.

Mở dự án Contacts.WebUi, và mở tệp Program.cs. Ngay sau khi chúng ta tạo biến builder, chúng ta cần thêm dòng sau:

builder.AddServiceDefaults();

Sau đó sau dòng var app = builder.Build() và trước dòng app.Run(), chúng ta cần thêm dòng sau:

app.MapDefaultEndpoints();

Bước cuối cùng là thêm một tham chiếu đến dự án Contacts.ServiceDefaults vào dự án Contacts.WebUi. Bạn có thể làm điều này bằng cách chạy lệnh sau trong terminal:

dotnet add .\Contacts.WebUi\Contacts.WebUi.csproj reference .\Contacts.ServiceDefaults\Contacts.ServiceDefaults.csproj

hoặc bằng cách thêm tham chiếu thông qua NuGet Package Manager trong Visual Studio hoặc Rider.

Lưu ý:: Nếu bạn đang sử dụng Visual Studio, các phương thức AddServiceDefaults và MapDefaultEndpoints được thêm tự động khi bạn thêm các dự án Aspiration vào giải pháp. Nếu bạn đang sử dụng Visual Studio Code hoặc JetBrains Rider, bạn cần thêm các dòng này một cách thủ công.

Bây giờ khi bạn chạy ứng dụng lại, dự án Web UI nên có thể kết nối với dự án API, và bạn nên có thể thấy các liên hệ trong Web UI.

Đến thời điểm này, mọi thứ trông tốt, và ứng dụng đang chạy như mong đợi. Bạn có thể thêm, chỉnh sửa, xóa và liệt kê các liên hệ bằng cách sử dụng Web UI, và API có thể kết nối với cơ sở dữ liệu. Tuy nhiên, chúng ta vẫn còn một bước nữa để hoàn thành trước khi chúng ta làm xong. Chúng ta cần thêm hỗ trợ để lưu trữ hình ảnh liên hệ trong Azure Blob storage.

Thêm Blob Storage vào Ứng dụng

Ứng dụng yêu cầu Azure Blob storage để lưu trữ các hình ảnh liên hệ đã tải lên. Dự án Contacts.ImageManager chịu trách nhiệm quản lý các hình ảnh. Nó sử dụng một thư viện tôi đã viết gọi là JosephGuadagno.AzureHelpers.Storage để tương tác với Azure Blob storage. Tôi biết, một cái tên gốc phải không? Thư viện này cung cấp một tập hợp các phương thức trợ giúp để làm cho việc làm việc với Azure Blob storage trong ứng dụng .NET dễ dàng hơn.

Dự án Contacts.WebUi sử dụng dự án Contacts.ImageManager để tải các hình ảnh liên hệ lên Azure Blob storage. Có hai blob containers được sử dụng trong dự án này:

  1. contact-images: Container này được sử dụng để lưu trữ các hình ảnh liên hệ.
  2. contact-images-thumbnails: Container này được sử dụng để lưu trữ các hình ảnh thu nhỏ của các hình ảnh liên hệ.

Đối với phát triển cục bộ, chúng ta sẽ sử dụng Azurite để mô phỏng Azure Blob storage. Azurite là một máy chủ nhẹ mô phỏng dịch vụ Azure Blob storage cục bộ. Nó hữu ích cho mục đích phát triển và thử nghiệm cục bộ.

Để thêm Azurite vào ứng dụng Aspiration, chúng ta sẽ sử dụng gói Aspire.Hosting.AzureStorage để dự án Contacts.AppHost. Gói này cung cấp hỗ trợ cho Azure Blob storage trong các ứng dụng Aspiration. Chúng ta sau đó sẽ cần cập nhật cấu hình ứng dụng để sử dụng phiên bản Azurite được quản lý bởi Aspiration.

Bạn có thể thêm gói Aspire.Hosting.Azure.Storage vào dự án Contacts.AppHost bằng cách chạy lệnh sau trong terminal:

dotnet add package Aspire.Hosting.Azure.Storage

hoặc bằng cách thêm nó thông qua NuGet Package Manager trong Visual Studio hoặc Rider.

Vì trình mô phỏng storage sẽ được sử dụng bởi dự án Contacts.WebUi, chúng ta cần thêm tham chiếu đến thành phần storage vào Aspiration trước khi chúng ta thêm dự án Contacts.WebUi. Mở tệp AppHost.cs trong dự án Contacts.AppHost, và thêm dòng sau sau dòng var db:

var blobStorage = builder.AddAzureStorage("AzureStorage")
    .RunAsEmulator(azurite =>
    {
        azurite.WithLifetime(ContainerLifetime.Persistent);
    });

Dòng này thêm thành phần Azure Blob storage vào ứng dụng Aspiration, với tên AzureStorage. Phương thức RunAsEmulator() chỉ định rằng trình mô phỏng Azurite nên được sử dụng thay vì một tài khoản Azure Blob storage thực. Giống như chúng ta đã làm với cơ sở dữ liệu SQL Server, chúng ta đang sử dụng phương thức WithLifetime(ContainerLifetime.Persistent) để chỉ định rằng trình mô phỏng Azurite nên là persistent, có nghĩa là nó sẽ không được tạo lại mỗi khi ứng dụng được chạy. Điều này hữu ích cho mục đích phát triển và thử nghiệm, vì nó cho phép bạn giữ dữ liệu trong trình mô phỏng Azurite giữa các lần chạy.

Lưu ý:: Đối với cơ sở dữ liệu SQL Server, Aspiration sẽ báo cáo nó unhealthy vì cơ sở dữ liệu chưa được tạo ra. Một khi cơ sở dữ liệu được tạo, nó sẽ được đánh dấu là healthy.

Lưu ý:: Nếu bạn đang sử dụng script của riêng mình, hãy đảm bảo rằng các script được đặt theo đúng thứ tự, và mỗi script kết thúc bằng một dòng mới/carriage return hoặc một dòng trống. Điều này quan trọng vì Aspiration sẽ thực thi các script theo thứ tự chúng được cung cấp, và nếu không có dòng mới ở cuối script, nó có thể không được thực thi chính xác.

Bây giờ chúng ta cần tạo các blob containers sẽ được sử dụng bởi ứng dụng. Chúng ta có thể làm điều này bằng cách gọi phương thức AddBlobContainer(“contact-images”) và AddBlobContainer(“contact-images-thumbnails”) trên biến blobStorage. Thêm dòng sau vào tệp AppHost.cs sau dòng AddBlobs(“cwjgcontacts”):

var imageContainer = blobStorage.AddBlobContainer("contact-images");
var thumbnailContainer = blobStorage.AddBlobContainer("contact-images-thumbnails");

Bây giờ chúng ta cần cập nhật builder.AddProject<Projects.Contacts_WebUi>(“contacts-web-ui”) để truyền chuỗi kết nối Azure Blob storage đến dự án Contacts.WebUi. Chúng ta có thể làm điều này bằng cách gọi chuỗi phương thức WithEnvironment trên phương thức AddProject cho dự án Contacts.WebUi.

builder.AddProject<Projects.Contacts_WebUi>("contacts-web-ui")
    .WithReference(api)
    .WaitFor(api)
    .WaitFor(blobStorage)
    .WithEnvironment("Settings__ContactBlobStorageAccount", imageContainer)
    .WithEnvironment("Settings__ContactThumbnailBlobStorageAccountName", thumbnailContainer);

Bây giờ chúng ta cần thực hiện một vài điều chỉnh cho dự án Contacts.WebUi để có thể lập trình đặt cài đặt ContactImageUrl trong ứng dụng. Vì một số lý do, tính năng service discovery của Aspiration không tự động kết nối các URL blob storage, vì vậy chúng ta cần làm điều đó một cách thủ công.

Mở tệp Program.cs trong dự án Contacts.WebUi, và thêm dòng sau ngay sau dòng configuration.Bind(“Settings”, settings); trong phương thức ConfigureServices:

if (environment.IsDevelopment())
{
    var endpoint = settings.ContactBlobStorageAccount.Split(';').First(x => x.StartsWith("BlobEndpoint"));
    settings.ContactImageUrl =  endpoint.Split('=')[1].TrimEnd('/') + "/";
}

Mã này kiểm tra xem ứng dụng có đang chạy trong chế độ phát triển hay không, và nếu có, nó đặt cài đặt ContactImageUrl thành URL endpoint của Blob storage bằng cách phân tích chuỗi kết nối.

Một điều khác mà chúng ta cần làm, để ứng dụng này hoạt động, là lập trình đặt mức truy cập công khai của blob container thành PublicAccessType.Blob. Thay thế dòng services.TryAddScoped<IImageStore> bằng mã sau:

services.TryAddScoped<IImageStore>(_ =>
{
    if (environment.IsDevelopment())
    {
        var blob = new Blobs(settings.ContactBlobStorageAccount, settings.ContactImageContainerName);
        blob.BlobContainerClient.SetAccessPolicy(PublicAccessType.Blob);
        
        return new ImageStore(blob);
    }

    return new ImageStore(
        new Blobs(settings.ContactBlobStorageAccountName, null, settings.ContactImageContainerName));

});

Lưu ý:: Tôi sẽ tạo hai vấn đề để thêm điều này vào gói Aspire.Hosting.Azure.Storage, để chúng ta không cần phải làm điều này một cách thủ công trong tương lai.

Mã này kiểm tra xem ứng dụng có đang chạy trong chế độ phát triển hay không, và nếu có, nó đặt mức truy cập công khai của blob container thành PublicAccessType.Blob. Điều này cho phép các hình ảnh được truy cập công khai, điều cần thiết để Web UI hiển thị các hình ảnh.

Bây giờ bạn có thể tải lên một hình ảnh cho một liên hệ, hình ảnh sẽ được tải lên trình mô phỏng Azure Blob storage, và Web UI sẽ hiển thị hình ảnh. Nếu bạn đã cài đặt Azure Storage Explorer, bạn có thể kết nối với trình mô phỏng Azurite và xem các hình ảnh đã được tải lên.

Tóm tắt

Wow! Đó là rất nhiều công việc, nhưng chúng ta đã thành công thêm Aspiration vào ứng dụng Contacts và cấu hình nó để sử dụng SQL Server, Azure Blob storage, và Web UI. Chúng ta cũng đã tạo schema cơ sở dữ liệu và dữ liệu seed cho ứng dụng, và chúng ta đã có thể chạy ứng dụng với tất cả các thành phần hoạt động cùng nhau. Bây giờ, bạn có thể tập trung vào việc xây dựng các tính năng cho ứng dụng mà không cần lo lắng về cơ sở hạ tầng và cấu hình cơ bản.

Trong một ứng dụng “thực tế”, nó không nên mất nhiều thời gian như vậy để bắt đầu với Aspiration. Mục tiêu của bài viết này là hướng dẫn bạn thiết lập một ứng dụng để cho bạn thấy cách bắt đầu với Aspiration, và cách thêm các thành phần cần thiết vào ứng dụng của bạn.

Nếu bạn muốn theo dõi cùng tôi học Aspiration, bạn có thể xem tập AspiriFridays nơi chúng tôi thêm Aspiration vào ứng dụng JosephGuadagno.NET Broadcasting. Bạn cũng có thể xem tài liệu chính thức để có thêm thông tin về cách bắt đầu với Aspiration.

Tài liệu tham khảo

Chỉ mục