Hearth.ArcGIS 1.2.0

dotnet add package Hearth.ArcGIS --version 1.2.0
                    
NuGet\Install-Package Hearth.ArcGIS -Version 1.2.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.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hearth.ArcGIS" Version="1.2.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.2.0
                    
#r "nuget: Hearth.ArcGIS, 1.2.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.2.0
                    
Install Hearth.ArcGIS as a Cake Addin
#tool nuget:?package=Hearth.ArcGIS&version=1.2.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 (1)

Showing the top 1 NuGet packages that depend on Hearth.ArcGIS:

Package Downloads
Hearth.ArcGIS.Infrastructure

Hearth ArcGIS 框架基础设施扩展

GitHub repositories

This package is not used by any popular GitHub repositories.

优化代码结构