diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-usage.md index cd642523e..60e3bc202 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-usage.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-usage.md @@ -138,6 +138,9 @@ public void ConfigureServices(IServiceCollection services) { // Register actor types and configure actor settings options.Actors.RegisterActor(); + + // Register MyActor to a specific interface + options.Actors.RegisterActor(); // Configure default settings options.ActorIdleTimeout = TimeSpan.FromMinutes(10); @@ -150,6 +153,12 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); } ``` +> [!IMPORTANT] +> When registering actors, note the return type requirements for the methods inside the interfaces: +> * Pattern 1: `options.Actors.RegisterActor()` +> * In this case, all interfaces implemented by `MyActor` must have methods that return only `Task` or `Task`. This applies to every method in all interfaces `MyActor` implements. +> * Pattern 2: `options.Actors.RegisterActor()` +> * Here, only the `IMyActor` interface is required to have methods that return `Task` or `Task`. Other interfaces `MyActor` are not subject to this restriction. ### Configuring JSON options @@ -241,4 +250,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) ## Next steps -Try the [Running and using virtual actors example]({{< ref dotnet-actors-howto.md >}}). \ No newline at end of file +Try the [Running and using virtual actors example]({{< ref dotnet-actors-howto.md >}}). 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; + } + } } }