Taneli çağrı filtreleri

Taneli çağrı filtreleri, hububat çağrılarını kesmek için bir yol sağlar. Filtreler, bir taneli çağrıdan önce ve sonra kodu yürütebilir. Aynı anda birden çok filtre yüklenebilir. Filtreler zaman uyumsuz ve , bağımsız değişkenlerini ve çağrılan yöntemin dönüş değerini değiştirebilir RequestContext. Filtreler ayrıca MethodInfo grain sınıfında çağrılan yöntemi inceleyebilir ve özel durumları atmak veya işlemek için kullanılabilir.

Hububat çağrı filtrelerinin bazı örnek kullanımları şunlardır:

  • Yetkilendirme: Bir filtre çağrılan yöntemi ve bağımsız değişkenleri veya içindeki bazı yetkilendirme bilgilerini RequestContext inceleyip çağrının devam etmesine izin verilip verilmeyeceğini belirleyebilir.
  • Günlük/Telemetri: Bir filtre bilgileri günlüğe kaydedebilir ve zamanlama verilerini ve yöntem çağırma hakkındaki diğer istatistikleri yakalayabilir.
  • Hata İşleme: Filtre, bir yöntem çağrısı tarafından oluşturulan özel durumları kesebilir ve başka bir özel duruma dönüştürebilir veya filtreden geçerken özel durumu işleyebilir.

Filtreler iki farklı şekilde gelir:

  • Gelen arama filtreleri
  • Giden arama filtreleri

Gelen arama filtreleri bir çağrı alınırken yürütülür. Arama yapılırken giden arama filtreleri yürütülür.

Gelen arama filtreleri

Gelen tanecik çağrı filtreleri, tek bir yöntemi olan arabirimi uygular IIncomingGrainCallFilter :

public interface IIncomingGrainCallFilter
{
    Task Invoke(IIncomingGrainCallContext context);
}

IIncomingGrainCallContext yöntemine Invoke geçirilen bağımsız değişken aşağıdaki şekle sahiptir:

public interface IIncomingGrainCallContext
{
    /// <summary>
    /// Gets the grain being invoked.
    /// </summary>
    IAddressable Grain { get; }

    /// <summary>
    /// Gets the <see cref="MethodInfo"/> for the interface method being invoked.
    /// </summary>
    MethodInfo InterfaceMethod { get; }

    /// <summary>
    /// Gets the <see cref="MethodInfo"/> for the implementation method being invoked.
    /// </summary>
    MethodInfo ImplementationMethod { get; }

    /// <summary>
    /// Gets the arguments for this method invocation.
    /// </summary>
    object[] Arguments { get; }

    /// <summary>
    /// Invokes the request.
    /// </summary>
    Task Invoke();

    /// <summary>
    /// Gets or sets the result.
    /// </summary>
    object Result { get; set; }
}

Yöntemin IIncomingGrainCallFilter.Invoke(IIncomingGrainCallContext) sonraki yapılandırılmış filtreyi ve sonunda grain yönteminin kendisini yürütmek için öğesinin sonucunu IIncomingGrainCallContext.Invoke() beklemesi veya döndürmesi gerekir. Result yöntemi beklendikten Invoke() sonra özelliği değiştirilebilir. ImplementationMethod özelliği, uygulama sınıfının değerini döndürürMethodInfo. MethodInfo arabirim yönteminin özelliği kullanılarak InterfaceMethod erişilebilir. Taneli çağrı filtreleri, bir taneye yapılan tüm yöntem çağrıları için çağrılır ve bu, tahıla yüklenen tanecik uzantılarına (uygulamaları) yapılan çağrıları IGrainExtensioniçerir. Örneğin, Akışlar ve İptal Belirteçlerini uygulamak için tanecik uzantıları kullanılır. Bu nedenle değerinin ImplementationMethod tahıl sınıfının kendisinde her zaman bir yöntem olmaması beklenmelidir.

Gelen tanecik çağrı filtrelerini yapılandırma

uygulamaları IIncomingGrainCallFilter Bağımlılık Ekleme yoluyla silo genelinde filtreler olarak kaydedilebilir veya doğrudan uygulanan IIncomingGrainCallFilter bir tanecik aracılığıyla tane düzeyi filtreler olarak kaydedilebilir.

Silo çapında grenli çağrı filtreleri

Temsilci, Bağımlılık Ekleme kullanılarak silo genelinde bir taneli çağrı filtresi olarak kaydedilebilir:

siloHostBuilder.AddIncomingGrainCallFilter(async context =>
{
    // If the method being called is 'MyInterceptedMethod', then set a value
    // on the RequestContext which can then be read by other filters or the grain.
    if (string.Equals(
        context.InterfaceMethod.Name,
        nameof(IMyGrain.MyInterceptedMethod)))
    {
        RequestContext.Set(
            "intercepted value", "this value was added by the filter");
    }

    await context.Invoke();

    // If the grain method returned an int, set the result to double that value.
    if (context.Result is int resultValue)
    {
        context.Result = resultValue * 2;
    }
});

Benzer şekilde, bir sınıf yardımcı yöntemi kullanılarak AddIncomingGrainCallFilter bir taneli çağrı filtresi olarak kaydedilebilir. Aşağıda, her taneli yöntemin sonuçlarını günlüğe kaydeden bir taneli çağrı filtresi örneği verilmiştir:

public class LoggingCallFilter : IIncomingGrainCallFilter
{
    private readonly Logger _logger;

    public LoggingCallFilter(Factory<string, Logger> loggerFactory)
    {
        _logger = loggerFactory(nameof(LoggingCallFilter));
    }

    public async Task Invoke(IIncomingGrainCallContext context)
    {
        try
        {
            await context.Invoke();
            var msg = string.Format(
                "{0}.{1}({2}) returned value {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                context.Result);
            _logger.Info(msg);
        }
        catch (Exception exception)
        {
            var msg = string.Format(
                "{0}.{1}({2}) threw an exception: {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                exception);
            _logger.Info(msg);

            // If this exception is not re-thrown, it is considered to be
            // handled by this filter.
            throw;
        }
    }
}

Bu filtre daha sonra uzantı yöntemi kullanılarak AddIncomingGrainCallFilter kaydedilebilir:

siloHostBuilder.AddIncomingGrainCallFilter<LoggingCallFilter>();

Alternatif olarak, filtre uzantı yöntemi olmadan kaydedilebilir:

siloHostBuilder.ConfigureServices(
    services => services.AddSingleton<IIncomingGrainCallFilter, LoggingCallFilter>());

Tanecik başına grain çağrı filtreleri

Bir taneli sınıf, kendisini bir taneli çağrı filtresi olarak kaydedebilir ve aşağıdaki gibi uygulayarak bu sınıfa yapılan çağrıları filtreleyebilir IIncomingGrainCallFilter :

public class MyFilteredGrain
    : Grain, IMyFilteredGrain, IIncomingGrainCallFilter
{
    public async Task Invoke(IIncomingGrainCallContext context)
    {
        await context.Invoke();

        // Change the result of the call from 7 to 38.
        if (string.Equals(
            context.InterfaceMethod.Name,
            nameof(this.GetFavoriteNumber)))
        {
            context.Result = 38;
        }
    }

    public Task<int> GetFavoriteNumber() => Task.FromResult(7);
}

Yukarıdaki örnekte, dönüş değeri filtre tarafından değiştirildiği için GetFavoriteNumber yöntemine yapılan tüm çağrılar yerine 7döndürülecektir38.

Filtreler için başka bir kullanım örneği de erişim denetimindedir, örneğin:

[AttributeUsage(AttributeTargets.Method)]
public class AdminOnlyAttribute : Attribute { }

public class MyAccessControlledGrain
    : Grain, IMyFilteredGrain, IIncomingGrainCallFilter
{
    public Task Invoke(IIncomingGrainCallContext context)
    {
        // Check access conditions.
        var isAdminMethod =
            context.ImplementationMethod.GetCustomAttribute<AdminOnlyAttribute>();
        if (isAdminMethod && !(bool) RequestContext.Get("isAdmin"))
        {
            throw new AccessDeniedException(
                $"Only admins can access {context.ImplementationMethod.Name}!");
        }

        return context.Invoke();
    }

    [AdminOnly]
    public Task<int> SpecialAdminOnlyOperation() => Task.FromResult(7);
}

Yukarıdaki örnekte yöntemi SpecialAdminOnlyOperation yalnızca içinde RequestContextolarak ayarlandıysa "isAdmin"true çağrılabilir. Bu şekilde, yetkilendirme için taneli çağrı filtreleri kullanılabilir. Bu örnekte, değerin doğru ayarlandığından ve kimlik doğrulamasının doğru yapıldığından emin olmak çağıranın "isAdmin" sorumluluğundadır. özniteliğinin [AdminOnly] grain sınıfı yönteminde belirtildiğine dikkat edin. Bunun nedeni, özelliğinin ImplementationMethod arabirimini değil uygulamanın sonucunu döndürmesidir MethodInfo . Filtre özelliği de denetleyebiliyor InterfaceMethod .

Taneli çağrı filtresi sıralama

Grain call filters follow a defined ordering:

  1. IIncomingGrainCallFilter bağımlılık ekleme kapsayıcısında, kaydedildikleri sırayla yapılandırılan uygulamalar.
  2. Tanecik uygulanıyorsa IIncomingGrainCallFilter, tanecik düzeyinde filtre.
  3. Tahıl yöntemi uygulaması veya tanecik uzantısı yöntemi uygulaması.

Her çağrısı bir sonraki tanımlı filtreyi IIncomingGrainCallContext.Invoke() kapsüller, böylece her filtre zincirdeki bir sonraki filtreden önce ve sonra kod yürütme şansına sahip olur ve sonunda taneli yöntemin kendisi olur.

Giden arama filtreleri

Giden tanecik çağrı filtreleri, çağrılan (dilim) yerine arayanda (istemci) çağrılmaları arasındaki en büyük fark olan gelen tanecik çağrı filtrelerine benzer.

Giden tanecik çağrı filtreleri, tek bir yöntemi olan arabirimini uygular IOutgoingGrainCallFilter :

public interface IOutgoingGrainCallFilter
{
    Task Invoke(IOutgoingGrainCallContext context);
}

IOutgoingGrainCallContext yöntemine Invoke geçirilen bağımsız değişken aşağıdaki şekle sahiptir:

public interface IOutgoingGrainCallContext
{
    /// <summary>
    /// Gets the grain being invoked.
    /// </summary>
    IAddressable Grain { get; }

    /// <summary>
    /// Gets the <see cref="MethodInfo"/> for the interface method being invoked.
    /// </summary>
    MethodInfo InterfaceMethod { get; }

    /// <summary>
    /// Gets the arguments for this method invocation.
    /// </summary>
    object[] Arguments { get; }

    /// <summary>
    /// Invokes the request.
    /// </summary>
    Task Invoke();

    /// <summary>
    /// Gets or sets the result.
    /// </summary>
    object Result { get; set; }
}

Yöntemin IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext) sonraki yapılandırılmış filtreyi ve sonunda grain yönteminin kendisini yürütmek için öğesinin sonucunu IOutgoingGrainCallContext.Invoke() beklemesi veya döndürmesi gerekir. Result yöntemi beklendikten Invoke() sonra özelliği değiştirilebilir. MethodInfo Çağrılan arabirim yönteminin özelliği kullanılarak InterfaceMethod erişilebilir. Giden tanecik çağrı filtreleri, bir taneye yapılan tüm yöntem çağrıları için çağrılır ve bu, tarafından Orleansyapılan sistem yöntemlerine yapılan çağrıları içerir.

Giden tanecik çağrı filtrelerini yapılandırma

uygulamaları IOutgoingGrainCallFilter , Bağımlılık Ekleme kullanılarak hem silolara hem de istemcilere kaydedilebilir.

Temsilci, şu şekilde bir çağrı filtresi olarak kaydedilebilir:

builder.AddOutgoingGrainCallFilter(async context =>
{
    // If the method being called is 'MyInterceptedMethod', then set a value
    // on the RequestContext which can then be read by other filters or the grain.
    if (string.Equals(
        context.InterfaceMethod.Name,
        nameof(IMyGrain.MyInterceptedMethod)))
    {
        RequestContext.Set(
            "intercepted value", "this value was added by the filter");
    }

    await context.Invoke();

    // If the grain method returned an int, set the result to double that value.
    if (context.Result is int resultValue)
    {
        context.Result = resultValue * 2;
    }
});

Yukarıdaki kodda veya builderIClientBuilderörneği ISiloHostBuilder olabilir.

Benzer şekilde, bir sınıf giden bir taneli çağrı filtresi olarak kaydedilebilir. Aşağıda, her taneli yöntemin sonuçlarını günlüğe kaydeden bir taneli çağrı filtresi örneği verilmiştir:

public class LoggingCallFilter : IOutgoingGrainCallFilter
{
    private readonly Logger _logger;

    public LoggingCallFilter(Factory<string, Logger> loggerFactory)
    {
        _logger = loggerFactory(nameof(LoggingCallFilter));
    }

    public async Task Invoke(IOutgoingGrainCallContext context)
    {
        try
        {
            await context.Invoke();
            var msg = string.Format(
                "{0}.{1}({2}) returned value {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                context.Result);
            _logger.Info(msg);
        }
        catch (Exception exception)
        {
            var msg = string.Format(
                "{0}.{1}({2}) threw an exception: {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                exception);
            this.log.Info(msg);

            // If this exception is not re-thrown, it is considered to be
            // handled by this filter.
            throw;
        }
    }
}

Bu filtre daha sonra uzantı yöntemi kullanılarak AddOutgoingGrainCallFilter kaydedilebilir:

builder.AddOutgoingGrainCallFilter<LoggingCallFilter>();

Alternatif olarak, filtre uzantı yöntemi olmadan kaydedilebilir:

builder.ConfigureServices(
    services => services.AddSingleton<IOutgoingGrainCallFilter, LoggingCallFilter>());

Temsilci arama filtresi örneğinde olduğu gibi, builder veya IClientBuilderörneği ISiloHostBuilder olabilir.

Kullanım örnekleri

Özel durum dönüştürme

Sunucudan oluşturulan bir özel durum istemcide seri durumdan çıkarıldığında, bazen gerçek özel durum yerine aşağıdaki özel durumu alabilirsiniz: TypeLoadException: Could not find Whatever.dll.

Bu durum, özel durumu içeren derleme istemcinin kullanımına sunulmazsa gerçekleşir. Örneğin, hububat uygulamalarınızda Entity Framework kullandığınızı varsayalım; o zaman bir EntityException atılabilir. Öte yandan istemci, temel alınan veri erişim katmanını bilmediğinden başvurmaz EntityFramework.dll (ve olmamalıdır).

İstemci dosyasını seri durumdan EntityExceptionçıkarmaya çalıştığında, eksik DLL nedeniyle başarısız olur; sonuç olarak, özgün EntityExceptionöğesini gizleyen bir TypeLoadException oluşturulur.

İstemci hiçbir zaman işleyemeyecek EntityExceptionolduğundan bunun oldukça iyi olduğunu iddia edebilir; aksi takdirde başvurması EntityFramework.dllgerekir.

Peki istemci en azından özel durumu günlüğe kaydetmek isterse ne olur? Sorun, özgün hata iletisinin kaybolmasıdır. Bu sorunu geçici olarak çözmenin bir yolu, sunucu tarafı özel durumlarını kesmek ve özel durum türü Exception istemci tarafında büyük olasılıkla bilinmiyorsa bunları düz özel durumlarla değiştirmektir.

Bununla birlikte, göz önünde bulundurmamız gereken önemli bir şey vardır: Yalnızca arayanın grain istemcisi olması durumunda bir özel durumu değiştirmek istiyoruz. Arayanın başka bir tanecik (veya tahıl çağrıları Orleans yapan altyapı, örneğin tahıl üzerinde GrainBasedReminderTable ) olması durumunda bir özel durumu değiştirmek istemeyiz.

Sunucu tarafında bu işlem silo düzeyinde bir kesme noktası ile yapılabilir:

public class ExceptionConversionFilter : IIncomingGrainCallFilter
{
    private static readonly HashSet<string> KnownExceptionTypeAssemblyNames =
        new HashSet<string>
        {
            typeof(string).Assembly.GetName().Name,
            "System",
            "System.ComponentModel.Composition",
            "System.ComponentModel.DataAnnotations",
            "System.Configuration",
            "System.Core",
            "System.Data",
            "System.Data.DataSetExtensions",
            "System.Net.Http",
            "System.Numerics",
            "System.Runtime.Serialization",
            "System.Security",
            "System.Xml",
            "System.Xml.Linq",
            "MyCompany.Microservices.DataTransfer",
            "MyCompany.Microservices.Interfaces",
            "MyCompany.Microservices.ServiceLayer"
        };

    public async Task Invoke(IIncomingGrainCallContext context)
    {
        var isConversionEnabled =
            RequestContext.Get("IsExceptionConversionEnabled") as bool? == true;

        if (!isConversionEnabled)
        {
            // If exception conversion is not enabled, execute the call without interference.
            await context.Invoke();
            return;
        }

        RequestContext.Remove("IsExceptionConversionEnabled");
        try
        {
            await context.Invoke();
        }
        catch (Exception exc)
        {
            var type = exc.GetType();

            if (KnownExceptionTypeAssemblyNames.Contains(
                type.Assembly.GetName().Name))
            {
                throw;
            }

            // Throw a base exception containing some exception details.
            throw new Exception(
                string.Format(
                    "Exception of non-public type '{0}' has been wrapped."
                    + " Original message: <<<<----{1}{2}{3}---->>>>",
                    type.FullName,
                    Environment.NewLine,
                    exc,
                    Environment.NewLine));
        }
    }
}

Bu filtre daha sonra siloya kaydedilebilir:

siloHostBuilder.AddIncomingGrainCallFilter<ExceptionConversionFilter>();

Giden arama filtresi ekleyerek istemci tarafından yapılan çağrılar için filtreyi etkinleştirin:

clientBuilder.AddOutgoingGrainCallFilter(context =>
{
    RequestContext.Set("IsExceptionConversionEnabled", true);
    return context.Invoke();
});

Bu şekilde istemci sunucuya özel durum dönüştürmeyi kullanmak istediğini söyler.

Kesme hatlarından tanecikleri çağırma

Kesme noktası sınıfına ekleyerek IGrainFactory bir kesme aracından taneli çağrılar yapmak mümkündür:

private readonly IGrainFactory _grainFactory;

public CustomCallFilter(IGrainFactory grainFactory)
{
    _grainFactory = grainFactory;
}

public async Task Invoke(IIncomingGrainCallContext context)
{
    // Hook calls to any grain other than ICustomFilterGrain implementations.
    // This avoids potential infinite recursion when calling OnReceivedCall() below.
    if (!(context.Grain is ICustomFilterGrain))
    {
        var filterGrain = _grainFactory.GetGrain<ICustomFilterGrain>(
            context.Grain.GetPrimaryKeyLong());

        // Perform some grain call here.
        await filterGrain.OnReceivedCall();
    }

    // Continue invoking the call on the target grain.
    await context.Invoke();
}