JSON.Net Serialization of NHibernate Proxies (NH 3

2019-01-20 11:55发布

I'm still having continuous difficulties getting Json.Net and NHibernate to play nicely together. Namely, in getting Json.NET to serialize a proxied NHibernate object.

I've followed the recommendations here, both for the accepted answer and the ammendments, but no dice.

The biggest problem with the above solution is that it seems that modern versions of NHibernate are using the INHibernateProxyProxy interface to create proxies (rather than INHibernateProxy? Can anyne else confirm this?), whose base class in my case is NHibernate.Proxy.DynamicProxy.ProxyDummy, which reveals nothing about the underlying object when I attempt to create the Json constract using my custom scontract resolver, ie:

    protected override JsonContract CreateContract(Type objectType)
    {
        if (typeof(NHibernate.Proxy.INHibernateProxy).IsAssignableFrom(objectType))
            return base.CreateContract(objectType.BaseType);
        else
            return base.CreateContract(objectType);
    }

Does anyone have any advice for how to deal with INHibernateProxyProxy effectively?

4条回答
迷人小祖宗
2楼-- · 2019-01-20 12:20

From forever ago but I think the problem here was actually that the proxy was not initialized before trying to serialize it.

You have to call NHibernateUtil.Initialize(aPersistentObject.LazyProperty); to initialize a proxy object.

Afterward, the BaseType probably would have been correct... i.e. not ProxyDummy but the actual type needed.

For me, the solution is something like this:

namespace com.example.DataAccess
{
    public static class Helper
    {
        // the implementation I use creates a ThreadStatic ISession, 
        // and then orphans and disposes that ISession when the 
        // result of this method is disposed.
        public static IDisposable GetSession(); 

        public static Type GetUnproxiedType(Type objectType)
        {
            if (typeof(INhibernateProxy).IsAssignableFrom(objectType))
                return objectType.BaseType;
            return objectType;
        }
        public static void Initialize(object proxy)
        {
            NHibernateUtil.Initialize(proxy);
        }
    }
}

namespace com.example.WebService
{
    internal static class Helper
    {
        private class ProxyResolver : CamelCasePropertyNamesContractResolver
        {
            protected override JsonContract CreateContract(Type objectType)
            {
                return base.CreateContract(DataAccess.Helper.GetUnproxiedType(objectType));
            }
        }

        public static readonly JsonSerializer serializer = new JsonSerializer
        {
            ContractResolver = new ProxyResolver(),
            Converters = 
                { 
                    new StringEnumConverter(),
                },
            DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat,
            Formatting = Formatting.Indented,
            // etc.
        };
    }
}

So when I have a REST service endpoint with a proxied type, it looks like kind of like this:

[WebGet(UriTemplate= "foo/{id}/bar")]
public Bar GetFooBar(string id)
{
    using (DataAccess.Helper.GetSession())
    {
        var foo = GetFoo(id);
        if (foo == null) return null;
        DataAccess.Helper.Initialize(foo.Bar);
        return foo.Bar;
    }
}

and the serializer defined in WebService.Helper is used to serialize the result.

Note that if the serialization process happens outside of your method (as it does for me) you will always need to call to initialize the object before you actually serialize it. You may be able to do this with Global.asax events, but I just handle it directly in my service methods.

查看更多
forever°为你锁心
3楼-- · 2019-01-20 12:21

The full Solution:

In Global.asax.cs :

 //Define Formatters
        var formatters = GlobalConfiguration.Configuration.Formatters;
        var jsonFormatter = formatters.JsonFormatter;
        var settings = jsonFormatter.SerializerSettings;
        settings.Formatting = Formatting.Indented;           
        jsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
        jsonFormatter.SerializerSettings.PreserveReferencesHandling  = Newtonsoft.Json.PreserveReferencesHandling.Objects;
        jsonFormatter.SerializerSettings.ContractResolver = new NHibernateContractResolver();
        //------------//

And the custom contract:

public class NHibernateContractResolver : DefaultContractResolver
{
    private static readonly MemberInfo[] NHibernateProxyInterfaceMembers = typeof(INHibernateProxy).GetMembers();

    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        var members = base.GetSerializableMembers(objectType);

        members.RemoveAll(memberInfo =>
                          (IsMemberPartOfNHibernateProxyInterface(memberInfo)) ||
                          (IsMemberDynamicProxyMixin(memberInfo)) ||
                          (IsMemberMarkedWithIgnoreAttribute(memberInfo, objectType)) ||
                          (IsMemberInheritedFromProxySuperclass(memberInfo, objectType)));

        var actualMemberInfos = new List<MemberInfo>();

        foreach (var memberInfo in members)
        {
            var infos = memberInfo.DeclaringType.BaseType.GetMember(memberInfo.Name);
            actualMemberInfos.Add(infos.Length == 0 ? memberInfo : infos[0]);
        }

        return actualMemberInfos;
    }

    private static bool IsMemberDynamicProxyMixin(MemberInfo memberInfo)
    {
        return memberInfo.Name == "__interceptors";
    }

    private static bool IsMemberInheritedFromProxySuperclass(MemberInfo memberInfo, Type objectType)
    {
        return memberInfo.DeclaringType.Assembly == typeof(INHibernateProxy).Assembly;
    }

    private static bool IsMemberMarkedWithIgnoreAttribute(MemberInfo memberInfo, Type objectType)
    {
        var infos = typeof(INHibernateProxy).IsAssignableFrom(objectType)
                      ? objectType.BaseType.GetMember(memberInfo.Name)
                      : objectType.GetMember(memberInfo.Name);

        return infos[0].GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Length > 0;
    }

    private static bool IsMemberPartOfNHibernateProxyInterface(MemberInfo memberInfo)
    {
        return Array.Exists(NHibernateProxyInterfaceMembers, mi => memberInfo.Name == mi.Name);
    }


protected override JsonContract CreateContract(Type objectType)
{
    if (typeof(INHibernateProxy).IsAssignableFrom(objectType))
    {
        var oType = objectType.GetInterfaces().FirstOrDefault(i => i.FullName.StartsWith("Your.Domain.Namespace"));
        return oType != null ? base.CreateContract(oType) : base.CreateContract(objectType.BaseType);
    }
    return base.CreateContract(objectType);
}

Don't forget to replace : "Your.Domain.Namespace"

查看更多
爷、活的狠高调
4楼-- · 2019-01-20 12:42

My problem was that not all sublists are resolved by Newtonsoft.JSON.

I added a contract resolver

public class ProjectContractResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        if (typeof(INHibernateProxy).IsAssignableFrom(objectType))
        {
            if (objectType.FullName.Equals("DeviceModelProxy"))
                return CreateContract(typeof(DeviceModel));

            if (objectType.FullName.Equals("JobModelProxy"))
                return CreateContract(typeof(JobModel));

            base.CreateContract(objectType.BaseType);
        }
        return base.CreateContract(objectType);
    }
}

At serialization add the custom contract resolver

JsonConvert.SerializeObject(backupDataModel, Formatting.Indented, new JsonSerializerSettings()
        {
            ContractResolver = new ProjectContractResolver()
        });

This solves my problem.

查看更多
爷、活的狠高调
5楼-- · 2019-01-20 12:43

Found it. The original type is available via .GetInterfaces(), ie:

    protected override JsonContract CreateContract(Type objectType)
    {
        if (typeof (INHibernateProxy).IsAssignableFrom(objectType))
        {
            var oType = objectType.GetInterfaces().FirstOrDefault(i => i.FullName.StartsWith("Your.Domain.Namespace"));
            return oType != null ? base.CreateContract(oType) : base.CreateContract(objectType.BaseType);
        }
        return base.CreateContract(objectType);
    }
查看更多
登录 后发表回答