Получение всех команд NET, часть 1
На самом деле тут и про получение команд, и про интерфейсы, и про... Короче, много про что будет.
Идея принадлежит doctorRaz, с моей стороны в лучшем случае – реализация.
Задача проекта – получить все команды, зарегистрированные в NET-сборках, загруженных в NCad. Вывод выполнять в 2 вариантах: либо в ком.строку, либо в окно.
Классы, поля, методы – буду стараться комментировать. Комментарии на русском. Если охота – переводите на нужный язык самостоятельно
Ну что ж, начнем-с Создаю решение 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 и его преимущества. В текущей реалиации кода посчитал не настолько сильно необходимым притаскивать сюда еще и его - и так код не сильно простой.
В статье ты краем коснулся темы нэйминга.
Удивительно, но именно так и я обзываю
Поля, свойства и переменные с маленькой буквы (поВерблюжьи)
Методы, классы с большой.
Прикол в том, что у меня так и не получилось настроить студию, что б она не ругалась на строчные в начале...
Возможно ты сможешь выкроить время в своем плотном графике и развернешь спойлер в статью, традиционно с домино и веселыми старушками