5

J'ai une couche d'accès aux données, une couche de service et une couche de présentation. Le calque de présentation est ASP.NET MVC2 RTM (Web) et le calque de service est WCF (services). Tout est .NET 3.5 SP1.Utilisation de WCF DataContract dans MVC SessionState à l'aide du cache AppFabric

Le problème est que dans les services, les objets renvoyés sont marqués avec l'attribut [DataContract]. Le Web utilise le cache AppFabric Cache (a.k.a Velocity) SessionStateProvider pour stocker l'état de la session. Pour cette raison, tout ce que je stocke dans la session doit être sérialisable.

Voici le problème: les DataContracts ne sont pas marqués avec [Serializable] et autant que je me souvienne, en l'introduisant sur une classe déjà marquée avec [DataContract] certains problèmes surviennent, et donc je ne crois pas que ce soit une solution . J'ai d'abord prévu d'utiliser les DataContracts directement dans la couche Web, en les utilisant comme modèles pour les vues liées au rendu des DataContracts (probablement imbriquées dans une classe ViewModel de niveau supérieur). Mais étant donné que le fournisseur d'état de session exige que tous les objets stockés à l'intérieur soient sérialisables, je commence à repenser cette stratégie. Ce serait bien d'avoir, car ils contiennent une logique de validation utilisant l'interface IDataErrorInfo, et la même logique de validation pourrait être réutilisée dans MVC dans le cadre de la liaison de modèle.

Selon vous, quelle est la meilleure façon de me permettre de réduire le travail nécessaire?

J'ai pensé actuellement des façons suivantes:

A. Créer une partie « ServiceIntegration » dans le projet web.

Ce serait un intermédiaire entre mes contrôleurs et ma couche de service WCF. La partie ServiceIntegration parlait à la couche de service à l'aide de DataContracts et à la couche Web à l'aide de ViewModels, mais devait être transformée entre DataContracts et ViewModels à l'aide d'un Transformer bidirectionnel. Etant donné que la validation IDataErrorInfo ne serait pas réutilisable, il serait également nécessaire de créer un Validator par DataContract, qui utilise Transformer pour convertir ViewModel en DataContract, effectuer une validation en utilisant IDataErrorInfo et renvoyer ses résultats. Ce serait alors utilisé à l'intérieur des méthodes d'action des contrôleurs (par exemple if (!MyValidator.IsValid(viewModel)) return View();)

Différentes classes requises: xDataContract, xViewModel, xTransformer, xValidator

B. Créer une partie 'SessionIntegration' dans le projet web

Ce serait un intermédiaire entre les contrôleurs (ou tout ce qui accède à la session) et la session elle-même. Tout ce qui nécessite l'accès à la session passerait par cette classe. DataContracts serait utilisé dans l'ensemble de l'application, à moins qu'ils ne soient stockés dans la session. La partie SessionIntegration prend la responsabilité de transformer le DataContract en une forme ISerializable, et inversement. Aucun validateur supplémentaire n'est nécessaire en raison de l'utilisation de l'interface IDataErrorInfo sur le DataContract.

Différentes classes requises: xDataContract, xTransformer, xSerializableForm


Note: il y aurait encore ViewModels autour dans les deux scénarios, mais avec (B) je serais capable de composer ViewModels de DataContracts.

(B) a l'avantage de ne pas avoir besoin d'un validateur supplémentaire.


Avant de commencer et de mettre en œuvre complètement (A)/(B), j'aimerais avoir vos commentaires. Pour l'instant, je commence à pencher vers (B), cependant, (A) pourrait être plus flexible. De toute façon, il semble que ce soit trop de travail pour ce que ça vaut. Quelqu'un d'autre a-t-il rencontré ce problème, êtes-vous d'accord/en désaccord avec moi et/ou avez-vous un autre moyen de résoudre le problème?

Merci,

James

+0

Je pourrais utiliser AutoMapper dans le cadre de la Tr ansformer, et donc les détails de la cartographie pourraient ne pas être un tel overhead. Mapper manuellement les ViewModels à DataContracts et vice versa est certainement beaucoup de travail pour ce que ça vaut, IMHO – jamiebarrow

Répondre

5

Sans aller la route complète soufflé de A ou B, pourriez-vous faire juste un objet générique et emballage ISerializable mettre ceux de votre 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

Merci pour la réponse. Je vais l'essayer quand je suis à côté de ma machine de dev. Je suis un peu nouveau pour C#, donc pas 100% sur les détails. Le constructeur que vous avez créé et qui contient SerializationInfo et StreamingContext, à quoi cela sert-il? Je suppose que l'utilisation prévue serait: MyDataContract c = ...; Session ["mykey"] = new Wrapper {Valeur = c}; – jamiebarrow

+0

Lorsque vous héritez de ISerializable, vous devez définir un constructeur avec cette signature, ce que BinaryFormatter (utilisé en interne par SessionState pour la sérialisation) utilise pour reconstruire l'objet lors de la désérialisation. C'est une bouchée, mais lisez ceci: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx (les premières lignes sont ce qui répond à votre question) – Jeff

+0

Oui, c'est l'intention utilisation. – Jeff

3

Comme une extension à la réponse fournie, j'ai ajouté ces deux méthodes pour faciliter le stockage/récupération des données.

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

Cela rend un peu plus facile de définir/obtenir des valeurs de la session:

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

Pour le rendre encore plus facile, ajouter des méthodes d'extension:

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

et l'utilisation comme ci-dessous :

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

très agréable:) ... – Jeff