понедельник, 17 января 2011 г.

Тестовое задание от Электронные торги и безопасность, ФГУП

Чото совсем писать не хочется...

Было найдено интересное тестовое задание раскрывающее суть динамического подключения сборок на .NET платформе.

Тестовое задание звучит так:
"Разработать WinForms приложение. Основное окно должно содержать грид для отображения списка объектов. На форме должны быть кнопки: добавить, изменить, удалить. Кнопки должны открывать диалоговые формы для ввода данных, изменения данных и подтверждения удаления. Кнопка добавления должна предварительно открывать список классов объектов для выбора класса создаваемого объекта (например: физ. лицо, юр. лицо, инд. предприниматель).
Приложение не должно знать заранее список классов объектов.
Настройки хранить в файле Options.xml. Каждый класс сущности реализовать в динамически подключаемой библиотеке. В файле Options.xml указать список классов и ДЛЛ, которые их реализуют. Для каждого класса своя ДЛЛ и своя форма для создания/изменения, но одна форма на удаление объекта любого класса. Свойства объектов хранить в xml-файле. После обработки список в гриде должен обновляться."

Мне оно показалось интересным в первую очередь тем, что я редко, да и думаю что любой другой программист не часто, используют подобную фичу в своих проектах, хотя в Self подготовке к 70-536 эта тема описывается.

Ну в общем перейдем к процессу решения этой задачи. Для начала я прикинул что?где? когда? Почитал немного доков от мелколегких здесь. И решил что структура проекта будет следующая:
1. WinForms приложение - приложение подключающее библиотеки - как сущности и отображающее их объекты.
2. Связующая библиотека - аля ядро, содержащая необходимые интерфейсы для Сущности и ее класса (самое главное тут).
3. Множество различных сборок,  удовлетворяющие интерфейсам из ядра.
Немного теории о доменах (не DNS а о Application доменах) и сборках можно почитать тут .

Сказано - сделано:
где: 
DynamicObject_t - WinForms
EntityLayer - ядро
TestEntity1 и TestEntity2 - множество библиотек.

По условию задачи в WinForms приложении у нас 2-е формы:
1. Основная  - с тремя "кнопыщками" и "гридом".
2. Для выбора типа (сущности, класса) добавляемого объекта.

Снова, сказано - сделано:




Но это все косметика, самое интересное это что же из себя представляет EntityLayer.

EntityLayer содержит два интерфейса: 1. IClassEntity.cs - отображает класс для работы со сборкой, 2. - IObject.cs отображает класс объектов сборки. И класс XmlHelper.cs - содержит функционал сериализации и десериализации объектов сборки в Xml.

,где IClassEntity.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms; 

namespace EntityLayer
{
    public interface IClassEntity
    {
        string Name { get; } //Имя сборки

        Form   Edit(int ID); //Возвращает объект формы для редактирования, объекта       

        Form   Add();//Возвращает объект формы для добавления нового объекта

        void   Delete(int ID); //Удаляет объект

        List<IObject> Objects { get;}//Возвращает список объектов сборки
    }
}


,а  - IObject.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; 

namespace EntityLayer
{
   public interface IObject
    {
       int ID {get; set; }//Идентификатор объекта в сборке

       string ObjectType { get;}//Имя сборки

       string Summary { get; }  // Общее поле, возвращает суммарное значение полей объекта.

    }
}

Таким образом, получается что с помощью интерфейса IClassEntity мы можем получить общие методы взаимодействия с любыми динамически подгружаемыми сборками - классами наследуемые от данного интерфейса.
Как мы будем это делать в нашем WinForms приложении?
Для этого в этом проекте я создал класс Options.Helper.cs со статическим методом GetEntities, он считывает заказанный нам в условии Options.xml со сборками и классами и записывает уже интерпретированные классы сборок в List<EntityLayer.IClassEntity>

public static List<EntityLayer.IClassEntity> GetEntities(out bool IsError)
        {
            List<EntityLayer.IClassEntity> list = new List<EntityLayer.IClassEntity>();
            IsError = false;
            try
            {
                XElement root =  Element.Load(Properties.Settings.Default.OptionFile);               

                    var Ents = root.Elements();

                    foreach (var h in Ents)
                    {                      
list.Add(GetEntity(h.Attribute(XName.Get("class")).Value, h.Attribute(XName.Get("path")).Value));
                    }               

            }
            catch(Exception ex)
            {
                Log.WriteErrorLine(ex.ToString());
                IsError = true;
            }
            return list;

        }

Что такое GetEntity? Сейчас разберемся....

static EntityLayer.IClassEntity GetEntity(string ClassName, string
AssemblyFile)
        {
            if(!File.Exists(AssemblyFile)) throw new FileNotFoundException("Can't
find assembly file."
);
 

            Assembly assembly = Assembly.LoadFile(Application.StartupPath + "\\"
+ AssemblyFile);
 

            return (EntityLayer.IClassEntity)Activator.CreateInstance(assembly.GetType(ClassName));//Именно здесь происходит загрузка инициированного объекта класса со сборки, и оборачивание его в IClassEntity интерфейс.

        }
Ок, теперь ясно как это считывается и интерпретируется, а как выглядит класс самой подключаемой сборки? Пожалуйста...



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EntityLayer; 

namespace TestEntity1
{
   public class CompanyEntity:IClassEntity
    {  
        List<Company> Companies;//Список наших объектов, в данном случае компании

        XmlHelper<Company> helper; 

        public const string ObjectXmlFile = "TestEntity1.xml"; //Файл для сериализации и обратного процесса

        public string Name
        {
            get{ return "Company";}
        }

        public CompanyEntity()
        {
            helper = new XmlHelper<Company>(CompanyEntity.ObjectXmlFile);

            Companies = helper.Deserialise();
        }



        public System.Windows.Forms.Form Edit(int ID)
        {
            return new EditCompany(ID,Companies);//Вовращение формы для редактирования, реализуемой в данной сборке
        }     



        public System.Windows.Forms.Form Add()
        {
            return new EditCompany(null, Companies);
        }



        public List<IObject> Objects//Возвращение объектов 
        {
            get
            {
                List<IObject> result = new List<IObject>();               

                foreach(var h in Companies)
                    result.Add(h);

                return result;
            }
        }



        public void Delete(int ID)//Удаление объекта
        {
            var h = Companies.Where(i=>i.ID==ID).FirstOrDefault();
            if(h != null)
            {
                Companies.Remove(h);
                helper.Serialise(Companies);
            }
        }      

    }
}


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

Полный код проекта: SVN Тестового Проекта (как хорошо что есть дядя Гугл).

3 комментария:

objMihail комментирует...

Да, Гугл это хорошо. И что там дальше было, если не секрет? )

Кстати, почему может происходить "Error while loading assemblies, look at log file for details." при запуске? Лог не найти. Хотелось посмотреть, чего-там на гриде-то должно быть.

objMihail комментирует...

Перестроил проекты и всё заработало.

Молниеносный Молдаванин комментирует...

И что там дальше было, если не секрет? >> Где дальше было =)?
Лог не найти.>> Лог это тектовый файл, католог логов прописан в app.config