5

Tengo una capa de acceso a datos, una capa de servicio y una capa de presentación. La capa de presentación es ASP.NET MVC2 RTM (web), y la capa de servicio es WCF (servicios). Es todo .NET 3.5 SP1.Uso de WCF DataContract en MVC SessionState usando el caché de AppFabric

El problema es que en los servicios, los objetos que se devuelven se marcan con el atributo [DataContract]. La web está utilizando el caché AppFabric Cache (a.k.a Velocity) SessionStateProvider para almacenar el estado de la sesión. Debido a esto, todo lo que almacene en la sesión debe ser serializable.

Aquí viene el problema: los DataContracts no están marcados con [Serializable] y, por lo que puedo recordar, al introducirlo en una clase ya marcada con [DataContract] surgen algunos problemas, por lo que no creo que esta sea una solución .

Al principio estaba planeando usar los DataContracts directamente en la capa web, utilizándolos como modelos para las vistas relacionadas con la representación de los DataContracts (probablemente anidados dentro de una clase ViewModel de nivel superior). Pero debido a que el proveedor del estado de la sesión requiere que todos los objetos almacenados en él sean serializables, estoy empezando a replantear esta estrategia. Sin embargo, sería bueno tenerlo, ya que contienen una lógica de validación usando la interfaz IDataErrorInfo, y la misma lógica de validación podría reutilizarse en MVC como parte del enlace del modelo.

¿Cuál cree que es la mejor manera de permitirme reducir el trabajo necesario?

He pensado en la actualidad de las siguientes maneras:

A. Crear una parte 'ServiceIntegration' en el proyecto web.

Este sería un intermediario entre mis controladores y mi capa de servicio WCF. La parte de Integración de servicio hablaría a la capa de servicio usando DataContracts, y a la capa web usando ViewModels, pero tendría que transformarse entre DataContracts y ViewModels usando un transformador bidireccional.

Además, dado que la validación de IDataErrorInfo no sería reutilizable, también sería necesario crear un Validator por DataContract, que utiliza el transformador para convertir de ViewModel a DataContract, realizar la validación utilizando IDataErrorInfo y devolver sus resultados. Esto entonces se utilizaría métodos de acción dentro de los controladores (por ejemplo if (!MyValidator.IsValid(viewModel)) return View();)

Diferentes clases requerido: xDataContract, xViewModel, xTransformer, xValidator

B. Crear una parte 'SessionIntegration' en el proyecto web

Este sería un intermediario entre los controladores (o cualquier cosa que acceda a la sesión) y la sesión en sí. Todo lo que requiera acceso a la sesión pasará por esta clase. DataContracts se usará en toda la aplicación, a menos que se almacenen en la sesión. La parte SessionIntegration tendría la responsabilidad de transformar el DataContract en algún formulario de ISerializable, y viceversa. No se necesita ningún Validador adicional debido al uso de la interfaz IDataErrorInfo en el DataContract.

Diferentes clases requeridas: xDataContract, xTransformer, xSerializableForm


Nota: todavía habría ViewModels vueltas en ambos escenarios, sin embargo con (B) que sería capaz de componer ViewModels de DataContracts.

(B) tiene la ventaja de no necesitar un validador adicional.


Antes de salir e implementar (A)/(B) por completo, me gustaría recibir algunos comentarios. Por el momento, estoy empezando a inclinarme hacia (B), sin embargo, (A) podría ser más flexible. De cualquier manera, parece demasiado trabajo para lo que vale. ¿Alguien más ha encontrado este problema, está de acuerdo/en desacuerdo conmigo, y/o tiene alguna otra forma de resolver el problema?

Gracias,

James

+0

Podría usar AutoMapper como parte del Tr ansformer, por lo que los detalles del mapeo pueden no ser una sobrecarga. Mapeo manual de ViewModels a DataContracts y viceversa definitivamente es mucho trabajo para lo que vale, IMHO – jamiebarrow

Respuesta

5

Sin ir la ruta en toda regla de A o B, ¿podrías hacer un objeto envoltorio ISerializable genérica y poner los de su SessionState?

[Serializable] 
    public class Wrapper : ISerializable 
    { 
     public object Value { get; set; } 

     void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) 
     { 
      if (Value != null) 
      { 
       info.AddValue("IsNull", false); 
       if (Value.GetType().GetCustomAttributes(typeof(DataContractAttribute), false).Length == 1) 
       { 
        using (var ms = new MemoryStream()) 
        { 
         var serializer = new DataContractSerializer(Value.GetType()); 
         serializer.WriteObject(ms, Value); 
         info.AddValue("Bytes", ms.ToArray()); 
         info.AddValue("IsDataContract", true); 
        } 
       } 
       else if (Value.GetType().IsSerializable) 
       { 
        info.AddValue("Value", Value); 
        info.AddValue("IsDataContract", false); 
       } 
       info.AddValue("Type", Value.GetType()); 
      } 
      else 
      { 
       info.AddValue("IsNull", true); 
      } 
     } 

     public Wrapper(SerializationInfo info, StreamingContext context) 
     { 
      if (!info.GetBoolean("IsNull")) 
      { 
       var type = info.GetValue("Type", typeof(Type)) as Type; 

       if (info.GetBoolean("IsDataContract")) 
       { 
        using (var ms = new MemoryStream(info.GetValue("Bytes", typeof(byte[])) as byte[])) 
        { 
         var serializer = new DataContractSerializer(type); 
         Value = serializer.ReadObject(ms); 
        } 
       } 
       else 
       { 
        Value = info.GetValue("Value", type); 
       } 
      } 
     } 
    } 
+0

Gracias por la respuesta. Lo probaré cuando sea el próximo en mi máquina de desarrollo. Soy un poco nuevo en C#, así que no estoy al 100% en los detalles. El constructor que creaste que contiene SerializationInfo y StreamingContext, ¿para qué sirve eso? Supongo que el uso previsto sería: MyDataContract c = ...; Session ["mykey"] = nuevo Wrapper {Value = c}; – jamiebarrow

+0

Cuando hereda ISerializable, debe definir un constructor con esa firma, que es lo que utiliza BinaryFormatter (utilizado internamente por el SessionState para la serialización) para reconstruir el objeto durante la deserialización. Es un bocado, pero lea esto: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx (las primeras líneas son lo que aborda su pregunta) – Jeff

+0

Sí, esa es la intención utilizar. – Jeff

3

Como una extensión de la respuesta proporcionada, agregué estos dos métodos para facilitar el almacenamiento/recuperación de los datos.

public static void Set<T>(HttpSessionStateBase session, string key, T value) 
    { 
     session[key] = new Wrapper(value); 
    } 

    public static T Get<T>(HttpSessionStateBase session, string key) 
    { 
     object value = session[key]; 
     if (value != null && typeof(T) == value.GetType()) 
     { 
      return (T) value; 
     } 
     Wrapper wrapper = value as Wrapper; 
     return (T) ((wrapper == null) ? null : wrapper.Value); 
    } 

Esto hace que sea un poco más fácil para establecer/obtener valores de la sesión:

MyDataContract c = ...; 
    Wrapper.Set(Session, "mykey", c); 
    c = Wrapper.Get<MyDataContract>(Session, "mykey"); 

para que sea aún más fácil, añadir los métodos de extensión:

public static class SessionWrapperEx 
{ 
    public static void SetWrapped<T>(this HttpSessionStateBase session, string key, T value) 
    { 
     Wrapper.Set<T>(session, key, value); 
    } 

    public static T GetWrapped<T>(this HttpSessionStateBase session, string key) 
    { 
     return Wrapper.Get<T>(session, key); 
    } 
} 

y el uso de la siguiente :

MyDataContract c = ...; 
    Session.SetWrapped("mykey", c); 
    c = Session.GetWrapped<MyDataContract>("mykey"); 
+0

muy agradable:) ... – Jeff

Cuestiones relacionadas