Question

I have a WCF service that is using a custom UsernamePasswordValidator. The validator needs to access my entity framework context.

I would like to create one ObjectContext for the entire service call and then destroy/dispose it at the end of the call. So I created a singleton static class that provided this functionality, however, what's happening now is that if two service calls happen concurrently, one of the calls disposes the singleton.

I either keep a local reference to the ObjectContext, in which case the second service to use it sees it as disposed and throws and error, or, I put a wrapper property around the Singleton class wherever I need it and then all my changes get thrown away because I'm getting a new instance of the object if another call has disposed it.

So basically my question is how do I instantiate an ObjectContext per service call?

NOTE: The instance needs to be accesible in both the service code AND the custom UsernamePasswordValidator code.

I can't just do it in the constructor or use a using statement because then the custom UsernamePasswordValidator doesn't have access to it. Is there a way to have a static class per call? It does sound impossible, but what's the way around this? Should I be caching the object in a session?

My service is hosted in IIS.

UPDATE:
So I've nailed this down to storing state in the InstanceContext using an IExtension object. But How do I access the current InstanceContext in a UsernamePasswordValidator?

Was it helpful?

Solution

Ok, so in the end I solved it by using the following static class and relying on ASP.NET to cache the context for me.

I'm not sure if this is the best way to do things, but this allows me to use one ObjectContext per request so I'm not spinning up too many and this also means I don't have to use a lock on the object which would become a nightmare if many users were using the service.

public static class MyContextProvider
    {
        public static MyModel Context
        {
            get
            {
                if (HttpContext.Current.Items["context"].IsNull())
                {
                    HttpContext.Current.Items["context"] = new MyModel();
                }

                return HttpContext.Current.Items["context"] as MyModel;
            }
        }    
    }

Then wherever I need an ObjectContext in the app I just call

var context = MyContextProvider.Context;

OTHER TIPS

You have one instance per call, you also have 1 call per instance.

So it should be very simple, use a using () { } block in the toplevel of your OperationContract method.

Ok, here is the class with thread-safe static method that provides single ObjectContext entity model object for any WCF service call and automatically dispose it at the end of call:

public static class EntityModelProvider
{
    private static readonly Dictionary<OperationContext, MyEntityModel> _entityModels = new Dictionary<OperationContext, MyEntityModel>();

    public static MyEntityModel GetEntityModel()
    {
        if (OperationContext.Current == null)
            throw new Exception("OperationContext is missing");

        lock (_entityModels)
        {
            if (!_entityModels.ContainsKey(OperationContext.Current))
            {
                _entityModels[OperationContext.Current] = new MyEntityModel();
                OperationContext.Current.OperationCompleted += delegate
                {
                    lock (_entityModels)
                    {
                        _entityModels[OperationContext.Current].Dispose();
                        _entityModels.Remove(OperationContext.Current);
                    }
                };
            }

            return _entityModels[OperationContext.Current];
        }
    }

For your service, you can specify a service behaviour which details the instance mode of the service:

[ServiceBehaviour(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyService : IMyService {
    ObjectContext context;
}

A cleaner way may be to use the ServiceAuthenticationManager, which is in .NET 4.

http://msdn.microsoft.com/en-us/library/system.servicemodel.serviceauthenticationmanager.aspx

From the Authenticate method (which you'll override) you can access the Message object and set properties on it. I've not used it in anger, so YMMV :)

EDIT the problem with this approach is that you don't have the Username and Password, so will still need the custom Authentication.

Take a look at the UsernameSecurityTokenAuthenticator... http://msdn.microsoft.com/en-us/library/system.identitymodel.selectors.usernamesecuritytokenauthenticator(v=vs.90).aspx


Further reading from my research:

Answers to this question gives some hints about how to use it:

Custom WCF authentication with System.ServiceModel.ServiceAuthenticationManager?

If you can read (or ignore) the Russian, I found useful hints at:

http://www.sql.ru/forum/actualthread.aspx?tid=799046

This rather good CodeProject article goes further (encryption and compression as well as custom authorization)

http://www.codeproject.com/Articles/165844/WCF-Client-Server-Application-with-Custom-Authenti

Why not pass in the context into your CustomValidator when you assign to the service - store your object context in your validator, and in the overridden validation method new it up if need be. Then you still have access to the object through the Services CutomUserNameValidator ..

Depending on what you are asking : Create your separate ObjectContext class as a dynamic object - add that as a property to you CustomValidator. In your custom Validator - you can now check if the object is disposed and create the object again if need be. Otherwise if this is not what you are after - just store the Context in the validator - you still have access on server side. The code here is just generalized idea - I am just posting it as a frame of reference so you can have an idea of what I talking about.

public DynamicObjectContextObjectClass
{
  ObjectContext internalObjectContext;

}
public class ServiceUserNamePasswordValidator : UserNamePasswordValidator
{

    public DynamicObjectContextObjectClass dynamiccontext;


    public override void Validate(string userName, string password)
    {
        if(dynamiccontext.internalObjectContext.isdisposed)
        {

        dynamiccontext.internalObjectContext = new Context;

            }
            try
            {
                if (string.IsNullOrEmpty(userName) || password == null)
                {
                    //throw new ArgumentNullException();
                    throw new FaultException("Username cannot be null or empty; Password cannot be null and should not be empty");
                }
       }
   }
} 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top