Context Based Dependency Injection in Multi-Threaded Application - c#

I have a Service running on a server which listens to a Message Que. When a message is received, a new Thread is started and the message is passed to that Thread for processing.
I have defined an interface which provides access the current user for consumption in various classes used for the message processing:
public interface IUserContext {
User CurrentUser { get; }
}
This user will likely change from message to message.
My question is how do I register an implementation of IUserContext in SimpleInjector so that the correct User, contained in the incoming message, is properly returned by the CurrentUser property?
In my Asp.Net application this was accomplished by the following:
container.Register<IUserContext>(() => {
User user = null;
try {
user = HttpContext.Current?.Session[USER_CONTEXT] as IUser;
}
catch { }
return new UserContext(user);
});
I would imagine this would be accomplished using Lifetime Scoping, but I can't define that in a static class and set the User in each thread, because it could corrupt another process. This is my best guess at the implementation?
public static Func<User> UserContext { get; set; }
Then in my code in the new Thread:
using (container.BeginLifetimeScope()) {
.....
var user = GetUserContext(message);
UserContextInitializer.UserContext = () => new UserContext(user);
.....
}
Then registration would look something like this:
container.Register<IUserContext>(() => UserContextInitializer.UserContext);
Thread Safety aside, Is this the correct approach to implement this in SimpleInjector? Is there another pattern which would be more correct?

Let's start with your ASP.NET-specific IUserContext registration:
container.Register<IUserContext>(() => {
User user = null;
try {
user = HttpContext.Current?.Session[USER_CONTEXT] as IUser;
}
catch { }
return new UserContext(user);
});
This registration is problematic, because the UserContext component depends on the availability of runtime data, while, as described here, the creation of object graphs should be separated from runtime data and runtime data should flow through the system.
In other words, you should rewrite your UserContext class to the following:
public class AspNetUserContext : IUserContext
{
User CurrentUser => (User)HttpContext.Current.Session[USER_CONTEXT];
}
This allows this ASP.NET-specific IUserContext implementation to be registered as follows:
container.RegisterInstance<IUserContext>(new AspNetUserContext());
Of course, the previous does not solve the problem in your Windows Service, but the previous does lay the foundation for the solution.
For the Windows Service you need a custom implementation (an adapter) as well:
public class ServiceUserContext : IUserContext
{
User CurrentUser { get; set; }
}
This implementation is much simpler and here ServiceUserContext's CurrentUser property is a writable property. This solves your problem elegantly, because you can now do the following:
// Windows Service Registration:
container.Register<IUserContext, ServiceUserContext>(Lifestyle.Scoped);
container.Register<ServiceUserContext>(Lifestyle.Scoped);
// Code in the new Thread:
using (container.BeginLifetimeScope())
{
.....
var userContext = container.GetInstance<ServiceUserContext>();
// Set the user of the scoped ServiceUserContext
userContext.CurrentUser = GetUserContext(message);
var handler = container.GetInstance<IHandleMessages<SomeMessage>>();
handler.Handle(message);
.....
}
Here, the solution is, as well, the separation of creation of object graphs and the use of runtime data. In this case, the runtime data is provided to the object graph after construction (i.e. using userContext.CurrentUser = GetUserContext(message)).

Related

Logging Hangfire jobs to Application Insights and correlating activity to an Operation Id

I feel like this should be a lot simpler than it's turning out to be, or I am just over thinking it too much.
I have a .NET Core 3.1 Web API application, which is using HangFire to process some jobs in the background. I have also configured Application Insights to log Telemetry from the .NET Core API.
I can see logging events and dependency telemetry data logged in Application Insights. However, each event/log/dependency is recorded against a unique OperationId and Parent Id.
I am trying to determine how to ensure that any activity which is logged, or any dependencies which are used in the context of the background job are logged against the OperationId and/or Parent Id of the original request which queued the background job.
When I queue a job, I can get the current OperationId of the incoming HTTP request, and I push that into the HangFire queue with the job. When the job is then performed, I can get back that OperationId. What I then need to do is make that OperationID available throughout the context/lifetime of the job execution, so that it is attached to any Telemetry sent to Application Insightd.
I thought I could create a IJobContext interface, which could be injected into the class which performs the job. Within that context I could push the OperationID. I could then create a ITelemetryInitializer which would also take the IJobContext as a dependency. In the ITelemetryInitializer I could then set the OperationID and ParentId of the telemetry being sent to Application Insights. Here's some simple code:
public class HangFirePanelMessageQueue : IMessageQueue
{
private readonly MessageProcessor _messageProcessor;
private readonly IHangFireJobContext _jobContext;
private readonly TelemetryClient _telemetryClient;
public HangFirePanelMessageQueue(MessageProcessor panelMessageProcessor,
IIoTMessageSerializer iotHubMessageSerialiser,
IHangFireJobContext jobContext, TelemetryClient telemetryClient)
{
_messageProcessor = panelMessageProcessor;
_jobContext = jobContext;
_telemetryClient = telemetryClient;
}
public async Task ProcessQueuedMessage(string message, string operationId)
{
var iotMessage = _iotHubMessageSerialiser.GetMessage(message);
_jobContext?.Set(iotMessage.CorrelationID, iotMessage.MessageID);
await _messageProcessor.ProcessMessage(iotMessage);
}
public Task QueueMessageForProcessing(string message)
{
var dummyTrace = new TraceTelemetry("Queuing message for processing", SeverityLevel.Information);
_telemetryClient.TrackTrace(dummyTrace);
string opId = dummyTrace.Context.Operation.Id;
BackgroundJob.Enqueue(() =>
ProcessQueuedMessage(message, opId));
return Task.CompletedTask;
}
}
The IJobContext would look something like this:
public interface IHangFireJobContext
{
bool Initialised { get; }
string OperationId { get; }
string JobId { get; }
void Set(string operationId, string jobId);
}
And then I would have an ITelemetryInitializer which enriches any ITelemetry:
public class EnrichBackgroundJobTelemetry : ITelemetryInitializer
{
private readonly IHangFireJobContext jobContext;
public EnrichBackgroundJobTelemetry(IHangFireJobContext jobContext)
{
this.jobContext = jobContext;
}
public void Initialize(ITelemetry telemetry)
{
if (!jobContext.Initialised)
{
return;
}
telemetry.Context.Operation.Id = jobContext.OperationId;
}
}
The problem I have however is that the ITelemetryInitializer is a singleton, and so it would be instantiated once with a IHangFireJobContext which would then never update for any subsequent HangFire job.
I did find the https://github.com/skwasjer/Hangfire.Correlate project, which extends https://github.com/skwasjer/Correlate. Correlate creates a correlation context which can be accessed via a ICorrelationContextAccessor which is similar to the IHttpContextAccessor.
However, the footnotes for Correlate state "Please consider that .NET Core 3 now has built-in support for W3C TraceContext (blog) and that there are other distributed tracing libraries with more functionality than Correlate." which lists Application Insights as one of the alternatives for more Advanced distributed tracing.
So can anyone help me understand how I can enrich any Telemetry going to Application Insights when it is created within the context of a HangFire job? I feel the correct answer is to use an ITelemetryInitializer and populate the OperationId on that ITelemetry item, however, I am not sure what dependancy to inject into the ITelemetryInitialzer in order to get access to the HangFire Job Context.
When I queue a job, I can get the current OperationId of the incoming HTTP request, and I push that into the HangFire queue with the job.
So, am I correct to say that you have a controller action that pushes work to hangfire? If so What you can do is inside the controller method get the operation id and pass it to the job. Use that operation id to start a new operation using the operation Id. That operation, together with all the telemetry generated during that operation, will be linked to the original request.
I have no hangfire integration but the code below shows the general idea: some work is queued to be done in the background and should be linked to the request regarding the telemetry:
[HttpGet("/api/demo5")]
public ActionResult TrackWorker()
{
var requestTelemetry = HttpContext.Features.Get<RequestTelemetry>();
_taskQueue.QueueBackgroundWorkItem(async ct =>
{
using(var op = _telemetryClient.StartOperation<DependencyTelemetry>("QueuedWork", requestTelemetry.Context.Operation.Id))
{
_ = await new HttpClient().GetStringAsync("http://blank.org");
await Task.Delay(250);
op.Telemetry.ResultCode = "200";
op.Telemetry.Success = true;
}
});
return Accepted();
}
The full example can be found here.
Working from Peter Bons' example I did it like this:
Code originally triggered from a controller action:
// Get the current ApplicationInsights Id. Could use .RootId if
// you only want the OperationId, but I want the ParentId too
var activityId = System.Diagnostics.Activity.Current?.Id;
_backgroundJobClient.Enqueue<JobDefinition>(x =>
x.MyMethod(queueName, otherMethodParams, activityId));
In my JobDefinition class:
// I use different queues, but you don't need to.
// otherMethodParams is just an example. Have as many as you need, like normal.
[AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete, Attempts = 10)]
[QueueNameFromFirstParameter]
public async Task MyMethod(string queueName, string otherMethodParams,
string activityId)
{
var (operationId, parentId) = SplitCorrelationIdIntoOperationIdAndParentId(
activityId);
// Starting this new operation will initialise
// System.Diagnostics.Activity.Current.
using (var operation = _telemetryClient.StartOperation<DependencyTelemetry>(
"JobDefinition.MyMethod", operationId, parentId))
{
try
{
operation.Telemetry.Data = $"something useful here";
// If you have other state you'd like in App Insights logs,
// call AddBaggage and they show up as a customDimension,
// e.g. in any trace logs.
System.Diagnostics.Activity.Current.AddBaggage("QueueName", queueName);
// ... do the real background work here...
operation.Telemetry.Success = true;
}
catch (Exception)
{
operation.Telemetry.Success = false;
throw;
}
}
}
// Splits full value from System.Diagnostics.Current.Activity.Id
// like "00-12994526f1cb134bbddd0f256e8bc3f0-872b3bd78c345a46-00"
// into values ( "12994526f1cb134bbddd0f256e8bc3f0", "872b3bd78c345a46" )
private static (string, string) SplitCorrelationIdIntoOperationIdAndParentId(string activityId)
{
if (string.IsNullOrEmpty(activityId))
return (null, null);
var splits = activityId.Split('-');
// This is what should happen
if (splits.Length >= 3)
return (splits[1], splits[2]);
// Must be in a weird format. Try to return something useful.
if (splits.Length == 2)
return (splits[0], splits[1]);
return (activityId, null);
}
I'm not sure using the OperationId and ParentId is quite right here, e.g. it does tie the background job to the originating request's OperationId, but if the originating Request has a ParentId then this background job should really have its ParentId set as the Request, not as the Request's ParentId. Anyone know?

Autofac shared objects require different registrations per controller but InstancePerApiControllerType won't work

As detailed in InstancePerApiControllerType not working, I am unable to use the InstancePerApiControllerType to configure my solution. The answer provided there works so long as I am directly injecting a ConnectionContext into the controller, or otherwise know that a class is only used by a specific controller. Unfortunately that is not the case in my situation:
ControllerA -> EngineA -> RepositoryA -> GenericEntityAccessor
ControllerB -> EngineB -> RepositoryB -> GenericEntityAccessor
The issue is when we come in through ControllerA, GenericEntityAccessor needs "string A" and from ControllerB it needs "string B".
Of course, the real situation is a little more complicated and there are some bad practices such as code that directly "news"-up a ConnectionContext (it's legacy code). I'm currently exploring providing another component that provides the connection string that is injected via Autofac and configured in the controller using Lazy, but the bad practices are causing problems there also (i.e. once I start to change things in the interface, all the dominoes start to fall over and I end up 15 classes later wondering how I got there).
Are there any patterns, techniques, etc. that address this type of thing? I can't imagine it's all that uncommon.
UPDATE:
To provide a few more specifics, since I'm having some trouble getting this to work, in general we have the following hierarchy, showing which scopes I've applied
Controller -> InstancePerApiRequest()
I*Repository -> ?
I*Manager -> ?
I*Builder -> ?
I*Adapter -> ?
ISqlServerConnectionContext -> ?
IConnectionContextCache -> InstancePerApiRequest()
I've got a number of components that directly take ISqlServerConntectionContext and I'm trying to provide it like so:
container.Register(c =>
{
var connectionContextCache = c.Resolve<IConnectionContextCache>();
var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;
return connection;
}).As<ISqlServerConnectionContext>().InstancePerDependency();
Unfortunately at that point I'm getting a null for CurrectConnectionContext. My guess at this point is I've got some component that isn't rooted from the controller and I'm currently going through the dependencies manually attempting to find it (AFAIK the isn't a way for my to find out which object triggered Autofac to attempt to provide the ISqlServerConnectionContext when I'm debugging).
UPDATE 2:
It turns out I did have some issues where I was registering things improperly, and creating a dependency on ISqlServerConnectionContext for DocumentController, even though it did not have one (this was created through the delegate for something it did depend on).
Now I've got a circular reference that I'm pretty sure I've created myself in the registrations:
container.Register(x =>
{
if (x.IsRegistered<HttpRequestMessage>())
{
var httpRequestMethod = x.Resolve<HttpRequestMessage>();
var tokenHelper = x.Resolve<ITokenHelper>();
var token = tokenHelper.GetToken(httpRequestMethod);
return token ?? new NullMinimalSecurityToken();
}
return new NullMinimalSecurityToken();
}).As<IMinimalSecurityToken>().InstancePerApiRequest();
container.Register(c =>
{
var connectionContextCache = c.Resolve<IConnectionContextCache>();
var token = c.Resolve<IMinimalSecurityToken>();
var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;
connection.Token = token;
return connection;
}).As<ISqlServerConnectionContext>().InstancePerApiRequest();
The problem is ISqlServerConnectionContext has a property of type IMinimalSecurityToken which is optional, and definitely not used when the ISqlServerConnectionContext is being used to look up IMinimalSecurityToken, which depends on ISqlServerConnectionContext through ITokenHelper.
UPDATE 3:
For completeness, in order to solve my circular reference problem I needed to use named services, and use a SqlServerConnectionContext that did not have the IMinimalSecurityToken property set for the IOAuthTokenManager registration. Now I'm getting the dreaded
No scope with a Tag matching 'AutofacWebRequest' is visible
error, but I think that warrants a new question if I'm not able to solve it.
container.Register(c =>
{
var productId = WellKnownIdentifierFactory.Instance.GetWellKnownProductIdentifier(WellKnownProductIdentifiers.RESTSearchService);
var connectionString = ConfigurationManager.AppSettings[AppSettingsNames.DatabaseConnection];
var newConnectionContext = new SqlServerConnectionContext(connectionString) { ProductID = productId };
newConnectionContext.Open();
return newConnectionContext;
}).Named<ISqlServerConnectionContext>("OAuthTokenConnectionContext").InstancePerApiRequest();
container.Register(c => new SqlServerBuilderFactory(c.ResolveNamed<ISqlServerConnectionContext>("OAuthTokenConnectionContext"))).Named<IBuilderFactory>("OAuthTokenBuilderFactory").InstancePerApiRequest();
container.Register(c =>new OAuthTokenManager(c.ResolveNamed<IBuilderFactory>("OAuthTokenBuilderFactory"))).As<IOAuthTokenManager>().InstancePerApiRequest();
This can be solved using AutoFac's support for object graph lifetime scoping.
Cache the current SqlServerConnectionContext in an object scoped to the lifetime of your controller.
Within the SqlServerConnectionContext factory type, once the connection is created assign it to the backing field of the current lifetime-scoped cache
Any types scoped within the lifetimes scope of a controller can then access the connection associated with that controller through the cache
The only complexities I can think of are:
If the controller is not actually the root of a lifetime scope for all types with a dependency on a specific connection. I.e. if they fall outside the lifetime of the controller.
If any of the dependencies are registered as single instance. In which case they will not be able to resolve the Cache as it is currently implemented as it is PerApiRequest.
For example:
public interface ISqlServerConnectionContextCache
{
ISqlServerConnectionContext CurrentContext { get; set; }
}
public class SqlServerConnectionContextScopeCache : ISqlServerConnectionContextCache
{
public ISqlServerConnectionContext CurrentContext { get; set; }
}
public interface ISqlServerConnectionContextFactory
{
ISqlServerConnectionContext Create();
}
// The factory has the cache as a dependancy
// This will be the first use of the cache and hence
// AutoFac will create a new one at the scope of the controller
public class SqlServerConnectionContextFactory : ISqlServerConnectionContextFactory
{
private string _connectionString;
private ISqlServerConnectionContextCache _connectionCache;
public SqlServerConnectionContextFactory(ISqlServerConnectionContextCache connectionCache,
string connectionString)
{
_connectionCache = connectionCache;
_connectionString = connectionString;
}
public ISqlServerConnectionContext Create()
{
var connectionContext = new SqlServerConnectionContext(_connectionString);
connectionContext.Open();
_sqlServerConnectionContextProvider.CurrentContext = connectionContext;
return connectionContext;
}
}
public class MyController : ApiController
{
private ISqlServerConnectionContext _sqlServerConnectionContext;
public MyController(Func<string, ISqlServerConnectionContextFactory> connectionFactory)
{
_sqlServerConnectionContext = connectionFactory("MyConnectionString");
}
}
// As the cache is lifetime scoped it will receive the single instance
// of the cache associated with the current lifetime scope
// Assuming we are within the scope of the controller this will receive
// the cache that was initiated by the factory
public class MyTypeScopedByController
{
public MyTypeScopedByController(ISqlServerConnectionContextCache connectionCache)
{
var sqlServerConnectionContext = connectionCache.CurrentContext;
}
}
// AutoFac wiring
builder.RegisterType<SqlServerConnectionContextScopeCache>()
.As<ISqlServerConnectionContextCache>()
.InstancePerApiRequest();
builder.RegisterType<SqlServerConnectionContextFactory>()
.As<ISqlServerConnectionContextFactory>()
.InstancePerDependency();

Is it bad practice to reference System.Web.Security in the model when using entity service/repository pattern?

Throughout my ASP.net site i need to check if the logged in users belongs to a role or has a field in my "UserInstance" table in my database set to true. To do this i can do the following.
if(Roles.IsUserInRole("Global Admin")
|| uow.UserInstanceService.GetUserInstance(userId,InstanceId).Admin)
{
//Do something
}
However as i am going to be using this code a lot as much of the permissions depend on the logged in user either being a "Global Admin" or a field of my table being true I don't want to write this out constantly.
One solution i have found is to create a method in the "UserInstance" Service which checks for both as seen in the "IsUserAdminOrGlobalAdmin" method.
public class UserInstanceService
{
IRepository<UserInstance> userInstanceRepository;
public UserInstanceService(IRepository<UserInstance> userInstanceRepository)
{
this.userInstanceRepository = userInstanceRepository;
}
public UserInstance GetByUserIdAndInstanceId(Guid userId, int instanceId)
{
return userInstanceRepository.GetSingle(u => u.UserId == userId && u.InstanceId == instanceId);
}
public bool IsUserAdminOrGlobalAdmin(Guid userId,int instanceId)
{
bool valid = false;
if (System.Web.Security.Roles.IsUserInRole("Global Admin"))
valid = true;
if (GetByUserIdAndInstanceId(userId, instanceId).Admin)
valid = true;
return valid;
}
//Removed rest of methods for readability
}
As this is buisness logic I put this method is in my "UserInstanceService" class which interacts with the repository class which contains the entity context. This service class resides in a seperate Model project so i had to add a reference to System.Web.Security and i am not sure if doing this is good practice. One thing i have noticed is that i can not write unit tests for this method as it relies on a user being logged in.
So my question is, is it acceptable to combine HttpContext specific functionality like the Logged in users roles, in a service?
Edit - After reading the answers I have changed my code so a Auth service (in the Web app project) is called which in turn calls the UserInstanceService to something like this.
public class Auth: IAuthService {
public bool IsUserAdminOrGlobalAdmin(Guid userId,int instanceId) {
myEntities entityContext = new myEntities
//RepsitoryBase inherits my IRepository<TEntity> class
UserInstanceService uis = new UserInstanceService(new RepositoryBase<UserInstance>(entityContext));
bool valid = false
if(Roles.IsUserInRole("Global Admin"))
valid = true;
if(uis.GetByUserIdAndInstanceId(userId,instanceId).Admin)
valid = true;
return valid;
}
}
So i could call this in my pages like this
if(Auth.IsUserAdminOrGlobalAdmin(userId,InstanceId)
{
//Do stuff
}
The original answer was written assuming the UserAccess requires the Authentication, but it appears that the Authentication consumes the UserAccess; simply invert the dependencies, but everything else should be usable in about the same manner.
Original answer:
Pull the ASP.NET-specific code into it's own service separate from the repository. Then that service - say, the Auth Service - can be used by any component (such as the UserInstanceService) that needs access to centralized authentication/authorization logic.
Consume the Auth as a dependency per IoC principles, hopefully using some DI to make life easier.
If the Auth service is kept separate it can also be trivially mocked for testing, such as testing what happens when the use is authenticated or not, which entirely avoids the need to setup a full ASP.NET stack for the User service.
In addition, because services (interfaces) and components (classes) are separate, the actualy HTTP-utilizing component can live in a separate project from the service and wired in later - this will avoid pulling in Web dependencies to the Model project.
For example,
// This is the Service Contract and can live in the Model
public class IAuthService {
void AssertCurrentUserIsAdminOrGlobalAdmin();
void AssertIsUserAdminOrGlobalAdmin(Guid userId,int instanceId);
}
// This is the Component, which provides the Service, and is part
// of the Web/HTTP-specific project. It is wired up via IoC/DI from
// the large context of the application.
public class Auth: IAuthService {
public void AssertCurrentUserIsAdminOrGlobalAdmin() {
// This one already knows the applicable HTTP/User Context
}
public void AssertIsUserAdminOrGlobalAdmin(Guid userId,int instanceId) {
// Do whatever
}
}
// This Component is part of the Model
public class UserInstanceService
{
// IoC dependencies
IRepository<UserInstance> userInstanceRepository;
IAuthService authService;
}
You could set the current principal on the thread and use that instead. I think thats most of what ASP.Net does for you as well.

How to specify the implementation you want to inject

I'm in the process of implementing a notification service. Essentially, customers can get notified in a number of ways, such as via email, text message, fax etc. Below is a rough implementation that is not wired together.
public class NotificationService
{
private readonly INotification _notification;
private readonly INotificationFormatter _formatter;
public NotificationService(
INotificationMethod notification,
INotificationFormatter formatter)
{
_notification = notification;
_formatter = formatter;
}
public void Notify(SomeParameterObject obj)
{
var formattedMessage = _formatter.Format(obj);
_notification.SendNotification(formattedMessage);
}
}
public interface INotificationFormatter
{
NotificationMessage Format(SomeParameterObject obj);
}
public interface INotification
{
void SendNotification();
}
public EmailNotification : INotification
{
public void SendNotification(NotificationMessage message)
{
// Use Exchange Web Services to send email
}
}
The NotificationService class essentially takes in a method of notification and a formatter. Obviously, each method of notification requires different formatting.
Based on business criteria, how do I select which implementation of INotification and NotificationFormatter I wish to use? Note that within the lifetime of the user using the application each notification will most likely be used. I say this because it's not as simple as instructing my container to inject implementation Foobar as it will change while the user is using the application.
I've thought of creating some sort of class that could handle pairs because it seems to makes sense to me that you wouldn't want use a text message notification formatter for a fax notification. However, I can't seem to wrap my head around a decent implementation of this.
I also own the book Dependency Injection in .NET by Mark Seemann. Did I perhaps miss something obvious?
Thank you.
How is it that you decide what kind of notification a user wants? If it can change while they're using your app, it seems like the NotificationService for that user msut be created anew for each notification you want to send them. That's ok - just use some sort of lookup to select a INotification impelmentation with an IoC container.
IoC's (I use AutoFac) let you use string-indexes to select a specific implementation. That string could come from a DB or whatever to represent the user's preference. Then you'd pass it to your IoC asking for an INotification 'decorated' with your string-choice. Upon startup, all the various implementations are registered with thier choice-strings.
I think you may be on to something with your 'pairs' comment - if INotificationFormat is closely tied to INotification and there is a possiblity of mixing them up then perhaps the INotification implementation itself should select its formatter.
What you need to do is to provide some kind of configuration infrastructure. For example, assuming that you want to keep the service just the way you've defined it, I would implement a factory returning an instance of NotificationService according to your model:
public struct NotificaitonSettings<T>
{
public Predicate<T> Predicate;
public NotificationService Service;
}
public class NotificationServiceFactory<T> : INotificationServiceFactory<T>
{
protected static List<NotificaitonSettings<T>> settings = new List<NotificaitonSettings<T>>();
static NotificationServiceFactory()
{
settings.Add(new NotificaitonSettings<T>
{
Predicate = m => !String.IsNullOrEmpty(m.Email),
Service = new NotificationService(new EmailNotification(), new EmailFormatter())
});
settings.Add(new NotificaitonSettings<T>
{
Predicate = m => !String.IsNullOrEmpty(m.Fax),
Service = new NotificationService(new FaxNotification(), new FaxFormatter())
});
}
public NotificationService Create(T model)
{
return settings.FirstOrDefault(s => s.Predicate(model)).Service;
}
}
This implementation configures the factory using static list, you could use a IoC container if it supports this kind of operations.

Async WCF: wait for another call

We have an old Silverlight UserControl + WCF component in our framework and we would like to increase the reusability of this feature. The component should work with basic functionality by default, but we would like to extend it based on the current project (without modifying the original, so more of this control can appear in the full system with different functionality).
So we made a plan, where everything looks great, except one thing. Here is a short summary:
Silverlight UserControl can be extended and manipulated via ContentPresenter at the UI and ViewModel inheritance, events and messaging in the client logic.
Back-end business logic can be manipulated with module loading.
This gonna be okay I think. For example you can disable/remove fields from the UI with overriden ViewModel properties, and at the back-end you can avoid some action with custom modules.
The interesting part is when you add new fields via the ContentPresenter. Ok, you add new properties to the inherited ViewModel, then you can bind to them. You have the additional data. When you save base data, you know it's succeeded, then you can start saving your additional data (additional data can be anything, in a different table at back-end for example). Fine, we extended our UserControl and the back-end logic and the original userControl still doesn't know anything about our extension.
But we lost transaction. For example we can save base data, but additional data saving throws an exception, we have the updated base data but nothing in the additional table. We really doesn't want this possibility, so I came up with this idea:
One WCF call should wait for the other at the back-end, and if both arrived, we can begin cross thread communication between them, and of course, we can handle the base and the additional data in the same transaction, and the base component still doesn't know anything about the other (it just provide a feature to do something with it, but it doesn't know who gonna do it).
I made a very simplified proof of concept solution, this is the output:
1 send begins
Press return to send the second piece
2 send begins
2 send completed, returned: 1
1 send completed, returned: 2
Service
namespace MyService
{
[ServiceContract]
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service1
{
protected bool _sameArrived;
protected Piece _same;
[OperationContract]
public Piece SendPiece(Piece piece)
{
_sameArrived = false;
Mediator.Instance.WaitFor(piece, sameArrived);
while (!_sameArrived)
{
Thread.Sleep(100);
}
return _same;
}
protected void sameArrived(Piece piece)
{
_same = piece;
_sameArrived = true;
}
}
}
Piece (entity)
namespace MyService
{
[DataContract]
public class Piece
{
[DataMember]
public long ID { get; set; }
[DataMember]
public string SameIdentifier { get; set; }
}
}
Mediator
namespace MyService
{
public sealed class Mediator
{
private static Mediator _instance;
private static object syncRoot = new Object();
private List<Tuple<Piece, Action<Piece>>> _waitsFor;
private Mediator()
{
_waitsFor = new List<Tuple<Piece, Action<Piece>>>();
}
public static Mediator Instance
{
get
{
if (_instance == null)
{
lock (syncRoot)
{
_instance = new Mediator();
}
}
return _instance;
}
}
public void WaitFor(Piece piece, Action<Piece> callback)
{
lock (_waitsFor)
{
var waiter = _waitsFor.Where(i => i.Item1.SameIdentifier == piece.SameIdentifier).FirstOrDefault();
if (waiter != null)
{
_waitsFor.Remove(waiter);
waiter.Item2(piece);
callback(waiter.Item1);
}
else
{
_waitsFor.Add(new Tuple<Piece, Action<Piece>>(piece, callback));
}
}
}
}
}
And the client side code
namespace MyClient
{
class Program
{
static void Main(string[] args)
{
Client c1 = new Client(new Piece()
{
ID = 1,
SameIdentifier = "customIdentifier"
});
Client c2 = new Client(new Piece()
{
ID = 2,
SameIdentifier = "customIdentifier"
});
c1.SendPiece();
Console.WriteLine("Press return to send the second piece");
Console.ReadLine();
c2.SendPiece();
Console.ReadLine();
}
}
class Client
{
protected Piece _piece;
protected Service1Client _service;
public Client(Piece piece)
{
_piece = piece;
_service = new Service1Client();
}
public void SendPiece()
{
Console.WriteLine("{0} send begins", _piece.ID);
_service.BeginSendPiece(_piece, new AsyncCallback(sendPieceCallback), null);
}
protected void sendPieceCallback(IAsyncResult result)
{
Piece returnedPiece = _service.EndSendPiece(result);
Console.WriteLine("{0} send completed, returned: {1}", _piece.ID, returnedPiece.ID);
}
}
}
So is it a good idea to wait for another WCF call (which may or may not be invoked, so in a real example it would be more complex), and process them together with cross threading communication? Or not and I should look for another solution?
Thanks in advance,
negra
If you want to extend your application without changing any existing code, you can use MEF that is Microsoft Extensibility Framework.
For using MEF with silverlight see: http://development-guides.silverbaylabs.org/Video/Silverlight-MEF
I would not wait for 2 WCF calls from Silverlight, for the following reasons:
You are making your code more complex and less maintainable
You are storing business knowledge, that two services should be called together, in the client
I would call a single service that aggreagated the two services.
It doesn't feel like a great idea to me, to be honest. I think it would be neater if you could package up both "partial" requests in a single "full" request, and wait for that. Unfortunately I don't know the best way of doing that within WCF. It's possible that there's a generalized mechanism for this, but I don't know about it. Basically you'd need some loosely typed service layer where you could represent a generalized request and a generalized response, routing the requests appropriately in the server. You could then represent a collection of requests and responses easily.
That's the approach I'd look at, personally - but I don't know how neatly it will turn out in WCF.

Categories