.NET MVC 5 разделяется на разные проекты: избегайте циклических ссылок

#c# #asp.net-mvc-5

#c# #asp.net-mvc-5

Вопрос:

У меня есть решение, которое содержит только один проект (MVC 5 с идентификатором и EF). Контекст БД представляет собой ApplicationDbContext (подкласс IdentityDbContext, который сам по себе является подклассом EF DbContext). У меня также есть пользовательский атрибут проверки, который должен использовать контекст БД для выполнения своих задач:

 public class RequiredIfRol : ValidationAttribute, IClientValidatable {
    //...
    protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
        // ...
        string nomRol = "";
        //instance and use the ApplicationDbContext
        using (ApplicationDbContext db = new ApplicationDbContext()) {
            var roldb = db.Roles.Find(rolId);
            if (roldb == null) return ValidationResult.Success;
            nomRol = db.Roles.Find(rolId).Name;
        }
        // more code here
    }
}
 

Это работает нормально.

Теперь, прочитав об этом, я пытаюсь разделить этот проект MVC на несколько проектов:

  • библиотека классов для DAL (она будет содержать модели EF и ApplicationDbContext),
  • еще один для бизнес-логики,
  • еще один с общими вещами
  • а затем проект MVC.

Я новичок в этом, поскольку у меня всегда все было внутри одного и того же проекта MVC, поэтому я в замешательстве: я думаю, что атрибут должен находиться в общем проекте, чтобы на него можно было ссылаться из DAL (для украшения моделей), а также из MVC (для украшения viewmodels).

Поскольку на общий проект будут ссылаться все другие проекты, я думаю, я не могу ссылаться ни на один из этих проектов из общей (циклической ссылки?). и, поскольку ApplicationDbContext (который должен использовать атрибут) будет находиться в проекте DAL, у меня здесь проблема…

Я почти уверен, что я плохо проектирую это, но не могу найти правильный способ сделать это.. Любая помощь?


ДОБАВЛЕНО:

это то, что я пробовал:

1. — в общей библиотеке определен интерфейс:

 public interface IMyInterface {
    System.Data.Entity.IDbSet<CustomRole> Roles { get; set; }
}
 

2.- в ApplicationDbContext изменил его объявление, чтобы оно реализовывало интерфейс:

 public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>, IMyInterface {...}
 

3.- затем в атрибуте попытайтесь получить реализацию интерфейса, добавив свойство getter:

 private IMyInterface _dbcontext { get { return DependencyResolver.Current.GetService<IMyInterface>(); } }
 

а затем в методе isValid():

     var roldb = _dbcontext.Roles.Find(rolId);
    if (roldb == null) return ValidationResult.Success;
    nomRol = _dbcontext.Roles.Find(rolId).Name;
 

но это не сработало…

Ответ №1:

Одним из способов обойти это было бы создать абстракцию для вашего кода с использованием контекста — IRoleFinder и поместить в общий проект вместе с вашим RequiredIfRol . Затем реализуйте его на уровне бизнес-логики и внедряйте его в проект MVC. Таким образом, вы сможете отделить свой атрибут от контекста.

ValidationAttribute.IsValid() имеет ValidationContext параметр, который вы можете использовать для разрешения зависимости с помощью ValidationContext.GetService метода.

UPD

По вашему запросу в комментарии:

Для упрощения предположим, что у меня есть только 2 проекта (библиотека классов с атрибутом и проект MVC со всем остальным).

В библиотеке у вас будет что-то вроде этого:

 interface IRoleFinder
{
    CustomRole Find(int id);
}
 
 public class RequiredIfRol : ValidationAttribute, IClientValidatable {
    //...
    protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
       // ...
       var roleFinder = (IRoleFinder) validationContext.GetService(typeof(IRoleFinder));
    }
}
 

В вашем проекте MVC:

 public class RoleFinder : IRoleFinder
{
    public CustomRole Find(int id)
    {
        // use context here
    }
}
 

И при запуске (это для ASP.NET Ядро, для MVC 5 вы должны найти другой способ):

    services.AddTransient<IRoleFinder, RoleFinder>()
 

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

1. Это звучит многообещающе, но я не думаю, что я это полностью понимаю … не могли бы вы подробнее рассказать об этом с помощью какого-нибудь примера кода? (кстати, я забыл упомянуть, что я не использую какую-либо платформу DI) для упрощения, допустим, у меня есть только 2 проекта (библиотека классов с атрибутом и проект MVC со всем остальным). Я расширил вопрос, чтобы добавить то, что я пробовал…

2. @patsy2k, если вы используете стандартный ASP.NET Затем вы автоматически используете встроенный DI-контейнер.

3. @patsy2k там все еще должен быть доступен DI. Также вы можете сделать то же самое без DI, но с наследованием.

4. @patsy2k какую ошибку выдал вам ваш код? TBH Я работал с MVC 5 очень давно, и на моей машине он недоступен.

5. спасибо, я, наконец, заставил это работать, используя ваш ответ с некоторыми незначительными изменениями, а также установив Unity в проект в качестве DI framework.