Получение всех команд NET, часть 1

На самом деле тут и про получение команд, и про интерфейсы, и про... Короче, много про что будет.

Идея принадлежит doctorRaz, с моей стороны в лучшем случае – реализация.

Задача проекта – получить все команды, зарегистрированные в NET-сборках, загруженных в NCad. Вывод выполнять в 2 вариантах: либо в ком.строку, либо в окно.

Нейминг
Немного оффтопа по поводу нейминга переменных, методов, локальных переменных и т.п.

Параметры вызова – имя начинается с заглавной буквы, каждое последующее значимое слово начинается с заглавной буквы (SomeParameter)

Локальные переменные уровня метода – начинаются с маленькой буквы (someValue)

Локальные переменные уровня класса – начинаются со знака подчеркивания, далее – с маленькой буквы (_someClassValue).

Если какой-то проект относится к NET6, в конце проекта добавляется “.NET”. Для остальных такого добавления не будет.

ЗЫ поскольку код ориентирован только на NCAD, никаких тестов придумать (и тем более реализовать) мне не удалось.

Классы, поля, методы – буду стараться комментировать. Комментарии на русском. Если охота – переводите на нужный язык самостоятельно :P
Ну что ж, начнем-с ;) Создаю решение KpblcCadInfrastructure (любого типа), созданный автоматом проект – нафиг.
Внутри создаю NET Standard 2.0 проект, с именем KpblcCadInfrastructure.Abstractions – для интерфейсов, абстрактных классов и прочей подобной шняги. Проект ни на какой другой проект не завязан. Standard 2.0 только для того, чтоб его можно было безболезненно использовать в NET Framework, и в NET
Прежде всего в подкаталоге \Entitites добавляю класс для описания команды. Для начала так (хотя я практически уверен, что потом придется переделывать. И не факт, что только на этом удастся остановиться ;) ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.Reflection;

namespace KpblcCadInfrastructure.Abstractions.Entities
{
    public class CommandInfo
    {
        /// <summary>
        /// Глобальное имя команды
        /// </summary>
        public string GlobalName { get; set; }
        /// <summary>
        /// Локализованное имя команды. М.б. пустым
        /// </summary>
        public string LocalizedName { get; set; }
        /// <summary>
        /// Описание команды. М.б. пустым
        /// </summary>
        public string Desctiption { get; set; }
        /// <summary>
        /// Сборка, из которой загружается команда
        /// </summary>
        public Assembly Assembly { get; set; }
    }
}

Следом добавляю два интерфейса – один для получения команд, другой – для получения сборок (естественно, каждый интерфейс в отдельном файле):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using KpblcCadInfrastructure.Abstractions.Entities;
using System.Collections.Generic;
using System.Reflection;

namespace KpblcCadInfrastructure.Abstractions.Interfaces
{
    public interface ICommandInfoRepository
    {
        /// <summary>
        /// Получение перечня команд
        /// </summary>
        /// <param name="Assemblies">Перечень сборок, которые надо "проходить" на предмет описаний команд</param>
        /// <returns></returns>
        IEnumerable<CommandInfo> Get(IEnumerable<Assembly> Assemblies);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System.Collections.Generic;
using System.Reflection;

namespace KpblcCadInfrastructure.Abstractions.Interfaces
{
    public interface IAssemblyRepository
    {
        /// <summary>
        /// Получение списка загруженных сборок
        /// </summary>
        /// <returns></returns>
        IEnumerable<Assembly> Get();
    }
}

А, еще интерфейс для сообщений. Ну так, на всякий случай ;)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;

namespace KpblcCadInfrastructure.Abstractions.Interfaces
{
    /// <summary>
    /// Сервис сообщений
    /// </summary>
    public interface IMessageService
    {
        /// <summary>
        /// Сообщение в консоль
        /// </summary>
        /// <param name="Message">Выводимое сообщение, без символов переноса строки</param>
        /// <param name="CallerName">Вызывающий метод</param>
        void ConsoleMessage(string Message, string CallerName = null);
        /// <summary>
        /// Информационное сообщение
        /// </summary>
        /// <param name="Message">Выводимое сообщение, без символов переноса строки</param>
        /// <param name="CallerName">Вызывающий метод</param>
        void InfoMessage(string Message, string CallerName = null);
        /// <summary>
        /// Сообщение об ошибке
        /// </summary>
        /// <param name="Message">Выводимое сообщение, без символов переноса строки</param>
        /// <param name="CallerName">Вызывающий метод</param>
        void ErrorMessage(string Message, string CallerName = null);
        /// <summary>
        /// Сообщение об исключении
        /// </summary>
        /// <param name="Ex">Сгенерированное сообщение об ошибке</param>
        /// <param name="CallerName">Вызывающий метод</param>
        void ExceptionMessage(Exception Ex, string CallerName = null);
    }
}

Так, поскольку у меня дома установлен только NC23.1, то и проект я буду делать под него. С именем KpblcCadInfrastructure.CAD.NET, как и было оговорено в самом начале.

Ну и первым делом – связь с KpblcCadInfrastructure.Abstractions и параметры вызова/отладки.

Следом – реализации нужных интерфейсов. Для начала можно не париться на предмет "как оно будет работать". Создаю пару команд – одна выведет перечень загруженных сборок, вторая – будет выводить перечень всех команд. Естественно, при попытке вызова в NCAD я получу ошибки, но пока это не сильно важно. И каждая команда – в варианте вывода результата в ком.строку или в диалоговое WPF-окно.

Если для режима ком.строки все плюс-минус прозрачно:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[CommandMethod("_get-all-assemblies")]
public static void GetAllAssembliesCommandLineMode()
{
    IAssemblyRepository rep = new AssemblyRepository();
    IMessageService messageService = new MessageService();
    foreach (Assembly assembly in rep.Get().OrderBy(o => o.GetName(false).Name))
    {
        messageService.ConsoleMessage(assembly.FullName);
    }
}

[CommandMethod("-get-all-commands")]
public static void GetAllCommandsCommandLineMode()
{
    IAssemblyRepository assemblyRepository = new AssemblyRepository();
    ICommandInfoRepository commandInfoRepository = new CommandInfoRepository();
    IMessageService messageService = new MessageService();
    foreach (CommandInfo info in commandInfoRepository.Get(assemblyRepository.Get()))
    {
        string message = info.GlobalName;
        if (!string.IsNullOrWhiteSpace(info.LocalizedName))
        {
            message += $" {info.LocalizedName}";
        }

        if (!string.IsNullOrWhiteSpace(info.Desctiption))
        {
            message += $" {info.Desctiption}";
        }

        if (info.Assembly!=null)
        {
            message += $" {info.Assembly.FullName}";
        }

        messageService.ConsoleMessage(message);
    }
}

То для варианта диалогового окна придется помучиться. С другой стороны, уже как-то хочется добиться хоть какого-то результата. Так что окно "отодвигаю" на второй план, и начинаю реализации интерфейсов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using HostMgd.ApplicationServices;
using HostMgd.EditorInput;
using KpblcCadInfrastructure.Abstractions.Interfaces;
using System.Runtime.CompilerServices;
using Application = HostMgd.ApplicationServices.Application;

namespace KpblcCadInfrastructure.CAD.NET.Infrastructure
{
    internal class MessageService : IMessageService
    {
        public MessageService()
        {
            Version _version = typeof(MessageService).Assembly.GetName().Version;
            _title = "Kpblc.CAD.NET v." + _version + " : ";
        }

        public void ConsoleMessage(string Message, [CallerMemberName] string CallerName = null)
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            if (doc == null)
            {
                InfoMessage(Message, CallerName);
            }

            Editor ed = doc.Editor;
            ed.WriteMessage("\n" + Message);
        }

        public void InfoMessage(string Message, [CallerMemberName] string CallerName = null)
        {
            MessageBox.Show((string.IsNullOrWhiteSpace(CallerName) ? "" : $"{CallerName} : ") + Message,
                _title + "Инфо", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        public void ErrorMessage(string Message, [CallerMemberName] string CallerName = null)
        {
            MessageBox.Show((string.IsNullOrWhiteSpace(CallerName) ? "" : $"{CallerName} : ") + Message,
                _title + "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        public void ExceptionMessage(Exception Ex, [CallerMemberName] string CallerName = null)
        {
            MessageBox.Show(
                (string.IsNullOrWhiteSpace(CallerName) ? "" : $"{CallerName} : ") + "\n" + Ex.Message + "\n" +
                Ex.StackTrace, _title + "Системная ошибка", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private string _title;
    }
}

Теперь рисуем получение всех сборок:

1
2
3
4
5
6
7
8
9
10
11
12
13
using KpblcCadInfrastructure.Abstractions.Interfaces;
using System.Reflection;

namespace KpblcCadInfrastructure.CAD.NET.Infrastructure
{
    internal class AssemblyRepository : IAssemblyRepository
    {
        public IEnumerable<Assembly> Get()
        {
            return AppDomain.CurrentDomain.GetAssemblies().ToList();
        }
    }
}

Ну и получение команд:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using KpblcCadInfrastructure.Abstractions.Entities;
using KpblcCadInfrastructure.Abstractions.Interfaces;
using System.ComponentModel;
using System.Reflection;
using Teigha.Runtime;

namespace KpblcCadInfrastructure.CAD.NET.Infrastructure
{
    internal class CommandInfoRepository : ICommandInfoRepository
    {
        public IEnumerable<CommandInfo> Get(IEnumerable<Assembly> Assemblies)
        {
            List<CommandInfo> cmdInfos = new List<CommandInfo>();
            foreach (Assembly assembly in Assemblies)
            {
                try
                {
                    Type[] expTypes = assembly.GetTypes();
                    foreach (Type t in expTypes)
                    {
                        MethodInfo[] methods = t.GetMethods();
                        foreach (MethodInfo methodInfo in methods)
                        {
                            CommandInfo item = GetCommandInfo(assembly, methodInfo);
                            if (!string.IsNullOrWhiteSpace(item.GlobalName))
                            {
                                cmdInfos.Add(item);
                            }
                        }
                    }
                }
                catch
                {
                }
            }

            return cmdInfos;
        }

        private CommandInfo GetCommandInfo(Assembly Assembly, MethodInfo Method)
        {
            object[] attributes = Method.GetCustomAttributes(true);
            CommandInfo cmdInfo = new CommandInfo();
            foreach (object attribute in attributes)
            {
                cmdInfo.Assembly = Assembly;
                if (attribute is CommandMethodAttribute cmdAttr)
                {
                    cmdInfo.GlobalName = cmdAttr.GlobalName;
                    cmdInfo.LocalizedName = cmdAttr.LocalizedNameId;
                }
                else if (attribute is DescriptionAttribute descAttribute)
                {
                    cmdInfo.Desctiption = descAttribute.Description;
                }
            }

            return cmdInfo;
        }
    }
}

Если запустить "как есть", ничего хорошего не получится: сборок овердофига, и получить ошибку выполнения на любой из них – раз плюнуть. Так что слегка меняю код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[CommandMethod("-get-all-assemblies")]
public static void GetAllAssembliesCommandLineMode()
{
    IAssemblyRepository rep = new AssemblyRepository();
    IMessageService messageService = new MessageService();
    try
    {
        string excludeFolder = Environment.GetEnvironmentVariable("programfiles").ToUpper();
        foreach (Assembly assembly in rep.Get()
                     .Where(o => !o.Location.ToUpper().StartsWith(excludeFolder))
                     .OrderBy(o => o.FullName))
        {
            messageService.ConsoleMessage(assembly.FullName + " >> " + assembly.ManifestModule + " >> " +
                                          assembly.Location);
        }
    }
    catch (Exception e)
    {
        messageService.ExceptionMessage(e);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[CommandMethod("-get-all-commands")]
public static void GetAllCommandsCommandLineMode()
{
    IAssemblyRepository assemblyRepository = new AssemblyRepository();
    ICommandInfoRepository commandInfoRepository = new CommandInfoRepository();
    IMessageService messageService = new MessageService();
    try
    {
        string excludeFolder = Environment.GetEnvironmentVariable("programfiles").ToUpper();

        foreach (CommandInfo info in commandInfoRepository.Get(assemblyRepository.Get()
                     .Where(o => !o.Location.ToUpper().StartsWith(excludeFolder))
                     .OrderBy(o => o.FullName)))
        {
            string message = info.GlobalName;
            if (!string.IsNullOrWhiteSpace(info.LocalizedName))
            {
                message += $" {info.LocalizedName}";
            }

            if (!string.IsNullOrWhiteSpace(info.Desctiption))
            {
                message += $" {info.Desctiption}";
            }

            if (info.Assembly != null)
            {
                message += $" {info.Assembly.FullName}";
            }

            messageService.ConsoleMessage(message);
        }
    }
    catch (Exception e)
    {
        messageService.ExceptionMessage(e);
    }
}

Исходный код по состоянию на текущий момент: https://github.com/kpblc2000/KpblcCadInfrastructure

Окна и все, что с ними связано - уже потом. Во второй части. И так текста получилось немного слишком много.

P.S. Я в курсе про StringBuilder и его преимущества. В текущей реалиации кода посчитал не настолько сильно необходимым притаскивать сюда еще и его - и так код не сильно простой.

Размещено в .NET, MVVM, nanoCAD · Метки: , , , ,



Комментарии

Есть 1 комментарий к “Получение всех команд NET, часть 1”
  1. drz пишет:

    В статье ты краем коснулся темы нэйминга.
    Удивительно, но именно так и я обзываю
    Поля, свойства и переменные с маленькой буквы (поВерблюжьи)
    Методы, классы с большой.
    Прикол в том, что у меня так и не получилось настроить студию, что б она не ругалась на строчные в начале...
    Возможно ты сможешь выкроить время в своем плотном графике и развернешь спойлер в статью, традиционно с домино и веселыми старушками

Поделитесь своим мнением


Я не робот.