ILogger サポートを Umbraco v8 に追加


Vendr .NET Core の準備に関する私の最近の作業の一環として、および multi-targeted approach を使用しているため、Umbraco v8 と v9 の間の大きな変更点の 1 つは、Umbraco ロガー インターフェイスから移動した場所でのログ記録です.

public interface ILogger {
    Info<T>(...);
    Debug<T>(...);
    Error<T>(...);
}


の .NET Core ロガー インターフェイスに

public interface ILogger<T> {
    Info(...);
    Debug(...);
    Error(...);
}


機能的にはそれほど大きな変更ではありませんが、マルチターゲット アプローチに関しては、管理しなければならない相違点の数を最小限に抑えるために、両方の実装で標準 API を維持しようとすることが最大の目的の 1 つです.

また、ロギングは横断的な関心事であるため、多くの場所で使用されているため、あらゆる場所で条件付きディレクティブを使用する必要はありません.

#if NET
    private readonly ILogger<MyClass> _logger;

    public MyClass(ILogger<MyClass> logger) 
    {
        _logger = logger;
    }
#else
    private readonly ILogger _logger;

    public MyClass(ILogger logger) 
    {
        _logger = logger;
    }
#endif

    public void MyMethod() 
    {
        // Do some common logic and log a result
        var result = "Some value";
#if NET
        _logger.Debug(result);
#else
        _logger.Debug<MyClass>(result);
#endif
    }



うん! 🤮

独自のロガー抽象化の導入



パズルの最初の部分は、独自のロガー抽象化を導入し、異なるフレームワークに 2 つの実装を提供することです.これは、ほとんどのコードが単純にインターフェイスに依存し、2 つの実装が違いを隠して処理することを意味します.

簡単な例は次のようになります.

// Our custom ILogger interface
public interface ILogger<T>
{
    Info(string message);
    Debug(string message);
    Error(Exception exception, string message);
}

#if NET

// The Umbravo v9 logger implementation
public class MicrosoftLogger<T> : ILogger<T>
{
    private global::Microsoft.Extensions.Logging.ILogger<T> _logger;

    public MicrosoftLogger(global::Microsoft.Extensions.Logging.ILogger<T> logger)
        => _logger = logger;

    public void Info(string message)
        => _logger.LogInfo(message);

    public void Debug(string message)
        => _logger.LogDebug(message);

    public void Error(Exception exception, , string message)
        => _logger.LogDebug(exception, message);
}

#else

// The Umbravo v8 logger implementation
public class UmbracoLogger<T> : ILogger<T>
{
    private global::Umbraco.Core.Logging.ILogger _logger;

    public UmbracoLogger(global::Umbraco.Core.Logging.ILogger logger)
        => _logger = logger;

    public void Info(string message)
        => _logger.Info(typeof(T), message);

    public void Debug(string message)
        => _logger.Debug(typeof(T), message);

    public void Error(Exception exception, , string message)
        => _logger.Error(typeof(T), exception, message);
}

#endif



抽象化を導入することで、コードは独自のインターフェイスに依存するだけで済み、実装方法を気にする必要がなくなりました.


private readonly ILogger<MyClass> _logger;

public MyClass(ILogger<MyClass> logger) 
{
    _logger = logger;
}

public void MyMethod() 
{
    // Do some common logic and log a result
    var result = "Some value";

    _logger.Debug(result);
}



はるかに良い😻

実装を DI コンテナーに登録する



カスタム ロガーを使用できるようにするために最後に行う必要があるのは、実装を DI コンテナーに登録することです.

Umbraco v9 と .NET Core の場合、MSDI は汎用インターフェイスの登録をサポートしているため、これは非常に簡単です.そのため、パッケージを追加するために IUmbracoBuilder 拡張機能で次のコードを呼び出して登録します.

public static class MyPackageExtensions
{
    public static IUmbracoBuilder AddMyPackage(this IUmbracoBuilder builder)
    {
        builder.Services.AddSingleton(typeof(ILogger<>), typeof(MicrosoftLogger<>));
        // Register your other services...
    }
}


ただし、Umbraco v8 の場合、少し問題があります.上記の v9 スニペットでわかるように、ILogger<MyClass> のような型指定されたロガーは登録しません.これは、ログインできるようにするすべてのクラスのインスタンスを登録する必要があるためです.代わりに、ジェネリック インターフェイスをジェネリック実装に登録し、DI コンテナーが、注入された依存関係の型に基づいてジェネリック型を自動的に解決できるようにする必要があります.

残念ながら、Umbraco はすぐに使用できる機能を提供していないため、自分で追加する必要があります.しかし、ありがたいことに、彼らは do have something pretty close that we can modify .

次のコードを使用して、Umbraco Composer のカスタム拡張メソッドを定義する必要があります.

public static class ComposerExtensions
{
    internal static void RegisterAuto(this Composer composer, Type serviceBaseType, Type implementingBaseType)
    {
        var container = composer.Concrete as ServiceContainer;
        if (container != null)
        {
            container.RegisterFallback((serviceType, serviceName) =>
            {
                if (serviceType.IsInterface && !implementingBaseType.IsInterface
                    && serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == serviceBaseType)
                {
                    var genericArgs = serviceType.GetGenericArguments();
                    var implementingType = implementingBaseType.MakeGenericType(genericArgs);

                    container.Register(serviceType, implementingType);
                }

                return false;
            }, null);
        }
    }
}


これが行っているのは、要求された型が見つからない場合に呼び出される LightInject コンテナー (これは、Umbraco v8 がフードの下で DI に使用するものです) にフォールバック メソッドを登録することです.

この関数では、要求された型が関心のあるインターフェイスであるかどうかを確認し、そうであれば、その型からジェネリック型引数を抽出し、それらのジェネリック引数を渡す具体的な型を構築し、そのサービスをコンテナーに登録します.今後のリクエストについて.

それが整ったら、コンポーザー内で v8 実装をこのように登録できます.

public class MyComposer : IUserComposer
{
    public void Compose(Composition composition)
    {
        composition.RegisterAuto(typeof(ILogger<>), typeof(UmbracoLogger<>));
        // Register your other services...
    }
}


そしてワラ!カスタム汎用ロガーを Umbraco v9 および v8 に登録し、コードベース全体で一貫した使用法を維持できるようになりました 😎

これが他のマルチターゲット パッケージ開発者の役に立つことを願っています.