Arhn - архитектура программирования

Как повторно использовать проверку метода действия среди нескольких классов контроллера

я работаю над веб-приложением asp.net mvc-5 с сущностью framework-6. и я сопоставил свои таблицы БД, используя структуру сущности, которая генерирует файл .edmx, содержащий класс DBContext. и в настоящее время у меня есть два класса контроллеров: один для управления серверами, а другой для управления vms. Когда я добавляю/редактирую сервер или виртуальные машины, я хочу проверить, существуют ли уже их IP- и mac-адреса. в настоящее время я провожу эти проверки самого метода действия следующим образом: -

public class Server : Controller{
    [HttpPost]
     [ValidateAntiForgeryToken]
     public ActionResult Create(ServerJoin sj)
            {
                bool ITipunique = repository.ISITIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
                bool ITmacunique = repository.ISITMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);
                bool Tipunique = repository.ISTIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
                bool Tmacunique = repository.ISTMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);

                try
                {

                    if ((sj.IsIPUnique == true) && (!ITipunique || !Tipunique))
                    {

                        ModelState.AddModelError("NetworkInfo2[0].IPAddress", "Error occurred. The Same IP is already assigned.");

                    }
                    if ((sj.IsMACUnique == true) && (!ITmacunique || !Tmacunique))
                    {

                        ModelState.AddModelError("NetworkInfo2[0].MACAddress", "Error occurred. The Same MAC Address is already assigned.");

                    }

&

public class VM : Controlelr {    
    [HttpPost]
    [ValidateAntiForgeryToken]

            public ActionResult Create(VMJoin vmj)
            {

                bool ITipunique = repository.ISITIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
                bool ITmacunique = repository.ISITMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);
                bool Tipunique = repository.ISTIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
                bool Tmacunique = repository.ISTMACUnique(vmj.NetworkInfo2.FirstOrDefault().MACADDRESS);
                try
                {
                    if ((vmj.IsIPUnique == true) && (!ITipunique || !Tipunique))
                    {

                        ModelState.AddModelError("NetworkInfo2[0].IPAddress", "Error occurred. The Same IP is already assigned.");

                    }
                    if ((vmj.IsMACUnique == true) && (!ITmacunique || !Tmacunique))
                    {

                        ModelState.AddModelError("NetworkInfo2[0].MACAddress", "Error occurred. The Same MAC Address is already assigned.");

                    }
                    if (!repository.IshypervisorServers(vmj.VirtualMachine.ServerID))
                    {
                        ModelState.AddModelError("VirtualMachine.ServerID", "Error occurred. Please select a valid hypervisor server. ");
                    }

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

ИЗМЕНИТЬ

ServerJoin выглядит следующим образом: -

public class ServerJoin : IValidatableObject
    {
        public Server Server { get; set; }
        public Resource Resource { get; set; }
        public Technology Technology { get; set; }
        public SDOrganization Site { get; set; }
        public SDOrganization Customer { get; set; }
        public NetworkInfo NetworkInfo { get; set; }
        public ICollection<NetworkInfo> NetworkInfo2 { get; set; }
        [Display(Name="Unique")]
        public bool IsMACUnique { get; set; }
        [Display(Name = "Unique")]
        public bool IsIPUnique { get; set; }
        public Nullable<double> SPEED { get; set; }
        public Nullable<Int64> PROCESSORCOUNT { get; set; }
        [Display(Name = "IP Unique")]
        public bool IsTIPUnique { get; set; }
        [Display(Name = "MAC Unique")]
        public bool IsTMACUnique { get; set; }
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (Server != null)
            {


                if (Server.RackUnitID != null && Server.RackID == null)
                {
                    yield return new ValidationResult("Please select a Rack, or remove the current Rack Unit", new[] { "Server.RackUnitID" });
                }
                if (Server.RackUnitIDTo != null && Server.RackUnitID == null)
                {
                    yield return new ValidationResult("Please select a Rack From Value", new[] { "Server.RackUnitID" });
                }
                if (Server.RackUnitIDTo != null && Server.RackUnitID != null && Server.RackUnitID > Server.RackUnitIDTo)
                {
                    yield return new ValidationResult("Rack Unit From must be less than or equal Rack Unit To", new[] { "Server.RackUnitID" });
                }


            }

&

 public class VMJoin
    {
         public VirtualMachine VirtualMachine { get; set; }
         public Resource Resource { get; set; }
         public Technology Technology { get; set; }
         public SDOrganization Site { get; set; }
         public SDOrganization Customer { get; set; }
         public NetworkInfo NetworkInfo { get; set; }
         public ICollection<NetworkInfo> NetworkInfo2 { get; set; }
         public ICollection<TechnologyIP> TechnologyIP { get; set; }
         [Display(Name = "Unique")]
         public bool IsMACUnique { get; set; }
         [Display(Name = "Unique")]
         public bool IsIPUnique { get; set; }
         public Nullable<double> SPEED { get; set; }
         public TechnologyIP TechnologyIP2 { get; set; }
         [Display(Name = "IP Unique")]
         public bool IsTIPUnique  { get; set; }
         [Display(Name = "MAC Unique")]
         public bool IsTMACUnique { get; set; }

    }
}

  • vmj.NetworkInfo2.FirstOrDefault().IPADDRESS: довольно антипаттерн. Он выдаст бесполезное исключение NullReferenceException в случае пустой коллекции (и если NetworkInfo является class, а не struct). Это должно быть vmj.NetworkInfo2.First().IPADDRESS, чтобы получить более значимую ошибку в случае пустой коллекции. 20.04.2016

Ответы:


1

Начните с создания базового класса для ваших моделей с общими свойствами, что также уменьшит код внутри конкретных классов.

public class BaseModel
{
    public bool IsIPUnique { get; set; }
    public bool IsMACUnique { get; set; }
    .... // other common properties
}
public class ServerJoin : BaseModel
{
    .... //  properties specific to ServerJoin 
}
public class VMJoin : BaseModel
{
    .... //  properties specific to VMJoin
}

и создайте базовый контроллер для общего кода проверки

public class BaseController : Controller
{
    public void Validate(BaseModel model)
    {
        bool ITipunique = repository.ISITIPUnique(vmj.NetworkInfo2.FirstOrDefault().IPADDRESS);
        ....
        if ((model.IsIPUnique == true) && (!ITipunique || !Tipunique))
        {
            ModelState.AddModelError("NetworkInfo2[0].IPAddress", "Error occurred. The Same IP is already assigned.");
        }
        if ((model.IsMACUnique == true) && (!ITmacunique || !Tmacunique))
        {
            ModelState.AddModelError("NetworkInfo2[0].MACAddress", "Error occurred. The Same MAC Address is already assigned.");
        }
        .... // other common validation  
    }
}

а затем на конкретных контроллерах

public class ServerController : BaseController
{
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(ServerJoin sj)
    {
        Validate(sj); // adds the ModelStateErrors
        if (!ModelState.IsValid)
        {
            return View(sj);
        }
        ....
    }
}

public class VMController : BaseController
{
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(VMJoin vmj)
    {
        Validate(vmj); // adds the ModelStateErrors
        if (!ModelState.IsValid)
        {
            return View(vmj);
        }
        ....
    }
}
20.04.2016
  • хотя ваш подход потребует серьезных изменений в моем коде, но, похоже, сэкономит много времени и усилий при расширении и поддержке моего приложения. но у меня есть вопрос о том, как вы создали базовый класс в качестве контроллера? как обычно классы контроллера используются для доступа пользователей к своим методам действий, поэтому есть ли какие-либо недостатки в определении BaseController как обычного класса, а не как контроллера самого по себе? 20.04.2016
  • Он просто расширяет стандартный класс MVC Controller, чтобы добавить дополнительный метод (и вы также можете включить защищенное свойство или поле для репозитория, чтобы к нему можно было получить доступ во всех производных от него контроллерах). Нет никакой разницы с использованием стандартного класса Controller — вы просто получаете все свои контроллеры, которым нужен доступ к общему методу, из BaseController, а не из Controller. 20.04.2016

  • 2

    Если VMJoin и ServerJoin имеют одинаковый интерфейс, вы можете просто создать метод расширения с ModelState в качестве второго параметра.

    Обновить Вот пример метода расширения

        public static void TestMethod<T>(this T context, ModelStateDictionary modelsState) where T : YourDbBaseClass
        {
            //check context
            //add errors if exist
            modelsState.AddModelError("Error", "Big Error");
        }
    
        //Usage
        TestMethod<YourDbContext>(ModelState);
    
    14.04.2016
  • Можете ли вы посоветовать, что именно вы имеете в виду? теперь VMjoin и ServerJoin представляют разные объекты контекста (разные таблицы БД) 14.04.2016
  • @johnG Я обновил ответ. Я надеюсь, что это поможет вам, но именно так я видел вашу проблему и свое решение. Если вы добавите больше деталей об этих контекстах, это поможет нам понять их более четко. 14.04.2016
  • спасибо за обновление, но как я буду использовать их в своих классах контроллера сервера и виртуальной машины, я не понял вашей точки зрения? 14.04.2016
  • для объекта контекста я сопоставил свои таблицы БД с помощью Entity Framework-6, который генерирует файл .edmx, содержащий класс DBContext 14.04.2016
  • @johnG Просто определите метод расширения в другом классе (любом статическом классе), и вы сможете вызывать этот метод из контроллера, например vmj.TestMethod<VMJoin>(ModelState). 14.04.2016
  • Я привел только пример, и, конечно, вы должны заменить код в методе своей проверкой. 14.04.2016
  • но как я могу получить доступ к свойствам vmjoin и serverjoin внутри TestMethod? я имею в виду, как я могу получить текущий IP-адрес, в настоящее время внутри метода действия я делаю следующее vmj.NetworkInfo2.FirstOrDefault().IPADDRESS 14.04.2016
  • @johnG, не могли бы вы предоставить код VMJoin или ServerJoin? Вы можете просто добавить их объявления с базовыми классами. 14.04.2016
  • хорошо, я отредактировал свой вопрос и предоставил часть классов serverjoin и vmjoin 14.04.2016
  • @johnG Спасибо. К сожалению, теперь я вижу, что метод расширения не может вам помочь. 14.04.2016
  • Можете ли вы посоветовать, почему это не может помочь? 15.04.2016

  • 3

    На мой взгляд, самый чистый способ сделать это — использовать настраиваемый атрибут аннотации данных проверки.

    Вы просто создаете собственный атрибут

    public class IPUniqueValidator : ValidationAttribute
    {
        public string ShouldCheck { get; set; }
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value != null)
            {
                var IP = value.ToString();
                var isValid = false;
    
                // Using reflection to get the other property value 
                var shouldCheckPropert = validationContext.ObjectInstance.GetType().GetProperty(this.ShouldCheck);
                var shouldCheckPropertValue = (bool)shouldCheckPropert.GetValue(validationContext.ObjectInstance, null);
                if (shouldCheckPropertValue)
                {
                    // do validation code...
                }
    
                if (isValid)
                {
                    return ValidationResult.Success;
                }
                else
                {
                    return new ValidationResult("Error occurred. The Same IP is already assigned.");
                }
            }
            else
            {
                return new ValidationResult("" + validationContext.DisplayName + " is required");
            }
        }
    }
    

    Отметьте свои модели новым атрибутом:

    public class VMJoin
    {
        [IPUniqueValidator(ShouldCheck = "ShouldCheck")]
        public string IpAddress { get; set; }
    
        public bool ShouldCheck { get; set; }
    }
    
    public class ServerJoin
    {
        [IPUniqueValidator(ShouldCheck = "ShouldCheck")]
        public string IpAddress { get; set; }
    
        public bool ShouldCheck { get; set; }
    }
    

    И вам осталось только добавить проверку состояния валидации. Фреймворк сделает всю работу за вас.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(ServerJoin sj)
    {
        if (ModelState.IsValid)
        {
            // do staff 
        }
    }
    
    22.04.2016
  • ваш подход кажется правильным, но, как видно из моего исходного кода, у пользователя есть возможность выбрать, хочет ли он выполнить проверку или нет. я имею в виду, что в представлении есть флажок, который позволяет пользователю указать, хочет ли он проверить, является ли IP уникальным или нет, следующим образом: - ‹‹( if ((model.IsMACUnique == true) && (!ITmacunique || ! Уникальный)))›› . так что не уверены, можно ли этого добиться с помощью пользовательской аннотации данных проверки? как если бы я использовал пользовательский атрибут аннотации данных проверки, он всегда будет выполнять проверку, и я не могу передать ему дополнительное значение поля (выбор флажка в моем случае) 22.04.2016
  • У вас есть доступ к другим свойствам текущего экземпляра объекта. validationContext.ObjectInstance.GetType().GetProperty(PropertyName) Я мог бы расширить свой ответ примером использования. 22.04.2016
  • Я добавил пример, как получить другое свойство из метода проверки 22.04.2016

  • 4

    Я бы выбрал пользовательский ActionFilter. Узнайте, как получить доступ к ModelState из фильтра действий здесь:

    Как получить доступ к ModelState из ActionFilter?

    Вы также можете настроить внедрение зависимостей в настраиваемый фильтр действий для получения необходимых репозиториев, что еще больше повысит удобство тестирования и обслуживания.

    20.04.2016
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..