5

Ich habe eine Datenzugriffsebene, eine Serviceebene und eine Präsentationsebene. Der Präsentations-Layer ist ASP.NET MVC2 RTM (Web) und der Service-Layer ist WCF (Services). Es ist alles .NET 3.5 SP1.Verwenden von WCF DataContract in MVC SessionState mit AppFabric-Cache

Das Problem besteht darin, dass in den Diensten die zurückgegebenen Objekte mit dem Attribut [DataContract] gekennzeichnet sind. Das Web verwendet den SessionStateProvider von AppFabric Cache (a.k.a Velocity), um den Sitzungsstatus zu speichern. Aus diesem Grund muss alles, was ich in der Sitzung speichere, serialisierbar sein.

Hier kommt das Problem: die DataContracts sind nicht mit [Serializable] markiert und soweit ich mich erinnern kann, treten bei Einführung in eine Klasse, die bereits mit [DataContract] gekennzeichnet ist, Probleme auf, und ich glaube nicht, dass dies eine Lösung ist .

Ich plante ursprünglich, die DataContracts direkt in der Webschicht zu verwenden und sie als Modelle für Ansichten zu verwenden, die sich auf das Rendern der DataContracts beziehen (wahrscheinlich in einer höheren ViewModel-Klasse verschachtelt). Da jedoch der Sitzungszustandsanbieter alle darin gespeicherten Objekte serialisieren muss, überlege ich, diese Strategie zu überdenken. Es wäre jedoch nett, dies zu tun, da sie eine Validierungslogik unter Verwendung der IDataErrorInfo Schnittstelle enthalten und dieselbe Validierungslogik in MVC als Teil der Modellbindung wiederverwendet werden könnte.

Was glauben Sie, ist der beste Weg, um die Arbeit zu reduzieren?

ich zur Zeit habe gedacht, die folgenden verschiedenen Möglichkeiten:

A. Erstellen Sie einen ‚ServiceIntegration‘ an dem Web-Projekt.

Das wäre ein Mittelmann zwischen meinen Controllern und meiner WCF-Dienstschicht. Der ServiceIntegration-Teil würde mit DataContracts mit der Service-Schicht und mit ViewModels mit der Web-Schicht kommunizieren, müsste jedoch zwischen den DataContracts und ViewModels mit einem bidirektionalen Transformer transformieren.

Da die IDataErrorInfo-Validierung nicht wiederverwendbar wäre, müsste auch ein Validator pro DataContract erstellt werden, der Transformer zum Konvertieren von ViewModel in DataContract verwendet, die Validierung mit IDataErrorInfo durchführt und die Ergebnisse zurückgibt. Dies würde dann in Aktion Methoden des Controller verwendet werden (zB if (!MyValidator.IsValid(viewModel)) return View();)

Verschiedene Klassen benötigt: xDataContract, xViewModel, xTransformer, xValidator

B. erstellen 'SessionIntegration' an dem Web-Projekt

Dies wäre ein Mittelmann zwischen den Controllern (oder jedem, der auf die Sitzung zugreift) und der Sitzung selbst. Alles, was Zugriff auf die Sitzung erfordert, würde diese Klasse durchlaufen. DataContracts werden in der gesamten Anwendung verwendet, sofern sie nicht in der Sitzung gespeichert werden. Der SessionIntegration-Teil würde die Verantwortung übernehmen, den DataContract in eine ISerializable-Form und zurück zu transformieren. Aufgrund der Verwendung der IDataErrorInfo-Schnittstelle auf dem DataContract ist kein zusätzlicher Validator erforderlich.

Verschiedene Klassen benötigt: xDataContract, xTransformer, xSerializableForm


Hinweis: es würden immer noch Viewmodel sein um in beiden Szenarien, aber mit (B) ich in der Lage sein würde Viewmodel von Datacontracts zu komponieren.

(B) hat den Vorteil, dass kein zusätzlicher Validator benötigt wird.


Bevor ich ausgehe und (A)/(B) vollständig implementiere, möchte ich ein Feedback geben. Im Moment fange ich an, mich zu (B) zu neigen, aber (A) könnte flexibler sein. So oder so, es scheint viel zu viel Arbeit für das, was es wert ist. Ist jemand anderes auf dieses Problem gestoßen, stimmen Sie mir zu oder haben Sie eine andere Möglichkeit, das Problem zu lösen?

Danke,

James

+0

Ich könnte AutoMapper als Teil der Tr verwenden Ansager, und so sind die Mapping-Details möglicherweise nicht so ein Overhead. Manuelles Zuordnen der ViewModels zu DataContracts und umgekehrt ist definitiv eine Menge Arbeit für das, was es wert ist, IMHO – jamiebarrow

Antwort

5

Ohne den ausgewachsenen Weg von A oder B zu gehen, könnten Sie einfach ein generisches Objekt ISerializable Wrapper machen und diese in Ihrer Session setzen?

[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

Danke für die Antwort. Werde es ausprobieren, wenn ich das nächste Mal an meiner Dev-Maschine bin. Ich bin irgendwie neu in C#, also nicht 100% auf die Besonderheiten. Der Konstruktor, den Sie erstellt haben und der SerializationInfo und StreamingContext enthält, wofür? Ich würde annehmen, die beabsichtigte Verwendung wäre: MyDataContract c = ...; Sitzung ["mykey"] = neuer Wrapper {Wert = c}; – jamiebarrow

+0

Wenn Sie ISerializable erben, müssen Sie einen Konstruktor mit dieser Signatur definieren. Dies wird vom BinaryFormatter (der intern von SessionState für die Serialisierung verwendet wird) verwendet, um das Objekt während der Deserialisierung neu zu erstellen. Es ist ein Bissen, aber lesen Sie dies: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx (die ersten Zeilen sind, was Ihre Frage anspricht) – Jeff

+0

Ja, das ist die Absicht benutzen. – Jeff

3

Als Erweiterung der Antwort zur Verfügung gestellt, fügte ich diese zwei Methoden, um die Daten zu erleichtern, Speichern/Abrufen.

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

Dies macht es ein wenig einfacher zu setzen/get Werte aus der Sitzung:

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

Um es noch einfacher, fügen Sie Erweiterungsmethoden:

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

Und wie unten verwenden :

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

sehr schön:) ... – Jeff

Verwandte Themen