Câu Hỏi Phỏng Vấn C# và .NET: Hướng Dẫn Đầy Đủ 2026
17 câu hỏi phỏng vấn C# và .NET thường gặp nhất. LINQ, async/await, dependency injection, Entity Framework và các phương pháp tốt nhất với đáp án chi tiết.

Các buổi phỏng vấn C# và .NET đánh giá khả năng thông thạo ngôn ngữ, hiểu biết về hệ sinh thái Microsoft và năng lực thiết kế ứng dụng mạnh mẽ, hiệu suất cao. Bài hướng dẫn này bao gồm các câu hỏi thiết yếu từ kiến thức cơ bản về ngôn ngữ đến các pattern kiến trúc nâng cao.
Nhà tuyển dụng đánh giá cao những câu trả lời thể hiện sự hiểu biết về cơ chế nội tại của .NET, không chỉ là cú pháp. Giải thích "tại sao" đằng sau mỗi khái niệm sẽ tạo nên sự khác biệt.
Cơ Bản C#
Câu 1: Sự khác nhau giữa value types và reference types là gì?
Sự phân biệt cơ bản này ảnh hưởng đến việc cấp phát bộ nhớ, hiệu suất và hành vi khi truyền tham số.
// Demonstrating behavior differences
// VALUE TYPES: stored on the Stack, copied by value
struct Point
{
public int X;
public int Y;
}
// REFERENCE TYPES: stored on the Heap, copied by reference
class Person
{
public string Name;
}
public class Demo
{
public static void Main()
{
// Value type: independent copy
Point p1 = new Point { X = 10, Y = 20 };
Point p2 = p1; // Complete value copy
p2.X = 100; // Does NOT modify p1
Console.WriteLine($"p1.X = {p1.X}"); // 10
// Reference type: same object in memory
Person person1 = new Person { Name = "Alice" };
Person person2 = person1; // Reference copy
person2.Name = "Bob"; // MODIFIES person1 too
Console.WriteLine($"person1.Name = {person1.Name}"); // Bob
// Special case: string is immutable
string s1 = "Hello";
string s2 = s1;
s2 = "World"; // Creates a new string
Console.WriteLine($"s1 = {s1}"); // Hello
}
}Value types (int, struct, enum) được cấp phát trên Stack và giải phóng tự động. Reference types (class, interface, delegate) được cấp phát trên Heap và quản lý bởi Garbage Collector.
Câu 2: Giải thích các từ khóa ref, out và in
Các modifier này kiểm soát cách tham số được truyền vào phương thức, với những ảnh hưởng đến hiệu suất và tính bất biến.
// The three pass-by-reference modifiers
public class ParameterDemo
{
// REF: variable MUST be initialized before the call
// Can be read AND modified in the method
public static void ModifyWithRef(ref int value)
{
Console.WriteLine($"Received value: {value}");
value = value * 2; // Modification visible to caller
}
// OUT: variable does NOT need to be initialized
// MUST be assigned before method exits
public static bool TryParse(string input, out int result)
{
// result MUST be assigned in all execution paths
if (int.TryParse(input, out result))
{
return true;
}
result = 0; // Required assignment
return false;
}
// IN: read-only pass-by-reference (C# 7.2+)
// Avoids copying for large structs without allowing modification
public static double CalculateDistance(in Point3D p1, in Point3D p2)
{
// p1.X = 10; // ERROR: cannot modify 'in' parameter
return Math.Sqrt(
Math.Pow(p2.X - p1.X, 2) +
Math.Pow(p2.Y - p1.Y, 2) +
Math.Pow(p2.Z - p1.Z, 2)
);
}
public static void Main()
{
// Using ref
int number = 5;
ModifyWithRef(ref number);
Console.WriteLine($"After ref: {number}"); // 10
// Using out
if (TryParse("123", out int parsed))
{
Console.WriteLine($"Parsed: {parsed}"); // 123
}
// Using in (optimal for large structs)
var point1 = new Point3D(0, 0, 0);
var point2 = new Point3D(3, 4, 0);
var distance = CalculateDistance(in point1, in point2);
}
}
public readonly struct Point3D
{
public readonly double X, Y, Z;
public Point3D(double x, double y, double z) => (X, Y, Z) = (x, y, z);
}in đặc biệt hữu ích cho các struct lớn vì nó tránh việc sao chép trong khi đảm bảo tính bất biến. Đây là pattern phổ biến trong mã hiệu suất cao.
Sử dụng in cho các struct lớn hơn 16 byte giúp cải thiện hiệu suất bằng cách tránh sao chép. Đối với struct nhỏ, truyền theo giá trị vẫn hiệu quả hơn.
Câu 3: Garbage Collector trong .NET hoạt động như thế nào?
GC của .NET sử dụng thuật toán phân thế hệ để tối ưu hóa quản lý bộ nhớ tự động.
// Understanding GC behavior
public class GCDemo
{
public static void DemonstrateGenerations()
{
// Generation 0: newly allocated objects
var shortLived = new byte[1000];
Console.WriteLine($"Generation: {GC.GetGeneration(shortLived)}"); // 0
// Force collection to promote the object
GC.Collect();
Console.WriteLine($"After GC: {GC.GetGeneration(shortLived)}"); // 1
GC.Collect();
Console.WriteLine($"After 2nd GC: {GC.GetGeneration(shortLived)}"); // 2
// Memory statistics
var info = GC.GetGCMemoryInfo();
Console.WriteLine($"Total heap: {info.HeapSizeBytes / 1024 / 1024}MB");
}
// IDisposable pattern for unmanaged resources
public class DatabaseConnection : IDisposable
{
private IntPtr _nativeHandle;
private bool _disposed = false;
public DatabaseConnection()
{
_nativeHandle = AllocateNativeResource();
}
// Public Dispose method
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this); // Prevents finalizer call
}
// Protected Dispose pattern
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Free managed resources
}
// Free unmanaged resources
if (_nativeHandle != IntPtr.Zero)
{
FreeNativeResource(_nativeHandle);
_nativeHandle = IntPtr.Zero;
}
_disposed = true;
}
}
// Finalizer (destructor) - called by GC if Dispose wasn't called
~DatabaseConnection()
{
Dispose(disposing: false);
}
private IntPtr AllocateNativeResource() => IntPtr.Zero;
private void FreeNativeResource(IntPtr handle) { }
}
}
// Recommended usage with using
public class Usage
{
public void Example()
{
// C# 8+: using declaration
using var connection = new GCDemo.DatabaseConnection();
// ... usage
// Dispose() called automatically at end of scope
}
}GC thu gom Generation 0 thường xuyên (mili giây), Generation 1 thỉnh thoảng, và Generation 2 hiếm khi. Các đối tượng LOH (Large Object Heap > 85KB) được xử lý riêng biệt.
LINQ và Collections
Câu 4: Sự khác nhau giữa IEnumerable và IQueryable là gì?
Câu hỏi này rất quan trọng để hiểu về thực thi trì hoãn và hiệu suất truy vấn.
// Fundamental execution differences
public class LinqDemo
{
public static void CompareExecution(AppDbContext context)
{
// IEnumerable: executes IN MEMORY (client-side)
IEnumerable<Product> enumerable = context.Products.AsEnumerable();
var filteredEnum = enumerable
.Where(p => p.Price > 100) // Filtering in C#
.ToList();
// Generated SQL: SELECT * FROM Products (ALL loaded)
// IQueryable: executes on DATABASE (server-side)
IQueryable<Product> queryable = context.Products;
var filteredQuery = queryable
.Where(p => p.Price > 100) // Translated to SQL WHERE
.ToList();
// Generated SQL: SELECT * FROM Products WHERE Price > 100
// Query composition with IQueryable
var query = context.Products.AsQueryable();
// Each operation adds to the Expression Tree
query = query.Where(p => p.IsActive);
query = query.Where(p => p.CategoryId == 5);
query = query.OrderBy(p => p.Name);
// Execution happens HERE, with a single optimized SQL query
var results = query.ToList();
}
// Generic method that works with both
public static IEnumerable<T> FilterByCondition<T>(
IEnumerable<T> source,
Func<T, bool> predicate)
{
return source.Where(predicate);
}
// Optimized version for IQueryable
public static IQueryable<T> FilterByCondition<T>(
IQueryable<T> source,
Expression<Func<T, bool>> predicate)
{
// Expression<Func<>> enables SQL translation
return source.Where(predicate);
}
}Sử dụng IQueryable với Entity Framework để lọc dữ liệu ở phía cơ sở dữ liệu. IEnumerable phù hợp cho các collection trong bộ nhớ hoặc khi toàn bộ dữ liệu đã được tải.
Câu 5: Giải thích thực thi trì hoãn trong LINQ
Thực thi trì hoãn là khái niệm cơ bản ảnh hưởng đến hiệu suất và hành vi truy vấn.
// Understanding when queries actually execute
public class DeferredExecutionDemo
{
public static void Demonstrate()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Query is DEFINED but NOT EXECUTED
var query = numbers.Where(n => {
Console.WriteLine($"Evaluating {n}");
return n > 2;
});
Console.WriteLine("Query defined, but nothing happened yet");
// Modifying source BEFORE execution
numbers.Add(6);
numbers.Add(7);
Console.WriteLine("Starting iteration:");
// EXECUTION happens HERE during enumeration
foreach (var n in query)
{
Console.WriteLine($"Result: {n}");
}
// Output includes 6 and 7 because they were added before execution
}
// Methods that FORCE immediate execution
public static void ImmediateExecution()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// ToList(), ToArray(), ToDictionary() = immediate execution
var list = numbers.Where(n => n > 2).ToList();
// Count(), First(), Single(), Any() = immediate execution
var count = numbers.Where(n => n > 2).Count();
var first = numbers.First(n => n > 2);
// Aggregate(), Sum(), Max(), Min() = immediate execution
var sum = numbers.Where(n => n > 2).Sum();
}
// Danger: multiple enumeration
public static void MultipleEnumerationProblem()
{
var numbers = GetNumbers(); // IEnumerable returned by yield
// WARNING: EACH use re-executes the query
var count = numbers.Count(); // 1st enumeration
var first = numbers.First(); // 2nd enumeration
// SOLUTION: materialize once
var materializedList = numbers.ToList();
var countOk = materializedList.Count; // No re-execution
var firstOk = materializedList.First(); // No re-execution
}
private static IEnumerable<int> GetNumbers()
{
Console.WriteLine("GetNumbers called");
yield return 1;
yield return 2;
yield return 3;
}
}Sử dụng các công cụ phân tích như ReSharper hoặc Rider để phát hiện vấn đề đa enumeration có thể gây ra lỗi tiềm ẩn và vấn đề hiệu suất.
Async/Await và Đa Luồng
Câu 6: Giải thích async/await và cách Task hoạt động
Lập trình bất đồng bộ là yếu tố thiết yếu cho các ứng dụng hiện đại. Hiểu rõ cơ chế nội tại cho thấy trình độ chuyên môn cao.
// Internal mechanisms of asynchronous programming
public class AsyncDemo
{
// async transforms the method into a state machine
public async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
// await releases the thread during I/O wait
// Thread returns to pool and can process other requests
var response = await client.GetStringAsync(url);
// After await, execution resumes (possibly on different thread)
return ProcessData(response);
}
// Pattern for parallel execution
public async Task<(User, List<Order>)> GetUserWithOrdersAsync(int userId)
{
// Both calls start SIMULTANEOUSLY
var userTask = GetUserAsync(userId);
var ordersTask = GetOrdersAsync(userId);
// await waits for both results
await Task.WhenAll(userTask, ordersTask);
return (userTask.Result, ordersTask.Result);
}
// ConfigureAwait for libraries
public async Task<string> LibraryMethodAsync()
{
// ConfigureAwait(false) avoids capturing SynchronizationContext
// Recommended in libraries to avoid deadlocks
var data = await FetchDataAsync("https://api.example.com")
.ConfigureAwait(false);
return data.ToUpper();
}
// Anti-pattern: async void (except for event handlers)
public async void BadAsyncMethod()
{
// Exceptions cannot be caught
// Impossible to await completion
await Task.Delay(100);
}
// Correct: async Task
public async Task GoodAsyncMethod()
{
await Task.Delay(100);
}
private Task<User> GetUserAsync(int id) => Task.FromResult(new User());
private Task<List<Order>> GetOrdersAsync(int id) => Task.FromResult(new List<Order>());
private string ProcessData(string data) => data;
}
public class User { }
public class Order { }Trình biên dịch chuyển đổi các phương thức async thành state machine. Mỗi await đại diện cho một điểm tạm dừng tại đó luồng được giải phóng.
Câu 7: Làm thế nào để tránh deadlock với async/await?
Deadlock bất đồng bộ là bẫy cổ điển, đặc biệt trong các ứng dụng có SynchronizationContext (UI, ASP.NET classic).
// Patterns to avoid deadlocks
public class DeadlockDemo
{
private readonly IDataService _service;
// DEADLOCK in classic ASP.NET or WinForms/WPF
public string GetDataDeadlock()
{
// .Result or .Wait() blocks the UI/Request thread
// async tries to resume on that same thread = deadlock
return _service.FetchAsync().Result;
}
// Solution 1: async all the way
public async Task<string> GetDataAsync()
{
return await _service.FetchAsync();
}
// Solution 2: ConfigureAwait(false) in the library
public async Task<string> FetchAsync()
{
var data = await HttpClient.GetStringAsync("url")
.ConfigureAwait(false); // Don't capture context
return data;
}
// Solution 3: Task.Run to isolate (if really necessary)
public string GetDataWithTaskRun()
{
// Runs on thread pool without SynchronizationContext
return Task.Run(async () => await _service.FetchAsync()).Result;
}
// Pattern for proper cancellation
public async Task<string> FetchWithCancellation(CancellationToken cancellationToken)
{
using var client = new HttpClient();
try
{
var response = await client.GetStringAsync("url", cancellationToken);
return response;
}
catch (OperationCanceledException)
{
// Handle cancellation gracefully
return string.Empty;
}
}
// Timeout pattern
public async Task<string> FetchWithTimeout(TimeSpan timeout)
{
using var cts = new CancellationTokenSource(timeout);
try
{
return await FetchWithCancellation(cts.Token);
}
catch (OperationCanceledException)
{
throw new TimeoutException("Request timed out");
}
}
private static readonly HttpClient HttpClient = new();
}
public interface IDataService
{
Task<string> FetchAsync();
}Quy tắc vàng: "async all the way". Tránh pha trộn mã đồng bộ và bất đồng bộ. Trong ASP.NET Core, SynchronizationContext không tồn tại, giảm thiểu rủi ro deadlock.
Sẵn sàng chinh phục phỏng vấn .NET?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Dependency Injection và Kiến Trúc
Câu 8: Giải thích các vòng đời DI khác nhau (Scoped, Transient, Singleton)
Hiểu rõ các vòng đời là điều cần thiết để tránh lỗi đồng thời và rò rỉ bộ nhớ.
// The three lifetimes and their implications
// SINGLETON: single instance for the entire application
public class SingletonService
{
private readonly Guid _id = Guid.NewGuid();
public Guid Id => _id;
// DANGER: no mutable state without synchronization
// private int _counter; // Possible race conditions
}
// SCOPED: one instance per HTTP request (or scope)
public class ScopedService
{
private readonly Guid _id = Guid.NewGuid();
public Guid Id => _id;
// Safe: each request has its own instance
// Ideal for DbContext, UnitOfWork
}
// TRANSIENT: new instance on every injection
public class TransientService
{
private readonly Guid _id = Guid.NewGuid();
public Guid Id => _id;
// Ideal for lightweight, stateless services
}
// Configuration in Program.cs
public static class ServiceConfiguration
{
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<SingletonService>();
services.AddScoped<ScopedService>();
services.AddTransient<TransientService>();
// Entity Framework: ALWAYS Scoped
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
// HttpClient: use IHttpClientFactory
services.AddHttpClient<IApiClient, ApiClient>();
}
}
// CAPTIVE DEPENDENCY: Singleton depending on Scoped
public class BadSingletonService
{
// WARNING: ScopedService will be captured and reused indefinitely
// Causes concurrency bugs and stale data
private readonly ScopedService _scoped;
public BadSingletonService(ScopedService scoped)
{
_scoped = scoped;
}
}
// SOLUTION: use IServiceScopeFactory
public class GoodSingletonService
{
private readonly IServiceScopeFactory _scopeFactory;
public GoodSingletonService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task DoWork()
{
// Create explicit scope to get fresh ScopedService
using var scope = _scopeFactory.CreateScope();
var scoped = scope.ServiceProvider.GetRequiredService<ScopedService>();
// Use scoped...
}
}Quy tắc: một service không bao giờ nên phụ thuộc vào service có vòng đời ngắn hơn. Singleton -> Scoped -> Transient.
Câu 9: Các design pattern chính trong .NET là gì?
Nhà tuyển dụng mong đợi kiến thức thực tế về pattern, không chỉ là định nghĩa.
// Common patterns in C#/.NET
// REPOSITORY: data access abstraction
public interface IUserRepository
{
Task<User?> GetByIdAsync(int id);
Task<IEnumerable<User>> GetAllAsync();
Task AddAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int id);
}
public class UserRepository : IUserRepository
{
private readonly AppDbContext _context;
public UserRepository(AppDbContext context) => _context = context;
public async Task<User?> GetByIdAsync(int id)
=> await _context.Users.FindAsync(id);
public async Task<IEnumerable<User>> GetAllAsync()
=> await _context.Users.ToListAsync();
public async Task AddAsync(User user)
=> await _context.Users.AddAsync(user);
public async Task UpdateAsync(User user)
=> _context.Users.Update(user);
public async Task DeleteAsync(int id)
{
var user = await GetByIdAsync(id);
if (user != null) _context.Users.Remove(user);
}
}
// UNIT OF WORK: transaction coordination
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
IOrderRepository Orders { get; }
Task<int> SaveChangesAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
public UnitOfWork(AppDbContext context)
{
_context = context;
Users = new UserRepository(context);
Orders = new OrderRepository(context);
}
public IUserRepository Users { get; }
public IOrderRepository Orders { get; }
public async Task<int> SaveChangesAsync()
=> await _context.SaveChangesAsync();
public void Dispose() => _context.Dispose();
}
// FACTORY: complex object creation
public interface INotificationFactory
{
INotification Create(NotificationType type);
}
public class NotificationFactory : INotificationFactory
{
public INotification Create(NotificationType type) => type switch
{
NotificationType.Email => new EmailNotification(),
NotificationType.Sms => new SmsNotification(),
NotificationType.Push => new PushNotification(),
_ => throw new ArgumentException($"Unknown type: {type}")
};
}
// DECORATOR: adding behaviors dynamically
public interface IUserService
{
Task<User> GetUserAsync(int id);
}
public class UserService : IUserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository) => _repository = repository;
public async Task<User> GetUserAsync(int id)
=> await _repository.GetByIdAsync(id)
?? throw new NotFoundException($"User {id} not found");
}
// Decorator that adds caching
public class CachedUserService : IUserService
{
private readonly IUserService _inner;
private readonly IMemoryCache _cache;
public CachedUserService(IUserService inner, IMemoryCache cache)
{
_inner = inner;
_cache = cache;
}
public async Task<User> GetUserAsync(int id)
{
var cacheKey = $"user:{id}";
if (_cache.TryGetValue(cacheKey, out User? cached))
return cached!;
var user = await _inner.GetUserAsync(id);
_cache.Set(cacheKey, user, TimeSpan.FromMinutes(5));
return user;
}
}Các pattern này được sử dụng hàng ngày trong các ứng dụng .NET chuyên nghiệp. Pattern Repository kết hợp với Unit of Work đặc biệt phổ biến với Entity Framework.
Entity Framework Core
Câu 10: Làm thế nào để tối ưu hiệu suất với EF Core?
EF Core có thể rất nhanh hoặc rất chậm tùy thuộc vào cách sử dụng. Câu hỏi này đánh giá kiến thức về các phương pháp tốt nhất.
// Query optimization techniques
public class EFCorePerformance
{
private readonly AppDbContext _context;
// N+1 problem: one query per order
public async Task<List<User>> GetUsersWithOrdersBad()
{
var users = await _context.Users.ToListAsync();
foreach (var user in users)
{
// N additional queries!
var orders = await _context.Orders
.Where(o => o.UserId == user.Id)
.ToListAsync();
}
return users;
}
// Eager Loading with Include
public async Task<List<User>> GetUsersWithOrdersGood()
{
return await _context.Users
.Include(u => u.Orders) // SQL JOIN
.ThenInclude(o => o.Products) // Nested include
.ToListAsync();
}
// Projection to load only necessary data
public async Task<List<UserDto>> GetUserSummaries()
{
return await _context.Users
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
OrderCount = u.Orders.Count, // Calculated SQL-side
TotalSpent = u.Orders.Sum(o => o.Total)
})
.ToListAsync();
}
// Split Query for large collections
public async Task<List<User>> GetUsersWithSplitQuery()
{
return await _context.Users
.Include(u => u.Orders)
.AsSplitQuery() // Generates separate queries instead of large JOIN
.ToListAsync();
}
// No Tracking for read-only operations
public async Task<List<User>> GetUsersReadOnly()
{
return await _context.Users
.AsNoTracking() // No change tracking = faster
.ToListAsync();
}
// Batch operations (EF Core 7+)
public async Task DeleteInactiveUsers()
{
// Single DELETE query instead of load then delete
await _context.Users
.Where(u => !u.IsActive && u.LastLoginAt < DateTime.UtcNow.AddYears(-1))
.ExecuteDeleteAsync();
}
// Bulk update
public async Task DeactivateOldUsers()
{
await _context.Users
.Where(u => u.LastLoginAt < DateTime.UtcNow.AddMonths(-6))
.ExecuteUpdateAsync(u => u.SetProperty(x => x.IsActive, false));
}
// Compiled Queries for frequent queries
private static readonly Func<AppDbContext, int, Task<User?>> GetUserById =
EF.CompileAsyncQuery((AppDbContext ctx, int id) =>
ctx.Users.FirstOrDefault(u => u.Id == id));
public async Task<User?> GetUserOptimized(int id)
{
return await GetUserById(_context, id);
}
}Bật ghi log SQL trong môi trường phát triển với optionsBuilder.LogTo(Console.WriteLine) để xác định các truy vấn có vấn đề. Trong môi trường production, sử dụng các công cụ như MiniProfiler hoặc Application Insights.
Câu 11: Giải thích migration và quản lý schema
Quản lý migration là yếu tố then chốt cho việc triển khai production.
// Professional EF Core migration management
// DbContext configuration with conventions
public class AppDbContext : DbContext
{
public DbSet<User> Users => Set<User>();
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Apply all IEntityTypeConfiguration configurations
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
// Global convention for dates
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(DateTime))
{
property.SetColumnType("datetime2");
}
}
}
}
}
// Separate fluent configuration
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("Users");
builder.HasKey(u => u.Id);
builder.Property(u => u.Email)
.IsRequired()
.HasMaxLength(256);
builder.HasIndex(u => u.Email)
.IsUnique();
builder.HasMany(u => u.Orders)
.WithOne(o => o.User)
.HasForeignKey(o => o.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
// Data seeding
public class DataSeeder
{
public static void Seed(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>().HasData(
new Role { Id = 1, Name = "Admin" },
new Role { Id = 2, Name = "User" }
);
}
}Các lệnh migration thiết yếu:
dotnet ef migrations add MigrationName- Tạo migrationdotnet ef database update- Áp dụng migrationdotnet ef migrations script- Tạo script SQLdotnet ef migrations remove- Xóa migration cuối cùng
ASP.NET Core
Câu 12: Giải thích pipeline middleware của ASP.NET Core
Pipeline middleware là trái tim của ASP.NET Core. Hiểu cách nó hoạt động là điều thiết yếu.
// Request pipeline architecture
// Custom Middleware - full class
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// BEFORE: executed on the way in (request)
var stopwatch = Stopwatch.StartNew();
_logger.LogInformation("Request: {Method} {Path}",
context.Request.Method,
context.Request.Path);
try
{
// Pass to next middleware
await _next(context);
}
finally
{
// AFTER: executed on the way out (response)
stopwatch.Stop();
_logger.LogInformation("Response: {StatusCode} in {ElapsedMs}ms",
context.Response.StatusCode,
stopwatch.ElapsedMilliseconds);
}
}
}
// Extension for registration
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder app)
{
return app.UseMiddleware<RequestLoggingMiddleware>();
}
}
// Pipeline configuration in Program.cs
public class Startup
{
public void Configure(IApplicationBuilder app)
{
// ORDER is CRITICAL!
// 1. Exception handling (must be first)
app.UseExceptionHandler("/error");
// 2. HTTPS Redirection
app.UseHttpsRedirection();
// 3. Static files (short-circuits if found)
app.UseStaticFiles();
// 4. Routing (determines endpoint)
app.UseRouting();
// 5. CORS (must be between Routing and Auth)
app.UseCors();
// 6. Authentication (who are you?)
app.UseAuthentication();
// 7. Authorization (are you allowed?)
app.UseAuthorization();
// 8. Custom middleware
app.UseRequestLogging();
// 9. Endpoints (executes controller/action)
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
}
// Conditional middleware
public static class ConditionalMiddleware
{
public static IApplicationBuilder UseWhen(
this IApplicationBuilder app,
Func<HttpContext, bool> predicate,
Action<IApplicationBuilder> configuration)
{
// Conditional branch of the pipeline
return app.UseWhen(predicate, configuration);
}
public static void Example(IApplicationBuilder app)
{
// Apply middleware only for /api/*
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/api"),
apiApp => apiApp.UseMiddleware<ApiRateLimitingMiddleware>()
);
}
}Middleware thực thi theo thứ tự đăng ký khi đi vào (request) và theo thứ tự ngược lại khi đi ra (response).
Câu 13: Làm thế nào để triển khai xác thực JWT?
Xác thực JWT là tiêu chuẩn cho các REST API hiện đại.
// Complete JWT authentication configuration
public static class JwtConfiguration
{
public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration config)
{
var jwtSettings = config.GetSection("Jwt").Get<JwtSettings>()!;
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.Issuer,
ValidAudience = jwtSettings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),
ClockSkew = TimeSpan.Zero // No tolerance on expiration
};
// Events for logging/debugging
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception is SecurityTokenExpiredException)
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
}
}
public class JwtSettings
{
public string SecretKey { get; set; } = string.Empty;
public string Issuer { get; set; } = string.Empty;
public string Audience { get; set; } = string.Empty;
public int ExpirationMinutes { get; set; } = 60;
}
// Token generation service
public class TokenService
{
private readonly JwtSettings _settings;
public TokenService(IOptions<JwtSettings> settings)
{
_settings = settings.Value;
}
public string GenerateToken(User user, IEnumerable<string> roles)
{
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_settings.SecretKey));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new(JwtRegisteredClaimNames.Email, user.Email),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new("name", user.Name)
};
// Add roles as claims
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var token = new JwtSecurityToken(
issuer: _settings.Issuer,
audience: _settings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_settings.ExpirationMinutes),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public ClaimsPrincipal? ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_settings.SecretKey);
try
{
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _settings.Issuer,
ValidateAudience = true,
ValidAudience = _settings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}, out _);
return principal;
}
catch
{
return null;
}
}
}
// Usage in a controller
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly TokenService _tokenService;
private readonly IUserService _userService;
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginDto dto)
{
var user = await _userService.ValidateCredentialsAsync(dto.Email, dto.Password);
if (user == null)
return Unauthorized(new { message = "Invalid credentials" });
var roles = await _userService.GetRolesAsync(user.Id);
var token = _tokenService.GenerateToken(user, roles);
return Ok(new { token, expiresIn = 3600 });
}
[Authorize] // Requires valid token
[HttpGet("profile")]
public IActionResult GetProfile()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return Ok(new { userId });
}
[Authorize(Roles = "Admin")] // Requires Admin role
[HttpGet("admin")]
public IActionResult AdminOnly()
{
return Ok(new { message = "Welcome, Admin!" });
}
}Sẵn sàng chinh phục phỏng vấn .NET?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Câu Hỏi Nâng Cao
Câu 14: Span<T> và Memory<T> là gì?
Các kiểu dữ liệu này cho phép thao tác bộ nhớ không cần cấp phát, thiết yếu cho mã hiệu suất cao.
// Types for performant memory manipulation
public class HighPerformanceDemo
{
// Span`<T>`: view over contiguous memory region (stack only)
public static void SpanBasics()
{
// Span over an array
int[] numbers = { 1, 2, 3, 4, 5 };
Span<int> span = numbers.AsSpan();
// Slice without allocation
Span<int> slice = span.Slice(1, 3); // [2, 3, 4]
// Modification affects original array
slice[0] = 100;
Console.WriteLine(numbers[1]); // 100
// Span on the stack (stackalloc)
Span<int> stackSpan = stackalloc int[100];
stackSpan.Fill(42);
}
// Parsing without allocation using Span
public static bool TryParseDate(ReadOnlySpan<char> input, out DateTime date)
{
// Format: "2024-01-15"
date = default;
if (input.Length != 10) return false;
// Slicing without creating new strings
var yearSpan = input.Slice(0, 4);
var monthSpan = input.Slice(5, 2);
var daySpan = input.Slice(8, 2);
if (!int.TryParse(yearSpan, out int year)) return false;
if (!int.TryParse(monthSpan, out int month)) return false;
if (!int.TryParse(daySpan, out int day)) return false;
date = new DateTime(year, month, day);
return true;
}
// Memory`<T>`: like Span but can be stored on the heap
public async Task<int> ProcessDataAsync(Memory<byte> buffer)
{
// Memory can cross async boundaries
await Task.Delay(100);
// Convert to Span for processing
Span<byte> span = buffer.Span;
int sum = 0;
foreach (var b in span)
{
sum += b;
}
return sum;
}
// ArrayPool: array reuse to avoid allocations
public static void UseArrayPool()
{
// Rent an array from the pool
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024);
try
{
// Use the buffer...
// Note: may be larger than requested
Console.WriteLine($"Buffer size: {buffer.Length}");
}
finally
{
// ALWAYS return to pool
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
}
}
// Comparative benchmark
public static string SubstringTraditional(string input, int start, int length)
{
// Creates new string = allocation
return input.Substring(start, length);
}
public static ReadOnlySpan<char> SubstringWithSpan(ReadOnlySpan<char> input, int start, int length)
{
// Returns a view = NO allocation
return input.Slice(start, length);
}
}Span<T> lý tưởng cho xử lý chuỗi, phân tích cú pháp và thao tác mảng mà không cần cấp phát bộ nhớ.
Câu 15: Giải thích records và các trường hợp sử dụng
Records (C# 9+) là kiểu tham chiếu bất biến với tính bằng giá trị, hoàn hảo cho DTOs và value objects.
// Features and use cases for records
// Record class (reference, immutable by default)
public record Person(string FirstName, string LastName, DateOnly BirthDate)
{
// Computed property
public int Age => DateTime.Today.Year - BirthDate.Year;
// Additional method
public string FullName => $"{FirstName} {LastName}";
}
// Record with validation
public record Email
{
public string Value { get; }
public Email(string value)
{
if (!IsValidEmail(value))
throw new ArgumentException("Invalid email format");
Value = value;
}
private static bool IsValidEmail(string email)
=> !string.IsNullOrEmpty(email) && email.Contains('@');
}
// Record struct (value, C# 10+)
public readonly record struct Point(double X, double Y)
{
public double Distance => Math.Sqrt(X * X + Y * Y);
}
public class RecordUsageDemo
{
public void DemonstrateFeatures()
{
// Creation
var person1 = new Person("John", "Doe", new DateOnly(1990, 5, 15));
// Value-based equality (not reference)
var person2 = new Person("John", "Doe", new DateOnly(1990, 5, 15));
Console.WriteLine(person1 == person2); // True
// Mutation with 'with' (creates a copy)
var person3 = person1 with { LastName = "Smith" };
Console.WriteLine(person1.LastName); // "Doe" (unchanged)
Console.WriteLine(person3.LastName); // "Smith"
// Deconstruction
var (firstName, lastName, _) = person1;
Console.WriteLine($"{firstName} {lastName}");
// Auto-generated ToString()
Console.WriteLine(person1);
// Output: Person { FirstName = John, LastName = Doe, BirthDate = 15/05/1990 }
}
// Records as DTOs (data transfer)
public record CreateUserRequest(string Email, string Password, string Name);
public record UserResponse(int Id, string Email, string Name, DateTime CreatedAt);
// Records as Value Objects (DDD)
public record Money(decimal Amount, string Currency)
{
public static Money operator +(Money a, Money b)
{
if (a.Currency != b.Currency)
throw new InvalidOperationException("Currency mismatch");
return new Money(a.Amount + b.Amount, a.Currency);
}
}
// Record with inheritance
public abstract record Shape(string Color);
public record Circle(string Color, double Radius) : Shape(Color);
public record Rectangle(string Color, double Width, double Height) : Shape(Color);
}Records lý tưởng cho: DTOs, Value Objects, cấu hình bất biến và bất kỳ đối tượng nào mà danh tính dựa trên giá trị thay vì tham chiếu.
Câu 16: Làm thế nào để triển khai hệ thống cache phân tán?
Cache là yếu tố thiết yếu cho hiệu suất ứng dụng quy mô lớn.
// Cache implementation with Redis
public interface ICacheService
{
Task<T?> GetAsync<T>(string key);
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
Task RemoveAsync(string key);
Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
}
public class RedisCacheService : ICacheService
{
private readonly IDistributedCache _cache;
private readonly JsonSerializerOptions _jsonOptions;
public RedisCacheService(IDistributedCache cache)
{
_cache = cache;
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
}
public async Task<T?> GetAsync<T>(string key)
{
var data = await _cache.GetStringAsync(key);
if (string.IsNullOrEmpty(data))
return default;
return JsonSerializer.Deserialize<T>(data, _jsonOptions);
}
public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
{
var options = new DistributedCacheEntryOptions();
if (expiration.HasValue)
{
options.AbsoluteExpirationRelativeToNow = expiration;
}
else
{
options.SlidingExpiration = TimeSpan.FromMinutes(10);
}
var json = JsonSerializer.Serialize(value, _jsonOptions);
await _cache.SetStringAsync(key, json, options);
}
public async Task RemoveAsync(string key)
{
await _cache.RemoveAsync(key);
}
// Cache-Aside pattern with factory
public async Task<T> GetOrSetAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? expiration = null)
{
var cached = await GetAsync<T>(key);
if (cached != null)
return cached;
var value = await factory();
await SetAsync(key, value, expiration);
return value;
}
}
// Usage in a service
public class ProductService
{
private readonly ICacheService _cache;
private readonly IProductRepository _repository;
public ProductService(ICacheService cache, IProductRepository repository)
{
_cache = cache;
_repository = repository;
}
public async Task<Product?> GetProductAsync(int id)
{
var cacheKey = $"product:{id}";
return await _cache.GetOrSetAsync(
cacheKey,
async () => await _repository.GetByIdAsync(id),
TimeSpan.FromMinutes(30)
);
}
// Cache invalidation
public async Task UpdateProductAsync(int id, UpdateProductDto dto)
{
await _repository.UpdateAsync(id, dto);
// Invalidate cache
await _cache.RemoveAsync($"product:{id}");
}
}
// Configuration in Program.cs
public static class CacheConfiguration
{
public static void AddCaching(this IServiceCollection services, IConfiguration config)
{
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = config.GetConnectionString("Redis");
options.InstanceName = "MyApp:";
});
services.AddSingleton<ICacheService, RedisCacheService>();
}
}"Chỉ có hai điều khó trong Khoa học Máy tính: vô hiệu hóa cache và đặt tên." Xác định chiến lược vô hiệu hóa cache rõ ràng là điều thiết yếu để tránh dữ liệu cũ.
Câu 17: Làm thế nào để xử lý giao dịch phân tán?
Trong kiến trúc microservices, giao dịch phân tán đòi hỏi các pattern cụ thể.
// Patterns for consistency in distributed systems
// SAGA Pattern with Orchestration
public class OrderSaga
{
private readonly IOrderRepository _orderRepository;
private readonly IPaymentService _paymentService;
private readonly IInventoryService _inventoryService;
private readonly INotificationService _notificationService;
public async Task<OrderResult> ProcessOrderAsync(CreateOrderCommand command)
{
Order? order = null;
PaymentResult? payment = null;
InventoryReservation? reservation = null;
try
{
// Step 1: Create order
order = await _orderRepository.CreateAsync(command);
// Step 2: Reserve inventory
reservation = await _inventoryService.ReserveAsync(order.Items);
// Step 3: Process payment
payment = await _paymentService.ProcessAsync(order.Total, command.PaymentMethod);
// Step 4: Confirm order
await _orderRepository.ConfirmAsync(order.Id);
// Step 5: Notification (non-critical)
await _notificationService.SendOrderConfirmationAsync(order);
return OrderResult.Success(order.Id);
}
catch (Exception ex)
{
// COMPENSATION: undo previous steps in reverse order
if (payment?.IsSuccessful == true)
{
await _paymentService.RefundAsync(payment.TransactionId);
}
if (reservation != null)
{
await _inventoryService.ReleaseReservationAsync(reservation.Id);
}
if (order != null)
{
await _orderRepository.CancelAsync(order.Id, ex.Message);
}
return OrderResult.Failure(ex.Message);
}
}
}
// Outbox Pattern for reliable event publishing
public class OutboxProcessor
{
private readonly AppDbContext _context;
private readonly IMessageBus _messageBus;
public async Task ProcessOutboxAsync()
{
var pendingMessages = await _context.OutboxMessages
.Where(m => m.ProcessedAt == null)
.OrderBy(m => m.CreatedAt)
.Take(100)
.ToListAsync();
foreach (var message in pendingMessages)
{
try
{
// Publish message
await _messageBus.PublishAsync(message.Type, message.Payload);
// Mark as processed
message.ProcessedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
message.RetryCount++;
message.Error = ex.Message;
await _context.SaveChangesAsync();
}
}
}
}
// Outbox model
public class OutboxMessage
{
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty;
public string Payload { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime? ProcessedAt { get; set; }
public int RetryCount { get; set; }
public string? Error { get; set; }
}
// Extension to add outbox message within a transaction
public static class DbContextExtensions
{
public static void AddOutboxMessage<T>(this AppDbContext context, T @event)
{
var message = new OutboxMessage
{
Id = Guid.NewGuid(),
Type = typeof(T).Name,
Payload = JsonSerializer.Serialize(@event),
CreatedAt = DateTime.UtcNow
};
context.OutboxMessages.Add(message);
}
}Pattern SAGA đảm bảo tính nhất quán cuối cùng trong hệ thống phân tán. Pattern Outbox đảm bảo việc phát hành sự kiện đáng tin cậy ngay cả khi xảy ra lỗi.
Kết Luận
Các buổi phỏng vấn C# và .NET đánh giá sự kết hợp giữa kiến thức lý thuyết về runtime và ngôn ngữ, cùng với kỹ năng thực tế trong kiến trúc và phát triển ứng dụng. Thông thạo các khái niệm cơ bản đồng thời hiểu các pattern nâng cao là yếu tố phân biệt các lập trình viên senior.
Danh Sách Chuẩn Bị
- Hiểu sự khác biệt giữa value types và reference types
- Thông thạo async/await và tránh deadlock
- Nắm vững sự khác nhau giữa IEnumerable và IQueryable
- Tối ưu hóa truy vấn Entity Framework Core
- Triển khai pattern IDisposable đúng cách
- Cấu hình dependency injection với các vòng đời phù hợp
- Bảo mật API với JWT
- Sử dụng Span
<T>và Memory<T>cho mã hiệu suất cao
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Việc chuẩn bị nên kết hợp lý thuyết và thực hành. Xây dựng các dự án cá nhân, đóng góp vào hệ sinh thái mã nguồn mở .NET và giải bài tập trên các nền tảng như HackerRank hoặc LeetCode sẽ củng cố kiến thức này cho những buổi phỏng vấn khắt khe nhất.
Thẻ
Chia sẻ
Bài viết liên quan

.NET 8: Xây dựng API với ASP.NET Core
Hướng dẫn đầy đủ về xây dựng REST API chuyên nghiệp với .NET 8 và ASP.NET Core. Controller, Entity Framework Core, validation và các phương pháp tốt nhất được giải thích chi tiết.

Cau Hoi Phong Van Laravel va PHP: Top 25 Nam 2026
25 cau hoi phong van Laravel va PHP thuong gap nhat. Service Container, Eloquent ORM, middleware, queues va trien khai production voi dap an chi tiet kem code mau.

Cau hoi phong van Django va Python: Top 25 nam 2026
25 cau hoi phong van Django va Python thuong gap nhat. ORM, views, middleware, DRF, signals va toi uu hoa voi dap an chi tiet va vi du code.