View Component là gì?

View Components là một tính năng mới trong ASP.NET Core MVC, tương tự như partial views nhưng mạnh hơn nhiều. Chúng đặc biệt hữu ích cho những phần giao diện có logic phức tạp, như:
  • Navigation menus
  • Shopping cart sidebar
  • Login panel
  • Recent posts widget
  • Admin dashboards
Lưu ý: View Components hoàn toàn tương thích với Razor Pages và có thể được sử dụng ở bất kỳ đâu bạn cần logic phức tạp kết hợp với rendering.

View Component vs Partial View

Tiêu chíPartial ViewView Component
LogicEmbedded hoặc qua ViewBagTách riêng trong class
RenderingSynchronousSynchronous hoặc Asynchronous
Model bindingVia weakly-typed ViewDataStrongly-typed parameters
TestabilityKhó unit testDễ unit test
Controller requiredKhôngKhông
Dependency InjectionKhông (trừ @inject)Đầy đủ DI support
Use caseSimple renderingComplex business logic + rendering

Tạo View Component

Bước 1: Tạo View Component Class

Tạo class kế thừa từ ViewComponent:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentsSample.Models;

namespace ViewComponentsSample.Views.Components
{
    // View Component class
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        // InvokeAsync - main entry point
        public async Task<IViewComponentResult> InvokeAsync(
            int maxPriority,
            bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }

        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo
                .Where(x => x.IsDone == isDone && x.Priority <= maxPriority)
                .ToListAsync();
        }
    }
}

Bước 2: Tạo Razor View

Tạo view tại đường dẫn:
/Views/{ControllerName}/Components/{ViewComponentName}/{ViewName}.cshtml
/Views/Shared/Components/{ViewComponentName}/{ViewName}.cshtml
@model IEnumerable<ViewComponentsSample.Models.TodoItem>

<h3>Priority Items</h3>
<ul>
    @foreach (var item in Model)
    {
        <li>
            @item.Name
            @if (item.IsDone)
            {
                <span>✓</span>
            }
        </li>
    }
</ul>

Quy ước đặt tên

Class Name

Cách đặtVí dụ
Suffix ViewComponentPriorityListViewComponent
Hoặc attribute@[ViewComponent] PriorityList
// Cách 1: Suffix
public class PriorityListViewComponent : ViewComponent { }

// Cách 2: Attribute
[ViewComponent]
public class PriorityList
{
    public Task<IViewComponentResult> InvokeAsync() { /* ... */ }
}

View Name

Theo mặc định, View Component tìm view tên Default:
/Views/Shared/Components/PriorityList/Default.cshtml

Gọi View Component

Từ View

Tag Helper (Khuyến nghị)

@using ViewComponentsSample.Views.Components

@* Gọi không có tham số *@
@Component.Invoke("PriorityList")

@* Gọi với tham số *@
@await Component.InvokeAsync("PriorityList",
    new {
        maxPriority = 4,
        isDone = false
    })

Tag Helper (Razor Class Library)

@addTagHelper *, ViewComponentsSample

@* Tag Helper syntax *@
<vc:priority-list max-priority="4" is-done="false">
</vc:priority-list>

Từ Controller

public class HomeController : Controller
{
    public async Task<IActionResult> Index()
    {
        // Render View Component và trả về như partial view
        var result = await Component.InvokeAsync("PriorityList",
            new { maxPriority = 3, isDone = false });

        return View(result);
    }
}

Từ Razor Page

@page
@model IndexModel
@await Component.InvokeAsync("PriorityList",
    new { maxPriority = 4, isDone = false })

View Component với Strongly-Typed Data

Định nghĩa Model

public class TodoItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsDone { get; set; }
    public int Priority { get; set; }
}

View Component Class

public class TodoListViewComponent : ViewComponent
{
    private readonly ToDoContext _context;

    public TodoListViewComponent(ToDoContext context)
    {
        _context = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(bool showAll)
    {
        var items = showAll
            ? await _context.ToDo.ToListAsync()
            : await _context.ToDo.Where(x => !x.IsDone).ToListAsync();

        return View(items);
    }
}

Razor View với Strong Type

@model IEnumerable<TodoItem>
@* hoặc @model MyNamespace.TodoItemViewModel *@

<h2>Todo List</h2>
<table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Priority</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.Name</td>
                <td>@item.Priority</td>
                <td>@(item.IsDone ? "✓" : "○")</td>
            </tr>
        }
    </tbody>
</table>

View Component Synchronous

Nếu không cần async, có thể override phương thức Invoke:
public class FeaturedBooksViewComponent : ViewComponent
{
    private readonly IBookService _bookService;

    public FeaturedBooksViewComponent(IBookService bookService)
    {
        _bookService = bookService;
    }

    // Synchronous Invoke
    public IViewComponentResult Invoke(int count)
    {
        var books = _bookService.GetFeaturedBooks(count);
        return View(books);
    }
}

Các phương thức View Component

View Method

// Trả về view mặc định
return View(books);

// Trả về view cụ thể
return View("SpecialView", books);

// Trả về view với model tùy chỉnh
return View("~/Views/Shared/Components/FeaturedBooks/VIP.cshtml", books);

Content Method

// Trả về raw HTML content
return Content("<div class='alert'>Hello World</div>");

Other Methods

MethodMô tả
View(model)Trả về view với model
View(viewName, model)Trả về view cụ thể với model
Content(content)Trả về HTML content string
EmptyResult()Trả về empty response (204 No Content)

View Component Context

ViewContext

Truy cập HttpContext, RouteData, và các thông tin request:
public class UserPanelViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        var userName = User.Identity?.Name;
        var isAdmin = User.IsInRole("Admin");
        var currentUrl = ViewContext.HttpContext.Request.Path;

        var model = new UserPanelViewModel
        {
            UserName = userName,
            IsAdmin = isAdmin,
            CurrentUrl = currentUrl
        };

        return View(model);
    }
}

ViewBag trong View Component

public class NavigationViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        ViewBag.CurrentPage = ViewContext.RouteData.Values["action"];
        ViewBag.CurrentController = ViewContext.RouteData.Values["controller"];

        return View();
    }
}

View Component trong Razor Class Library

Cấu trúc thư mục

/Components
  /PriorityList
      Default.cshtml
  /ShoppingCart
      Default.cshtml

Đăng ký trong Program.cs

builder.Services.AddViewComponents();

Sử dụng

@addTagHelper *, MyLibrary.Components

<vc:priority-list max-priority="3"></vc:priority-list>
<vc:shopping-cart></vc:shopping-cart>

Dependency Injection trong View Component

Constructor Injection

public class WeatherViewComponent : ViewComponent
{
    private readonly IWeatherService _weatherService;

    // DI qua constructor
    public WeatherViewComponent(IWeatherService weatherService)
    {
        _weatherService = weatherService;
    }

    public async Task<IViewComponentResult> InvokeAsync(string location)
    {
        var forecast = await _weatherService.GetForecastAsync(location);
        return View(forecast);
    }
}

Đăng ký Service

builder.Services.AddScoped<IWeatherService, WeatherServiceImpl>();

Ví dụ: Shopping Cart View Component

ShoppingCartViewComponent.cs

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace WebApp.ViewComponents
{
    public class ShoppingCartViewComponent : ViewComponent
    {
        private readonly ICartService _cartService;

        public ShoppingCartViewComponent(ICartService cartService)
        {
            _cartService = cartService;
        }

        public IViewComponentResult Invoke()
        {
            var cart = _cartService.GetCart(HttpContext.User.Identity?.Name);

            var viewModel = new CartViewModel
            {
                Items = cart.Items,
                TotalItems = cart.Items.Sum(x => x.Quantity),
                TotalPrice = cart.Items.Sum(x => x.Price * x.Quantity)
            };

            return View(viewModel);
        }
    }

    public class CartViewModel
    {
        public List<CartItem> Items { get; set; }
        public int TotalItems { get; set; }
        public decimal TotalPrice { get; set; }
    }
}

Default.cshtml

@model CartViewModel

<div class="cart-panel">
    <h4>
        <span class="oi oi-cart"></span>
        Giỏ hàng (@Model.TotalItems)
    </h4>

    @if (!Model.Items.Any())
    {
        <p class="text-muted">Giỏ hàng trống</p>
    }
    else
    {
        <ul class="list-unstyled">
            @foreach (var item in Model.Items)
            {
                <li>
                    @item.ProductName × @item.Quantity
                    <span class="badge">@item.Price ₫</span>
                </li>
            }
        </ul>

        <hr />
        <strong>Tổng: @Model.TotalPrice ₫</strong>
    }
</div>

Sử dụng trong Layout

<!-- _Layout.cshtml -->
<header>
    <nav>...</nav>

    <div class="cart-summary">
        @await Component.InvokeAsync("ShoppingCart")
    </div>
</header>

So sánh: Các cách gọi View Component


Tài liệu tham khảo