【问题标题】:Using WCF DataContract in MVC SessionState using AppFabric cache在使用 AppFabric 缓存的 MVC SessionState 中使用 WCF DataContract
【发布时间】:2011-04-06 10:47:27
【问题描述】:

我有一个数据访问层、一个服务层和一个表示层。表示层是 ASP.NET MVC2 RTM(Web),服务层是 WCF(服务)。都是 .NET 3.5 SP1。

问题在于,在服务中,返回的对象被标记为[DataContract] 属性。 Web 使用 AppFabric 缓存(又名 Velocity)SessionStateProvider 来存储会话状态。因此,我存储在会话中的任何内容都必须是可序列化的。

问题来了:DataContracts 没有用[Serializable] 标记,据我所知,通过将它引入一个已经用[DataContract] 标记的类会出现一些问题,所以我不相信这一点是一个解决方案。

我最初计划在 Web 层中使用 DataContracts,将它们用作与渲染 DataContracts 相关的视图的模型(可能嵌套在更高级别的 ViewModel 类中)。但是由于会话状态提供程序要求存储在其中的所有对象都是可序列化的,我开始重新考虑这种策略。不过最好有,因为它们包含使用 IDataErrorInfo 接口的验证逻辑,并且相同的验证逻辑可以在 MVC 中作为模型绑定的一部分重复使用。

您认为让我减少所需工作的最佳方法是什么?

我目前想到了以下几种不同的方法:

A.在 Web 项目中创建一个“ServiceIntegration”部件。

这将是我的控制器和我的 WCF 服务层之间的中间人。 ServiceIntegration 部分将使用 DataContracts 与服务层通信,并使用 ViewModels 与 Web 层通信,但必须使用双向 Transformer 在 DataContracts 和 ViewModels 之间进行转换。

此外,由于 IDataErrorInfo 验证不可重复使用,因此也有必要为每个 DataContract 创建一个验证器,它使用 Transformer 从 ViewModel 转换为 DataContract,使用 IDataErrorInfo 执行验证并返回其结果。这将在控制器的操作方法中使用(例如if (!MyValidator.IsValid(viewModel)) return View();

需要不同的类:xDataContract、xViewModel、xTransformer、xValidator

B.在 Web 项目中创建一个“SessionIntegration”部分

这将是控制器(或任何访问会话的对象)和会话本身之间的中间人。任何需要访问会话的东西都将通过这个类。 DataContracts 将在整个应用程序中使用,除非它们被存储到会话中。 SessionIntegration 部分负责将 DataContract 转换为某种 ISerializable 形式并返回。由于在 DataContract 上使用了 IDataErrorInfo 接口,因此不需要额外的 Validator。

需要不同的类:xDataContract、xTransformer、xSerializableForm


注意:在这两种情况下仍然存在 ViewModel,但是使用 (B) 我可以从 DataContracts 组合 ViewModel。

(B) 的好处是不需要额外的验证器。


在我开始全面实施 (A)/(B) 之前,我想要一些反馈。目前,我开始倾向于(B),但是,(A)可能更灵活。无论哪种方式,对于它的价值来说,这似乎是太多的工作。有没有其他人遇到过这个问题,您同意/不同意我的观点,和/或您有其他解决问题的方法吗?

谢谢,

詹姆斯

【问题讨论】:

  • 我可能会使用 AutoMapper 作为 Transformer 的一部分,因此映射细节可能不会是这样的开销。手动将 ViewModels 映射到 DataContracts,反之亦然,这绝对是一项值得的工作,恕我直言

标签: asp.net-mvc session datacontract iserializable session-state-provider


【解决方案1】:

如果不走完整的 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);   
                }
            }
        }
    }

【讨论】:

  • 感谢您的回答。当我下次在我的开发机器上时会尝试一下。我对 C# 有点陌生,所以在细节上不是 100%。您创建的包含 SerializationInfo 和 StreamingContext 的构造函数,这是干什么用的?我假设预期用途是: MyDataContract c = ...; Session["mykey"] = new Wrapper { Value = c };
  • 当您继承 ISerializable 时,您必须使用该签名定义一个构造函数,这是 BinaryFormatter(由 SessionState 内部用于序列化)在反序列化期间用于重新构造对象的内容。这是一口,但请阅读:msdn.microsoft.com/en-us/library/…(前几行是解决您的问题的内容)
  • 是的,这就是预期用途。
  • 好的。这就说得通了。因此,我将扩展您的类以添加一个构造函数,该构造函数采用初始值,然后 [ public Wrapper(object value) { this.Value = value; } ] 然后存储在会话中的代码将是 [ MyDataContract c = ...;会话["mykey"] = 新包装器(c); ] 然后要检索的代码将是 [ Wrapper w = Session["mykey"] as Wrapper; MyDataContract b = (MyDataContract) ((w==null) ? null : w.Value); ]。明天我会正确尝试这个解决方案,让它在控制台应用程序中运行,明天将与会话提供者一起尝试。
  • 旁注:我希望 StackOverflow 的 cmets 和帖子都具有富文本功能。
【解决方案2】:

作为对所提供答案的扩展,我添加了这两种方法来简化数据的存储/检索。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 2011-03-21
    • 2011-09-27
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    相关资源
    最近更新 更多