.NET 10: สร้าง REST API ด้วย ASP.NET Core
คู่มือฉบับสมบูรณ์สำหรับการสร้าง REST API ระดับมืออาชีพด้วย .NET 10 และ ASP.NET Core ครอบคลุม Controller, Entity Framework Core, validation ในตัว และแนวทางปฏิบัติที่ดีที่สุด

.NET 10 เป็นเวอร์ชัน Long-Term Support (LTS) ปัจจุบัน มาพร้อม validation ในตัวสำหรับ Minimal API, OpenAPI 3.1 เป็นค่าเริ่มต้น และการปรับปรุงประสิทธิภาพอย่างมีนัยสำคัญ ASP.NET Core ผสานรวม C# เข้ากับสถาปัตยกรรมแบบโมดูลที่เหมาะกับแอปพลิเคชันระดับองค์กร คู่มือนี้ครอบคลุมการสร้าง REST API ที่พร้อมใช้งานจริง ตั้งแต่การตั้งค่าเริ่มต้นจนถึงการ deploy
.NET 10 รองรับจนถึงปลายปี 2028 Validation ในตัวสำหรับ Minimal API, รองรับ OpenAPI 3.1 และการปรับปรุง Native AOT ทำให้เป็นตัวเลือกที่เหมาะสมที่สุดสำหรับโปรเจกต์ API ใหม่
ตั้งค่าโปรเจกต์ด้วย .NET 10 CLI
การสร้างโปรเจกต์ API ASP.NET Core ใช้ .NET CLI เพื่อสร้างโครงสร้างโปรเจกต์ที่เหมาะสม การกำหนดค่าแพ็กเกจ NuGet ที่จำเป็นจะเตรียมพื้นฐานสำหรับการพัฒนา
# terminal
# Check installed .NET version
dotnet --version
# Expected: 10.0.x
# Create the API project
dotnet new webapi -n ProductApi -o ProductApi
cd ProductApi
# Add essential packages
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Swashbuckle.AspNetCoreคำสั่งเหล่านี้สร้างโปรเจกต์ API พร้อม dependency สำหรับ Entity Framework Core และเอกสาร Swagger FluentValidation ไม่จำเป็นอีกต่อไปสำหรับ validation พื้นฐานใน .NET 10
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.Services;
var builder = WebApplication.CreateBuilder(args);
// Configure Entity Framework Core with SQL Server
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Register business services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<ICategoryService, CategoryService>();
// Configure controllers with built-in validation (.NET 10)
builder.Services.AddControllers();
builder.Services.AddValidation();
// Configure OpenAPI 3.1 (default in .NET 10)
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "Product API", Version = "v1" });
});
var app = builder.Build();
// Middleware pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();เมธอด AddValidation() เปิดใช้งาน validation ในตัว ที่เปิดตัวใน ASP.NET Core 10 ช่วยลดความจำเป็นในการใช้ไลบรารีภายนอกเช่น FluentValidation สำหรับสถานการณ์ทั่วไป
Data Model และ Entity Framework Core 10
Model แทน business entity ของแอปพลิเคชัน Entity Framework Core จัดการ object-relational mapping ด้วยการกำหนดค่าแบบ fluent และ convention ที่ชาญฉลาด
namespace ProductApi.Models;
public class Product
{
// Primary key with auto-increment
public int Id { get; set; }
// Required properties (non-nullable in C# 14)
public required string Name { get; set; }
public required string Description { get; set; }
// Price with decimal precision
public decimal Price { get; set; }
// Stock with default value
public int StockQuantity { get; set; } = 0;
// Product status
public bool IsActive { get; set; } = true;
// Relationship with Category (foreign key)
public int CategoryId { get; set; }
public Category? Category { get; set; }
// Automatic tracking dates
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
}คีย์เวิร์ด required รับประกันว่า property ที่สำคัญจะถูกกำหนดค่าเสมอเมื่อสร้างออบเจกต์
namespace ProductApi.Models;
public class Category
{
public int Id { get; set; }
public required string Name { get; set; }
// Slug for friendly URLs
public required string Slug { get; set; }
public string? Description { get; set; }
// Inverse navigation: list of products in this category
public ICollection<Product> Products { get; set; } = new List<Product>();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}using Microsoft.EntityFrameworkCore;
using ProductApi.Models;
namespace ProductApi.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
// DbSets for each entity
public DbSet<Product> Products => Set<Product>();
public DbSet<Category> Categories => Set<Category>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Product entity configuration
modelBuilder.Entity<Product>(entity =>
{
// Index on name for fast search
entity.HasIndex(p => p.Name);
// Price precision: 18 digits, 2 decimals
entity.Property(p => p.Price)
.HasPrecision(18, 2);
// Relationship with Category
entity.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Restrict);
});
// Category entity configuration
modelBuilder.Entity<Category>(entity =>
{
// Unique slug
entity.HasIndex(c => c.Slug).IsUnique();
// Maximum name length
entity.Property(c => c.Name).HasMaxLength(100);
});
}
}การกำหนดค่า Fluent API ให้การควบคุมที่แม่นยำต่อ database schema ที่สร้างโดย EF Core migrations
Migration จัดการเวอร์ชันของ database schema ใช้คำสั่ง dotnet ef migrations add InitialCreate แล้วตามด้วย dotnet ef database update เพื่อนำการเปลี่ยนแปลงไปใช้
DTO และ Validation ในตัวใน ASP.NET Core 10
DTO (Data Transfer Object) แยก domain model ออกจากข้อมูลที่เปิดเผยผ่าน API ASP.NET Core 10 เปิดตัว validation ในตัวโดยใช้ DataAnnotations ลดความจำเป็นในการใช้ไลบรารีภายนอกสำหรับกรณีส่วนใหญ่
using System.ComponentModel.DataAnnotations;
namespace ProductApi.DTOs;
// DTO for product creation with DataAnnotations
public record CreateProductDto(
[Required(ErrorMessage = "Product name is required.")]
[MaxLength(200, ErrorMessage = "Name cannot exceed 200 characters.")]
string Name,
[Required(ErrorMessage = "Description is required.")]
[MinLength(10, ErrorMessage = "Description must contain at least 10 characters.")]
string Description,
[Range(0.01, 999999.99, ErrorMessage = "Price must be between 0.01 and 999,999.99.")]
decimal Price,
[Range(0, int.MaxValue, ErrorMessage = "Stock cannot be negative.")]
int StockQuantity,
[Required(ErrorMessage = "A valid category is required.")]
int CategoryId
);
// DTO for product update
public record UpdateProductDto(
[MaxLength(200)]
string? Name,
string? Description,
[Range(0.01, 999999.99)]
decimal? Price,
[Range(0, int.MaxValue)]
int? StockQuantity,
bool? IsActive
);
// DTO for response (read)
public record ProductDto(
int Id,
string Name,
string Description,
decimal Price,
int StockQuantity,
bool IsActive,
string CategoryName,
DateTime CreatedAt
);
// DTO for list with pagination
public record ProductListDto(
int Id,
string Name,
decimal Price,
int StockQuantity,
bool IsActive,
string CategoryName
);การใช้ record C# ทำให้ DTO เป็น immutable และกระชับ พร้อม value equality อัตโนมัติ Validation ในตัวจะส่งคืน error ที่มีโครงสร้างพร้อมรหัส 400 โดยอัตโนมัติเมื่อ validation ล้มเหลว สำหรับการเตรียมสัมภาษณ์เกี่ยวกับ pattern ของ ASP.NET Core ดูคู่มือ คำถามสัมภาษณ์ ASP.NET Core
พร้อมที่จะพิชิตการสัมภาษณ์ .NET แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Business Service และ Abstraction Layer
Service layer ห่อหุ้ม business logic และการดำเนินการกับ database ช่วยให้การทดสอบและการดูแลรักษาทำได้ง่ายขึ้น
using ProductApi.DTOs;
namespace ProductApi.Services;
public interface IProductService
{
// Retrieval with pagination
Task<(IEnumerable<ProductListDto> Items, int TotalCount)> GetAllAsync(
int page = 1,
int pageSize = 10,
string? search = null,
int? categoryId = null);
// Retrieval by ID
Task<ProductDto?> GetByIdAsync(int id);
// Creation
Task<ProductDto> CreateAsync(CreateProductDto dto);
// Update
Task<ProductDto?> UpdateAsync(int id, UpdateProductDto dto);
// Deletion
Task<bool> DeleteAsync(int id);
}using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.DTOs;
using ProductApi.Models;
namespace ProductApi.Services;
public class ProductService : IProductService
{
private readonly AppDbContext _context;
public ProductService(AppDbContext context)
{
_context = context;
}
public async Task<(IEnumerable<ProductListDto> Items, int TotalCount)> GetAllAsync(
int page = 1,
int pageSize = 10,
string? search = null,
int? categoryId = null)
{
// Build base query
var query = _context.Products
.Include(p => p.Category)
.AsQueryable();
// Filter by text search
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(p =>
p.Name.Contains(search) ||
p.Description.Contains(search));
}
// Filter by category
if (categoryId.HasValue)
{
query = query.Where(p => p.CategoryId == categoryId.Value);
}
// Total count before pagination
var totalCount = await query.CountAsync();
// Apply pagination
var items = await query
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(p => new ProductListDto(
p.Id,
p.Name,
p.Price,
p.StockQuantity,
p.IsActive,
p.Category!.Name))
.ToListAsync();
return (items, totalCount);
}
public async Task<ProductDto?> GetByIdAsync(int id)
{
// Retrieve with category inclusion
var product = await _context.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(p => p.Id == id);
if (product == null) return null;
// Map to DTO
return new ProductDto(
product.Id,
product.Name,
product.Description,
product.Price,
product.StockQuantity,
product.IsActive,
product.Category?.Name ?? "Uncategorized",
product.CreatedAt);
}
public async Task<ProductDto> CreateAsync(CreateProductDto dto)
{
// Create entity
var product = new Product
{
Name = dto.Name,
Description = dto.Description,
Price = dto.Price,
StockQuantity = dto.StockQuantity,
CategoryId = dto.CategoryId
};
// Add and save
_context.Products.Add(product);
await _context.SaveChangesAsync();
// Load category for response
await _context.Entry(product)
.Reference(p => p.Category)
.LoadAsync();
return new ProductDto(
product.Id,
product.Name,
product.Description,
product.Price,
product.StockQuantity,
product.IsActive,
product.Category?.Name ?? "Uncategorized",
product.CreatedAt);
}
public async Task<ProductDto?> UpdateAsync(int id, UpdateProductDto dto)
{
// Retrieve existing entity
var product = await _context.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(p => p.Id == id);
if (product == null) return null;
// Conditional field updates
if (!string.IsNullOrEmpty(dto.Name))
product.Name = dto.Name;
if (!string.IsNullOrEmpty(dto.Description))
product.Description = dto.Description;
if (dto.Price.HasValue)
product.Price = dto.Price.Value;
if (dto.StockQuantity.HasValue)
product.StockQuantity = dto.StockQuantity.Value;
if (dto.IsActive.HasValue)
product.IsActive = dto.IsActive.Value;
// Update modification date
product.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return new ProductDto(
product.Id,
product.Name,
product.Description,
product.Price,
product.StockQuantity,
product.IsActive,
product.Category?.Name ?? "Uncategorized",
product.CreatedAt);
}
public async Task<bool> DeleteAsync(int id)
{
// Direct deletion without prior loading (EF Core 7+)
var result = await _context.Products
.Where(p => p.Id == id)
.ExecuteDeleteAsync();
return result > 0;
}
}การใช้ ExecuteDeleteAsync ช่วยเพิ่มประสิทธิภาพโดยหลีกเลี่ยงการโหลด entity ก่อนลบ สำหรับ pattern ขั้นสูง ดูคู่มือ Clean Architecture กับ .NET
API Controller และ REST Endpoint
Controller เปิดให้บริการ REST endpoint และประสานงานการเรียกใช้ business service พร้อมการจัดการรหัสสถานะ HTTP ที่เหมาะสม
using Microsoft.AspNetCore.Mvc;
using ProductApi.DTOs;
using ProductApi.Services;
namespace ProductApi.Controllers;
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
/// <summary>
/// Retrieves the list of products with pagination and filters.
/// </summary>
[HttpGet]
[ProducesResponseType(typeof(PaginatedResponse<ProductListDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10,
[FromQuery] string? search = null,
[FromQuery] int? categoryId = null)
{
// Validate pagination parameters
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 100) pageSize = 10;
var (items, totalCount) = await _productService.GetAllAsync(
page, pageSize, search, categoryId);
// Standardized paginated response
var response = new PaginatedResponse<ProductListDto>
{
Items = items,
Page = page,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
};
return Ok(response);
}
/// <summary>
/// Retrieves a product by its identifier.
/// </summary>
[HttpGet("{id:int}")]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
{
return NotFound(new { message = $"Product with ID {id} not found." });
}
return Ok(product);
}
/// <summary>
/// Creates a new product.
/// </summary>
[HttpPost]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create([FromBody] CreateProductDto dto)
{
// Validation is automatic via AddValidation()
var product = await _productService.CreateAsync(dto);
// Returns 201 with the created resource URL
return CreatedAtAction(
nameof(GetById),
new { id = product.Id },
product);
}
/// <summary>
/// Updates an existing product.
/// </summary>
[HttpPut("{id:int}")]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateProductDto dto)
{
var product = await _productService.UpdateAsync(id, dto);
if (product == null)
{
return NotFound(new { message = $"Product with ID {id} not found." });
}
return Ok(product);
}
/// <summary>
/// Deletes a product.
/// </summary>
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(int id)
{
var deleted = await _productService.DeleteAsync(id);
if (!deleted)
{
return NotFound(new { message = $"Product with ID {id} not found." });
}
// 204 No Content for successful deletion
return NoContent();
}
}Attribute ProducesResponseType บันทึกรหัส response ที่เป็นไปได้สำหรับการสร้างเอกสาร Swagger โดยอัตโนมัติ
namespace ProductApi.DTOs;
public class PaginatedResponse<T>
{
public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public int TotalPages { get; set; }
public bool HasPreviousPage => Page > 1;
public bool HasNextPage => Page < TotalPages;
}การใช้ constraint เช่น {id:int} ป้องกันความขัดแย้งของ routing และส่งคืน 404 โดยอัตโนมัติหากรูปแบบไม่ถูกต้อง
Middleware จัดการ Error แบบ Global
Middleware จัดการ error รวมศูนย์การประมวลผล exception เพื่อ response ที่สม่ำเสมอและปลอดภัย
using System.Net;
using System.Text.Json;
namespace ProductApi.Middleware;
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
private readonly IHostEnvironment _env;
public ExceptionMiddleware(
RequestDelegate next,
ILogger<ExceptionMiddleware> logger,
IHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
// Continue pipeline
await _next(context);
}
catch (Exception ex)
{
// Log the error
_logger.LogError(ex, "An unhandled exception occurred");
// Prepare response
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// Different response based on environment
var response = _env.IsDevelopment()
? new ErrorResponse(
StatusCode: context.Response.StatusCode,
Message: ex.Message,
Details: ex.StackTrace)
: new ErrorResponse(
StatusCode: context.Response.StatusCode,
Message: "An internal error occurred.",
Details: null);
// Serialize with camelCase options
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(response, options);
await context.Response.WriteAsync(json);
}
}
}
// DTO for error responses
public record ErrorResponse(int StatusCode, string Message, string? Details);
// Extension to register middleware
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<ExceptionMiddleware>();
}
}var app = builder.Build();
// Exception middleware must be first
app.UseExceptionMiddleware();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// ... rest of configurationการกำหนดค่าและ Environment Variable
การกำหนดค่าภายนอกช่วยให้ปรับแอปพลิเคชันให้เหมาะกับสภาพแวดล้อมต่างๆ ได้โดยไม่ต้องแก้ไขโค้ด
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=ProductDb;User Id=sa;Password=YourPassword;TrustServerCertificate=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"ApiSettings": {
"DefaultPageSize": 10,
"MaxPageSize": 100,
"ApiVersion": "1.0"
}
}namespace ProductApi.Configuration;
public class ApiSettings
{
public int DefaultPageSize { get; set; } = 10;
public int MaxPageSize { get; set; } = 100;
public string ApiVersion { get; set; } = "1.0";
}builder.Services.Configure<ApiSettings>(
builder.Configuration.GetSection("ApiSettings"));
// Usage in a service
public class ProductService : IProductService
{
private readonly ApiSettings _settings;
public ProductService(IOptions<ApiSettings> settings)
{
_settings = settings.Value;
}
}Unit Testing ด้วย xUnit
Unit test ตรวจสอบพฤติกรรมของ service และ controller แบบแยกส่วน
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.DTOs;
using ProductApi.Models;
using ProductApi.Services;
using Xunit;
namespace ProductApi.Tests;
public class ProductServiceTests
{
private AppDbContext CreateInMemoryContext()
{
// Configure in-memory database
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new AppDbContext(options);
}
[Fact]
public async Task CreateAsync_ValidDto_ReturnsProductDto()
{
// Arrange
using var context = CreateInMemoryContext();
// Add test category
var category = new Category { Id = 1, Name = "Electronics", Slug = "electronics" };
context.Categories.Add(category);
await context.SaveChangesAsync();
var service = new ProductService(context);
var dto = new CreateProductDto(
Name: "Test Product",
Description: "Test Description",
Price: 99.99m,
StockQuantity: 10,
CategoryId: 1);
// Act
var result = await service.CreateAsync(dto);
// Assert
Assert.NotNull(result);
Assert.Equal("Test Product", result.Name);
Assert.Equal(99.99m, result.Price);
Assert.Equal("Electronics", result.CategoryName);
}
[Fact]
public async Task GetByIdAsync_NonExistent_ReturnsNull()
{
// Arrange
using var context = CreateInMemoryContext();
var service = new ProductService(context);
// Act
var result = await service.GetByIdAsync(999);
// Assert
Assert.Null(result);
}
[Fact]
public async Task DeleteAsync_ExistingProduct_ReturnsTrue()
{
// Arrange
using var context = CreateInMemoryContext();
var category = new Category { Id = 1, Name = "Test", Slug = "test" };
var product = new Product
{
Id = 1,
Name = "To Delete",
Description = "Will be deleted",
Price = 10.00m,
CategoryId = 1
};
context.Categories.Add(category);
context.Products.Add(product);
await context.SaveChangesAsync();
var service = new ProductService(context);
// Act
var result = await service.DeleteAsync(1);
// Assert
Assert.True(result);
Assert.Null(await context.Products.FindAsync(1));
}
}รันเทสต์ด้วยคำสั่ง dotnet test จากไดเรกทอรีหลักของโปรเจกต์ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ pattern ประสิทธิภาพ EF Core รวมถึงกลยุทธ์การทดสอบ ดูคู่มือเฉพาะ
แหล่งข้อมูล
- What's new in ASP.NET Core 10 - Validation ในตัว, รองรับ OpenAPI 3.1
- What's new in .NET 10 - รายละเอียดเวอร์ชัน LTS, ฟีเจอร์ C# 14
- Entity Framework Core documentation - อัปเดต EF Core 10
- Announcing .NET 10 - ประกาศเปิดตัวอย่างเป็นทางการ
Checklist สำหรับการสร้าง .NET API ระดับ Production
.NET 10 กับ ASP.NET Core มอบระบบนิเวศที่สมบูรณ์และมีประสิทธิภาพสูงสำหรับการสร้าง REST API ระดับมืออาชีพ การผสานระหว่าง validation ในตัว Entity Framework Core สำหรับการเข้าถึงข้อมูล และ dependency injection แบบ native ช่วยให้สร้างแอปพลิเคชันที่ดูแลรักษาและทดสอบได้ง่าย
- แยก DTO ออกจาก domain model
- สร้าง service layer สำหรับ business logic
- ใช้ validation ในตัวกับ DataAnnotations (.NET 10+)
- กำหนดค่า middleware จัดการ error แบบ global
- กำหนดค่าภายนอกด้วย IOptions
- เขียน unit test สำหรับ service
- ทำเอกสาร API ด้วย OpenAPI 3.1
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
สถาปัตยกรรมแบบแบ่งชั้น (Controller, Service, Repository/DbContext) ส่งเสริมการแยกหน้าที่ความรับผิดชอบและช่วยให้แอปพลิเคชันพัฒนาได้ง่าย ฟีเจอร์ของ .NET 10 เช่น validation ในตัว OpenAPI 3.1 เป็นค่าเริ่มต้น และการรองรับ Native AOT ที่ปรับปรุง ทำให้การพัฒนา API ทันสมัยพร้อมทั้งปรับปรุงประสิทธิภาพ
คุณหาบั๊กใน .NET เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 22 สิงหาคม 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Clean Architecture บน .NET: คู่มือเชิงปฏิบัติ
เชี่ยวชาญ Clean Architecture บน .NET ด้วย C# เรียนรู้หลักการ SOLID การแยกชั้น และรูปแบบการนำไปใช้สำหรับแอปพลิเคชันที่บำรุงรักษาง่าย

คำถามสัมภาษณ์ C# และ .NET: คู่มือฉบับสมบูรณ์ 2026
คำถามสัมภาษณ์ C# และ .NET ที่พบบ่อยที่สุด 17 ข้อ LINQ, async/await, dependency injection, Entity Framework และ best practice พร้อมคำตอบละเอียดและตัวอย่างโค้ด

Clean Code Architecture C#: คู่มือฉบับสมบูรณ์และคำถามสัมภาษณ์ 2026
เรียนรู้ Clean Code Architecture ใน C# พร้อมหลักการ SOLID, Repository Pattern, Dependency Injection และคำถามสัมภาษณ์ยอดนิยมสำหรับนักพัฒนา .NET