Доступ к текущему InstanceContext в WCF UsernamePasswordValidator

У меня есть служба WCF, которая использует настраиваемый UsernamePasswordValidator. Валидатор должен получить доступ к контексту моей сущности.

Я хотел бы создать один ObjectContext для всего вызова службы, а затем уничтожить/удалить его в конце вызова. Итак, я создал статический класс синглтона, который обеспечивал эту функциональность, однако сейчас происходит то, что если два вызова службы происходят одновременно, один из вызовов удаляет синглтон.

Я либо сохраняю локальную ссылку на ObjectContext, и в этом случае второй сервис, который его использует, видит его как удаленный и выдает и выдает ошибку, или я помещаю свойство-оболочку вокруг класса Singleton везде, где мне это нужно, и тогда все мои изменения выбрасываются прочь, потому что я получаю новый экземпляр объекта, если он был удален другим вызовом.

Итак, в основном мой вопрос заключается в том, как создать экземпляр ObjectContext для каждого вызова службы?

ПРИМЕЧАНИЕ. Экземпляр должен быть доступен как в сервисном коде, так и в пользовательском коде UsernamePasswordValidator.

Я не могу просто сделать это в конструкторе или использовать оператор using, потому что тогда пользовательский UsernamePasswordValidator не имеет к нему доступа. Есть ли способ иметь статический класс для каждого вызова? Это кажется невозможным, но как обойти это? Должен ли я кэшировать объект в сеансе?

Мой сервис размещен в IIS.

ОБНОВЛЕНИЕ:
Итак, я прибил это к сохранению состояния в InstanceContext с помощью объекта IExtension. Но как мне получить доступ к текущему InstanceContext в UsernamePasswordValidator?


person Robert Fall    schedule 23.07.2010    source источник


Ответы (6)


Итак, в конце концов я решил это, используя следующий статический класс и полагаясь на ASP.NET для кэширования контекста для меня.

Я не уверен, что это лучший способ сделать что-то, но это позволяет мне использовать один ObjectContext для каждого запроса, поэтому я не раскручиваю слишком много, и это также означает, что мне не нужно использовать блокировку объекта что стало бы кошмаром, если бы многие пользователи использовали эту услугу.

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;
            }
        }    
    }

Затем везде, где мне нужен ObjectContext в приложении, я просто вызываю

var context = MyContextProvider.Context;
person Robert Fall    schedule 23.07.2010

У вас есть один экземпляр на вызов, у вас также есть 1 вызов на экземпляр.

Так что это должно быть очень просто, используйте блок using () { } на верхнем уровне вашего метода OperationContract.

person Henk Holterman    schedule 23.07.2010
comment
Лол, не могу поверить, что не подумал о чем-то настолько очевидном :( - person Matthew Abbott; 23.07.2010
comment
Это не сработает. Это нормально для метода OperationContract, но настраиваемый UsernamePasswordValidator вызывается ДО того, как я достигну метода OperationContract. Я не особенно хочу раскручивать два ObjectContexts за вызов... Если есть ЛЮБОЙ другой способ, я прыгну на него. - person Robert Fall; 23.07.2010

Хорошо, вот класс с потокобезопасным статическим методом, который предоставляет один объект модели сущности ObjectContext для любого вызова службы WCF и автоматически удаляет его в конце вызова:

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];
        }
    }
person Vlad Rudenko    schedule 02.08.2013

Для вашей службы вы можете указать поведение службы, в котором подробно описывается режим экземпляра службы:

[ServiceBehaviour(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyService : IMyService {
    ObjectContext context;
}
person Matthew Abbott    schedule 23.07.2010
comment
У меня уже есть этот атрибут. Проблема, с которой я сталкиваюсь, заключается в совместном использовании контекста между фактическим кодом службы и кодом UsernamePasswordValidator. - person Robert Fall; 23.07.2010

Более чистым способом может быть использование ServiceAuthenticationManager, который есть в .NET 4.

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

Из метода Authenticate (который вы переопределите) вы можете получить доступ к объекту Message и установить для него свойства. Я не использовал его в гневе, так что YMMV :)

РЕДАКТИРОВАТЬ проблема с этим подходом заключается в том, что у вас нет имени пользователя и пароля, поэтому вам все равно потребуется пользовательская аутентификация.

Взгляните на UsernameSecurityTokenAuthenticator... http://msdn.microsoft.com/en-us/library/system.identitymodel.selectors.usernamesecuritytokenauthenticator(v=vs.90).aspx


Дальнейшее чтение из моего исследования:

Ответы на этот вопрос дают некоторые советы о том, как его использовать:

Пользовательская проверка подлинности WCF с помощью System.ServiceModel.ServiceAuthenticationManager?

Если вы умеете читать (или игнорировать) по-русски, я нашел полезные подсказки:

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

Эта довольно хорошая статья CodeProject идет дальше (шифрование и сжатие, а также пользовательская авторизация).

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

person CJBrew    schedule 12.12.2012

Почему бы не передать контекст в ваш CustomValidator при назначении службе - сохраните контекст вашего объекта в вашем валидаторе и в переопределенном методе проверки обновите его, если это необходимо. Тогда у вас все равно есть доступ к объекту через Сервисы CutomUserNameValidator..

В зависимости от того, что вы просите: создайте свой отдельный класс ObjectContext как динамический объект — добавьте его как свойство в CustomValidator. В вашем пользовательском валидаторе теперь вы можете проверить, удален ли объект, и снова создать объект, если это необходимо. В противном случае, если это не то, что вам нужно - просто сохраните контекст в валидаторе - у вас все еще есть доступ на стороне сервера. Код здесь является просто обобщенной идеей - я просто публикую его как систему отсчета, чтобы вы могли иметь представление о том, о чем я говорю.

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");
                }
       }
   }
} 
person Ken    schedule 17.11.2015