Hearth.ArcGIS 1.1.0

dotnet add package Hearth.ArcGIS --version 1.1.0
                    
NuGet\Install-Package Hearth.ArcGIS -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Hearth.ArcGIS" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hearth.ArcGIS" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Hearth.ArcGIS" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Hearth.ArcGIS --version 1.1.0
                    
#r "nuget: Hearth.ArcGIS, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=Hearth.ArcGIS&version=1.1.0
                    
Install Hearth.ArcGIS as a Cake Addin
#tool nuget:?package=Hearth.ArcGIS&version=1.1.0
                    
Install Hearth.ArcGIS as a Cake Tool

Hearth ArcGIS 框架扩展(DryIoC、Options、Nlog、AutoMapper...)

1 使用IoC、DI

1.1 服务注册

1.1.1 标记服务

1. 方式一

需要注册服务类型时,在服务类型上添加[Service]标记:

namespace Hearth.ArcGIS.Samples.Services
{
    public interface IHelloService
    {
        void SayHello();
    }
}
using ArcGIS.Desktop.Framework.Dialogs;

namespace Hearth.ArcGIS.Samples.Services
{
    [Service]
    public class HelloService : IHelloService
    {
        public void SayHello()
        {
            MessageBox.Show("Hello, World!", this.GetType().Name);
        }
    }
}

ServiceAttribute服务标记特性

namespace Hearth.ArcGIS
{
    /// <summary>
    /// 服务标记特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public sealed class ServiceAttribute : Attribute
    {
        /// <summary>
        /// 服务注册键
        /// </summary>
        public string? ServiceKey { get; set; }

        /// <summary>
        /// 服务注册类型
        /// </summary>
        public Type? ServiceType { get; set; }

        /// <summary>
        /// 服务重用模式
        /// </summary>
        public ReuseEnum Reuse { get; set; }

        /// <summary>
        /// 服务特性
        /// </summary>
        /// <param name="serviceType"> 服务注册类型 </param>
        /// <param name="serviceKey"> 服务注册键 </param>
        /// <param name="reuse"> 服务重用模式 </param>
        public ServiceAttribute(Type? serviceType = null, string? serviceKey = null, ReuseEnum reuse = ReuseEnum.Default)
        {
            ServiceType = serviceType;
            ServiceKey = serviceKey;
            Reuse = reuse;
        }
    }
}

ReuseEnum服务重用模式:

using DryIoc;

namespace Hearth.ArcGIS
{
    /// <summary>
    /// 重用模式枚举
    /// </summary>
    public enum ReuseEnum
    {
        /// <summary>
        /// 默认。
        /// </summary>
        Default,

        /// <summary>
        /// 与作用域相同,但需要 <see cref="ThreadScopeContext"/> 。
        /// </summary>
        InThread,

        /// <summary>
        /// 作用域为任何作用域,可以有名称也可以没有名称。
        /// </summary>
        Scoped,

        /// <summary>
        /// 与 <see cref="Scoped"/> 相同,但在没有可用作用域的情况下,将回退到 <see cref="Singleton"/> 重用。
        /// </summary>
        ScopedOrSingleton,

        /// <summary>
        /// 容器中单例。
        /// </summary>
        Singleton,

        /// <summary>
        /// 瞬态,即不会重复使用。
        /// </summary>
        Transient,
    }
}

1. 方式二

使需要注册服务类型实现ITransientServiceISingletonServiceIScopedServiceIScopedOrSingletonServiceIInThreadService接口:

using ArcGIS.Desktop.Framework.Dialogs;

namespace Hearth.ArcGIS.Samples.Services
{
    public class HelloService : IHelloService, ITransientService
    {
        public void SayHello()
        {
            MessageBox.Show("Hello, World!", this.GetType().Name);
        }
    }
}
1.1.2 注册服务

在模块加载时调用HearthApp.Container.RegisterAssemblyAndRefrencedAssembliesTypes(Assembly assembly)方法,自动注册模块Assembly及所引用的全部Assembly中的服务类型。

注册程序集类型:

using ArcGIS.Desktop.Framework;
using ArcGIS.Desktop.Framework.Contracts;

namespace Hearth.ArcGIS.Samples
{
    internal class Module1 : Module
    {
        private static Module1 _this = null;

        public static Module1 Current => _this ??= (Module1)FrameworkApplication.FindModule("Hearth_ArcGIS_Samples_Module");

        public Module1()
        {
            HearthApp.App.RegisterAssemblyAndRefrencedAssembliesTypes(this.GetType().Assembly);
        }
    }
}

1.2 依赖注入

1.2.1 SDK底层创建实例类型依赖注入

在使用 ArcGISProSDK 进行 Addin 开发时,由 SDK 创建的ButtonPaneDockpaneArcGIS.Desktop.Framework.Contracts.PlugIn组件在整个工程中充当的是ViewModel角色,而底层是根据Type使用Activator.CreateInstance()方法创建的实例,仅支持无参构造函数,因此无法直接使用构造函数注入。

解决思路有两个:

  1. 使用类似PostSharp切片的方式,使用源生成器,在编译时将依赖注入逻辑编织到代码中;
    • 优点:编码简单,只需要在类上加一个切片特性即可;
    • 缺点:逻辑相对复杂,由于是编译时将切面逻辑代码编制到源码中,导致调试热重载无法使用;
  2. 在使用SDK底层创建实例(Button、Pane等)时,在构造函数中主动调用依赖注入;
    • 优点:逻辑简单,在底层创建实例类型(Button、Pane等)上再封装个通用类即可;
    • 缺点:编码麻烦了点;

由于方式1无法使用调试热重载,导致debug困难,所以选择方式2实现;

using ArcGIS.Desktop.Framework.Contracts;
using Hearth.ArcGIS.Samples.Services;

namespace Hearth.ArcGIS.Samples.PlugIns.Menus
{
    internal class SampleButton1 : Button, IInjectable // IScopeInjectable 使用作用域注入
    {
        [Inject]
        private readonly IHelloService? _helloService;

        public SampleButton1()
        {
            this.InjectServices();
            // this.InjectPropertiesAndFields(); // 不需要[Inject]特性标注注入字段/属性,但字段/属性也不能使用 readonly/init
        }

        protected override void OnClick()
        {
            _helloService?.SayHello();
        }
    }
}

为了方便编码,同时防止忽略了this.InjectServices();调用注入方法,可以提取一个基类;

using ArcGIS.Desktop.Framework.Contracts;

namespace Hearth.ArcGIS.Samples.PlugIns.Contracts
{
    public class InjectableButton : Button, IInjectable
    {
        public InjectableButton()
        {
            this.InjectPropertiesAndFields();
        }
    }
}

简化代码:

using Hearth.ArcGIS.Samples.PlugIns.Contracts;
using Hearth.ArcGIS.Samples.Services;

namespace Hearth.ArcGIS.Samples.PlugIns.Menus
{
    internal class SampleButton1 : InjectableButton
    {
        private IHelloService? _helloService;

        protected override void OnClick()
        {
            _helloService?.SayHello();
        }
    }
}
1.2.2 InjectAttribute特性
namespace Hearth.ArcGIS
{
    /// <summary>
    /// 自动注入特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
    public sealed class InjectAttribute : Attribute
    {
        /// <summary>
        /// 服务注册键
        /// </summary>
        public object? Key { get; set; }

        /// <summary>
        /// 服务类型
        /// </summary>
        public Type? ServiceType { get; set; }

        /// <summary>
        /// 注入服务特性
        /// </summary>
        /// <param name="key"> 服务注册键 </param>
        /// <param name="serviceType"> 服务类型 </param>
        public InjectAttribute(object? key = null, Type? serviceType = null)
        {
            Key = key;
            ServiceType = serviceType;
        }
    }
}
1.2.3 服务类型依赖注入

在自定义的服务类型中(如IHelloService),可以直接使用构造函数注入,也可以和1.1.1中一样,使用属性注入,推荐使用构造函数注入

namespace Hearth.ArcGIS.Samples.Services
{
    public interface IHearthHelloService
    {
        void SayHello();
    }
}

构造函数注入:

using ArcGIS.Desktop.Framework.Dialogs;

namespace Hearth.ArcGIS.Samples.Services
{
    [Service(typeof(IHearthHelloService))]
    public class HearthHelloService : IHearthHelloService
    {
        private readonly IHelloService _helloService;
        public HearthHelloService(IHelloService helloService)
        {
            _helloService = helloService;
        }

        public void SayHello()
        {
            _helloService.SayHello();
            MessageBox.Show("Hello, Hearth!", this.GetType().Name);
        }
    }
}

属性注入:

using ArcGIS.Desktop.Framework.Dialogs;

namespace Hearth.ArcGIS.Samples.Services
{
    [Service(typeof(IHearthHelloService))]
    public class HearthHelloService : IHearthHelloService, IInjectable
    {
        [Inject]
        private readonly IHelloService _helloService;
        
        public HearthHelloService()
        {
            this.InjectServices();
        }

        public void SayHello()
        {
            _helloService.SayHello();
            MessageBox.Show("Hello, Hearth!", this.GetType().Name);
        }
    }
}
1.2.4 视图模型类型依赖注入

对于非SDK底层自动创建的 View 组件,如自定义的UserControlWindow等,可以在View的xaml中使用ViewModelLocator(视图模型定位器)来绑定View的视图模型。视图模型类型也可以使用Service标记来进行自定义注册,对于未注册的视图模型类型,Hearth会对绑定的视图模型进行默认注册、注入。

using ArcGIS.Desktop.Framework.Contracts;

namespace Hearth.ArcGIS.Samples.Dialogs
{
    public class SampleWindow1ViewModel : ViewModelBase
    {
        private string _sampleText = "Sample Text";
        public string SampleText
        {
            get => _sampleText;
            set => SetProperty(ref _sampleText, value, () => SampleText);
        }
    }
}
<Window
    x:Class="Hearth.ArcGIS.Samples.Dialogs.SampleWindow1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:ha="clr-namespace:Hearth.ArcGIS;assembly=Hearth.ArcGIS"
    xmlns:local="clr-namespace:Hearth.ArcGIS.Samples.Dialogs"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="SampleWindow1"
    Width="800"
    Height="450"
    ha:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d">
    
    <d:Window.DataContext>
        <local:SampleWindow1ViewModel />
    </d:Window.DataContext>
    <Grid>
        <TextBlock
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="24"
            FontWeight="Bold"
            Text="{Binding SampleText}" />
    </Grid>
</Window>

1.3 自定义容器初始化

HearthApp已经内置了DryIoc容器初始化、Nlog、ViewModelLocationProvider集成,当然也支持自定义初始化。

实现ContainerBuilderBaseHearthAppBase

using DryIoc;

namespace Hearth.ArcGIS.Samples
{
    public class CustomContainerBuilder : ContainerBuilder
    {
        public override Container Build()
        {
            Container container = new Container(
                rules => rules
                    .With(
                        FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic,
                        null,
                        PropertiesAndFields.All()));
            return container;
        }
    }
}
namespace Hearth.ArcGIS.Samples
{
    public class CustomHearthApp : HearthAppBase
    {
        private static CustomHearthApp? _instance;
        public static CustomHearthApp Instance => _instance ??= new CustomHearthApp(new CustomContainerBuilder());
        public CustomHearthApp(ContainerBuilderBase containerBuilder) : base(containerBuilder)
        {

        }
    }
}

在使用依赖注入之前完成容器初始化、服务注册。

CustomHearthApp.Instance.Container.RegisterAssemblyAndRefrencedAssembliesTypes(this.GetType().Assembly);

2 使用Options配置

2.1 创建配置类

namespace Hearth.ArcGIS.Samples.Configs
{
    public class SampleSettings
    {
        public string Value1 { get; set; }
        public int Value2 { get; set; }
        public double Value3 { get; set; }
        public string[] Value4 { get; set; }
    }
}

2.2 在模块初始化时注册配置

using ArcGIS.Desktop.Framework;
using ArcGIS.Desktop.Framework.Contracts;
using Hearth.ArcGIS.Samples.Configs;
using Microsoft.Extensions.Configuration;

namespace Hearth.ArcGIS.Samples
{
    internal class Module1 : Module
    {
        private static Module1 _this = null;

        public static Module1 Current => _this ??= (Module1)FrameworkApplication.FindModule("Hearth_ArcGIS_Samples_Module");

        public Module1()
        {
            // samplesettings.json文件内容
            // "SampleSettings": {
            //     "Value1": "asd",
            //     "Value2": 123,
            //     "Value3": 123.456,
            //     "Value4": [
            //         "asd",
            //         "zxc",
            //         "qwe"
            //     ]
            // }
            IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("samplesettings.json", true, true).Build();
            HearthApp.App.Configure<SampleSettings>(configuration.GetSection(typeof(SampleSettings).Name));

            HearthApp.App.RegisterAssemblyAndRefrencedAssembliesTypes(this.GetType().Assembly);
        }
    }
}

2.3 配置使用样例

<UserControl
    x:Class="Hearth.ArcGIS.Samples.PlugIns.Panes.OptionsSamplePaneView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:extensions="clr-namespace:ArcGIS.Desktop.Extensions;assembly=ArcGIS.Desktop.Extensions"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ui="clr-namespace:Hearth.ArcGIS.Samples.PlugIns.Panes"
    d:DesignHeight="600"
    d:DesignWidth="600"
    mc:Ignorable="d">
    <d:UserControl.DataContext>
        <ui:OptionsSamplePaneViewModel />
    </d:UserControl.DataContext>
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <extensions:DesignOnlyResourceDictionary Source="pack://application:,,,/ArcGIS.Desktop.Framework;component\Themes\Default.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <GroupBox Header="全局">
            <StackPanel Orientation="Vertical">
                <Button
                    Margin="3"
                    Command="{Binding RefreshOptionsCommand}"
                    Content="刷新"
                    Style="{StaticResource Esri_Button}" />
                <Label Content="OptionsValue: " />
                <TextBox
                    Height="180"
                    Margin="3"
                    Text="{Binding OptionsValue}" />
                <Label Content="OptionsMonitorValue: " />
                <TextBox
                    Height="180"
                    Margin="3"
                    Text="{Binding OptionsMonitorValue}" />
                <Label Content="OptionsSnapshotValue: " />
                <TextBox
                    Height="180"
                    Margin="3"
                    Text="{Binding OptionsSnapshotValue}" />
            </StackPanel>
        </GroupBox>
        <GroupBox Grid.Column="1" Header="作用域">
            <StackPanel Orientation="Vertical">
                <Button
                    Margin="3"
                    Command="{Binding RefreshScopeOptionsCommand}"
                    Content="刷新"
                    Style="{StaticResource Esri_Button}" />
                <Label Content="ScopeOptionsValue: " />
                <TextBox
                    Height="180"
                    Margin="3"
                    Text="{Binding ScopeOptionsValue}" />
                <Label Content="ScopeOptionsMonitorValue: " />
                <TextBox
                    Height="180"
                    Margin="3"
                    Text="{Binding ScopeOptionsMonitorValue}" />
                <Label Content="ScopeOptionsSnapshotValue: " />
                <TextBox
                    Height="180"
                    Margin="3"
                    Text="{Binding ScopeOptionsSnapshotValue}" />
            </StackPanel>
        </GroupBox>
    </Grid>
</UserControl>
using ArcGIS.Core.CIM;
using ArcGIS.Desktop.Framework;
using DryIoc;
using Hearth.ArcGIS.Samples.Configs;
using Hearth.ArcGIS.Samples.PlugIns.Contracts;
using Microsoft.Extensions.Options;
using System.Text.Json;
using System.Windows.Input;

namespace Hearth.ArcGIS.Samples.PlugIns.Panes
{
    internal class OptionsSamplePaneViewModel : InjectableViewStatePane
    {
        [Inject]
        private readonly IOptions<SampleSettings>? _options; // 在整个应用程序生命周期内只会初始化一次,不会发生变化
        [Inject]
        private readonly IOptionsMonitor<SampleSettings>? _optionsMonitor; // 实时监测配置变化,变化时可以触发通知
        [Inject]
        private readonly IOptionsSnapshot<SampleSettings>? _optionsSnapshot; // 在每个容器作用域内只会初始化一次,不会发生变化

        private IDisposable? _optionsMonitorDisposable;

        public OptionsSamplePaneViewModel() : base(null) { }
        public OptionsSamplePaneViewModel(CIMView cimView) : base(cimView)
        {
            _optionsMonitorDisposable = _optionsMonitor.OnChange(settings =>
            {
                JsonSerializerOptions options = new JsonSerializerOptions
                {
                    WriteIndented = true,
                };
                string json = JsonSerializer.Serialize(settings, options);
                Notification notification = new Notification
                {
                    Title = "配置更新",
                    Message = json,
                    ImageUrl = @"pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericInformation32.png"
                };
                FrameworkApplication.AddNotification(notification);
            });
        }

        protected override void OnClosed()
        {
            _optionsMonitorDisposable?.Dispose();
            base.OnClosed();
        }

        private string _optionsValue;
        public string OptionsValue
        {
            get => _optionsValue;
            set => SetProperty(ref _optionsValue, value);
        }

        private string _optionsMonitorValue;
        public string OptionsMonitorValue
        {
            get => _optionsMonitorValue;
            set => SetProperty(ref _optionsMonitorValue, value);
        }

        private string _optionsSnapshotValue;
        public string OptionsSnapshotValue
        {
            get => _optionsSnapshotValue;
            set => SetProperty(ref _optionsSnapshotValue, value);
        }


        private string _scopeOptionsValue;
        public string ScopeOptionsValue
        {
            get => _scopeOptionsValue;
            set => SetProperty(ref _scopeOptionsValue, value);
        }

        private string _scopeOptionsMonitorValue;
        public string ScopeOptionsMonitorValue
        {
            get => _scopeOptionsMonitorValue;
            set => SetProperty(ref _scopeOptionsMonitorValue, value);
        }

        private string _scopeOptionsSnapshotValue;
        public string ScopeOptionsSnapshotValue
        {
            get => _scopeOptionsSnapshotValue;
            set => SetProperty(ref _scopeOptionsSnapshotValue, value);
        }

        public ICommand RefreshOptionsCommand => new RelayCommand(RefreshOptions);
        public ICommand RefreshScopeOptionsCommand => new RelayCommand(RefreshScopeOptions);

        private void RefreshOptions()
        {
            JsonSerializerOptions options = new JsonSerializerOptions
            {
                WriteIndented = true,
            };
            // 修改配置文件后刷新
            OptionsValue = JsonSerializer.Serialize(_options?.Value, options); // 不会变化
            OptionsMonitorValue = JsonSerializer.Serialize(_optionsMonitor?.CurrentValue, options); // 会实时变化
            OptionsSnapshotValue = JsonSerializer.Serialize(_optionsSnapshot?.Value, options); // 不会变化(因为当前实例使用的是应用程序全局作用域)
        }

        private void RefreshScopeOptions()
        {
            JsonSerializerOptions options = new JsonSerializerOptions
            {
                WriteIndented = true,
            };
            // 修改配置文件后刷新
            using (IResolverContext resolver = HearthApp.AppContainer.OpenScope())
            {
                IOptions<SampleSettings> scopeOptions = resolver.Resolve<IOptions<SampleSettings>>();
                IOptionsMonitor<SampleSettings> scopeOptionsMonitor = resolver.Resolve<IOptionsMonitor<SampleSettings>>();
                IOptionsSnapshot<SampleSettings> scopeOptionsSnapshot = resolver.Resolve<IOptionsSnapshot<SampleSettings>>();
                ScopeOptionsValue = JsonSerializer.Serialize(scopeOptions.Value, options); // 不会变化
                ScopeOptionsMonitorValue = JsonSerializer.Serialize(scopeOptionsMonitor.CurrentValue, options); // 会实时变化
                ScopeOptionsSnapshotValue = JsonSerializer.Serialize(scopeOptionsSnapshot.Value, options); // 会实时变化
            }
        }
    }
}

3 使用日志

using Microsoft.Extensions.Logging;

namespace Hearth.ArcGIS.Samples.Services
{
    [Service(typeof(TestLogService))]
    public class TestLogService
    {
        private readonly ILogger<TestLogService> _logger;
        public TestLogService(ILogger<TestLogService> logger)
        {
            _logger = logger;
        }

        public void WriteLog()
        {
            _logger?.LogTrace("Configured Type Logger Class LogTrace");
            _logger?.LogDebug("Configured Type Logger Class LogDebug");
            _logger?.LogInformation("Configured Type Logger Class LogInformation");
            _logger?.LogWarning("Configured Type Logger Class LogWarning");
            _logger?.LogError("Configured Type Logger Class LogError");
            _logger?.LogCritical("Configured Type Logger Class LogCritical");
        }
    }
}
using Hearth.ArcGIS.Samples.PlugIns.Contracts;
using Hearth.ArcGIS.Samples.Services;
using Microsoft.Extensions.Logging;

namespace Hearth.ArcGIS.Samples.PlugIns.Menus
{
    internal class LogSampleButton : InjectableButton
    {
        [Inject]
        private readonly ILogger? _logger;
        [Inject]
        private readonly ILogger<LogSampleButton>? _typeLogger;
        [Inject]
        private readonly TestLogService? _testLogService;


        protected override void OnClick()
        {
            _logger?.LogTrace("Default Logger LogTrace");
            _logger?.LogDebug("Default Logger LogDebug");
            _logger?.LogInformation("Default Logger LogInformation");
            _logger?.LogWarning("Default Logger LogWarning");
            _logger?.LogError("Default Logger LogError");
            _logger?.LogCritical("Default Logger LogCritical");

            _typeLogger?.LogTrace("Not configured Type Logger Class LogTrace");
            _typeLogger?.LogDebug("Not configured Type Logger Class LogDebug");
            _typeLogger?.LogInformation("Not configured Type Logger Class LogInformation");
            _typeLogger?.LogWarning("Not configured Type Logger Class LogWarning");
            _typeLogger?.LogError("Not configured Type Logger Class LogError");
            _typeLogger?.LogCritical("Not configured Type Logger Class LogCritical");

            _testLogService?.WriteLog();
        }
    }
}

日志配置(.\Pro\bin\nlog.config):

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="OFF" internalLogFile="c:\temp\nlog-internal.log">
	<targets>
		<target xsi:type="File" name="f_all" fileName="${basedir}/GISAPP/logs/all-${shortdate}.log"
				archiveNumbering="Sequence" archiveEvery="Day" maxArchiveDays="30" archiveAboveSize="104857600"
				layout="[${longdate}] ${threadid} ${level} ${callsite} ${callsite-linenumber} ${message} ${exception}" />
		<target xsi:type="File" name="f_default" fileName="${basedir}/GISAPP/logs/default-${shortdate}.log"
				archiveNumbering="Sequence" archiveEvery="Day" maxArchiveDays="30" archiveAboveSize="104857600"
				layout="[${longdate}] ${threadid} ${level} ${callsite} ${callsite-linenumber} ${message} ${exception}" />
		<target xsi:type="File" name="f_test" fileName="${basedir}/GISAPP/logs/test-${shortdate}.log"
				archiveNumbering="Sequence" archiveEvery="Day" maxArchiveDays="30" archiveAboveSize="104857600"
				layout="[${longdate}] ${threadid} ${level} ${callsite} ${callsite-linenumber} ${message} ${exception}" />
	</targets>
	<rules>
		<logger name="*" minlevel="Trace" writeTo="f_all" />
		<logger name="." minlevel="Trace" writeTo="f_default" />
		<logger name="Hearth.ArcGIS.Samples.Services.TestLogService" minlevel="Trace" writeTo="f_test" />
	</rules>
</nlog>

日志:

all-2025-02-19.log

[2025-02-19 15:34:01.9110] 1 Trace Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 19 Default Logger LogTrace 
[2025-02-19 15:34:02.0040] 1 Debug Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 20 Default Logger LogDebug 
[2025-02-19 15:34:02.0040] 1 Info Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 21 Default Logger LogInformation 
[2025-02-19 15:34:02.0040] 1 Warn Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 22 Default Logger LogWarning 
[2025-02-19 15:34:02.0040] 1 Error Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 23 Default Logger LogError 
[2025-02-19 15:34:02.0141] 1 Fatal Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 24 Default Logger LogCritical 
[2025-02-19 15:34:02.0141] 1 Trace Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 26 Not configured Type Logger Class LogTrace 
[2025-02-19 15:34:02.0141] 1 Debug Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 27 Not configured Type Logger Class LogDebug 
[2025-02-19 15:34:02.0141] 1 Info Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 28 Not configured Type Logger Class LogInformation 
[2025-02-19 15:34:02.0141] 1 Warn Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 29 Not configured Type Logger Class LogWarning 
[2025-02-19 15:34:02.0141] 1 Error Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 30 Not configured Type Logger Class LogError 
[2025-02-19 15:34:02.0141] 1 Fatal Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 31 Not configured Type Logger Class LogCritical 
[2025-02-19 15:34:02.0141] 1 Trace Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 16 Configured Type Logger Class LogTrace 
[2025-02-19 15:34:02.0141] 1 Debug Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 17 Configured Type Logger Class LogDebug 
[2025-02-19 15:34:02.0141] 1 Info Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 18 Configured Type Logger Class LogInformation 
[2025-02-19 15:34:02.0141] 1 Warn Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 19 Configured Type Logger Class LogWarning 
[2025-02-19 15:34:02.0141] 1 Error Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 20 Configured Type Logger Class LogError 
[2025-02-19 15:34:02.0141] 1 Fatal Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 21 Configured Type Logger Class LogCritical 

default-2025-02-19.log

[2025-02-19 15:34:01.9110] 1 Trace Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 19 Default Logger LogTrace 
[2025-02-19 15:34:02.0040] 1 Debug Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 20 Default Logger LogDebug 
[2025-02-19 15:34:02.0040] 1 Info Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 21 Default Logger LogInformation 
[2025-02-19 15:34:02.0040] 1 Warn Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 22 Default Logger LogWarning 
[2025-02-19 15:34:02.0040] 1 Error Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 23 Default Logger LogError 
[2025-02-19 15:34:02.0141] 1 Fatal Hearth.ArcGIS.Samples.PlugIns.Menus.LogSampleButton.OnClick 24 Default Logger LogCritical 

test-2025-02-19.log

[2025-02-19 15:34:02.0141] 1 Trace Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 16 Configured Type Logger Class LogTrace 
[2025-02-19 15:34:02.0141] 1 Debug Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 17 Configured Type Logger Class LogDebug 
[2025-02-19 15:34:02.0141] 1 Info Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 18 Configured Type Logger Class LogInformation 
[2025-02-19 15:34:02.0141] 1 Warn Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 19 Configured Type Logger Class LogWarning 
[2025-02-19 15:34:02.0141] 1 Error Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 20 Configured Type Logger Class LogError 
[2025-02-19 15:34:02.0141] 1 Fatal Hearth.ArcGIS.Samples.Services.TestLogService.WriteLog 21 Configured Type Logger Class LogCritical 

4 使用AutoMapper

namespace Hearth.ArcGIS.Samples
{
    public class Person
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime Birthday { get; set; }
    }
}
using ArcGIS.Desktop.Framework.Contracts;

namespace Hearth.ArcGIS.Samples
{
    public class PersonVO : ViewModelBase
    {
        private Guid _id;
        public Guid Id
        {
            get => _id;
            set => SetProperty(ref _id, value);
        }
        private string _name;
        public string Name
        {
            get => _name;
            set => SetProperty(ref _name, value);
        }
        private int _age;
        public int Age
        {
            get => _age;
            set => SetProperty(ref _age, value);
        }
        private DateTime _birthday;
        public DateTime Birthday
        {
            get => _birthday;
            set => SetProperty(ref _birthday, value);
        }
    }
}

方式一:使用Profile配置

using AutoMapper;

namespace Hearth.ArcGIS.Samples
{
    public class PersonProfile : Profile
    {
        public PersonProfile()
        {
            CreateMap<Person, PersonVO>().ReverseMap();
        }
    }
}

方式二:使用[AutoMap]特性标记

[AutoMap(typeof(PersonVO))]
public class Person
{
    // ...
}

[AutoMap(typeof(Person))]
public class PersonVO : ViewModelBase
{
    // ...
}

使用:

public class SomeSample
{
    private readonly IMapper _mapper;
    public SomeSample(IMapper mapper)
    {
        _mapper = mapper;
    }

    public void DoSomeThings(PersonVO personVO)
    {
        // ...
        Person person = _mapper.Map<Person>(personVO);
    }
}
Product Compatible and additional computed target framework versions.
.NET net6.0-windows7.0 is compatible.  net7.0-windows was computed.  net8.0-windows was computed.  net8.0-windows7.0 is compatible.  net9.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

AutoMapper自动配置,优化代码结构,优化注册