I'm trying to follow a guide from http://kellabyte.com/2010/11/13/building-extensible-wcf-service-interfaces-with-datacontractresolver/ to create and attach a DataContractSerializer.
I've declared the serializer and implemented the methods, then attached it to both the client and server with the following code:
public class ModuleDataContractResolver : DataContractResolver {
public override bool TryResolveType(Type type, Type declaredType,
DataContractResolver knownTypeResolver,
out System.Xml.XmlDictionaryString typeName,
out System.Xml.XmlDictionaryString typeNamespace) {
....// I return a true/false here
}
public override Type ResolveName(string typeName, string typeNamespace,
Type declaredType, DataContractResolver knownTypeResolver) {
....// I return a type here
}
-
var endpoint = _svcHost.Description.Endpoints.FirstOrDefault()
ContractDescription cd = endpoint.Contract;
foreach (OperationDescription opdesc in cd.Operations) {
DataContractSerializerOperationBehavior serializerBehavior = opdesc.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (serializerBehavior == null) {
serializerBehavior = new DataContractSerializerOperationBehavior(opdesc);
opdesc.Behaviors.Add(serializerBehavior);
}
serializerBehavior.DataContractResolver = new ModuleDataContractResolver();
}
Despite attaching the resolver, these two methods are called on neither the service nor the client, so the service is throwing an exception. Am I missing a step?
UPDATE: I'm not entirely convinced this isn't due to using MEF to return these types. The type in question is a MEF type, which is detected by the service but only exposed as an interface to the client, so the assembly is not loaded.
The idea is to have the service load a list of MEF modules, then expose them over this WCF service to the client as an interface.
Service side:
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
operation.Behaviors.Find<DataContractSerializerOperationBehavior>()
.DataContractResolver = new ModuleDataContractResolver();
}
Client side:
foreach (var operation in factory.Endpoint.Contract.Operations)
{
operation.Behaviors.Find<DataContractSerializerOperationBehavior>()
.DataContractResolver = new ModuleDataContractResolver();
}
Eventually finding the last solution anywhere which I hadn't tried, a post by dpblogs showed how to use an attribute in the service interface's method declarations. This finally caused my resolving methods to be called.
Related
I am having serialization issues (exceptions) with NodaTime types and SignalR parameters such as
Error converting value to type 'NodaTime.ZonedDateTime
Error converting value \"2016-06-01T18:33:36.7279685+01 Europe/London\" to type 'NodaTime.ZonedDateTime'. Path '[0].DateCreated', line 1, position 79.
This is despite following the docs and replacing the default JsonSerializer and using the NodaTime extension methods and JSON.net nuget package e.g.
JsonSerializerSettings js = new JsonSerializerSettings();
js.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
var serializer = JsonSerializer.Create(js);
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);
Happily found a workaround from here thanks to BrannonKing
Essentially it uses a Customer Resolver for SignalR parameters which uses the correct serializer instead of creating a default.
Also referenced on SO here but of course only found that once had started to post my own question ;)
Reposting here for others googling for (the excellent) NodaTime specifically, and to share some other serialization fixes I needed, such as :
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property X with type Y Path Z
Server Startup
public void Configuration(IAppBuilder app)
{
JsonSerializerSettings js = new JsonSerializerSettings();
js.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
js.DateParseHandling = DateParseHandling.None;
js.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
js.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
var serializer = JsonSerializer.Create(js);
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);
var resolver = new Resolver(serializer);
GlobalHost.DependencyResolver.Register(typeof(IParameterResolver), () => resolver);
}
Custom Parameter Resolver
public class Resolver : DefaultParameterResolver
{
private readonly JsonSerializer _serializer;
public Resolver(JsonSerializer serializer)
{
_serializer = serializer;
}
private FieldInfo _valueField;
public override object ResolveParameter(ParameterDescriptor descriptor, Microsoft.AspNet.SignalR.Json.IJsonValue value)
{
if(value.GetType() == descriptor.ParameterType)
{
return value;
}
if(_valueField == null)
_valueField = value.GetType().GetField("_value", BindingFlags.Instance | BindingFlags.NonPublic);
var json = (string)_valueField.GetValue(value);
using(var reader = new StringReader(json))
return _serializer.Deserialize(reader, descriptor.ParameterType);
}
}
Many thanks Brannon !
I'm building on a previously answered question in which ICar implementations are bound using Ninject Conventions Extensions and a custom IBindingGenerator, and the ICarFactory interface is bound using the Ninject Factory Extensions' ToFactory() method and a custom instance provider.
I'm trying to refactor so that I can bind and make use of a IVehicleFactory<T>, where T is constrained to ICar, rather than the previous ICarFactory. This way, I can specify the vehicle I want in the generic type parameter, instead of passing in the name of the vehicle type in the factory's CreateCar() method.
Is it possible to bind open generic interfaces using the ToFactory() technique?
I have a feeling that I'm barking up the wrong tree, but when I was specifying an ICar type by its name, it seemed like the natural evolution to specify the ICar type itself as a generic type parameter...
Here's the test that currently fails:
[Fact]
public void A_Generic_Vehicle_Factory_Creates_A_Car_Whose_Type_Equals_Factory_Method_Generic_Type_Argument()
{
using (StandardKernel kernel = new StandardKernel())
{
// arrange
kernel.Bind(typeof(IVehicleFactory<>))
.ToFactory(() => new UseFirstGenericTypeArgumentInstanceProvider());
kernel.Bind(
scanner => scanner
.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<ICar>()
.BindWith(new BaseTypeBindingGenerator<ICar>()));
IVehicleFactory<Mercedes> factory
= kernel.Get<IVehicleFactory<Mercedes>>();
// act
var car = factory.CreateVehicle();
// assert
Assert.IsType<Mercedes>(car);
}
}
And the InvalidCastException thrown:
System.InvalidCastException was unhandled by user code
Message=Unable to cast object of type 'Castle.Proxies.ObjectProxy' to type 'IVehicleFactory`1[Mercedes]'.
Source=System.Core
StackTrace:
at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters) in c:\Projects\Ninject\ninject\src\Ninject\Syntax\ResolutionExtensions.cs:line 37
at NinjectFactoryTests.A_Generic_Vehicle_Factory_Creates_A_Car_Whose_Type_Name_Equals_Factory_Method_String_Argument() in C:\Programming\Ninject.Extensions.Conventions.Tests\NinjectFactoryTests.cs:line 37
InnerException:
And the factory interface:
public interface IVehicleFactory<T> where T : ICar
{
T CreateVehicle();
}
And the custom instance provider, whose breakpoints I can't even get the debugger to stop on, so I really don't know what's going on in there:
public class UseFirstGenericTypeArgumentInstanceProvider : StandardInstanceProvider
{
protected override string GetName(MethodInfo methodInfo, object[] arguments)
{
var genericTypeArguments = methodInfo.GetGenericArguments();
var genericMethodDefinition = methodInfo.GetGenericMethodDefinition();
var g = genericMethodDefinition.MakeGenericMethod(genericTypeArguments.First());
return g.MemberType.GetType().Name;
}
protected override ConstructorArgument[] GetConstructorArguments(MethodInfo methodInfo, object[] arguments)
{
return base.GetConstructorArguments(methodInfo, arguments).Skip(1).ToArray();
}
}
EDIT 1 - Change IVehicleFactory signature and custom instance provider
Here's I've changed the IVehicleFactory signature to use a generic Create<T>() method, and explicitly bound Mercedes to itself.
public interface IVehicleFactory
{
T CreateVehicle<T>() where T : ICar;
}
And the new custom instance provider which returns the name of the first generic type parameter:
public class UseFirstGenericTypeArgumentInstanceProvider : StandardInstanceProvider
{
protected override string GetName(MethodInfo methodInfo, object[] arguments)
{
var genericTypeArguments = methodInfo.GetGenericArguments();
return genericTypeArguments[0].Name;
}
}
Here's the new test, still not passing:
[Fact]
public void A_Generic_Vehicle_Factory_Creates_A_Car_Whose_Type_Name_Equals_Factory_Method_String_Argument()
{
using (StandardKernel kernel = new StandardKernel())
{
// arrange
kernel.Bind<IVehicleFactory>()
.ToFactory(() => new UseFirstGenericTypeArgumentInstanceProvider())
.InSingletonScope();
kernel.Bind<Mercedes>().ToSelf();
IVehicleFactory factory = kernel.Get<IVehicleFactory>();
// act
var car = factory.CreateVehicle<Mercedes>();
// assert
Assert.IsType<Mercedes>(car);
}
}
}
A Ninject.ActivationException is thrown:
Ninject.ActivationException: Error activating Mercedes
No matching bindings are available, and the type is not self-bindable.
Activation path:
1) Request for Mercedes
I don't know why it can't find the Mercedes class, since I explicitly self-bound it. Can you spot what I'm doing wrong?
Use generic methods:
public interface IVehicleFactory
{
CreateVehicle<T>();
}
Following the sample for compression by Microsoft. I have added the encoder, encoder factory, and binding element to my solution. The difference from their sample is that we do not register our endpoints via the config file (requirement), but instead use a custom Service Host Factory.
Service Host:
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
if (host.Description.Endpoints.Count == 0)
{
host.AddDefaultEndpoints();
}
host.Description.Behaviors.Add(new MessagingErrorHandler());
return host;
}
So what I have tried is to add a custom binding to my endpoint, but to register that endpoint with the binding it looks like I have to use the AddServiceEndpoint but that will require an interface which is unknown. I know I could get all the interfaces that the serviceType implements and do a getInterfaces()[0], but that seems to be an unsafe approach to me.
So is there a way to register my endpoint with the custom binding and not know the interface, or is there a maybe a better approach that I should take.
My attempt at adding custom binding:
CustomBinding compression = new CustomBinding();
compression.Elements.Add(new GZipMessageEncodingBindingElement());
foreach (var uri in baseAddresses)
{
host.AddServiceEndpoint(serviceType, compression, uri);//service type is not the interface and is causing the issue
}
Your custom binding needs a transport binding element; currently you only have a message encoding binding element. You need to add probably a HttpTransportBindingElement to your custom binding as well:
CustomBinding compression = new CustomBinding(
new GZipMessageEncodingBindingElement()
new HttpTransportBindingElement());
As far as finding the interface from the service type, there's no built-in logic for that. The logic used in the WebServiceHostFactory is similar to the one shown below (this code goes 1 inheritance / implementation level deep, but you could in theory go deeper too.
private Type GetContractType(Type serviceType)
{
if (HasServiceContract(serviceType))
{
return serviceType;
}
Type[] possibleContractTypes = serviceType.GetInterfaces()
.Where(i => HasServiceContract(i))
.ToArray();
switch (possibleContractTypes.Length)
{
case 0:
throw new InvalidOperationException("Service type " + serviceType.FullName + " does not implement any interface decorated with the ServiceContractAttribute.");
case 1:
return possibleContractTypes[0];
default:
throw new InvalidOperationException("Service type " + serviceType.FullName + " implements multiple interfaces decorated with the ServiceContractAttribute, not supported by this factory.");
}
}
private static bool HasServiceContract(Type type)
{
return Attribute.IsDefined(type, typeof(ServiceContractAttribute), false);
}
I have a set of SOAP webservices that are wrapping exceptions using IErrorHandler, specifically:
public sealed class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// don't wrap existing fault exceptions
if ((error is FaultException)) return;
// our basic service fault
var businessFault = new BusinessFault { FaultMessage = error.Message, FaultReference = "Internal" };
// Resource based faultReason
var faultReason = new FaultReason(Properties.Resources.BusinessFaultReason);
var faultcode = FaultCodeFactory.CreateVersionAwareSenderFaultCode(InternalFaultCodes.BusinessFailure.ToString(), Service.Namespace);
var faultException = new FaultException<BusinessFault>(
businessFault,
faultReason,
faultcode);
// Create message fault
var messageFault = faultException.CreateMessageFault();
// Create message using Message Factory method
fault = Message.CreateMessage(version, messageFault, faultException.Action);
}
}
I have now added extra endpoints for Json and Pox which work fine, unless an exception occurs. In the case of the Json endpoint the FaultException is returned as XML.
I am aware from other SO posts that in the case of REST I would be better throwing a WebHttpException:
throw new WebFaultException<BusinessFault>(detail, HttpStatusCode.BadRequest);
Or overriding the response message properties in ProvideFault, thus:
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
var rmp = new HttpResponseMessageProperty
{
StatusCode = System.Net.HttpStatusCode.BadRequest,
StatusDescription = "See fault object for more information."
};
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
However, MSDN has some interesting remarks about WebHttpException namely:
When using a WCF REST endpoint (WebHttpBinding and WebHttpBehavior or
WebScriptEnablingBehavior) the HTTP status code on the response is set
accordingly. However, WebFaultException can be used with non-REST
endpoints and behaves like a regular FaultException.
When using a WCF REST endpoint, the response format of the serialized
fault is determined in the same way as a non-fault response. For more
information about WCF REST formatting, see WCF REST Formatting.
It would suggest therefore that I need to convert my current ProvideFault method to provide a new WebHttpException (wrapping any existing Exceptions or FaultExceptions) and then SOAP would still work as well.
Would anyone like to take a stab at what that would look like (.Net4.0 btw)? I want one error handler to rule them all!
I was under the impression that using webHttpBinding was a way to get the "all-in-one" functionality of JSON/POX/SOAP as opposed to using separate bindings for each (i.e. wsHttpBinding, basicHttpBinding etc.). So wouldn't you be able to just throw the WebHttpException and then have that give you all the error details you needed regardless of the technology?
In a REST application I'm working on, I created a new class derived from WebFaultException<T> that attaches some additional data to caught service exceptions. Calling the CreatingMessageFault() method on the instance of the derived class let me return my selected exception data from the ProvideFault() method of the error handler as the SOAP fault, letting WCF determine the correct message format.
I am using webHttpBinding to bind all but some third-party services.
Edit: Added code example
public class ErrorHandler : IErrorHandler, IServiceBehavior
{
public virtual void ProvideFault( Exception error, MessageVersion version, ref Message fault )
{
// Include next level of detail in message, if any.
MyFaultException myFaultException =
((error is MyFaultException) &&
((MyFaultException)error).Detail != null)
? new MyFaultException(error.Message + " - " +
((MyFaultException)error).Detail.Message, error)
: new MyFaultException( error.Message, error );
MessageFault messageFault = myFaultException.CreateMessageFault();
fault = Message.CreateMessage( version, messageFault, myFaultException.Action );
}
}
and
/// <summary>
/// Class used to return exception data from my WCF services.
/// </summary>
/// <remarks>
/// This class is used by a web service to pass exception data back and a
/// data object to the client. This class inherits WebFaultException, which
/// is handled specially by the WCF WebServiceHost2 service class and
/// generates a WebException on the client.
/// </remarks>
public class MyFaultException : WebFaultException<BusinessFault>
{
public class MyFaultException : WebFaultException<BusinessFault>
{
public MyFaultException(string message)
: this(HttpStatusCode.BadRequest, message) { }
public MyFaultException(HttpStatusCode statusCode, string message)
: base(new BusinessFault(message), statusCode) { }
}
then in your service, you can throw the exception to pass fault data to your client:
try
{
// Successful operation proceeds normally.
}
catch (ApplicationException e)
{
// Failure generates MyFaultException.
throw new MyFaultException("Operation failed with " + e.Message);
}
Is anybody using JSON.NET with nHibernate? I notice that I am getting errors when I try to load a class with child collections.
I was facing the same problem so I tried to use #Liedman's code but the GetSerializableMembers() was never get called for the proxied reference.
I found another method to override:
public class NHibernateContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (typeof(NHibernate.Proxy.INHibernateProxy).IsAssignableFrom(objectType))
return base.CreateContract(objectType.BaseType);
else
return base.CreateContract(objectType);
}
}
We had this exact problem, which was solved with inspiration from Handcraftsman's response here.
The problem arises from JSON.NET being confused about how to serialize NHibernate's proxy classes. Solution: serialize the proxy instances like their base class.
A simplified version of Handcraftsman's code goes like this:
public class NHibernateContractResolver : DefaultContractResolver {
protected override List<MemberInfo> GetSerializableMembers(Type objectType) {
if (typeof(INHibernateProxy).IsAssignableFrom(objectType)) {
return base.GetSerializableMembers(objectType.BaseType);
} else {
return base.GetSerializableMembers(objectType);
}
}
}
IMHO, this code has the advantage of still relying on JSON.NET's default behaviour regarding custom attributes, etc. (and the code is a lot shorter!).
It is used like this
var serializer = new JsonSerializer{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new NHibernateContractResolver()
};
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new Newtonsoft.Json.JsonTextWriter(stringWriter);
serializer.Serialize(jsonWriter, objectToSerialize);
string serializedObject = stringWriter.ToString();
Note: This code was written for and used with NHibernate 2.1. As some commenters have pointed out, it doesn't work out of the box with later versions of NHibernate, you will have to make some adjustments. I will try to update the code if I ever have to do it with later versions of NHibernate.
I use NHibernate with Json.NET and noticed that I was getting inexplicable "__interceptors" properties in my serialized objects. A google search turned up this excellent solution by Lee Henson which I adapted to work with Json.NET 3.5 Release 5 as follows.
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);
}
}
To use it just put an instance in the ContractResolver property of your JsonSerializer. The circular dependency problem noted by jishi can be resolved by setting the ReferenceLoopHandling property to ReferenceLoopHandling.Ignore . Here's an extension method that can be used to serialize objects using Json.Net
public static void SerializeToJsonFile<T>(this T itemToSerialize, string filePath)
{
using (StreamWriter streamWriter = new StreamWriter(filePath))
{
using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter))
{
jsonWriter.Formatting = Formatting.Indented;
JsonSerializer serializer = new JsonSerializer
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new NHibernateContractResolver(),
};
serializer.Serialize(jsonWriter, itemToSerialize);
}
}
}
Are you getting a circular dependancy-error? How do you ignore objects from serialization?
Since lazy loading generates a proxy-objects, any attributes your class-members have will be lost. I ran into the same issue with Newtonsoft JSON-serializer, since the proxy-object didn't have the [JsonIgnore] attributes anymore.
You will probably want to eager load most of the object so that it can be serialized:
ICriteria ic = _session.CreateCriteria(typeof(Person));
ic.Add(Restrictions.Eq("Id", id));
if (fetchEager)
{
ic.SetFetchMode("Person", FetchMode.Eager);
}
A nice way to do this is to add a bool to the constructor (bool isFetchEager) of your data provider method.
I'd say this is a design problem in my opinion. Because NH makes connections to the database underneath all and has proxies in the middle, it is not good for the transparency of your application to serialize them directly (and as you can see Json.NET does not like them at all).
You should not serialize the entities themselves, but you should convert them into "view" objects or POCO or DTO objects (whatever you want to call them) and then serialize these.
The difference is that while NH entity may have proxies, lazy attributes, etc. View objects are simple objects with only primitives which are serializable by default.
How to manage FKs?
My personal rule is:
Entity level: Person class and with a Gender class associated
View level: Person view with GenderId and GenderName properties.
This means that you need to expand your properties into primitives when converting to view objects. This way also your json objects are simpler and easier to handle.
When you need to push the changes to the DB, in my case I use AutoMapper and do a ValueResolver class which can convert your new Guid to the Gender object.
UPDATE: Check http://blog.andrewawhitaker.com/blog/2014/06/19/queryover-series-part-4-transforming/ for a way to get the view directly (AliasToBean) from NH. This would be a boost in the DB side.
The problem can happen when NHibernate wraps the nested collection properties in a PersistentGenericBag<> type.
The GetSerializableMembers and CreateContract overrides cannot detect that these nested collection properties are "proxied". One way to resolve this is to override the CreateProperty method. The trick is to get the value from the property using reflection and test whether the type is of PersistentGenericBag. This method also has the ability to filter any properties that generated exceptions.
public class NHibernateContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = instance =>
{
try
{
PropertyInfo prop = (PropertyInfo)member;
if (prop.CanRead)
{
var value = prop.GetValue(instance, null);
if (value != null && typeof(NHibernate.Collection.Generic.PersistentGenericBag<>).IsSubclassOfRawGeneric(value.GetType()))
return false;
return true;
}
}
catch
{ }
return false;
};
return property;
}
}
The IsSubclassOfRawGeneric extension used above:
public static class TypeExtensions
{
public static bool IsSubclassOfRawGeneric(this Type generic, Type? toCheck)
{
while (toCheck != null && toCheck != typeof(object))
{
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (generic == cur)
{
return true;
}
toCheck = toCheck?.BaseType;
}
return false;
}
}
If you serialize objects that contain NHibernate proxy classes you might end up downloading the whole database, because once the property is accessed NHibernate would trigger a request to the database.
I've just implemented a Unit of Work for NHibernate: NHUnit that fixes two of the most annoying issues from NHibernate: proxy classes and cartesian product when using fetch.
How would you use this?
var customer = await _dbContext.Customers.Get(customerId) //returns a wrapper to configure the query
.Include(c => c.Addresses.Single().Country, //include Addresses and Country
c => c.PhoneNumbers.Single().PhoneNumberType) //include all PhoneNumbers with PhoneNumberType
.Unproxy() //instructs the framework to strip all the proxy classes when the Value is returned
.Deferred() //instructs the framework to delay execution (future)
.ValueAsync(token); //this is where all deferred queries get executed
The above code is basically configuring a query: return a customer by id with multiple child objects which should be executed with other queries (futures) and the returned result should be stripped of NHibernate proxies. The query gets executed when ValueAsync is called.
NHUnit determines if it should do join with the main query, create new future queries or make use of batch fetch.
There is a simple example project on Github to show you how to use NHUnit package. If others are interested in this project I will invest more time to make it better.
This is what I use:
Have a marker interface and inherit it on your entities, e.g. in my case empty IEntity.
We will use the marker interface to detect NHibernate entity types in the contract resolver.
public class CustomerEntity : IEntity { ... }
Create a custom contract resolver for JSON.NET
public class NHibernateProxyJsonValueProvider : IValueProvider {
private readonly IValueProvider _valueProvider;
public NHibernateProxyJsonValueProvider(IValueProvider valueProvider)
{
_valueProvider = valueProvider;
}
public void SetValue(object target, object value)
{
_valueProvider.SetValue(target, value);
}
private static (bool isProxy, bool isInitialized) GetProxy(object proxy)
{
// this is pretty much what NHibernateUtil.IsInitialized() does.
switch (proxy)
{
case INHibernateProxy hibernateProxy:
return (true, !hibernateProxy.HibernateLazyInitializer.IsUninitialized);
case ILazyInitializedCollection initializedCollection:
return (true, initializedCollection.WasInitialized);
case IPersistentCollection persistentCollection:
return (true, persistentCollection.WasInitialized);
default:
return (false, false);
}
}
public object GetValue(object target)
{
object value = _valueProvider.GetValue(target);
(bool isProxy, bool isInitialized) = GetProxy(value);
if (isProxy)
{
if (isInitialized)
{
return value;
}
if (value is IEnumerable)
{
return Enumerable.Empty<object>();
}
return null;
}
return value;
}
}
public class NHibernateContractResolver : CamelCasePropertyNamesContractResolver {
protected override JsonContract CreateContract(Type objectType)
{
if (objectType.IsAssignableTo(typeof(IEntity)))
{
return base.CreateObjectContract(objectType);
}
return base.CreateContract(objectType);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ValueProvider = new NHibernateProxyJsonValueProvider(property.ValueProvider);
return property;
}
}
Normal uninitialized lazy loaded properties will result in null in the json output.
Collection uninitialized lazy loaded properties will result in an [] empty array in json.
So for a lazy loaded property to appear in the json output you need to eagerly load it in the query or in code before serialization.
Usage:
JsonConvert.SerializeObject(entityToSerialize, new JsonSerializerSettings() {
ContractResolver = new NHibernateContractResolver()
});
Or globally in in ASP.NET Core Startup class
services.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new NHibernateContractResolver();
});
Using:
NET 5.0
NHibernate 5.3.8
JSON.NET latest via ASP.NET Core