Acesse o InstanceContext atual em um UsernamePasswordValidator do WCF
-
27-09-2020 - |
Pergunta
Eu tenho um serviço WCF que está usando um UsernamePasswordValidator personalizado.O validador precisa acessar o contexto da estrutura da minha entidade.
Gostaria de criar um ObjectContext para toda a chamada de serviço e, em seguida, destruí-lo/descartá-lo no final da chamada.Então criei uma classe estática singleton que fornecia essa funcionalidade, porém, o que está acontecendo agora é que se duas chamadas de serviço acontecerem simultaneamente, uma das chamadas descarta o singleton.
Eu mantenho uma referência local para o ObjectContext, caso em que o segundo serviço a usá-lo o vê como descartado e gera um erro, ou coloco uma propriedade wrapper em torno da classe Singleton onde quer que eu precise e então todas as minhas alterações são lançadas afastado porque estou obtendo uma nova instância do objeto se outra chamada o tiver descartado.
Então, basicamente, minha pergunta é como instanciar um ObjectContext por chamada de serviço?
OBSERVAÇÃO:A instância precisa estar acessível no código de serviço E no código UsernamePasswordValidator personalizado.
Não posso simplesmente fazer isso no construtor ou usar uma instrução using porque então o custom UsernamePasswordValidator não tem acesso a ele.Existe uma maneira de ter uma classe estática por chamada?Parece impossível, mas qual é a maneira de contornar isso?Devo armazenar o objeto em cache em uma sessão?
Meu serviço está hospedado no IIS.
ATUALIZAR:
Então, resolvi isso para armazenar o estado no InstanceContext usando um objeto IExtension.Mas como faço para acessar o InstanceContext atual em um UsernamePasswordValidator?
Solução
Ok, no final resolvi usando a seguinte classe estática e contando com o ASP.NET para armazenar em cache o contexto para mim.
Não tenho certeza se essa é a melhor maneira de fazer as coisas, mas isso me permite usar um ObjectContext por solicitação, para não gerar muitos e isso também significa que não preciso usar um bloqueio no objeto o que se tornaria um pesadelo se muitos usuários usassem o serviço.
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;
}
}
}
Então, sempre que eu precisar de um ObjectContext no aplicativo, eu simplesmente chamo
var context = MyContextProvider.Context;
Outras dicas
Você tem uma instância por chamada, você também tem 1 chamada por instância.
Por isso, deve ser muito simples, use um bloco using () { }
na Toplevel do seu método OperationContract.
Ok, aqui está a classe com método estático thread-safe que fornece um único objeto de modelo de entidade ObjectContext para qualquer chamada de serviço WCF e o descarta automaticamente no final da chamada:
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];
}
}
Para o seu serviço, você pode especificar um comportamento de serviço que detalha o modo de instância do serviço:
[ServiceBehaviour(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyService : IMyService {
ObjectContext context;
}
Uma maneira mais limpa pode ser usar o ServiceAuthenticationManager, que está no .NET 4.
http://msdn.microsoft.com/en-us/library/system.servicemodel.serviceauthenticationmanager.aspx
De Authenticate
método (que você substituirá), você pode acessar o objeto Message e definir propriedades nele.Eu não usei isso com raiva, então YMMV :)
EDITAR o problema com esta abordagem é que você não tem o nome de usuário e a senha, então ainda precisará da autenticação personalizada.
Dê uma olhada no UsernameSecurityTokenAuthenticator...http://msdn.microsoft.com/en-us/library/system.identitymodel.selectors.usernamesecuritytokenauthenticator(v=vs.90).aspx
Leitura adicional da minha pesquisa:
As respostas a esta pergunta dão algumas dicas sobre como usá-lo:
Autenticação WCF personalizada com System.ServiceModel.ServiceAuthenticationManager?
Se você consegue ler (ou ignorar) o russo, encontrei dicas úteis em:
http://www.sql.ru/forum/actualthread.aspx?tid=799046
Este bom artigo do CodeProject vai além (criptografia e compactação, bem como autorização personalizada)
http://www.codeproject.com/Articles/165844/WCF-Client-Server-Application-with-Custom-Authenti
Por que não passar o contexto para o seu CustomValidator quando você atribui ao serviço - armazene o contexto do objeto no seu validador e, no método de validação substituído, atualize-o, se necessário.Então você ainda tem acesso ao objeto através dos Serviços CutomUserNameValidator..
Dependendo do que você está perguntando:Crie sua classe ObjectContext separada como um objeto dinâmico - adicione-a como uma propriedade ao seu CustomValidator.No seu validador personalizado - agora você pode verificar se o objeto foi descartado e criá-lo novamente, se necessário.Caso contrário, se não é isso que você procura - basta armazenar o Contexto no validador - você ainda terá acesso no lado do servidor.O código aqui é apenas uma ideia generalizada - estou apenas postando-o como um quadro de referência para que você possa ter uma ideia do que estou falando.
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");
}
}
}
}