diff --git a/src/Dapr.Actors/Resources/SR.Designer.cs b/src/Dapr.Actors/Resources/SR.Designer.cs index f507b596a..866ebd3ba 100644 --- a/src/Dapr.Actors/Resources/SR.Designer.cs +++ b/src/Dapr.Actors/Resources/SR.Designer.cs @@ -230,7 +230,18 @@ internal static string ErrorNotAnActor { return ResourceManager.GetString("ErrorNotAnActor", resourceCulture); } } - + + /// + /// Looks up a localized string similar to The type '{0}' is not an Actor Interface. An actor type must derive from '{1}'.. + /// + internal static string ErrorNotAnActorInterface + { + get + { + return ResourceManager.GetString("ErrorNotAnActorInterface", resourceCulture); + } + } + /// /// Looks up a localized string similar to The type '{0}' is not an actor interface as it does not derive from the interface '{1}'.. /// diff --git a/src/Dapr.Actors/Runtime/ActorRegistrationCollection.cs b/src/Dapr.Actors/Runtime/ActorRegistrationCollection.cs index 69c01646d..bff587a53 100644 --- a/src/Dapr.Actors/Runtime/ActorRegistrationCollection.cs +++ b/src/Dapr.Actors/Runtime/ActorRegistrationCollection.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ public void RegisterActor(Action configure = null) public void RegisterActor(ActorRuntimeOptions typeOptions, Action configure = null) where TActor : Actor { - RegisterActor(null, typeOptions, configure); + RegisterActor(typeof(TActor), default, typeOptions, configure); } /// @@ -64,21 +64,81 @@ public void RegisterActor(ActorRuntimeOptions typeOptions, Action(string actorTypeName, Action configure = null) where TActor : Actor { - RegisterActor(actorTypeName, null, configure); + RegisterActor(typeof(TActor), actorTypeName, null, configure); } /// /// Registers an actor type in the collection. /// + /// Type of actor interface. + /// Type of actor. + /// An optional delegate used to configure the actor registration. + public void RegisterActor(Action configure = null) + where TActorInterface : IActor + where TActor : Actor, TActorInterface + { + RegisterActor(actorTypeName: null, configure); + } + + /// + /// Registers an actor type in the collection. + /// + /// Type of actor interface. + /// Type of actor. + /// An optional that defines values for this type alone. + /// An optional delegate used to configure the actor registration. + public void RegisterActor(ActorRuntimeOptions typeOptions, Action configure = null) + where TActorInterface : IActor + where TActor : Actor, TActorInterface + { + RegisterActor(typeof(TActorInterface), typeof(TActor), null, typeOptions, configure); + } + + /// + /// Registers an actor type in the collection. + /// + /// Type of actor interface. /// Type of actor. /// The name of the actor type represented by the actor. + /// An optional delegate used to configure the actor registration. + /// The value of will have precedence over the default actor type name derived from the actor implementation type or any type name set via . + public void RegisterActor(string actorTypeName, Action configure = null) + where TActorInterface : IActor + where TActor : Actor, TActorInterface + { + RegisterActor(typeof(TActorInterface), typeof(TActor), actorTypeName, null, configure); + } + + /// + /// Registers an actor type in the collection. + /// + /// Type of actor. + /// The name of the actor type represented by the actor. /// An optional that defines values for this type alone. /// An optional delegate used to configure the actor registration. /// The value of will have precedence over the default actor type name derived from the actor implementation type or any type name set via . - public void RegisterActor(string actorTypeName, ActorRuntimeOptions typeOptions, Action configure = null) - where TActor : Actor + public void RegisterActor(Type actorType, string actorTypeName, ActorRuntimeOptions typeOptions, Action configure = null) + { + RegisterActorInternal(default, actorType, actorTypeName, typeOptions, configure); + } + + /// + /// Registers an actor type in the collection. + /// + /// Type of actor interface. + /// Type of actor. + /// The name of the actor type represented by the actor. + /// An optional that defines values for this type alone. + /// An optional delegate used to configure the actor registration. + /// The value of will have precedence over the default actor type name derived from the actor implementation type or any type name set via . + public void RegisterActor(Type actorInterfaceType, Type actorType, string actorTypeName, ActorRuntimeOptions typeOptions, Action configure = null) + { + RegisterActorInternal(actorInterfaceType, actorType, actorTypeName, typeOptions, configure); + } + + private void RegisterActorInternal(Type actorInterfaceType, Type actorType, string actorTypeName, ActorRuntimeOptions typeOptions, Action configure = null) { - var actorTypeInfo = ActorTypeInformation.Get(typeof(TActor), actorTypeName); + var actorTypeInfo = ActorTypeInformation.Get(actorInterfaceType, actorType, actorTypeName); var registration = new ActorRegistration(actorTypeInfo, typeOptions); configure?.Invoke(registration); this.Add(registration); diff --git a/src/Dapr.Actors/Runtime/ActorTypeExtensions.cs b/src/Dapr.Actors/Runtime/ActorTypeExtensions.cs index 4acaa3912..ddb2e79ed 100644 --- a/src/Dapr.Actors/Runtime/ActorTypeExtensions.cs +++ b/src/Dapr.Actors/Runtime/ActorTypeExtensions.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/Dapr.Actors/Runtime/ActorTypeInformation.cs b/src/Dapr.Actors/Runtime/ActorTypeInformation.cs index 801524218..c4aa21b02 100644 --- a/src/Dapr.Actors/Runtime/ActorTypeInformation.cs +++ b/src/Dapr.Actors/Runtime/ActorTypeInformation.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -120,6 +120,40 @@ public static ActorTypeInformation Get(Type actorType) /// The value of will have precedence over the default actor type name derived from the actor implementation type or any type name set via . public static ActorTypeInformation Get(Type actorType, string actorTypeName) { + return GetInternal(default, actorType, actorTypeName); + } + + /// + /// Creates an from actorType. + /// + /// The type of interface implementing the actor to create ActorTypeInformation for. + /// The type of class implementing the actor to create ActorTypeInformation for. + /// The name of the actor type represented by the actor. + /// created from actorType. + /// + /// When for actorType is not of type . + /// When actorType does not implement an interface deriving from + /// and is not marked as abstract. + /// + /// The value of will have precedence over the default actor type name derived from the actor implementation type or any type name set via . + public static ActorTypeInformation Get(Type actorInterfaceType, Type actorType, string actorTypeName) + { + return GetInternal(actorInterfaceType, actorType, actorTypeName); + } + + private static ActorTypeInformation GetInternal(Type actorInterfaceType, Type actorType, string actorTypeName) + { + if (actorInterfaceType != default && !actorInterfaceType.IsActorInterface()) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + SR.ErrorNotAnActorInterface, + actorInterfaceType.FullName, + typeof(Actor).FullName), + "actorInterfaceType"); + } + if (!actorType.IsActor()) { throw new ArgumentException( @@ -132,7 +166,7 @@ public static ActorTypeInformation Get(Type actorType, string actorTypeName) } // get all actor interfaces - var actorInterfaces = actorType.GetActorInterfaces(); + var actorInterfaces = actorInterfaceType != default ? new Type[] { actorInterfaceType } : actorType.GetActorInterfaces(); // ensure that the if the actor type is not abstract it implements at least one actor interface if ((actorInterfaces.Length == 0) && (!actorType.GetTypeInfo().IsAbstract)) diff --git a/test/Dapr.Actors.AspNetCore.Test/ActorHostingTest.cs b/test/Dapr.Actors.AspNetCore.Test/ActorHostingTest.cs index 8e34eaffd..1c2e891f8 100644 --- a/test/Dapr.Actors.AspNetCore.Test/ActorHostingTest.cs +++ b/test/Dapr.Actors.AspNetCore.Test/ActorHostingTest.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,8 +11,10 @@ // limitations under the License. // ------------------------------------------------------------------------ +using System; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; using Dapr.Actors.Client; using Dapr.Actors.Runtime; using Microsoft.Extensions.DependencyInjection; @@ -88,6 +90,50 @@ public void CanAccessProxyFactoryWithCustomJsonOptions() Assert.Same(jsonOptions, factory.DefaultOptions.JsonSerializerOptions); } + [Fact] + public void CanRegisterActorsToSpecificInterface() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddOptions(); + services.AddActors(options => + { + options.Actors.RegisterActor(); + }); + + var runtime = services.BuildServiceProvider().GetRequiredService(); + + Assert.Collection( + runtime.RegisteredActors.Select(r => r.Type.ActorTypeName).OrderBy(t => t), + t => Assert.Equal(ActorTypeInformation.Get(typeof(IMyActor), typeof(InternalMyActor), actorTypeName: null).ActorTypeName, t)); + + Assert.Collection( + runtime.RegisteredActors.Select(r => r.Type.InterfaceTypes.First()).OrderBy(t => t), + t => Assert.Equal(ActorTypeInformation.Get(typeof(IMyActor), typeof(InternalMyActor), actorTypeName: null).InterfaceTypes.First(), t)); + + Assert.True(runtime.RegisteredActors.First().Type.InterfaceTypes.Count() == 1); + } + + [Fact] + public void RegisterActorThrowsArgumentExceptionWhenAnyInterfaceInTheChainIsNotIActor() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddOptions(); + services.AddActors(options => + { + Assert.Throws(() => options.Actors.RegisterActor()); + }); + } + + private interface INonActor + { + } + + private interface INonActor1 : INonActor, IActor + { + } + private interface ITestActor : IActor { } @@ -107,5 +153,32 @@ public TestActor2(ActorHost host) { } } + + public interface IMyActor : IActor + { + Task SomeMethod(); + } + + public interface IInternalMyActor : IMyActor + { + void SomeInternalMethod(); + } + + public class InternalMyActor : Actor, IInternalMyActor, INonActor1 + { + public InternalMyActor(ActorHost host) + : base(host) + { + } + + public void SomeInternalMethod() + { + } + + public Task SomeMethod() + { + return Task.CompletedTask; + } + } } }