JSON.Net - DeserializeObject Format - vb.net

I'm using JSON.Net to try and deserialize some survey responses from SurveyGizmo.
Here's a snapshot of the data I'm reading in:
{"result_ok":true,
"total_count":"44",
"page":1,
"total_pages":1,
"results_per_page":50,
"data":[
{"id":"1",
"contact_id":"",
"status":"Complete",
"is_test_data":"0",
"datesubmitted":"2011-11-13 22:26:53",
"[question(59)]":"11\/12\/2011",
"[question(60)]":"06:15 pm",
"[question(62)]":"72",
"[question(63)]":"One",
"[question(69), option(10196)]":"10",
I've setup a class as far as datesubmitted but I'm not sure how to setup the class to deserialize the questions given that the amount of questions will change? I also need to capture the option if it's present.
I'm using this code to use the JSON.NET Deserialize function:
Dim responses As Responses = JsonConvert.DeserializeObject(Of Responses)(fcontents)
Classes:
Public Class Responses
Public Property result_OK As Boolean
Public Property total_count As Integer
Public Property page As Integer
Public Property total_pages As Integer
Public Property results_per_page As Integer
Public Overridable Property data As List(Of surveyresponse)
End Class
Public Class SurveyResponse
Public Property id As Integer
Public Property status As String
Public Property datesubmitted As Date
End Class

This trick to support totally crazy mappings is to use JsonConverter and completely replace the parsing for that object, (I apologize for the C#, but I'm no good at VB syntax):
class Program
{
static void Main(string[] args)
{
var result = JsonConvert.DeserializeObject<Responses>(TestData);
}
const string TestData = #"{""result_ok"":true,
""total_count"":""44"",
""page"":1,
""total_pages"":1,
""results_per_page"":50,
""data"":[
{""id"":""1"",
""contact_id"":"""",
""status"":""Complete"",
""is_test_data"":""0"",
""datesubmitted"":""2011-11-13 22:26:53"",
""[question(59)]"":""11\/12\/2011"",
""[question(60)]"":""06:15 pm"",
""[question(62)]"":""72"",
""[question(63)]"":""One"",
""[question(69), option(10196)]"":""10"",
}]}";
}
[JsonObject]
class Responses
{
public bool result_ok { get; set; }
public string total_count { get; set; }
public int page { get; set; }
public int total_pages { get; set; }
public int results_per_page { get; set; }
public SurveyResponse[] Data { get; set; }
}
[JsonObject]
// Here is the magic: When you see this type, use this class to read it.
// If you want, you can also define the JsonConverter by adding it to
// a JsonSerializer, and parsing with that.
[JsonConverter(typeof(DataItemConverter))]
class SurveyResponse
{
public string id { get; set; }
public string contact_id { get; set; }
public string status { get; set; }
public string is_test_data { get; set; }
public DateTime datesubmitted { get; set; }
public Dictionary<int, string> questions { get; set; }
}
class DataItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SurveyResponse);
}
public override bool CanRead
{
get { return true; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = (SurveyResponse)existingValue;
if (value == null)
{
value = new SurveyResponse();
value.questions = new Dictionary<int, string>()
}
// Skip opening {
reader.Read();
while (reader.TokenType == JsonToken.PropertyName)
{
var name = reader.Value.ToString();
reader.Read();
// Here is where you do your magic
if (name.StartsWith("[question("))
{
int index = int.Parse(name.Substring(10, name.IndexOf(')') - 10));
value.questions[index] = serializer.Deserialize<string>(reader);
}
else
{
var property = typeof(SurveyResponse).GetProperty(name);
property.SetValue(value, serializer.Deserialize(reader, property.PropertyType), null);
}
// Skip the , or } if we are at the end
reader.Read();
}
return value;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now obviously there's a lot more you would want to do to get this really robust, but this gives you the basics of how to do it. There are more lightweight alternatives if you simply need to change property names (either JsonPropertyAttribute or overriding DefaultContractResolver.ResolvePropertyName(), but this gives you full control.

Related

How to use Patch method in minimal api application?

I am trying to use patch method in my minimal api application this is my code :
Car class
public class Car
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "producent")]
public Producent Producent { get; set; }
[JsonProperty(PropertyName = "age")]
public int Age { get; set; }
[JsonProperty(PropertyName = "yearCreated")]
public int YearCreated { get; set; }
[JsonProperty(PropertyName = "engine")]
public Engine Engine { get; set; }
public Car()
{
Id= Guid.NewGuid().ToString();
YearCreated = DateTime.Now.Year - Age;
}
}
ICarService:
public interface ICarService
{
Task<IEnumerable<Car>> GetAll();
Task<Car> GetById(string id,string partitionKey);
Task Create(Car car);
Task<bool> Update(Car car);
Task<Car> UpdatePatchAsync(string id, string partitionKey,List<PatchOperation> patchOperations);
Task<bool> Delete(string id,string partitionKey);
}
patch method in service
public async Task<Car> UpdatePatchAsync(string id, string partitionKey, List<PatchOperation> patchOperations)
{
var result = await _container.PatchItemAsync<Car>(id, new PartitionKey(partitionKey),
patchOperations:patchOperations );
return result;
}
my requests:
[HttpPatch]
public static async Task<IResult> Patch(ICarService service,string id,string partitionKey,
[FromBody]List<PatchOperation> operations)
{
var updatedCar = await service.UpdatePatchAsync(id,partitionKey,operations);
if (updatedCar == null)
{
return Results.NotFound();
}
return Results.Ok(updatedCar);
}
app.MapMethods("/cars/patch/{id}/{partitionKey}", new string[] { "PATCH" }, CarRequests.Patch);
I use cosmosDb database and when i code like this:
{
"op": "replace",
"path": "age",
"value": 22
}
i get the error
System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Microsoft.Azure.Cosmos.PatchOperation'. Path: $[0] | LineNumber: 3 | BytePositionInLine: 3.
---> System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'Microsoft.Azure.Cosmos.PatchOperation'.
The problem is you are using [FromBody]List<PatchOperation> operations.
This is making ASP.NET attempt to deserialize a JSON raw string into PatchOperation, which when we look at our source code, has no parameterless constructor: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos/src/Patch/PatchOperation.cs, it is an abstract class.
The way it is meant to be used is through PatchOperation.XXXX, like PatchOperation.Add, you cannot use it to deserialize into it. You could have your own MyPatchOperation<Car> that has the exposed properties for deserialization, and then simply have a method in that class that based on which properties are populated, calls PatchOperation<Car>.Add (or whatever operation).
Not the full code but:
public class MyPatchOperation
{
[JsonProperty(PropertyName = "op")]
public string Operation { get; set; }
[JsonProperty(PropertyName = "path")]
public string Path { get; set; }
[JsonProperty(PropertyName = "value")]
public string Value { get; set; }
public PatchOperation<Car> ToPatchOperation()
{
switch (this.Operation)
{
case "replace":
return PatchOperation<Car>.Replace(this.Path, this.Value);
/* other operations */
}
}
}

Setting a boolean property from a 3rd party endpoint that returns a string

I am attempting to deserialize a json object using JsonConvert - the data is coming from a 3rd party API
return JsonConvert.DeserializeObject<UserRegistration>(content,
JsonSnakeCaseNameStrategySettings.Settings());
The UserRegistration class:
public class UserRegistration
{
public UserRegistrationData UserRegistration { get; set; }
}
public class UserRegistrationData
{
public int UserId { get; set; }
public string Email { get; set; }
public UserRegistrationCustomFields CustomFields { get; set; }
}
public class UserRegistrationCustomFields
{
private bool emailDelivery;
public string DeliveryTime { get; set; }
public bool EmailDelivery {
get
{
return emailDelivery;
}
set
{
emailDelivery = value.ToString() == "1";
}
}
public bool SmsDelivery { get; set; }
public string PhoneNumber { get; set; }
}
I've tried several ways, this is my current iteration. The goal is to have "EmailDelivery" be a boolean, the value from the API will always be "1" or "0". This throws a JsonReaderException: Could not convert string to boolean: 0. Path 'user_registration.custom_fields.email_delivery', line 1, position 208.
You need custom JsonConverter to modify the deserialize principle.
Change your model like below:
public class UserRegistrationCustomFields
{
public string DeliveryTime { get; set; }
public bool EmailDelivery{get;set;}
public bool SmsDelivery { get; set; }
public string PhoneNumber { get; set; }
}
Custom a JsonConverter:
public class JsonBooleanConverter : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value.ToString().ToLower().Trim();
switch (value)
{
case "1": return true;
}
return false;
}
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(Boolean))
{
return true;
}
return false;
}
}
How to use:
JsonConvert.DeserializeObject<UserRegistration>(json, new JsonBooleanConverter());

How to define a System.Security.Claims.ClaimsPrincipal as a request argument in gRPC?

I would like to build a authorization service using gRPC under .Net Code. In order to do that, I need to pass a System.Security.Claims.ClaimsPrincipal object as a request argument from caller to the server so the server can use it to authorize the caller. But I don't know how to that - how can I define a .proto for a class that is a standard library. What am I supposed to do?
I'm doing the same, using protobuf-net grpc libraries.
As many of the Identity/Security classes (if you are using them) are from Microsoft, you'll need to expose their members for serialization; you can use:
RuntimeTypeModel.Default.Add(typeof(SignInResult), false).Add(
nameof(SignInResult.Succeeded),
nameof(SignInResult.IsLockedOut),
nameof(SignInResult.IsNotAllowed),
nameof(SignInResult.RequiresTwoFactor)
);
and list the members that need to be exposed over gRpc.
As for ClaimsPrincipal, specifically, that is what I'm currently trying to implement. For Claims, i'm using a surrogate class:
RuntimeTypeModel.Default.Add(typeof(Claim), true).SetSurrogate(typeof(ClaimSurrogate));
public class ClaimSurrogate
{
[DataMember, ProtoMember(1)]
public string Type { get; set; }
[DataMember, ProtoMember(2)]
public ClaimsIdentity Subject { get; set; }
[DataMember, ProtoMember(3)]
public IDictionary<string, string> Properties { get; set; }
[DataMember, ProtoMember(4)]
public string OriginalIssuer { get; set; }
[DataMember, ProtoMember(5)]
public string Issuer { get; set; }
[DataMember, ProtoMember(6)]
public string ValueType { get; set; }
[DataMember, ProtoMember(7)]
public string Value { get; set; }
public static implicit operator ClaimSurrogate(Claim claim)
{
if (claim == null)
return null;
return new ClaimSurrogate()
{
Type = claim.Type,
Subject = claim.Subject,
Properties = claim.Properties,
OriginalIssuer = claim.OriginalIssuer,
Issuer = claim.Issuer,
ValueType = claim.ValueType,
Value = claim.Value
};
}
public static implicit operator Claim(ClaimSurrogate surrogate)
{
if (surrogate == null)
return null;
return new Claim(surrogate.Type, surrogate.Value, surrogate.ValueType, surrogate.Issuer, surrogate.OriginalIssuer, surrogate.Subject);
}
}
And I'm assuming that ClaimsPrincipal can be done the same way, but, I'm having trouble with it. That is how I came across your question...
Actually, by, trying to provide an answer...Literally, I just realized what I overlooked, I need to also set up a surrogate for the ClaimsIdentity
So far, I've needed surrogates for 'third' party classes that have get; only properties. ClaimsPrincipal has these types of properties, and so does ClaimsIdentity (as does Claim). I'll update/comment if ClaimsIdentitySurrogate does the trick
Updates:
Yes, it can be done. Surrogates, like the example above, will be needed for ClaimsIdentity and IIdentity. These classes are used as members/properties within ClaimsPrincipal.
ClaimsIdentity: you can mix up the SetSurrogate and the Add(nameof(...)) as it has get onlies and get/sets (get/sets go in the Add portion). Do not include the Actor in the ClaimsIdentity surrogate as it will create a never ending loop in your service's startup. If you do include it, make sure it is not a DataMember/Protomember. And (private) set it in the surrogate operator. Same same with Claims.
Essentially, any surrogates with members that reference the parent class, or that of a another type with a surrogate that references this parent type, will create a circular reference and error out your service on startup.
IIdentity: This is a simple one, just
RuntimeTypeModel.Default.Add(typeof(IIdentity), false).
Lastly (I posted this update when I thought I had it, but, amidst all the UT tests and changes, etc, I posted a bit early; after making a breaking change on the ClaimPrincipal surrogate class)....
You'll want an IIdentity dummy class that will be used in your ClaimPrincipal surrogate, instead of the IIdentity Identity {get;set;}. This dummy class should inherit from IIdentity, e.g.
[DataContract]
public class IIdentityFraud : System.Security.Principal.IIdentity
And within your surrogate's implicit operator:
IIdentityFraud identityfraud = null;
if (claimPrincipal.Identity != null)
{
identityfraud = new IIdentityFraud(claimPrincipal.Identity.AuthenticationType, claimPrincipal.Identity.Name, claimPrincipal.Identity.IsAuthenticated);
}
Updates (11/05/2021):
[DataContract]
public class ClaimsPrincipalSurrogate
{
[DataMember, ProtoMember(1)]
public IIdentityFraud Identity { get; set; }
[DataMember, ProtoMember(2)]
public IEnumerable<ClaimsIdentity> Identities { get; set; }
[DataMember, ProtoMember(3)]
public IEnumerable<Claim> Claims { get; set; }
public static implicit operator ClaimsPrincipalSurrogate(ClaimsPrincipal claimPrincipal)
{
if (claimPrincipal == null)
{
return null;
}
else
{
IIdentityFraud identityfraud = null;
if (claimPrincipal.Identity != null)
{
identityfraud = new IIdentityFraud(claimPrincipal.Identity.AuthenticationType, claimPrincipal.Identity.Name, claimPrincipal.Identity.IsAuthenticated);
}
return new ClaimsPrincipalSurrogate()
{
Identity = identityfraud, // (System.Security.Principal.IIdentity)identityfraud,
Identities = claimPrincipal.Identities,
Claims = claimPrincipal.Claims
};
}
}
public static implicit operator ClaimsPrincipal(ClaimsPrincipalSurrogate surrogate)
{
if (surrogate == null)
return null;
if (surrogate.Identities != null && surrogate.Identities.Any() == true)
{
return new ClaimsPrincipal(surrogate.Identities);
}
else if (surrogate.Identity != null)
{
return new ClaimsPrincipal(surrogate.Identity);
}
return new ClaimsPrincipal();
}
}
[DataContract]
public class ClaimsIdentitySurrogate
{
[DataMember, ProtoMember(1)]
public string AuthenticationType { get; set; }
[DataMember, ProtoMember(2)]
public string Name { get; set; }
//[DataMember, ProtoMember(3)]
//public string Label { get; set; }
[DataMember, ProtoMember(4)]
public bool IsAuthenticated { get; set; }
[DataMember, ProtoMember(5)]
public IEnumerable<Claim> Claims { get; private set; }
//[DataMember, ProtoMember(6)]
//public object BootstrapContext { get; set; }
//[DataMember, ProtoMember(7)]
public ClaimsIdentity Actor { get; private set; }
[DataMember, ProtoMember(8)]
public string RoleClaimType { get; set; }
[DataMember, ProtoMember(9)]
public string NameClaimType { get; set; }
public static implicit operator ClaimsIdentitySurrogate(ClaimsIdentity claimIdentity)
{
if (claimIdentity == null)
return null;
return new ClaimsIdentitySurrogate()
{
AuthenticationType = claimIdentity.AuthenticationType,
Name = claimIdentity.Name,
//Label = claimIdentity.Label,
IsAuthenticated = claimIdentity.IsAuthenticated,
Claims = claimIdentity.Claims,
//BootstrapContext = claimIdentity.AuthenticationType,
Actor = claimIdentity.Actor,
RoleClaimType = claimIdentity.RoleClaimType,
NameClaimType = claimIdentity.NameClaimType
};
}
public static implicit operator ClaimsIdentity(ClaimsIdentitySurrogate surrogate)
{
if (surrogate == null)
{
return null;
}
if (surrogate.Claims?.Any() == true)
{
return new ClaimsIdentity(surrogate.Claims, surrogate.AuthenticationType);
}
else
{
return new ClaimsIdentity(surrogate.AuthenticationType, surrogate.NameClaimType, surrogate.RoleClaimType);
}
}
}
[DataContract]
public class IIdentityFraud : System.Security.Principal.IIdentity
{
[DataMember, ProtoMember(1)]
public string AuthenticationType { get; private set; }
[DataMember, ProtoMember(2)]
public string Name { get; private set; }
[DataMember, ProtoMember(3)]
public bool IsAuthenticated { get; private set; }
public IIdentityFraud() { }
public IIdentityFraud(string authenticationType, string name, bool isAuthenticated)
{
this.AuthenticationType = authenticationType;
this.Name = name;
this.IsAuthenticated = isAuthenticated;
}
}
[DataContract] //don't know if this is really needed. Too involved in testing out the rest of it and have yet to come back to this.
public class IIdentitySurrogate : System.Security.Principal.IIdentity
{
[DataMember, ProtoMember(1)]
public string AuthenticationType { get; set; }
[DataMember, ProtoMember(2)]
public string Name { get; set; }
[DataMember, ProtoMember(3)]
public bool IsAuthenticated { get; set; }
public static implicit operator IIdentitySurrogate(IIdentityFraud iidentity)
{
if (iidentity == null)
return null;
return new IIdentitySurrogate()
{
AuthenticationType = iidentity.AuthenticationType,
Name = iidentity.Name,
IsAuthenticated = iidentity.IsAuthenticated
};
}
public static implicit operator IIdentityFraud(IIdentitySurrogate surrogate)
{
if (surrogate == null)
return null;
return new IIdentityFraud(surrogate.AuthenticationType, surrogate.Name, surrogate.IsAuthenticated);
}
}
More of what is executed on startups:
#region ClaimsIdentity
RuntimeTypeModel.Default.Add(typeof(ClaimsIdentity), true).Add(
nameof(ClaimsIdentity.Label),
nameof(ClaimsIdentity.BootstrapContext),
nameof(ClaimsIdentity.Actor)
).SetSurrogate(typeof(ClaimsIdentitySurrogate));
#endregion ClaimsIdentity
#region ClaimsPrincipal
RuntimeTypeModel.Default.Add(typeof(ClaimsPrincipal), true).SetSurrogate(typeof(ClaimsPrincipalSurrogate));
#endregion ClaimsPrincipal
#region IIdentity
RuntimeTypeModel.Default.Add(typeof(IIdentity), true);
#endregion IIdentity

ASP.Net core web API encode string to base64

I am new to .Net Core development. I have a model:
public class CoreGoal
{
[Key]
public long CoreGoalId { get; set; }
public string Title { get; set; }
public string Effect { get; set; }
public string Target_Audience { get; set; }
public string Infrastructure { get; set; }
public virtual ICollection<Image> Images { get; set; }
public CoreGoal()
{
}
}
And Image model is as following:
public class Image
{
[Key]
public long ImagelId { get; set; }
public string Base64 { get; set; }
[ForeignKey("CoreGoalId")]
public long CoreGoalId { get; set; }
public Image()
{
}
}
I am using Repository pattern. My repository:
public interface ICoreGoalRepository
{
void CreateCoreGoal(CoreGoal coreGoal);
}
public class CoreGoalRepository : ICoreGoalRepository
{
private readonly WebAPIDataContext _db;
public CoreGoalRepository(WebAPIDataContext db)
{
_db = db;
}
//Find specific
public CoreGoal Find(long key)
{
return _db.CoreGoals.FirstOrDefault(t => t.CoreGoalId == key);
}
//Add new
public void CreateCoreGoal(CoreGoal coreGoal)
{
_db.CoreGoals.Add(coreGoal);
_db.SaveChanges();
}
}
And controller:
[Route("api/[controller]")]
public class CoreGoalController : Controller
{
private readonly ICoreGoalRepository _coreGoalRepository;
//Controller
public CoreGoalController(ICoreGoalRepository coreGoalRepository) {
_coreGoalRepository = coreGoalRepository;
}
[HttpGet("{id}", Name = "GetCoreGoal")]
public IActionResult GetById(long id)
{
var item = _coreGoalRepository.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
//Create
[HttpPost]
public IActionResult Create([FromBody] CoreGoal item)
{
if (item == null)
{
return BadRequest();
}
_coreGoalRepository.CreateCoreGoal(item);
return CreatedAtRoute("GetCoreGoal", new { id = item.CoreGoalId }, item);
}
}
On POST request for CoreGoal- While creating a new CoreGoal, I would like to convert Image model's Base64 attribute from string to byte[]. I found this (https://adrientorris.github.io/aspnet-core/manage-base64-encoding.html) blogpost, but I am not sure where Am I supposed to write this piece of code.
Can someone help me?
Initially you should chage you database model to save you binary image to db (also, it's still not good idea, but let leave it for a now):
public class Image
{
[Key]
public long ImagelId { get; set; }
[NotMapped]
public string Base64 { get; set; }
public byte[] Binary {get; set;}
[ForeignKey("CoreGoalId")]
public long CoreGoalId { get; set; }
public Image()
{
}
}
next you just should convert your image inside controller:
[HttpPost]
public IActionResult Create([FromBody] CoreGoal item)
{
if (item == null)
{
return BadRequest();
}
item.Binary = Convert.FromBase64String(item.Base64);
_coreGoalRepository.CreateCoreGoal(item);
return CreatedAtRoute("GetCoreGoal", new { id = item.CoreGoalId }, item);
}
BTW:you code still not good. It's not necessary to use Repository pattern with EF core (https://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework-core/). And you should introduce two model layers: public layer and model layer. You shouldn't expose EF Core contract to outside.

Can I use NHibernate to store an object in xml serialized form?

Say I have a class like this:
public class MyClass
{
public int Id { get; set; }
public DateTime Date { get; set; }
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public string String4 { get; set; }
}
Is it possible to get NHibernate to store it in the following schema?
CREATE TABLE [dbo].[MyClass](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Xml] [varchar](max) NOT NULL,
)
Where the Id maps to Id, but then all other fields get serialized into XML (or otherwise)? I don't mind if these other fields have to go on a child object like the below, if that helps:
public class MyClass
{
public int Id { get; set; }
public AllOtherOptions Options { get; set; }
}
public class AllOtherOptions
{
public DateTime Date { get; set; }
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public string String4 { get; set; }
}
I am thinking about doing something similar for an upcoming project. The project requires collecting a lot of data but only a few elements need to be stored in a relational database. I haven't started experimenting but these are my thoughts so far.
You can map an XML data type by creating a type that implements IUserType. If the child class (AllOtherOptions) is serializable, you should be able to map the XML field as a private member in MyClass and serialize/deserialize AllOtherOptions as needed. You could either dynamically maintain the XML field (sounds like a lot of work) or create an interceptor to do it. My thinking is that MyClass would implement an interface such as
public interface IXmlObjectContainer
{
void SerializeChildObjects();
void DeSerializeChildObjects();
}
and the interceptor would call those methods as needed. That's a proof of concept idea. I would probably refine that by exposing pairs of xml fields and serializable objects to remove the work of serializing from IXmlObjectContainer implementers. Or maybe handle serialization through the XML field's get/set accessors.
More info:
Working with XML Fields in NHibernate
Another XML implementation of IUserType
I had the same idea to save object in XML column. My idea was other. I took code from links and changed it to generic IUserType implementation. So any field/prop which is [Serializable] can be saved in XML column.
public class XmlUserType<T> : IUserType where T : class
{
public new bool Equals(object x, object y)
{
return x == y;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
if (names.Length != 1)
throw new InvalidOperationException("names array has more than one element. can't handle this!");
var val = rs[names[0]] as string;
if (string.IsNullOrWhiteSpace(val) == false)
{
return KRD.Common.GenericXmlSerialization.Deserialize<T>(val);
}
return null;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var parameter = (DbParameter)cmd.Parameters[index];
T toSave = value as T;
if (toSave != null)
{
parameter.Value = KRD.Common.GenericXmlSerialization.Serialize(toSave);
}
else
{
parameter.Value = DBNull.Value;
}
}
public object DeepCopy(object value)
{
T toCopy = value as T;
if (toCopy == null)
return null;
string serialized = KRD.Common.GenericXmlSerialization.Serialize(toCopy);
return KRD.Common.GenericXmlSerialization.Deserialize<T>(serialized);
}
public object Replace(object original, object target, object owner)
{
throw new NotImplementedException();
}
public object Assemble(object cached, object owner)
{
var str = cached as string;
if (string.IsNullOrWhiteSpace(str) == false)
{
return null;
}
return KRD.Common.GenericXmlSerialization.Deserialize<T>(str);
}
public object Disassemble(object value)
{
var toCache = value as T;
if (toCache != null)
{
return KRD.Common.GenericXmlSerialization.Serialize(toCache);
}
return null;
}
public SqlType[] SqlTypes
{
get
{
return new SqlType[] { new SqlXmlType() };
}
}
public Type ReturnedType
{
get { return typeof(XmlDocument); }
}
public bool IsMutable
{
get { return true; }
}
}
public class SqlXmlType : SqlType
{
public SqlXmlType()
: base(DbType.Xml)
{
}
}
Usage with FluentNHibernate:
public class MainObject
{
public int Id { get; set; }
public ObjectAsXml Data { get; set; }
}
public class ObjectAsXml
{
public string Name { get; set; }
public int Date { get; set; }
public ObjectAsXml OtherObject { get; set; }
}
private class MainObjectMap : ClassMap<MainObject>
{
public MainObjectMap()
{
Id(id => id.Id);
Map(m => m.Data).CustomType<XmlUserType<ObjectAsXml>>().Nullable();
}
}
Maybe it will help somebody.