Использование WCF DataContract в MVC SessionState с использованием кэша AppFabric - PullRequest
5 голосов
/ 31 августа 2010

У меня есть уровень доступа к данным, уровень обслуживания и уровень представления. Уровень представления - это ASP.NET MVC2 RTM (веб), а уровень обслуживания - WCF (службы). Это все .NET 3.5 SP1.

Проблема в том, что в сервисах возвращаемые объекты помечаются атрибутом [DataContract]. В Интернете используется SessionStateProvider AppFabric Cache (a.k.a Velocity) для хранения состояния сеанса. Из-за этого все, что я храню в сеансе, должно быть сериализуемым.

Здесь возникает проблема: DataContracts не помечены [Serializable] и, насколько я помню, путем введения его в класс, уже помеченный [DataContract], возникают некоторые проблемы, и поэтому я не верю в это это решение.

Я изначально планировал использовать DataContracts прямо в веб-слое, используя их в качестве моделей для представлений, связанных с рендерингом DataContracts (возможно, вложенных в класс ViewModel более высокого уровня). Но из-за того, что поставщик состояния сеанса требует, чтобы все объекты, хранящиеся в нем, были сериализуемыми, я начинаю переосмысливать эту стратегию. Хотя было бы неплохо, поскольку они содержат логику проверки с использованием интерфейса IDataErrorInfo, и ту же логику проверки можно повторно использовать в MVC как часть привязки модели.

Какой, по вашему мнению, лучший способ разрешить мне сократить необходимую работу?

В настоящее время я думаю о следующих разных путях:

A. Создайте часть «ServiceIntegration» в веб-проекте.

Это был бы посредник между моими контроллерами и моим сервисным уровнем WCF. Часть ServiceIntegration будет говорить с сервисным уровнем, используя DataContracts, и с веб-уровнем, используя ViewModels, но для преобразования между DataContracts и ViewModels придется использовать двусторонний Transformer.

Кроме того, поскольку проверка IDataErrorInfo не будет использоваться повторно, необходимо также создать валидатор для DataContract, который использует Transformer для преобразования из ViewModel в DataContract, выполнения проверки с использованием IDataErrorInfo и возврата его результатов. Затем это будет использоваться внутри методов действия контроллеров (например, if (!MyValidator.IsValid(viewModel)) return View();)

Требуются разные классы: xDataContract, xViewModel, xTransformer, xValidator

B. Создать часть 'SessionIntegration' в веб-проекте

Это будет посредник между контроллерами (или чем-либо, осуществляющим доступ к сеансу) и самим сеансом. Все, что требует доступа к сеансу, проходит через этот класс. DataContracts будет использоваться во всем приложении, если только они не сохраняются в сеансе. Часть SessionIntegration будет нести ответственность за преобразование DataContract в некоторую ISerializable форму и обратно. Дополнительный валидатор не требуется из-за использования интерфейса IDataErrorInfo в DataContract.

Требуются разные классы: xDataContract, xTransformer, xSerializableForm


Примечание: в обоих сценариях все еще будут присутствовать ViewModels, однако с помощью (B) я смогу составлять ViewModel из DataContracts.

(B) имеет то преимущество, что не требует дополнительного валидатора.


Прежде чем я уйду и полностью реализую (A) / (B), я хотел бы получить некоторую обратную связь. В данный момент я начинаю склоняться к (B), однако (A) может быть более гибким. В любом случае, кажется, что слишком много работы для того, что это стоит. Кто-нибудь еще сталкивался с этой проблемой, вы согласны / не согласны со мной и / или у вас есть какой-то другой способ решения проблемы?

Спасибо

Джеймс

Ответы [ 2 ]

5 голосов
/ 31 августа 2010

Не пройдя полный маршрут A или B, не могли бы вы просто сделать общий объект-оболочку ISerializable и поместить его в свой 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);   
                }
            }
        }
    }
3 голосов
/ 01 сентября 2010

В качестве дополнения к предоставленному ответу я добавил эти два метода для упрощения хранения / извлечения данных.

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

Это немного упрощает установку / получение значений из сеанса:

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

Чтобы сделать это еще проще, добавьте методы расширения:

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

И используйте, как показано ниже:

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