ASP.NET Базовая, универсальная реализация контроллера на основе универсального интерфейса репозитория

#c# #asp.net-core #generics #interface #repository-pattern

#c# #asp.net-ядро #универсальные #интерфейс #репозиторий-шаблон

Вопрос:

У меня проблема с реализацией универсального контроллера для операций CRUD с моими объектами.

В настоящее время у меня есть следующие классы:

 public class BaseItem
    {
        public int? Id { get; set; }
    }
 

Этот класс является родительским для нескольких объектов:

 public class Customer : BaseItem
    {
        // some fields
    }

    public class Project : BaseItem
    {
        // some fields
    }
 

Затем есть интерфейс для общего репозитория:

     public interface IGenericItemRepository<TDomain>
                     where TDomain : BaseItem
    {
        // methods
    }
 

И на основе этого интерфейса созданы более конкретные интерфейсы:

     public interface ICustomerRepository : IGenericItemRepository<Customer> {}
 

Чтобы не решать EF сейчас, я использую фиктивные классы. Они выполняются следующим образом:

 public abstract class GenericMockRepository<TDomain> : IGenericItemRepository<TDomain> where TDomain : BaseItem
...
 

и он реализует всю логику для базового CRUD.
Конкретный макет репозитория выполняется следующим образом:

     public class MockCustomerRepository : GenericMockRepository<Customer>, ICustomerRepository
    {
        public MockCustomerRepository() : base()
        {
            CreateCustomersList();
        }

        private void CreateCustomersList()
        {
            // method body
        }
    }
 

Таким образом, в целом я мог бы использовать свою существующую настройку для реализации контроллера таким образом:

 public class CustomersController : Controller
    {
        private ICustomerRepository _customerRepo;

        public CustomersController(ICustomerRepository customerRepo)
        {
            _customerRepo = customerRepo;
        }
    }
 

Затем я попытался реализовать универсальный контроллер:

     public interface IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>
        where TDomain           :   BaseItem
        where TDetailsViewModel :   BaseViewModel, new()
        where TEditViewModel    :   BaseViewModel, new()
        where TRepo             :   IGenericItemRepository<BaseItem>
    {
        [HttpGet] public IActionResult Edit(int? id);
        [HttpPost] public IActionResult Edit(TDomain entity);
        [HttpGet] public IActionResult Details(int? id);
    }

public class BaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo> : Controller, IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>
        where TDomain : BaseItem
        where TDetailsViewModel : BaseViewModel, new()
        where TEditViewModel : BaseViewModel, new()
        where TRepo : IGenericItemRepository<BaseItem>
    {
        private IGenericItemRepository<BaseItem> _repo;

        public BaseController(IGenericItemRepository<BaseItem> repo)
        {
            _repo = repo;
        }
    }
 

Но когда я пытаюсь изменить свой CustomerController на этот код ниже:

 public class CustomersController : BaseController<Customer, BaseViewModel, BaseViewModel, ICustomerRepository>
    {
        private ICustomerRepository _customerRepo;

        public CustomersController(ICustomerRepository customerRepo)
        {
            _customerRepo = customerRepo;
        }
    }
 

возникает следующая ошибка:

Ошибка CS0311 типа ‘DAL.Repositories.ICustomerRepository’ не может использоваться в качестве параметра типа ‘TRepo’ в универсальном типе или методе ‘BaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>’. Нет никакого неявного преобразования ссылок из ‘DAL.Repositories.ICustomerRepository’ to ‘DAL.Repositories.IGenericItemRepository<Домен.BaseItem>’.

Но мой ICustomerRepository — это тип IGenericItemRepository , не так ли?

Как я могу исправить ошибку и правильно реализовать универсальный контроллер, пожалуйста, чтобы я мог повторно использовать его в своих конкретных контроллерах? Я проверял похожие темы, но поскольку существует реализованный универсальный репозиторий по-другому, чем у меня, я не смог исправить это на основе примеров… Причина, по которой у меня есть дополнительные интерфейсы для конкретных репозиториев, заключается в том, что для каждого типа сущности будут более конкретные методы, поэтому я хотел бы определить их в конкретных интерфейсах (поэтому у меня нет просто чего-то вроде

 public interface IBaseRepository
{
  T Get<T>(int id);
  void Save<T>(T entity);
}
 

Большое спасибо за любую помощь заранее!

Комментарии:

1. Но мой ICustomerRepository тип IGenericItemRepository , не так ли? нет, посмотрите на ковариацию и контравариантность в общих чертах

Ответ №1:

Попробуйте IGenericItemRepository<TDomain> вместо IGenericItemRepository<BaseItem> этого в вашем BaseController и IBaseController .

 public class BaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo> : Controller, IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo> 
    where TDomain : BaseItem
    where TDetailsViewModel : BaseViewModel, new()
    where TEditViewModel : BaseViewModel, new()
    where TRepo : IGenericItemRepository<TDomain>
{
    private IGenericItemRepository<TDomain> _repo;

    public BaseController(IGenericItemRepository<TDomain> repo)
    {
        _repo = repo;
    }
}

 public interface IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>
    where TDomain           :   BaseItem
    where TDetailsViewModel :   BaseViewModel, new()
    where TEditViewModel    :   BaseViewModel, new()
    where TRepo             :   IGenericItemRepository<TDomain>
{
    [HttpGet] public IActionResult Edit(int? id);
    [HttpPost] public IActionResult Edit(TDomain entity);
    [HttpGet] public IActionResult Details(int? id);
}