Grain extensions
Grain extensions provide a way to add extra behavior to grains. By extending a grain with an interface that derives from IGrainExtension, you can add new methods and functionality to the grain.
In this article, you see two examples of grain extensions. The first example shows how to add a Deactivate
method to all grains that can be used to deactivate the grain. The second example shows how to add a GetState
and SetState
method to any grain, allowing you to manipulate the grain's internal state.
Deactivate extension example
In this example, you will learn how to add a Deactivate
method to all grains automatically. The method can be used to deactivate the grain and accepts a string as a message parameter. Orleans grains already support this functionality via the IGrainManagementExtension interface. Nevertheless, this example serves to show how you could add this or similar functionality yourself.
Deactivate extension interface
Start by defining an IGrainDeactivateExtension
interface, which contains the Deactivate
method. The interface must derive from IGrainExtension
.
public interface IGrainDeactivateExtension : IGrainExtension
{
Task Deactivate(string msg);
}
Deactivate extension implementation
Next, implement the GrainDeactivateExtension
class, which provides the implementation for the Deactivate
method.
To access the target grain, you retrieve the IGrainContext
from the constructor. It's injected when creating the extension with dependency injection.
public sealed class GrainDeactivateExtension : IGrainDeactivateExtension
{
private IGrainContext _context;
public GrainDeactivateExtension(IGrainContext context)
{
_context = context;
}
public Task Deactivate(string msg)
{
var reason = new DeactivationReason(DeactivationReasonCode.ApplicationRequested, msg);
_context.Deactivate(reason);
return Task.CompletedTask;
}
}
Deactivate extension registration and usage
Now that you've defined the interface and implementation, you register the extension when configuring the silo with the AddGrainExtension method.
siloBuilder.AddGrainExtension<IGrainDeactivateExtension, GrainDeactivateExtension>();
To use the extension on any grain, retrieve a reference to the extension and call the Deactivate
method.
var grain = client.GetGrain<SomeExampleGrain>(someKey);
var grainReferenceAsInterface = grain.AsReference<IGrainDeactivateExtension>();
await grainReferenceAsInterface.Deactivate("Because, I said so...");
State manipulation extension example
In this example, you learn how to add a GetState
and SetState
method to any grain through extensions, allowing you to manipulate the grain's internal state.
State manipulation extension interface
First, define the IGrainStateAccessor<T>
interface, which contains the GetState
and SetState
methods. Again, this interface must derive from IGrainExtension
.
public interface IGrainStateAccessor<T> : IGrainExtension
{
Task<T> GetState();
Task SetState(T state);
}
Once you have access to the target grain, you can use the extension to manipulate its state. In this example, you use an extension to access and modify a specific integer state value within the target grain.
State manipulation extension implementation
The extension you use is IGrainStateAccessor<T>
, which provides methods to get and set a state value of type T
. To create the extension, you implement the interface in a class that takes a getter
and a setter
as arguments in its constructor.
public sealed class GrainStateAccessor<T> : IGrainStateAccessor<T>
{
private readonly Func<T> _getter;
private readonly Action<T> _setter;
public GrainStateAccessor(Func<T> getter, Action<T> setter)
{
_getter = getter;
_setter = setter;
}
public Task<T> GetState()
{
return Task.FromResult(_getter.Invoke());
}
public Task SetState(T state)
{
_setter.Invoke(state);
return Task.CompletedTask;
}
}
In the preceding implementation, the GrainStateAccessor<T>
class takes getter
and setter
arguments in its constructor. These delegates are used to read and modify the target grain's state. The GetState()
method returns a Task<TResult> wraps the current value of the T
state, while the SetState(T state)
method sets the new value of the T
state.
State manipulation extension registration and usage
To use the extension to access and modify the target grain's state, you need to register the extension and set its components in the Grain.OnActivateAsync() method of the target grain.
public override Task OnActivateAsync()
{
// Retrieve the IGrainStateAccessor<T> extension
var accessor = new GrainStateAccessor<int>(
getter: () => this.Value,
setter: value => this.Value = value);
// Set the extension as a component of the target grain's context
((IGrainBase)this).GrainContext.SetComponent<IGrainStateAccessor<int>>(accessor);
return base.OnActivateAsync();
}
In the preceding example, you create a new instance of GrainStateAccessor<int>
that takes a getter
and a setter
for an integer state value. The getter
reads the Value
property of the target grain, while the setter
sets the new value of the Value
property. you then set this instance as a component of the target grain's context using the IGrainContext.SetComponent method.
Once the extension is registered, you can use it to get and set the target grain's state by accessing it through a reference to the extension.
// Get a reference to the IGrainStateAccessor<int> extension
var accessor = grain.AsReference<IGrainStateAccessor<int>>();
// Get the current value of the state
var value = await accessor.GetState();
// Set a new value of the state
await accessor.SetState(10);
In the preceding example, you get a reference to the IGrainStateAccessor<int>
extension for a specific grain instance using the GrainExtensions.AsReference method. you can then use this reference to call the GetState()
and SetState(T state)
methods to read and modify the state value of the target grain.