views:

135

answers:

2

Hi,

I have a Data Access Layer, a Service Layer, and a Presentation Layer. The Presentation Layer is ASP.NET MVC2 RTM (web), and the Service Layer is WCF (services). It's all .NET 3.5 SP1.

The problem is that in the services, the objects being returned are marked with the [DataContract] attribute. The web is using the AppFabric Cache (a.k.a Velocity) SessionStateProvider to store session state. Due to this, anything I store in the session must be serializable.

Here comes the problem: the DataContracts aren't marked with [Serializable] and as far as I can remember, by introducing it onto a class already marked with [DataContract] some issues arise, and so I don't believe this is a solution.

I was initially planning on using the DataContracts right in the web layer, using them as models to views related to rendering the DataContracts (probably nested inside a higher level ViewModel class). But due to the session state provider requiring all objects stored inside it to be serializable, I'm starting to rethink this strategy. It would be nice to have though, since they contain validation logic using the IDataErrorInfo interface, and the same validation logic could be re-used in MVC as part of model binding.

What do you believe is the best way to allow me to reduce the work needed?

I've currently thought of the following different ways:

A. Create a 'ServiceIntegration' part in the web project.

This would be a middle man between my controllers and my WCF service layer. The ServiceIntegration part would speak to the service layer using DataContracts, and to the Web layer using ViewModels, but would have to transform between the DataContracts and ViewModels using a two-way Transformer.

Also, since the IDataErrorInfo Validation wouldn't be re-usable, it would be necessary to create a Validator per DataContract too, that uses the Transformer to convert from ViewModel to DataContract, perform validation using IDataErrorInfo and return its results. This would then be used inside action methods of Controllers (e.g. if (!MyValidator.IsValid(viewModel)) return View();)

Different classes required: xDataContract, xViewModel, xTransformer, xValidator

B. Create a 'SessionIntegration' part in the web project

This would be a middle-man between the controllers (or anything accessing the session) and the session itself. Anything requiring access to the session would go through this class. DataContracts would be used in the entire application, unless they are being stored into the session. The SessionIntegration part would take the responsibility of transforming the DataContract to some ISerializable form, and back. No additional Validator is needed because of the use of of IDataErrorInfo interface on the DataContract.

Different classes required: xDataContract, xTransformer, xSerializableForm


Note: there would still be ViewModels around in both scenarios, however with (B) I'd be able to compose ViewModels from DataContracts.

(B) has the benefit of not needing an extra validator.


Before I go off and implement (A)/(B) fully, I'd like some feedback. At the moment, I'm starting to lean towards (B), however, (A) might be more flexible. Either way, it seems like way too much work for what it's worth. Has anyone else come across this problem, do you agree/disagree with me, and/or do you have any other way of solving the problem?

Thanks,

James

+1  A: 

Without going the full blown route of A or B, could you just make a generic ISerializable wrapper object and put those in your 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);   
                }
            }
        }
    }
JeffN825
Thanks for the answer. Will try it out when I'm next at my dev machine. I'm kinda new to C#, so not 100% on the specifics. The constructor you created that contains the SerializationInfo and StreamingContext, what is that for?I would assume the intended use would be:MyDataContract c = ...;Session["mykey"] = new Wrapper { Value = c };
jamiebarrow
When you inherit ISerializable, you have to define a constructor with that signature, which is what the BinaryFormatter (used internally by the SessionState for serialization) uses to re-construct the object during deserialization. It's a mouthful, but read this: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx (the first few lines are what addresses your question)
JeffN825
Yes, that's the intended use.
JeffN825
OK. That makes sense. So I would extend your class to add a constructor taking in the initial value then [ public Wrapper(object value) { this.Value = value; } ] and then the code to store in the session would be [ MyDataContract c = ...; Session["mykey"] = new Wrapper(c); ] and then the code to retrieve would be [ Wrapper w = Session["mykey"] as Wrapper; MyDataContract b = (MyDataContract) ((w==null) ? null : w.Value); ]. I'll try this solution properly tomorrow, got it working in a console app, will try with the session provider tomorrow.
jamiebarrow
Sidenote: I wish StackOverflow had rich text capabilities for it's comments as well as it's posts.
jamiebarrow
Yes. And yes, I wish that too :)
JeffN825
It works like a charm. Thanks!
jamiebarrow
I added two helper methods to make the solution a bit nicer.
jamiebarrow
+1  A: 

As an extension to the provided answer, I added these two methods to ease storing/retrieving the data.

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

This makes it a little easier to set/get values from the session:

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

To make it even easier, add extension methods:

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

And use as below:

    MyDataContract c = ...;
    Session.SetWrapped("mykey", c);
    c = Session.GetWrapped<MyDataContract>("mykey");
jamiebarrow
very nice :)...
JeffN825