Using WCF DataContract in MVC SessionState using A

2019-03-20 22:54发布

问题:

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:

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


回答2:

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