Thắc mắc về viết RESTful API

Hiện em đang viết một cái API theo hướng dẫn của một trang web. Khi test API trên swagger UI em có gặp phải một lỗi như ở dưới đây. Mn giúp em với. Em cảm ơn ạ.

CategoriesController.cs

using Microsoft.AspNetCore.Mvc;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Services;

namespace Supermarket.API.Controllers
{

    [Route("/api/[controller]")]
    public class CategoriesController : Controller
    {
        private readonly ICategoryService? _categoryService;   //a private, read-only field to access the methods of our category service implementation
        public CategoriesController(ICategoryService categoryService)
        {
            _categoryService = categoryService;     //receives an instance of ICategoryService
        }
        // Create a category service to return all categories
        [HttpGet]       // tells the ASP.NET Core pipeline to use it to handle GET requests
        public async Task<IEnumerable<Category>> GetAllAsync(ICategoryService _categoryService)
        {
            var categories = await _categoryService.ListAsync();
            return categories;
        }
    }
}

Category.cs

namespace Supermarket.API.Domain.Models
{

    public class Category
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public IList<Product>? Products { get; set; } = new List<Product>();
    }
}

EUnitOfMeasurement.cs

using System.ComponentModel;
namespace Supermarket.API.Domain.Models
{

    public enum EUnitOfMeasurement : byte
    {
        [Description("UN")]
        Unity = 1,

        [Description("MG")]
        Milligram = 2,

        [Description("G")]
        Gram = 3,

        [Description("KG")]
        Kilogram = 4,

        [Description("L")]
        Liter = 5
    }
}

Product.cs

namespace Supermarket.API.Domain.Models
{

    public class Product
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public short QuantityInPackage { get; set; }
        public EUnitOfMeasurement UnitOfMeasurement { get; set; }

        public int CategoryId { get; set; }
        public Category? Category { get; set; }
    }
}

AppDbContext.cs

using Microsoft.EntityFrameworkCore;
using Supermarket.API.Domain.Models;
namespace Supermarket.API.Domain.Persistence.Contexts
{

    // DbContext is a class EF Core uses to map your models to database tables
    // implement the database context class first
    public class AppDbContext : DbContext
    {
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }
        // constructor passes the database configuration to the base class through dependency injection
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

        // OnModelCreating map the models’ properties to the respective table columns 
        // specifying which properties are primary keys, which are foreign keys, the column types, etc
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<Category>().ToTable("Categories");
            builder.Entity<Category>().HasKey(p => p.Id);   // Set the primary key
            builder.Entity<Category>().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd();
            builder.Entity<Category>().Property(p => p.Name).IsRequired().HasMaxLength(30);
            builder.Entity<Category>().HasMany(p => p.Products).WithOne(p => p.Category).HasForeignKey(p => p.CategoryId);

            // add two example categories by default
            builder.Entity<Category>().HasData
            (
                new Category { Id = 100, Name = "Fruits and Vegetables" }, // Id set manually due to in-memory provider
                new Category { Id = 101, Name = "Dairy" }
            );

            builder.Entity<Product>().ToTable("Products");
            builder.Entity<Product>().HasKey(p => p.Id);
            builder.Entity<Product>().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd();
            builder.Entity<Product>().Property(p => p.Name).IsRequired().HasMaxLength(50);
            builder.Entity<Product>().Property(p => p.QuantityInPackage).IsRequired();
            builder.Entity<Product>().Property(p => p.UnitOfMeasurement).IsRequired();
        }
    }
}

BaseRepository.cs

using Supermarket.API.Domain.Persistence.Contexts;
namespace Supermarket.API.Domain.Persistence.Repositories
{

    public abstract class BaseRepository
    {
        protected readonly AppDbContext _context;
        //  receives an instance of our AppDbContext through dependency injection and exposes a protected property
        // _context gives access to all methods we need to handle database operations.
        public BaseRepository(AppDbContext context)
        {
            _context = context;
        }
    }
}

CategoryRepository.cs

using Microsoft.EntityFrameworkCore;
using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Persistence.Contexts;
using Supermarket.API.Domain.Repositories;

namespace Supermarket.API.Domain.Persistence.Repositories
{

    // The repository inherits the BaseRepository and implements ICategoryRepository.
    public class CategoryRepository : BaseRepository, ICategoryRepository
    {
        public CategoryRepository(AppDbContext context) : base(context)
        {

        }

        public async Task<IEnumerable<Category>> ListAsync()
        {
            return await _context.Categories.ToListAsync();
            // ToListAsync transforms the result of a query into a collection of categories.
        }
    }
}

ICategoryRepository.cs

using Supermarket.API.Domain.Models;
namespace Supermarket.API.Domain.Repositories
{

    public interface ICategoryRepository
    {
        Task<IEnumerable<Category>> ListAsync();
    }
}

ICategoryService.cs

using Supermarket.API.Domain.Models;

namespace Supermarket.API.Domain.Services
{

    // defines methods to handle some business logic
    // isolate the request and response handling from the real logic needed to complete tasks
    // this method returns all existing categories in the database.
    public interface ICategoryService
    {
        Task<IEnumerable<Category>> ListAsync();
         // wait for the database to complete some operation to return the data 
    }
}

CategoryService.cs

using Supermarket.API.Domain.Models;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Domain.Services;

namespace Supermarket.API.Services
{

    public class CategoryService : ICategoryService
    {
        private readonly ICategoryRepository _categoryRepository;
        public CategoryService(ICategoryRepository categoryRepository)
        {
            _categoryRepository = categoryRepository;
        }
        //  using an instance of ICategoryRepository to return the data.
        public async Task<IEnumerable<Category>> ListAsync()
        {
            return await _categoryRepository.ListAsync();
        }
    }
}

Program.cs

using Microsoft.EntityFrameworkCore;
using Supermarket.API.Domain.Persistence.Contexts;
using Supermarket.API.Domain.Services;
using Supermarket.API.Services;
using Supermarket.API.Domain.Repositories;
using Supermarket.API.Domain.Persistence.Repositories;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseSqlServer(builder.Configuration.GetConnectionString("Connect")));
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
builder.Services.AddScoped<ICategoryService, CategoryService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Bạn không thấy chỗ này vô lý sao khi mà class đã có _categoryService rồi lại truyền thêm _categoryService vào.
Bạn xem lại bài hướng dẫn đoạn này viết như thế nào, mình nghĩ là không có ICategoryService _categoryService đâu.

Còn chi tiết lỗi của bạn, trên stackoverflow đã giải thích

3 Likes

Anh có thể cho em hỏi thêm là tại sao lifetime DI của lệnh AddDbContext nó luôn mặc định là scoped không ạ?

Từ document của AddDbContext

AddDbContext(IServiceCollection, Action<IServiceProvider,DbContextOptionsBuilder>, ServiceLifetime, ServiceLifetime)

Ví dụ, code của bạn là

 services.AddDbContext<ApplicationDbContext>(
        options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));

Tương ứng

parameter element Type
serviceCollection services IServiceCollection
optionsAction options => options.UseSqlServer(“name=ConnectionStrings:DefaultConnection”) Action<IServiceProvider,DbContextOptionsBuilder>
contextLifetime - ServiceLifetime
optionsLifetime - ServiceLifetime

contextLifetime và optionsLifetime bạn không truyền vào, nên nó có giá trị default của ServiceLifetime là ServiceLifetime .Scoped.

Nghĩa là bạn có thể thay đổi lifetime bằng cách thêm parameter vào method AddDbContext

1 Like
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?