Reputation: 426
There's a class CoupledType
in my application that is used within a larger object of type UserObject
, where its instances are saved by being serialized using Json.NET and stored in a database.
This class in need of substantial refactoring in order to make it work in more contexts. Its name is also not ideal, and I'd want to move it to another library. However, it would still extend the same base class, and crucially, when users deserialize their saved work, the original instance must be converted.
The class in its current state looks, roughly, like this:
namespace FirstLibrary.Objects
{
[DataContract]
public class CoupledType : BaseType
{
[DataMember]
public double CoupledValue { get; set; }
}
}
And the type I want to convert it to looks like this:
namespace SecondLibrary.Objects
{
[DataContract]
public class DecoupledType : BaseType
{
[DataMember]
GenericContract Contract { get; set; }
}
}
The GenericContract
class mentioned above looks like this:
[DataContract]
public class GenericContract
{
[DataMember]
public ContractType ContractType { get; set; }
[DataMember]
public double ContractValue { get; set; }
}
In the conversion process, I would want to create a new GenericContract
object, getting the ContractType
from elsewhere (its value will always be the same, in this conversion) and the ContractValue
would be set to the value of the original CoupledValue
from CoupledType
.
The potentially trickier part is where I need access to this objects' parent UserObject
(ie. the one it is being deserialized from) to get a reference to the value of ContractType
.
To summarise, I need to write a converter for Json.NET which does the following:
FirstLibrary.Objects.CoupledType
to SecondLibrary.Objects.DecoupledType
double CoupledValue
with GenericContract Contract
(the construction of which requires access to the ParentObject
this CoupledType
instance is a member of / being deserialized from.I don't have a lot of experience with converting types in Json (having written a subclass of Json.Net
's JsonConverter
to convert a number of double
objects into double[]
s. I have no experience of changing the type or property names. If anyone could at least point me towards a potential solution, it would be much appreciated.
Sample of Existing (speculative, as actually serialized in BSON)
{
"$type": "FirstLibrary.Objects.CoupledType",
"CoupledValue": 4
}
{
"$type": "SecondLibrary.Objects.DecoupledType",
"Contract": {
"$type": "SecondLibrary.Objects.GenericContract",
"ContractType": {/*Refers to an object serialized elsewhere*/},
"ContractValue": 4
}
}
If it helps, the serialization settings are as follows:
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
TypeNameHandling = TypeNameHandling.All
Upvotes: 1
Views: 183
Reputation: 3154
I'm unsure if these approaches would fail to satisfy some unspoken requirement, but I can think of a few potential approaches that could get your DecoupledType
from the wire into your controller.
You could create a custom HttpParameterBinding
, and perform your mapping there. Your incoming parameter information is stored inside the HttpParameterDescriptor
, and you can interrogate the parameters to determine whether or not the particular binding applies.
public class GenericContractBinding : HttpParameterBinding
{
public GenericContractBinding(HttpParameterDescriptor descriptor) : base(descriptor){}
public override Task ExecuteBindingAsync(ModelMetadataProvider provider, HttpActionContext context, CancellationToken cancellationToken)
{
if(context.ControllerContext.Configuration.DependencyResolver != null)
{
//This is a naive eval based only on the incoming type. You'll likely want to map
var bazinga = context.Request.GetDependencyScope().GetService(Descriptor.ParameterType);
if(bazinga.GetType() == typeof(GenericContract)
context.ActionArguments[Descriptor.ParameterName] = bazinga;
}
}
}
Alternatively, you could also create a custom Web API ModelBinder
and decorate your type with a ModelBinderAttribute
(or declare the binder provider explicitly on the route method)
public class DecoupledTypeModelBinder : IModelBinder
{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
//I'm assuming your JSON is being sent in the content of the request
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
var json = JArray.Parse(content); //you really should switch to "array by default" processing;the number of items in it is an implementation detail.
//In my opinion, this "ContractTypeService" gets injected at CompositionRoot, which would be in the IHttpControllerActivator
var contractType = actionContext.RequestContext.Configuration.DependencyResolver.GetService(typeof(ContractTypeService)).GetAmbientContractType();
List<GenericContract> contracts = new List<GenericContract>();
foreach(var item in json.Children<JObject>())
{
var contract = new GenericContract();
contract.ContractValue = (double)item["ContractValue"].Value<JToken>();
contract.ContractType = contractType;
contracts.Add(contract);
}
}
//YMMV; You could enforce a hard requirement here for singularity or do something else if multiples are inbound on the wire
DecoupledType model = new DecoupledType()
{
Contract = contracts.Single()
};
bindingContext.Model = model;
}
}
public class DecoupledTypeModelBinderProvider : ModelBinderProvider
{
public override IModelBinder GetBinder(System.Web.Http.HttpConfiguration configuration, Type modelType)
{
return new DecoupledTypeModelBinder();
}
}
...(in your controller)
public dynamic Post([ModelBinder(typeof(DecoupledTypeModelBinderProvider))]DecoupledType bazinga)
{
var contract = bazinga.Contract;
var contractType = contract.ContractType;
var contractValue = contract.ContractValue;
}
Hope this sets you on a track towards success.
Upvotes: 1