How can I pass along auditing information between clients and services in an easy way without having to add that information as arguments for all service methods? Can I use message headers to set this data for a call?
Is there a way to allow service to pass that along downstream also, i.e., if ServiceA calls ServiceB that calls ServiceC, could the same auditing information be send to first A, then in A's call to B and then in B's call to C?
There is actually a concept of headers that are passed between client and service if you are using fabric transport for remoting. If you are using Http transport then you have headers there just as you would with any http request.
Note, below proposal is not the easiest solution, but it solves the issue once it is in place and it is easy to use then, but if you are looking for easy in the overall code base this might not be the way to go. If that is the case then I suggest you simply add some common audit info parameter to all your service methods. The big caveat there is of course when some developer forgets to add it or it is not set properly when calling down stream services. It's all about trade-offs, as alway in code :).
Down the rabbit hole
In fabric transport there are two classes that are involved in the communication: an instance of a IServiceRemotingClient on the client side, and an instance of IServiceRemotingListener on the service side. In each request from the client the messgae body and ServiceRemotingMessageHeaders are sent. Out of the box these headers include information of which interface (i.e. which service) and which method are being called (and that's also how the underlying receiver knows how to unpack that byte array that is the body). For calls to Actors, which goes through the ActorService, additional Actor information is also included in those headers.
The tricky part is hooking into that exchange and actually setting and then reading additional headers. Please bear with me here, it's a number of classes involved in this behind the curtains that we need to understand.
The service side
When you setup the IServiceRemotingListener for your service (example for a Stateless service) you usually use a convenience extension method, like so:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
yield return new ServiceInstanceListener(context =>
this.CreateServiceRemotingListener(this.Context));
}
(Another way to do it would be to implement your own listener, but that's not really what we wan't to do here, we just wan't to add things on top of the existing infrastructure. See below for that approach.)
This is where we can provide our own listener instead, similar to what that extention method does behind the curtains. Let's first look at what that extention method does. It goes looking for a specific attribute on assembly level on your service project: ServiceRemotingProviderAttribute. That one is abstract, but the one that you can use, and which you will get a default instance of, if none is provided, is FabricTransportServiceRemotingProviderAttribute. Set it in AssemblyInfo.cs (or any other file, it's an assembly attribute):
[assembly: FabricTransportServiceRemotingProvider()]
This attribute has two interesting overridable methods:
public override IServiceRemotingListener CreateServiceRemotingListener(
ServiceContext serviceContext, IService serviceImplementation)
public override IServiceRemotingClientFactory CreateServiceRemotingClientFactory(
IServiceRemotingCallbackClient callbackClient)
These two methods are responsible for creating the the listener and the client factory. That means that it is also inspected by the client side of the transaction. That is why it is an attribute on assembly level for the service assembly, the client side can also pick it up together with the IService derived interface for the client we want to communicate with.
The CreateServiceRemotingListener ends up creating an instance FabricTransportServiceRemotingListener, however in this implementation we cannot set our own specific IServiceRemotingMessageHandler. If you create your own sub class of FabricTransportServiceRemotingProviderAttribute and override that then you can actually make it create an instance of FabricTransportServiceRemotingListener that takes in a dispatcher in the constructor:
public class AuditableFabricTransportServiceRemotingProviderAttribute :
FabricTransportServiceRemotingProviderAttribute
{
public override IServiceRemotingListener CreateServiceRemotingListener(
ServiceContext serviceContext, IService serviceImplementation)
{
var messageHandler = new AuditableServiceRemotingDispatcher(
serviceContext, serviceImplementation);
return (IServiceRemotingListener)new FabricTransportServiceRemotingListener(
serviceContext: serviceContext,
messageHandler: messageHandler);
}
}
The AuditableServiceRemotingDispatcher is where the magic happens. It is our own ServiceRemotingDispatcher subclass. Override the RequestResponseAsync (ignore HandleOneWay, it is not supported by service remoting, it throws an NotImplementedException if called), like this:
public class AuditableServiceRemotingDispatcher : ServiceRemotingDispatcher
{
public AuditableServiceRemotingDispatcher(ServiceContext serviceContext, IService service) :
base(serviceContext, service) { }
public override async Task<byte[]> RequestResponseAsync(
IServiceRemotingRequestContext requestContext,
ServiceRemotingMessageHeaders messageHeaders,
byte[] requestBodyBytes)
{
byte[] userHeader = null;
if (messageHeaders.TryGetHeaderValue("user-header", out auditHeader))
{
// Deserialize from byte[] and handle the header
}
else
{
// Throw exception?
}
byte[] result = null;
result = await base.RequestResponseAsync(requestContext, messageHeaders, requestBodyBytes);
return result;
}
}
Another, easier, but less flexible way, would be to directly create an instance of FabricTransportServiceRemotingListener with an instance of our custom dispatcher directly in the service:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
yield return new ServiceInstanceListener(context =>
new FabricTransportServiceRemotingListener(this.Context, new AuditableServiceRemotingDispatcher(context, this)));
}
Why is this less flexible? Well, because using the attribute supports the client side as well, as we see below
The client side
Ok, so now we can read custom headers when receiving messages, how about setting those? Let's look at the other method of that attribute:
public override IServiceRemotingClientFactory CreateServiceRemotingClientFactory(IServiceRemotingCallbackClient callbackClient)
{
return (IServiceRemotingClientFactory)new FabricTransportServiceRemotingClientFactory(
callbackClient: callbackClient,
servicePartitionResolver: (IServicePartitionResolver)null,
traceId: (string)null);
}
Here we cannot just inject a specific handler or similar as for the service, we have to supply our own custom factory. In order not to have to reimplement the particulars of FabricTransportServiceRemotingClientFactory I simply encapsulate it in my own implementation of IServiceRemotingClientFactory:
public class AuditedFabricTransportServiceRemotingClientFactory : IServiceRemotingClientFactory, ICommunicationClientFactory<IServiceRemotingClient>
{
private readonly ICommunicationClientFactory<IServiceRemotingClient> _innerClientFactory;
public AuditedFabricTransportServiceRemotingClientFactory(ICommunicationClientFactory<IServiceRemotingClient> innerClientFactory)
{
_innerClientFactory = innerClientFactory;
_innerClientFactory.ClientConnected += OnClientConnected;
_innerClientFactory.ClientDisconnected += OnClientDisconnected;
}
private void OnClientConnected(object sender, CommunicationClientEventArgs<IServiceRemotingClient> e)
{
EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> clientConnected = this.ClientConnected;
if (clientConnected == null) return;
clientConnected((object)this, new CommunicationClientEventArgs<IServiceRemotingClient>()
{
Client = e.Client
});
}
private void OnClientDisconnected(object sender, CommunicationClientEventArgs<IServiceRemotingClient> e)
{
EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> clientDisconnected = this.ClientDisconnected;
if (clientDisconnected == null) return;
clientDisconnected((object)this, new CommunicationClientEventArgs<IServiceRemotingClient>()
{
Client = e.Client
});
}
public async Task<IServiceRemotingClient> GetClientAsync(
Uri serviceUri,
ServicePartitionKey partitionKey,
TargetReplicaSelector targetReplicaSelector,
string listenerName,
OperationRetrySettings retrySettings,
CancellationToken cancellationToken)
{
var client = await _innerClientFactory.GetClientAsync(
serviceUri,
partitionKey,
targetReplicaSelector,
listenerName,
retrySettings,
cancellationToken);
return new AuditedFabricTransportServiceRemotingClient(client);
}
public async Task<IServiceRemotingClient> GetClientAsync(
ResolvedServicePartition previousRsp,
TargetReplicaSelector targetReplicaSelector,
string listenerName,
OperationRetrySettings retrySettings,
CancellationToken cancellationToken)
{
var client = await _innerClientFactory.GetClientAsync(
previousRsp,
targetReplicaSelector,
listenerName,
retrySettings,
cancellationToken);
return new AuditedFabricTransportServiceRemotingClient(client);
}
public Task<OperationRetryControl> ReportOperationExceptionAsync(
IServiceRemotingClient client,
ExceptionInformation exceptionInformation,
OperationRetrySettings retrySettings,
CancellationToken cancellationToken)
{
return _innerClientFactory.ReportOperationExceptionAsync(
client,
exceptionInformation,
retrySettings,
cancellationToken);
}
public event EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> ClientConnected;
public event EventHandler<CommunicationClientEventArgs<IServiceRemotingClient>> ClientDisconnected;
}
This implementation simply passes along anything heavy lifting to the underlying factory, while returning it's own auditable client that similarily encapsulates a IServiceRemotingClient:
public class AuditedFabricTransportServiceRemotingClient : IServiceRemotingClient, ICommunicationClient
{
private readonly IServiceRemotingClient _innerClient;
public AuditedFabricTransportServiceRemotingClient(IServiceRemotingClient innerClient)
{
_innerClient = innerClient;
}
~AuditedFabricTransportServiceRemotingClient()
{
if (this._innerClient == null) return;
var disposable = this._innerClient as IDisposable;
disposable?.Dispose();
}
Task<byte[]> IServiceRemotingClient.RequestResponseAsync(ServiceRemotingMessageHeaders messageHeaders, byte[] requestBody)
{
messageHeaders.SetUser(ServiceRequestContext.Current.User);
messageHeaders.SetCorrelationId(ServiceRequestContext.Current.CorrelationId);
return this._innerClient.RequestResponseAsync(messageHeaders, requestBody);
}
void IServiceRemotingClient.SendOneWay(ServiceRemotingMessageHeaders messageHeaders, byte[] requestBody)
{
messageHeaders.SetUser(ServiceRequestContext.Current.User);
messageHeaders.SetCorrelationId(ServiceRequestContext.Current.CorrelationId);
this._innerClient.SendOneWay(messageHeaders, requestBody);
}
public ResolvedServicePartition ResolvedServicePartition
{
get { return this._innerClient.ResolvedServicePartition; }
set { this._innerClient.ResolvedServicePartition = value; }
}
public string ListenerName
{
get { return this._innerClient.ListenerName; }
set { this._innerClient.ListenerName = value; }
}
public ResolvedServiceEndpoint Endpoint
{
get { return this._innerClient.Endpoint; }
set { this._innerClient.Endpoint = value; }
}
}
Now, in here is where we actually (and finally) set the audit name that we want to pass along to the service.
Call chains and service request context
One final piece of the puzzle, the ServiceRequestContext, which is a custom class that allows us to handle an ambient context for a service request call. This is relevant because it gives us an easy way to propagate that context information, like the user or a correlation id (or any other header information we want to pass between client and service), in a chain of calls. The implementation ServiceRequestContext looks like:
public sealed class ServiceRequestContext
{
private static readonly string ContextKey = Guid.NewGuid().ToString();
public ServiceRequestContext(Guid correlationId, string user)
{
this.CorrelationId = correlationId;
this.User = user;
}
public Guid CorrelationId { get; private set; }
public string User { get; private set; }
public static ServiceRequestContext Current
{
get { return (ServiceRequestContext)CallContext.LogicalGetData(ContextKey); }
internal set
{
if (value == null)
{
CallContext.FreeNamedDataSlot(ContextKey);
}
else
{
CallContext.LogicalSetData(ContextKey, value);
}
}
}
public static Task RunInRequestContext(Func<Task> action, Guid correlationId, string user)
{
Task<Task> task = null;
task = new Task<Task>(async () =>
{
Debug.Assert(ServiceRequestContext.Current == null);
ServiceRequestContext.Current = new ServiceRequestContext(correlationId, user);
try
{
await action();
}
finally
{
ServiceRequestContext.Current = null;
}
});
task.Start();
return task.Unwrap();
}
public static Task<TResult> RunInRequestContext<TResult>(Func<Task<TResult>> action, Guid correlationId, string user)
{
Task<Task<TResult>> task = null;
task = new Task<Task<TResult>>(async () =>
{
Debug.Assert(ServiceRequestContext.Current == null);
ServiceRequestContext.Current = new ServiceRequestContext(correlationId, user);
try
{
return await action();
}
finally
{
ServiceRequestContext.Current = null;
}
});
task.Start();
return task.Unwrap<TResult>();
}
}
This last part was much influenced by the SO answer by Stephen Cleary. It gives us an easy way to handle the ambient information down a hierarcy of calls, weather they are synchronous or asyncronous over Tasks. Now, with this we have a way of setting that information also in the Dispatcher on the service side:
public override Task<byte[]> RequestResponseAsync(
IServiceRemotingRequestContext requestContext,
ServiceRemotingMessageHeaders messageHeaders,
byte[] requestBody)
{
var user = messageHeaders.GetUser();
var correlationId = messageHeaders.GetCorrelationId();
return ServiceRequestContext.RunInRequestContext(async () =>
await base.RequestResponseAsync(
requestContext,
messageHeaders,
requestBody),
correlationId, user);
}
(GetUser() and GetCorrelationId() are just helper methods that gets and unpacks the headers set by the client)
Having this in place means that any new client created by the service for any aditional call will also have the sam headers set, so in the scenario ServiceA -> ServiceB -> ServiceC we will still have the same user set in the call from ServiceB to ServiceC.
what? that easy? yes ;)
From inside a service, for instance a Stateless OWIN web api, where you first capture the user information, you create an instance of ServiceProxyFactoryand wrap that call in a ServiceRequestContext:
var task = ServiceRequestContext.RunInRequestContext(async () =>
{
var serviceA = ServiceProxyFactory.CreateServiceProxy<IServiceA>(new Uri($"{FabricRuntime.GetActivationContext().ApplicationName}/ServiceA"));
await serviceA.DoStuffAsync(CancellationToken.None);
}, Guid.NewGuid(), user);
Ok, so to sum it up - you can hook into the service remoting to set your own headers. As we see above there is some work that needs to be done to get a mechanism for that in place, mainly creating your own subclasses of the underlying infrastructure. The upside is that once you have this in place, then you have a very easy way for auditing your service calls.
I have WebApi Controller as mentioned below. This controller having Update method which will internally call service called CustomerDataService to Update Customer Records.Assume we have n customer records to update.
UpdateMethod in CustomerDataService will perform update and return the update response.
I have requirement to do some heavy processing asynchronously after the update response like manipulating data / managing the data cache. As this processing is time consuming not relevant to the consumer of this API as Update successfully happens So I have to perform this asynchronously. Can I do this with C# with the given scenario? Please suggest.
Note: I do not want to create any batch job to achieve this as I want to perform operation(s) which are user session specific.
Controller
public class CustomerController : ApiController
{
[HttpGet]
public string UpdateCustomer()
{
ICustomerService obj = new CustomerDataService();
return obj.UpdateCustomer(GetCustomerList());
}
private List<CustomerModel> GetCustomerList()
{
return new List<CustomerModel>()
{
new CustomerModel
{
CustomerId="1",
Name="John",
Category="P1"
},
new CustomerModel
{
CustomerId="2",
Name="Mike",
Category="P2"
}
//....n Records
};
}
}
Model
[Serializable]
[DataContract]
public class CustomerModel
{
[DataMember]
public string CustomerId { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Category { get; set; }
}
Interface and CustomerDataService
public interface ICustomerService
{
string UpdateCustomer(List<CustomerModel> customerList);
}
public class CustomerDataService : ICustomerService
{
public string UpdateCustomer(List<CustomerModel> customerList)
{
//Do Data Processing - DB Call
//Return Confirmation Message
return "Data Updated Successfully!!!";
//Needs to perform some processing asynchronously i.e. Call ProcessResults()
}
private void ProcessResults()
{
//DO Processing
}
}
What you are looking for is using async/await in c#, see this article on Microsofts website: Asynchronous Programming with Async and Await. Here is another article with plenty of examples: C# Async, Await.
Once you understand how this works it will be very easy to change your code to take advantage of this pattern. Let us know if you have specific questions or run into problems.
I have the following question that's been nagging at me for quite some time.
I'd like to model the following domain entity "Contact":
public class Contact:IEntity<Contact>
{
private readonly ContactId _Id;
public ContactId Id
{
get { return this._Id; }
}
private CoreAddress _CoreAddress;
public CoreAddress CoreAddress
{
get { return this._CoreAddress; }
set
{
if (value == null)
throw new ArgumentNullException("CoreAddress");
this._CoreAddress = value;
}
}
private ExtendedAddress _ExtendedAddress;
public ExtendedAddress ExtendedAddress
{
get { return this._ExtendedAddress; }
set
{
if (value == null)
throw new ArgumentNullException("ExtendedAddress");
this._ExtendedAddress = value;
}
}
private readonly IList<ContactExchangeSubscription> _Subscriptions
= new List<ContactExchangeSubscription>();
public IEnumerable<ContactExchangeSubscription> Subscriptions
{
get { return this._Subscriptions; }
}
public Contact(ContactId Id, CoreAddress CoreAddress, ExtendedAddress ExtendedAddress)
{
Validations.Validate.NotNull(Id);
this._Id = Id;
this._CoreAddress = CoreAddress;
this._ExtendedAddress = ExtendedAddress;
}
}
As you can see it has a collection of subscriptions. A subscription is modeled like this:
public class ContactExchangeSubscription
{
private ContactId _AssignedContact;
public ContactId AssignedContact
{
get { return this._AssignedContact; }
set
{
if (value == null)
throw new ArgumentNullException("AssignedContact");
this._AssignedContact = value;
}
}
private User _User;
public User User
{
get { return this._User; }
set
{
Validations.Validate.NotNull(value, "User");
this._User = value;
}
}
private ExchangeEntryId _EntryId;
public ExchangeEntryId EntryId
{
get { return this._EntryId; }
set
{
if (value == null)
throw new ArgumentNullException("EntryId");
this._EntryId = value;
}
}
public ContactExchangeSubscription(ContactId AssignedContact, User User, ExchangeEntryId EntryId)
{
this._AssignedContact = AssignedContact;
this._User = User;
this._EntryId = EntryId;
}
}
Now I've been thinking that I shouldnt model a storage technology (Exchange) in my domain, after all, we might want to switch our application to other subscription providers. The property "EntryId" is specific to Exchange. A subscription would always need a User and a ContactId, though.
Is there a better way to model the Subscription? Should I use a factory or abstract factory for the Subscription type to cover other types of subscriptions, should the need arise?
EDIT: So let's toss an abstract factory in the ring and introduce some interfaces:
public interface IContactSubscriptionFactory
{
IContactSubscription Create();
}
public interface IContactSubscription
{
ContactId AssignedContact { get;}
User User { get; }
}
How would a concrete factory for a ContactExchangeSubscription be coded? Remember that this type will need the EntryID field, so it has to get an additional ctr parameter. How to handle different constructor paremeters on different sub-types in factories in general?
I think the answer is staring you in the face in that you need to work against an interface making it easier to introduce new subscription providers (if that's the right term) in the future. I think this is more of an OO design question that DDD.
public interface ISubscriptionProvider
{
ContactId AssignedContact { get; }
User User { get; }
}
And the code in your contract becomes
private readonly IList<ISubscriptionProvider> _subscriptions
= new List<ISubscriptionProvider>();
public IEnumerable<ISubscriptionProvider> Subscriptions
{
get { return _subscriptions; }
}
With regards to using a factory; the purpose of a factory is to construct your domain objects when a creation strategy is required. For example a SubscriptionProviderFactory could be used within your repository when you rehydrate your aggregate and would make the decision to return the ContactExchangeSubscription (as an ISubscriptionProvider) or something else based on the data passed into it.
One final point but perhaps this is just because of the way you have shown your example. But I would say your not really following DDD, the lack of behaviour and with all your propeties having public getters and setters, suggestions your falling into the trap of building an Aemic Domain Model.
After some research I came up with this. Code first, explanation below:
public interface IContactFactory<TSubscription> where TSubscription : IContactSubscription
{
Contact Create(ContactId Id, CoreAddress CoreAddress, ExtendedAddress ExtendedAddress, TSubscription Subscription);
}
public class ContactFromExchangeFactory : IContactFactory<ContactExchangeSubscription>
{
public Contact Create(ContactId Id, CoreAddress CoreAddress, ExtendedAddress ExtendedAddress, ContactExchangeSubscription ExchangeSubscription)
{
Contact c = new Contact(Id, CoreAddress, ExtendedAddress);
c.AddSubscription(ExchangeSubscription);
return c;
}
}
I realized that I dont need a factory for the Contactsubscription but rather for the contact itself.
I learned some things about factories along the way:
They are only to be used when creating (really) new entities, not when rebuilding them from a SQL DB for example
They live in the domain layer (see above!)
Factories are more suitable for similar objects that differ in behaviour rather than data
I welcome comments and better answers.
I am using the Unit of Work and Generic Repository pattern. Here is the statement that checks for a duplicate entry:
int id = int.Parse(beer.id); //id comes from the item we're hoping to insert
if (_unitOfWork.BeerRepository.GetByID(id) == null)
\\create a new model br
_unitOfWork.BeerRepository.Insert(br);
_unitOfWork.save();
Apparently this is failing to check to see if the beer is already in the database because I get this inner exception:
Violation of PRIMARY KEY constraint 'PK_Beers_3214EC2703317E3D'.
Cannot insert duplicate key in object 'dbo.Beers'.\r\nThe statement
has been terminated.
I also get this message:
An error occurred while saving entities that do not expose foreign
key properties for their relationships. The EntityEntries property
will return null because a single entity cannot be identified as the
source of the exception. Handling of exceptions while saving can be
made easier by exposing foreign key properties in your entity types.
See the InnerException for details.
The UnitOfWork class has my BeerRecommenderContext which implements DbContext and the UnitOfWork has a generic repository for each entity:
namespace BeerRecommender.Models
{
public class GenericRepository<TEntity> where TEntity : class
{
internal BeerRecommenderContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(BeerRecommenderContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
}
I have a similar usage of repository using code-first. Occasionally, I would see conflicts like the one you described. My issue was with change tracking across multiple processes. Are you inserting items into the database inside one process (using a single entity context)?
If you are, you should look at the Merge Options available with Entity Framework. If you are using the default merge option (AppendOnly), then you could be querying the in memory context instead of going to the database. This could cause the behaviour you are describing.
Unfortunately, as far as I understand, all the merge options are not yet exposed to Code-First. You can choose the default (AppendOnly) or NoTracking, which will go to the database every time.
Hope this helps,
Davin
I was looking at Prism EventAggregator and its' great. I part i was most concerned was its capability to marshal thread correctly to UI thread.
I was wondering if i can use this capability to provide module developers a class which could be used to create threads in a similar way as BackgroundWorker. Interface of class can be somewhat similar to
public interface IMyTask
{
event DoWorkEventHandler DoWork;
event RunWorkerCompletedEventHandler RunWorkerCompleted;
void RunTaskAsync(object obj);
}
I have kept types similar to backgroundworker for better understanding. In implementation i am registering taskstart and taskcomplete events
public class TaskStartEventPayload
{
public SubscriptionToken token { get; set; }
public object Argument { get; set; }
}
public class TaskStartEvent : CompositePresentationEvent<TaskStartEventPayload>
{
}
public class TaskCompleteEventPayload
{
public SubscriptionToken token { get; set; }
public object Argument { get; set; }
public object Result { get; set; }
}
public class TaskCompleteEvent : CompositePresentationEvent<TaskCompleteEventPayload>
{
}
In the constructor for the MyTask class i take which thread the completion is required on as
public MyTask(IEventAggregator eventAggregator, bool isUICompletion)
{
if (eventAggregator == null)
{
throw new ArgumentNullException("eventAggregator");
}
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<TaskStartEvent>().Subscribe(TaskStartHandler, ThreadOption.BackgroundThread, false, new Predicate<TaskStartEventPayload>(StartTokenFilter));
if(isUICompletion)
_token = _eventAggregator.GetEvent<TaskCompleteEvent>().Subscribe(TaskCompleteHandler, ThreadOption.UIThread,true,new Predicate<TaskCompleteEventPayload>(CompleteTokenFilter));
else
_token = _eventAggregator.GetEvent<TaskCompleteEvent>().Subscribe(TaskCompleteHandler, ThreadOption.BackgroundThread, true, new Predicate<TaskCompleteEventPayload>(CompleteTokenFilter));
}
here i am registering with filters where filter function returns the event only if it has Payload has same token as while got while subscribing.
further I use
public void RunTaskAsync(object obj)
{
//create payload
_eventAggregator.GetEvent<TaskStartEvent>().Publish(payload);
}
public void TaskStartHandler(TaskStartEventPayload t)
{
//fire dowork and create payload
DoWork(this, args);
_eventAggregator.GetEvent<TaskCompleteEvent>().Publish(tc);
}
public void TaskCompleteHandler(TaskCompleteEventPayload t)
{
RunWorkerCompleted(this, args);
}
This class can be used as
MyTask et = new MyTaskagg, true);
et.DoWork += new System.ComponentModel.DoWorkEventHandler(et_DoWork);
et.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(et_RunWorkerCompleted);
et.RunTaskAsync("Test");
Benefit I see in this approach is
1. It uses threadpool so no overhead of creating threads as in backgroundWorker.
2. Proper thread marshalling in case RunWorkerCompleted to be executed on UI thread.
Please advice if this would be correct to use eventaggregator as Threader.
This will work, although it's code you have to debug for very little performance gain. Micro-optimizing is rarely worth the effort and support costs in my opinion.
EventAggregator is meant to be a message bus for your application and I typically prefer to use things for their original intention, lest I have to debug a lot of code, but that's my personal preference.
Event Aggregator is going to have to work a little harder than it is meant to cleaning up all of those subscriptions, which will likely exceed any performance gain you get from the thread pooling, but that is just a guess.