Nguon: Microsoft Learn · .NET 8.0

Tổng quan về các kiến thức cơ bản trong ASP.NET Core

Nguồn: ASP.NET Core fundamentals overview | Phiên bản: .NET 8.0

Lưu ý: Đây không phải phiên bản mới nhất của bài viết này. Để xem bản phát hành hiện tại, hãy xem bài viết phiên bản .NET 10.

Cảnh báo: Phiên bản ASP.NET Core này không còn được hỗ trợ. Để biết thêm thông tin, xem Chính sách hỗ trợ .NET và .NET Core. Để xem bản phát hành hiện tại, hãy xem bài viết phiên bản .NET 10.

Bài viết này cung cấp tổng quan về các kiến thức cơ bản để xây dựng ứng dụng ASP.NET Core, bao gồm Dependency Injection (tiêm phụ thuộc) (DI), Configuration (cấu hình), Middleware (phần mềm trung gian) và nhiều hơn nữa.

Để xem hướng dẫn về các kiến thức cơ bản của Blazor — phần bổ sung hoặc thay thế hướng dẫn trong bài viết này — hãy xem ASP.NET Core Blazor fundamentals.

Program.cs

Các ứng dụng ASP.NET Core được tạo bằng các web template chứa mã khởi động ứng dụng trong tệp Program.cs. Tệp Program.cs là nơi:

Đoạn mã khởi động ứng dụng sau đây hỗ trợ nhiều loại ứng dụng:

csharp
using WebAll.Components;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.UseAntiforgery();

app.Run();

Dependency Injection (tiêm phụ thuộc) (Services/Dịch vụ)

ASP.NET Core có tích hợp sẵn Dependency Injection (DI) giúp các service đã được cấu hình trở nên khả dụng trong toàn bộ ứng dụng. Các service được thêm vào DI container (bộ chứa DI) thông qua WebApplicationBuilder.Services, tức là builder.Services trong đoạn mã phía trên. Khi WebApplicationBuilder được khởi tạo, nhiều framework-provided services (dịch vụ do framework cung cấp) sẽ được thêm vào tự động. builder là một WebApplicationBuilder trong đoạn mã sau:

csharp
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

Trong đoạn mã trên, CreateBuilder thêm Configuration (cấu hình), Logging (ghi nhật ký) và nhiều service khác vào DI container. DI framework (framework tiêm phụ thuộc) cung cấp một instance (thực thể) của service được yêu cầu tại thời điểm chạy.

Đoạn mã sau thêm một DbContext tùy chỉnh và các Blazor component (thành phần Blazor) vào DI container:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextFactory<BlazorWebAppMoviesContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("MoviesContext")
        ?? throw new InvalidOperationException("Connection string not found.")));

builder.Services.AddQuickGridEntityFrameworkAdapter();

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();

Trong các Blazor Web App, service thường được resolve (giải quyết) từ DI tại thời điểm chạy bằng cách sử dụng directive (chỉ thị) @inject trong Razor component, như ví dụ sau:

razor
@page "/movies"
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore
@using Microsoft.AspNetCore.Components.QuickGrid
@using BlazorWebAppMovies.Models
@using BlazorWebAppMovies.Data
@implements IAsyncDisposable
@inject IDbContextFactory<BlazorWebAppMovies.Data.BlazorWebAppMoviesContext> DbFactory

<PageTitle>Index</PageTitle>

<h1>Index</h1>

<div>
    <input type="search" @bind="titleFilter" @bind:event="oninput" />
</div>

<p>
    <a href="movies/create">Create New</a>
</p>

<QuickGrid Class="table" Items="FilteredMovies" Pagination="pagination">
    <PropertyColumn Property="movie => movie.Title" Sortable="true" />
    <PropertyColumn Property="movie => movie.ReleaseDate" Title="Release Date" />
    <PropertyColumn Property="movie => movie.Genre" />
    <PropertyColumn Property="movie => movie.Price" />
    <PropertyColumn Property="movie => movie.Rating" />

    <TemplateColumn Context="movie">
        <a href="@($"movies/edit?id={movie.Id}")">Edit</a> |
        <a href="@($"movies/details?id={movie.Id}")">Details</a> |
        <a href="@($"movies/delete?id={movie.Id}")">Delete</a>
    </TemplateColumn>
</QuickGrid>

<Paginator State="pagination" />

@code {
    private BlazorWebAppMoviesContext context = default!;
    private PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    private string titleFilter = string.Empty;

    private IQueryable<Movie> FilteredMovies =>
        context.Movie.Where(m => m.Title!.Contains(titleFilter));

    protected override void OnInitialized()
    {
        context = DbFactory.CreateDbContext();
    }

    public async ValueTask DisposeAsync() => await context.DisposeAsync();
}

Trong đoạn mã trên:

Một cách khác để resolve service từ DI là sử dụng Constructor Injection (tiêm qua hàm tạo). Đoạn mã Razor Pages sau sử dụng constructor injection để resolve database context và logger từ DI:

csharp
public class IndexModel : PageModel
{
    private readonly RazorPagesMovieContext _context;
    private readonly ILogger<IndexModel> _logger;

    public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel> logger)
    {
        _context = context;
        _logger = logger;
    }

    public IList<Movie> Movie { get;set; }

    public async Task OnGetAsync()
    {
        _logger.LogInformation("IndexModel OnGetAsync.");
        Movie = await _context.Movie.ToListAsync();
    }
}

Trong đoạn mã trên, constructor của IndexModel nhận một tham số kiểu RazorPagesMovieContext, được resolve tại thời điểm chạy vào biến _context. Đối tượng context được dùng để tạo danh sách phim trong phương thức OnGetAsync.

Để biết thêm thông tin, xem ASP.NET Core Blazor dependency injectionDependency injection in ASP.NET Core.

Middleware (Phần mềm trung gian)

Pipeline xử lý yêu cầu được tạo thành từ một loạt các middleware component. Mỗi component thực hiện các thao tác trên HttpContext và hoặc gọi middleware tiếp theo trong pipeline, hoặc kết thúc yêu cầu.

Theo quy ước, một middleware component được thêm vào pipeline bằng cách gọi một extension method (phương thức mở rộng) có tên Use{Feature}. Việc sử dụng các phương thức có tên Use{Feature} để thêm middleware vào ứng dụng được minh họa trong đoạn mã sau:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextFactory<BlazorWebAppMoviesContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("MoviesContext")
        ?? throw new InvalidOperationException("Connection string not found.")));

builder.Services.AddQuickGridEntityFrameworkAdapter();

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    SeedData.Initialize(services);
}

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
    app.UseMigrationsEndPoint();
}
app.UseHttpsRedirection();

app.UseAntiforgery();

app.MapStaticAssets();
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

Để biết thêm thông tin, xem ASP.NET Core Middleware.

Host (Máy chủ lưu trữ)

Khi khởi động, một ứng dụng ASP.NET Core xây dựng một host. Host đóng gói tất cả các tài nguyên của ứng dụng, chẳng hạn như:

Có ba loại host khác nhau có khả năng chạy ứng dụng ASP.NET Core:

Các kiểu WebApplicationWebApplicationBuilder của ASP.NET Core được khuyến nghị sử dụng và được dùng trong tất cả các template ASP.NET Core. WebApplication hoạt động tương tự như .NET Generic Host và hiển thị nhiều interface giống nhau nhưng yêu cầu ít callback hơn để cấu hình. WebHost của ASP.NET Core chỉ khả dụng để đảm bảo tương thích ngược.

Ví dụ sau khởi tạo một WebApplication và gán nó vào biến có tên app:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextFactory<BlazorWebAppMoviesContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("MoviesContext")
        ?? throw new InvalidOperationException("Connection string not found.")));

builder.Services.AddQuickGridEntityFrameworkAdapter();

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();

Phương thức WebApplicationBuilder.Build cấu hình một host với một tập hợp các tùy chọn mặc định, chẳng hạn như:

Các kịch bản không phải web (Non-web scenarios)

Generic Host cho phép các loại ứng dụng khác sử dụng các framework extension (tiện ích mở rộng framework) xuyên suốt, chẳng hạn như logging, dependency injection (DI), configuration và quản lý vòng đời ứng dụng. Để biết thêm thông tin, xem .NET Generic Host trong ASP.NET CoreBackground tasks với hosted services trong ASP.NET Core.

Servers (Máy chủ)

Một ứng dụng ASP.NET Core sử dụng một HTTP server implementation để lắng nghe các HTTP request. Máy chủ biểu diễn các yêu cầu tới ứng dụng dưới dạng một tập hợp các request features (tính năng yêu cầu) được tổng hợp vào một HttpContext.

ASP.NET Core cung cấp các server implementation sau:

Để biết thêm thông tin, xem Web server implementations in ASP.NET Core.

Configuration (Cấu hình)

ASP.NET Core cung cấp một framework Configuration (cấu hình) lấy các thiết lập dưới dạng cặp name-value (tên-giá trị) từ một tập hợp có thứ tự các configuration provider (nhà cung cấp cấu hình). Các configuration provider tích hợp sẵn có sẵn cho nhiều nguồn khác nhau, chẳng hạn như tệp .json, tệp .xml, biến môi trường và đối số dòng lệnh. Viết các custom configuration provider (nhà cung cấp cấu hình tùy chỉnh) để hỗ trợ các nguồn khác.

Theo mặc định, các ứng dụng ASP.NET Core được cấu hình để đọc từ appsettings.json, biến môi trường, dòng lệnh và nhiều hơn nữa. Khi cấu hình của ứng dụng được tải, các giá trị từ biến môi trường sẽ ghi đè các giá trị từ appsettings.json.

Để quản lý dữ liệu cấu hình nhạy cảm như mật khẩu trong môi trường Development, .NET cung cấp Secret Manager. Đối với các secret trong production (môi trường sản xuất), chúng tôi khuyến nghị sử dụng Azure Key Vault.

Để biết thêm thông tin, xem Configuration in ASP.NET Core.

Environments (Môi trường)

Các môi trường thực thi, chẳng hạn như Development (phát triển), Staging (kiểm thử) và Production (sản xuất), đều khả dụng trong ASP.NET Core. Chỉ định môi trường mà ứng dụng đang chạy bằng cách thiết lập biến môi trường ASPNETCORE_ENVIRONMENT. ASP.NET Core đọc biến môi trường đó khi khởi động ứng dụng và lưu trữ giá trị trong một triển khai IWebHostEnvironment. Triển khai này khả dụng ở bất kỳ đâu trong ứng dụng thông qua Dependency Injection (DI).

Ví dụ sau cấu hình exception handler (bộ xử lý ngoại lệ) và middleware HTTP Strict Transport Security Protocol (HSTS) khi không chạy trong môi trường Development:

csharp
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
    app.UseMigrationsEndPoint();
}

Để biết thêm thông tin, xem ASP.NET Core runtime environments.

Logging (Ghi nhật ký)

ASP.NET Core hỗ trợ một logging API (giao diện lập trình ứng dụng ghi nhật ký) hoạt động với nhiều logging provider tích hợp sẵn và bên thứ ba. Các provider có sẵn bao gồm:

Để tạo log (nhật ký), hãy resolve một service ILogger\<TCategoryName\> từ Dependency Injection (DI) và gọi các phương thức logging như LogInformation. Ví dụ sau cho thấy cách lấy và sử dụng logger trong tệp .razor cho một trang trong Blazor Web App. Một đối tượng logger và một console provider cho nó được lưu tự động trong DI container khi phương thức CreateBuilder được gọi trong Program.cs.

csharp
@page "/weather"
@attribute [StreamRendering]
@inject ILogger<Weather> Logger

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data and logging.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th aria-label="Temperature in Celsius">Temp. (C)</th>
                <th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        // Simulate asynchronous loading to demonstrate streaming rendering
  
        await Task.Delay(500);

        Logger.LogInformation("This is an information log message.");
        Logger.LogWarning("This is a warning log message.");
        Logger.LogError("This is an error log message.");

        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[] { "Freezing", "Bracing", "Chilly",
            "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        }).ToArray();
    }

    private class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

Để biết thêm thông tin, xem Logging in .NET and ASP.NET Core.

Routing (Định tuyến)

Routing (định tuyến) trong ASP.NET Core là một cơ chế ánh xạ các request đến (yêu cầu gửi đến) tới các endpoint (điểm cuối) cụ thể trong ứng dụng. Nó cho phép bạn định nghĩa các URL pattern (mẫu URL) tương ứng với các component khác nhau, chẳng hạn như Razor component, Razor page, MVC controller action (hành động bộ điều khiển MVC) hoặc middleware.

Phương thức UseRouting thêm routing middleware vào request pipeline (đường ống yêu cầu). Middleware này xử lý thông tin định tuyến và xác định endpoint phù hợp cho mỗi request. Bạn không cần phải gọi UseRouting một cách tường minh trừ khi muốn thay đổi thứ tự xử lý middleware.

Đoạn mã sau, được tạo bởi web application template của ASP.NET Core, gọi UseRouting:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Để biết thêm thông tin, xem Routing in ASP.NET CoreASP.NET Core Blazor routing.

Error handling (Xử lý lỗi)

ASP.NET Core có các tính năng tích hợp sẵn để xử lý lỗi, chẳng hạn như:

Để biết thêm thông tin, xem Handle errors in ASP.NET Core.

Make HTTP requests (Thực hiện yêu cầu HTTP)

Một triển khai của IHttpClientFactory có sẵn để tạo các instance HttpClient. Factory (nhà máy) này:

Để biết thêm thông tin, xem HTTP requests with IHttpClientFactory - ASP.NET Core.

Content root (Thư mục gốc nội dung)

Content root (thư mục gốc nội dung) là đường dẫn cơ sở cho:

Trong quá trình phát triển, content root mặc định là thư mục gốc của dự án. Thư mục này cũng là đường dẫn cơ sở cho cả tệp nội dung của ứng dụng và web root. Chỉ định một content root khác bằng cách thiết lập đường dẫn của nó khi building the host (xây dựng host). Để biết thêm thông tin, xem Content root.

Web root (Thư mục gốc web)

Web root (thư mục gốc web) là đường dẫn cơ sở cho các tệp tài nguyên tĩnh công khai, chẳng hạn như:

Theo mặc định, các static file (tệp tĩnh) chỉ được phục vụ từ thư mục web root và các thư mục con của nó. Đường dẫn web root mặc định là {CONTENT ROOT}/wwwroot, trong đó placeholder (phần giữ chỗ) {CONTENT ROOT} là content root. Chỉ định một web root khác bằng cách thiết lập đường dẫn của nó khi building the host (xây dựng host). Để biết thêm thông tin, xem Web root.

Ngăn việc publish (xuất bản) tệp trong wwwroot bằng cách sử dụng <Content> project item trong tệp dự án. Ví dụ sau ngăn việc publish nội dung trong wwwroot/local và các thư mục con của nó:

xml
<ItemGroup>
  <Content Update="wwwroot\local\**\*.*" CopyToPublishDirectory="Never" />
</ItemGroup>

Trong các tệp Razor .cshtml, ~/ trỏ đến web root. Một đường dẫn bắt đầu bằng ~/ được gọi là virtual path (đường dẫn ảo).

Để biết thêm thông tin, xem Static files in ASP.NET Core.

Cách tải xuống mẫu (How to download a sample)

Nhiều bài viết và hướng dẫn có chứa liên kết đến sample code (mã mẫu).

  1. Tải xuống tệp zip của repository ASP.NET.
  2. Giải nén tệp AspNetCore.Docs-main.zip.
  3. Để truy cập ứng dụng mẫu của bài viết trong repository đã giải nén, sử dụng URL trong liên kết mẫu của bài viết để điều hướng đến thư mục của mẫu. Thông thường, liên kết mẫu của bài viết xuất hiện ở đầu bài viết với văn bản liên kết View or download sample code (Xem hoặc tải xuống mã mẫu).

Preprocessor directives (Chỉ thị tiền xử lý) trong sample code

Để minh họa nhiều tình huống, các ứng dụng mẫu sử dụng các preprocessor directive (chỉ thị tiền xử lý) #define#if-#else/#elif-#endif để chọn lọc biên dịch và chạy các phần code mẫu khác nhau. Đối với các mẫu sử dụng phương pháp này, hãy thiết lập directive #define ở đầu tệp C# để định nghĩa symbol (ký hiệu) liên quan đến tình huống bạn muốn chạy. Một số mẫu yêu cầu định nghĩa symbol ở đầu nhiều tệp để chạy một tình huống.

Ví dụ: danh sách symbol #define sau cho thấy bốn tình huống có sẵn (một tình huống cho mỗi symbol). Cấu hình mẫu hiện tại chạy tình huống TemplateCode:

csharp
#define TemplateCode // or LogFromMain or ExpandDefault or FilterInCode

Để thay đổi mẫu để chạy tình huống ExpandDefault, hãy định nghĩa symbol ExpandDefault và để các symbol còn lại được comment-out (chú thích):

csharp
#define ExpandDefault // TemplateCode or LogFromMain or FilterInCode

Để biết thêm thông tin về việc sử dụng C# preprocessor directives để chọn lọc biên dịch các phần code, xem #define (C# Reference)#if (C# Reference).

Regions (Vùng) trong sample code

Một số ứng dụng mẫu chứa các phần code được bao quanh bởi các directive C# #region#endregion. Hệ thống build tài liệu (documentation build system) chèn các vùng này vào các chủ đề tài liệu được render.

Tên vùng thường chứa từ "snippet" (đoạn). Ví dụ sau hiển thị một vùng có tên snippet_WebHostDefaults:

csharp
#region snippet_WebHostDefaults
Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    });
#endregion

Đoạn code C# phía trên được tham chiếu trong tệp markdown của chủ đề bằng dòng sau:

md
[!code-csharp[](sample/SampleApp/Program.cs?name=snippet_WebHostDefaults)]

Bạn có thể bỏ qua hoặc xóa các directive #region#endregion bao quanh code một cách an toàn. Không thay đổi code trong các directive này nếu bạn dự định chạy các tình huống mẫu được mô tả trong chủ đề.

Để biết thêm thông tin, xem Contribute to the ASP.NET documentation: Code snippets.

Tài nguyên bổ sung (Additional resources)