RSASSA-PSS without parameters using SHA-256 .Net 4.5 support - c#

I'm trying to use System.Security.Cryptography (targeted framework .NET 4.5) to create xml digital signatures, so far I managed to create and verify signatures using the following scheme :
RSA PKCS#1 v1.5 and SHA-256: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
However, I'm not able to use the following scheme:
‘RSASSA-PSS without parameters using SHA-256’ [RFC6931]: http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1
The error being displayed is clear "SignatureDescription could not be created for the signature algorithm supplied."
For 'RSA PKCS#1 v1.5 and SHA-256' I added the following public class as its signature:
public class RSAPKCS1SHA256SignatureDescription : SignatureDescription
{
public RSAPKCS1SHA256SignatureDescription()
{
base.KeyAlgorithm = "System.Security.Cryptography.RSACryptoServiceProvider";
base.DigestAlgorithm = "System.Security.Cryptography.SHA256Managed";
base.FormatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureFormatter";
base.DeformatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureDeformatter";
}
public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
{
AsymmetricSignatureDeformatter asymmetricSignatureDeformatter = (AsymmetricSignatureDeformatter)
CryptoConfig.CreateFromName(base.DeformatterAlgorithm);
asymmetricSignatureDeformatter.SetKey(key);
asymmetricSignatureDeformatter.SetHashAlgorithm("SHA256");
return asymmetricSignatureDeformatter;
}
}
However, I have no clue whether ‘RSASSA-PSS without parameters using SHA-256’ is supported by .Net 4.5 and if so how to set its signature definition.
I would be really thankful if anyone had similar experience and can provide some help.

I have finally figured it out. The trick is to register the algorithm that the XML signature specifies, in my case "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1". Register it to a custom class, that uses a custom signature formatter and deformatter, which apply RSASignaturePadding.Pss.
Here is an implementation where all you have to do is call RsaSsaPss.RegisterSha256RsaMgf1() once, such as from a static constructor in your client. Then, SignedXml.CheckSignature() and SignedXml.ComputeSignature() automatically work for any XML signature that specifies this algorithm.
Tested on Core 2.1 and Framework 4.7.1:
/// <summary>
/// Contains classes for using RSA-SSA-PSS, as well as methods for registering them.
/// Registering such types adds support for them to SignedXml.
/// </summary>
public class RsaSsaPss
{
/// <summary>
/// Registers an implementation for "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1".
/// </summary>
public static void RegisterSha256RsaMgf1()
{
CryptoConfig.AddAlgorithm(typeof(RsaPssSha256SignatureDescription), "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1");
}
// Can add further registrations here...
public class RsaPssSha256SignatureDescription : SignatureDescription
{
public RsaPssSha256SignatureDescription()
{
using (var rsa = RSA.Create())
{
this.KeyAlgorithm = rsa.GetType().AssemblyQualifiedName; // Does not like a simple algorithm name, but wants a type name (AssembyQualifiedName in Core)
}
this.DigestAlgorithm = "SHA256"; // Somehow wants a simple algorithm name
this.FormatterAlgorithm = typeof(RsaPssSignatureFormatter).FullName;
this.DeformatterAlgorithm = typeof(RsaPssSignatureDeformatter).FullName;
}
public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key)
{
var signatureFormatter = new RsaPssSignatureFormatter();
signatureFormatter.SetKey(key);
signatureFormatter.SetHashAlgorithm(this.DigestAlgorithm);
return signatureFormatter;
}
public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
{
var signatureDeformatter = new RsaPssSignatureDeformatter();
signatureDeformatter.SetKey(key);
signatureDeformatter.SetHashAlgorithm(this.DigestAlgorithm);
return signatureDeformatter;
}
public class RsaPssSignatureFormatter : AsymmetricSignatureFormatter
{
private RSA Key { get; set; }
private string HashAlgorithmName { get; set; }
public override void SetKey(AsymmetricAlgorithm key)
{
this.Key = (RSA)key;
}
public override void SetHashAlgorithm(string strName)
{
// Verify the name
Oid.FromFriendlyName(strName, OidGroup.HashAlgorithm);
this.HashAlgorithmName = strName;
}
public override byte[] CreateSignature(byte[] rgbHash)
{
return this.Key.SignHash(rgbHash, new HashAlgorithmName(this.HashAlgorithmName), RSASignaturePadding.Pss);
}
}
public class RsaPssSignatureDeformatter : AsymmetricSignatureDeformatter
{
private RSA Key { get; set; }
private string HashAlgorithmName { get; set; }
public override void SetKey(AsymmetricAlgorithm key)
{
this.Key = (RSA)key;
}
public override void SetHashAlgorithm(string strName)
{
// Verify the name
Oid.FromFriendlyName(strName, OidGroup.HashAlgorithm);
this.HashAlgorithmName = strName;
}
public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature)
{
return this.Key.VerifyHash(rgbHash, rgbSignature, new HashAlgorithmName(this.HashAlgorithmName), RSASignaturePadding.Pss);
}
}
}
}

Related

convert signature algorithm from sha1 to sha256 in c#

I have a webservice client application. My webservice provider inform me to change the signature method from sha1 to sha256 which is in requests header part. Currently i have a CustomSendFilter class, and securing the outgoing messages with the function below. How can i convert to sha 256? I searched but havent found a definite solution yet.
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
X509SecurityToken signatureToken;
signatureToken = new X509SecurityToken(CertificateManager.ClientCertificate);
security.Tokens.Add(signatureToken);
MessageSignature sig = new MessageSignature(signatureToken);
security.Elements.Add(sig);
security.Timestamp.TtlInSeconds = 60;
Logging.AddToLog(envelope.Envelope.InnerText);
}
You can set the signature algorithm to be used via MessageSignature.SignedInfo.SignatureMethod.
Unfortunately the .NET Framework may not have built-in support for http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 at the time of this writing, but the following code can be used to fix that (credits go to https://gist.github.com/sneal/f35de432115b840c4c1f ):
/// <summary>
/// SignatureDescription impl for http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
/// </summary>
public class RSAPKCS1SHA256SignatureDescription : SignatureDescription
{
/// <summary>
/// Registers the http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 algorithm
/// with the .NET CrytoConfig registry. This needs to be called once per
/// appdomain before attempting to validate SHA256 signatures.
/// </summary>
public static void Register()
{
CryptoConfig.AddAlgorithm(
typeof(RSAPKCS1SHA256SignatureDescription),
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
}
/// <summary>
/// .NET calls this parameterless ctor
/// </summary>
public RSAPKCS1SHA256SignatureDescription()
{
KeyAlgorithm = "System.Security.Cryptography.RSACryptoServiceProvider";
DigestAlgorithm = "System.Security.Cryptography.SHA256Managed";
FormatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureFormatter";
DeformatterAlgorithm = "System.Security.Cryptography.RSAPKCS1SignatureDeformatter";
}
public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
{
var asymmetricSignatureDeformatter =
(AsymmetricSignatureDeformatter)CryptoConfig.CreateFromName(DeformatterAlgorithm);
asymmetricSignatureDeformatter.SetKey(key);
asymmetricSignatureDeformatter.SetHashAlgorithm("SHA256");
return asymmetricSignatureDeformatter;
}
public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key)
{
var asymmetricSignatureFormatter =
(AsymmetricSignatureFormatter)CryptoConfig.CreateFromName(FormatterAlgorithm);
asymmetricSignatureFormatter.SetKey(key);
asymmetricSignatureFormatter.SetHashAlgorithm("SHA256");
return asymmetricSignatureFormatter;
}
}
Thank you for answering. I have a CustomSendFilter class for securing request as below. Where should i register the algorithm? I registered before calling the webservice function and inside the SecureMessage function below as well but didn't work.
public class CustomSendFilter : SendSecurityFilter
{
private string serviceDescription;
public CustomSendFilter(SecurityPolicyAssertion parentAssertion , string serviceDescription)
: base(parentAssertion.ServiceActor, true)
{
this.serviceDescription = serviceDescription;
}
public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
{
SoapFilterResult result = base.ProcessMessage(envelope);
Logging.SaveSoapEnvelope(envelope, SoapMessageDirection.Out , serviceDescription);
return result;
}
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
X509SecurityToken signatureToken;
signatureToken = new X509SecurityToken(CertificateManager.ClientCertificate);
RSAPKCS1SHA256SignatureDescription.Register();
security.Tokens.Add(signatureToken);
MessageSignature sig = new MessageSignature(signatureToken);
security.Elements.Add(sig);
security.Timestamp.TtlInSeconds = 60;
Logging.AddToLog(envelope.Envelope.InnerText);
}
}

Encrypt/Decrypt property while writing/reading to c# mongo db

Just going to lay out all the info i have:
In short, I am looking for something exactly (literally) like this but compatible with ASP Core (2.2) and the C# MongoDB Driver (2.7).
This seems like such a common requirement, I am very surprised i can't find anything already built.
Here is what i have so far:
Model:
public class Patient
{
//comes from the client as XXXXXXXXX, RegEx: "([0-9]{9})"
//[MongoEncrypt]
public EncryptedString SocialSecurityNumber { get; set; }
}
Attribute:
[AttributeUsage(AttributeTargets.Property)]
public class MongoEncryptAttribute : BsonSerializerAttribute
{
public MongoEncryptAttribute()
{
SerializerType = typeof(MongoEncryptSerializer);
}
}
Custom Serializer:
public interface IMongoEncryptSerializer : IBsonSerializer<EncryptedString>{ }
public class MongoEncryptSerializer : SerializerBase<EncryptedString>, IMongoEncryptSerializer
{
private readonly string _encryptionKey;
public MongoEncryptSerializer(IConfiguration configuration)
{
_encryptionKey = configuration.GetSection("MongoDb")["EncryptionKey"];
}
public override EncryptedString Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var encryptedString = context.Reader.ReadString();
return AesThenHmac.SimpleDecryptWithPassword(encryptedString, _encryptionKey);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EncryptedString value)
{
var encryptedString = AesThenHmac.SimpleEncryptWithPassword(value, _encryptionKey);
context.Writer.WriteString(encryptedString);
}
}
Open Items:
Use DI (vanilla .net core DI) to get the Serializer. thinking of something like BsonSerializer.RegisterSerializer(type,serializer) in a bootstrap method where i can access the service collection and do a GetInstance but then i would need string SocialSecurityNumber to use a custom type (maybe SecureString?)
Went with a custom type,EncryptedString, with implicit string conversion
Use DI in the serializer to get the key (initially from IConfiguration/appsettings.json and then ultimately from Azure KeyVault (whole new can of worms for me)) and the EncryptionProvider
deterministic encryption for searching. AesThenHmac comes from this popular post. I can store and retrieve data back fine in its current implementation. But in order to search for SSNs, I need deterministic encryption which this lib does not provide.
My Solution:
Model:
public class Patient
{
//comes from the client as XXXXXXXXX, RegEx: "([0-9]{9})"
public EncryptedString SocialSecurityNumber { get; set; }
}
Custom Type:
public class EncryptedString
{
private readonly string _value;
public EncryptedString(string value)
{
_value = value;
}
public static implicit operator string(EncryptedString s)
{
return s._value;
}
public static implicit operator EncryptedString(string value)
{
if (value == null)
return null;
return new EncryptedString(value);
}
}
Serializer(using Deterministic Encryption):
public interface IEncryptedStringSerializer : IBsonSerializer<EncryptedString> {}
public class EncryptedStringSerializer : SerializerBase<EncryptedString>, IEncryptedStringSerializer
{
private readonly IDeterministicEncrypter _encrypter;
private readonly string _encryptionKey;
public EncryptedStringSerializer(IConfiguration configuration, IDeterministicEncrypter encrypter)
{
_encrypter = encrypter;
_encryptionKey = configuration.GetSection("MongoDb")["EncryptionKey"];
}
public override EncryptedString Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var encryptedString = context.Reader.ReadString();
return _encrypter.DecryptStringWithPassword(encryptedString, _encryptionKey);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EncryptedString value)
{
var encryptedString = _encrypter.EncryptStringWithPassword(value, _encryptionKey);
context.Writer.WriteString(encryptedString);
}
}
Registering the serializer:
collection.AddScoped<IEncryptedStringSerializer, EncryptedStringSerializer>();
//then later...
BsonSerializer.RegisterSerializer<EncryptedString>(sp.GetService<IEncryptedStringSerializer>());

How to apply BsonRepresentation attribute by convention when using MongoDB

I'm trying to apply [BsonRepresentation(BsonType.ObjectId)] to all ids represented as strings withoput having to decorate all my ids with the attribute.
I tried adding the StringObjectIdIdGeneratorConvention but that doesn't seem to sort it.
Any ideas?
Yeah, I noticed that, too. The current implementation of the StringObjectIdIdGeneratorConvention does not seem to work for some reason. Here's one that works:
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
}
public class StringObjectIdIdGeneratorConventionThatWorks : ConventionBase, IPostProcessingConvention
{
/// <summary>
/// Applies a post processing modification to the class map.
/// </summary>
/// <param name="classMap">The class map.</param>
public void PostProcess(BsonClassMap classMap)
{
var idMemberMap = classMap.IdMemberMap;
if (idMemberMap == null || idMemberMap.IdGenerator != null)
return;
if (idMemberMap.MemberType == typeof(string))
{
idMemberMap.SetIdGenerator(StringObjectIdGenerator.Instance).SetSerializer(new StringSerializer(BsonType.ObjectId));
}
}
}
public class Program
{
static void Main(string[] args)
{
ConventionPack cp = new ConventionPack();
cp.Add(new StringObjectIdIdGeneratorConventionThatWorks());
ConventionRegistry.Register("TreatAllStringIdsProperly", cp, _ => true);
var collection = new MongoClient().GetDatabase("test").GetCollection<Person>("persons");
Person person = new Person();
person.Name = "Name";
collection.InsertOne(person);
Console.ReadLine();
}
}
You could programmatically register the C# class you intend to use to represent the mongo document. While registering you can override default behaviour (e.g map id to string):
public static void RegisterClassMap<T>() where T : IHasIdField
{
if (!BsonClassMap.IsClassMapRegistered(typeof(T)))
{
//Map the ID field to string. All other fields are automapped
BsonClassMap.RegisterClassMap<T>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
});
}
}
and later call this function for each of the C# classes you want to register:
RegisterClassMap<MongoDocType1>();
RegisterClassMap<MongoDocType2>();
Each class you want to register would have to implement the IHasIdField interface:
public class MongoDocType1 : IHasIdField
{
public string Id { get; set; }
// ...rest of fields
}
The caveat is that this is not a global solution and you still have to manually iterate over your classes.

C# Dictionary with Named/Labeled Types

I've searched just about everywhere and not even sure it's possible, but what the hey, I thought I would see what you C# wizards might have for a solution or workaround.
TL;DR:
I have a multi-dimensional collection using C# dictionaries and want to indicate what each string in the dictionary is for, something like this:
private Dictionary<string: Area, Dictionary<string: Controller, string: Action>> ActionCollection;
Which of-course does not work. For now I'm just commenting the dictionary.
Suggestions, thoughts, ideas?
You cannot do that, but you could add a summary.
For example:
/// <summary>
/// Dictionary<Area, Dictionary<Controller, Action>>
/// </summary>
private Dictionary<string, Dictionary<string, string>> ActionCollection;
These comments will show up in the intellisense.
Or:
If you want to extract info with reflection, you could use custom attributes
If it is just for readability, you could create aliases for it:
using Area = System.String;
using Controller = System.String;
using Action = System.String;
namespace MyApp
{
public class MyClass
{
private Dictionary<Area, Dictionary<Controller, Action>> ActionCollection;
}
}
But intellisense will show string
#MMM says about invalid xml, you can do this:
/// <summary>
/// Dictionary<Area, Dictionary<Controller, Action>>
/// </summary>
Make a class that pairs the key or the value with the annotation:
class AnnotatedVal {
public string Val {get;}
public string Annotation {get;}
public AnnotatedVal(string val, string annotation) {
// Do null checking
Val = val;
Annotation = annotation;
}
public bool Equals(object obj) {
var other = obj as AnnotatedVal;
return other != null && other.Val == Val && other.Annotation == Annotation;
}
public int GetHashCode() {
return 31*Val.GetHashCode() + Annotation.GetHashCode();
}
}
private Dictionary<AnnotatedVal,Dictionary<AnnotatedVal,AnnotatedVal>> ActionCollection;
Now you can use AnnotatedVal in your dictionaries to assure segregation:
ActionCollection.Add(new AnnotatedVal("hello", "Area"), someDictionary);
if (ActionCollection.ContainsKey(new AnnotatedVal("hello", "Area"))) {
Console.WriteLine("Yes");
} else {
Console.WriteLine("No");
}
if (ActionCollection.ContainsKey(new AnnotatedVal("hello", "Controller"))) {
Console.WriteLine("Yes");
} else {
Console.WriteLine("No");
}
The above should produce
Yes
No
because AnnotatedVal("hello", "Area") and AnnotatedVal("hello", "Controller") use different annotations.
You could wrap each string in it's own class. Then the declaration and intellisense will be descriptive:
public class Area
{
public string area { get; set; }
public override string ToString()
{
return area;
}
}
public class Controller
{
public string controller { get; set; }
public override string ToString()
{
return controller;
}
}
public class Action
{
public string action { get; set; }
public override string ToString()
{
return action;
}
}
private Dictionary<Area, Dictionary<Controller, Action>> ActionCollection;
It's possible by using Tuple Field Names within List:
private List<(string Area, List<(string Controller, string Action)>)> ActionCollection;
That's feature from C# 7.0 or from .NET 4.3 by importing System.ValueTuple nuget.

How to correctly update partially submitted (to a REST API) objects with LINQ to SQL

Our C# application communicates with a database through a LINQ-to-SQL Database Model, specifically using the MVC4 libraries.
I've been assigned the task of implementing a RESTful API. We thought it would be a good idea to version the API. That way, changes to the API can be introduced in a new version, and existing API clients won't break. To support this, every version of the API has its own set of Data Transfer Objects (DTOs) that it exposes and accepts. Some mapping is done (using AutoMapper) to translate between the API and Database Model.
Currently I'm working on updating and creating functionality. That is, if a client POSTs an Item object to the ItemsController, and the Item does not exist in the database yet (given its unique identifier), a new Item should be created. If the identifier is already present in the database, the existing data should be updated. So far so good.
Now, I'm converting a legacy code base to communicate with the RESTful API instead of with the database directly. Some parts of this codebase update a single property on a resource, and send just the identifier and the new value of that single property. The rest of the object should remain as it is in the database.
I'm having trouble implementing this using LINQ-to-SQL, specifically because of the DTO layer. This is the controller method:
[HttpPut]
[HttpPost]
public void UpdateOrCreateItem(ItemDTO data)
{
Item submittedItem = Map(data);
ItemRepository.UpdateOrCreateItem(submittedItem);
}
Now, instead of receiving a fully filled data object, only the identifier and one other property are filled. When the LINQ-to-SQL processes this data as follows:
public static void UpdateOrCreateItem(Item submittedItem)
{
if (submittedItem.Id > 0)
{
// update
using (DatabaseAccessor db = new DatabaseAccessor())
{
db.context.Items.Attach(submittedItem);
db.context.Refresh(RefreshMode.KeepCurrentValues, submittedItem);
db.context.SubmitChanges();
}
} else {
// create
// omitted...
}
}
Refreshing marks all the empty (missing) properties as changed, and its all saved to the database. Instead, only the properties that were submitted at the REST API level should be stored. What would be an elegant solution to this problem?
In the end I wrote some code to accept json-patch requests (see https://www.rfc-editor.org/rfc/rfc6902).
You need to add the media type "application/json-patch" to the collection of accepted formats.
You need to accept an identifier and an array of JsonPatchOperation objects as input to a HTTP PATCH method on your ApiController
The ApiController method:
[HttpPatch]
public void UpdatePartially(int id, JsonPatchOperation[] patchOperations)
{
if (id > 0)
{
// DatabaseAccessor is just a wrapper around my DataContext object
using (DatabaseAccessor db = new DatabaseAccessor())
{
SetDataLoadOptions(db); // optional of course
var item = db.context.Items.Single(i => i.id == id);
foreach (JsonPatchOperation patchOperation in patchOperations)
{
// when you want to set a foreign key identifier, LINQ-to-SQL throw a ForeignKeyReferenceAlreadyHasValueException
// the patchOperation will then use GetForeignKeyObject to fetch the object that it requires to set the foreign key object instead
patchOperation.GetForeignKeyObject = (PropertyInfo property, object identifier) =>
{
// this is just example code, make sure to correct this for the possible properties of your object...
if (property == typeof(Item).GetProperty("JobStatus", typeof(JobStatus)))
{
return db.context.JobStatus.Single(js => js.StatusId == (int)identifier);
}
else if (property == typeof(Item).GetProperty("User", typeof(User)))
{
return db.context.Users.Single(u => u.UserId == (Guid)identifier);
}
throw new ArgumentOutOfRangeException("property", String.Format("Missing getter for property '{0}'.", property.Name));
};
patchOperation.ApplyTo(item);
}
db.context.SubmitChanges();
}
}
}
And here are the dependencies of the above method:
/// <summary>
/// Add this to the global configuration Formatters collection to accept json-patch requests
/// </summary>
public class JsonPatchMediaTypeFormatter : JsonMediaTypeFormatter
{
public JsonPatchMediaTypeFormatter() : base()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json-patch"));
}
}
/// <summary>
/// All possible values for the "op" property of a json-patch object
/// docs: https://www.rfc-editor.org/rfc/rfc6902#section-4
/// </summary>
public enum JsonPatchOperationType
{
add,
remove,
replace,
move,
copy,
test
}
/// <summary>
/// json-patch is a partial update format for HTTP PATCH requests
/// docs: https://www.rfc-editor.org/rfc/rfc6902
/// </summary>
public class JsonPatchOperation
{
public string op { get; set; }
public string from { get; set; }
public string path { get; set; }
public string value { get; set; }
public Func<PropertyInfo, object, object> GetForeignKeyObject { get; set; }
public JsonPatchOperationType Operation
{
get
{
return (JsonPatchOperationType)Enum.Parse(typeof(JsonPatchOperationType), op);
}
}
public void ApplyTo(object document)
{
switch (Operation)
{
case JsonPatchOperationType.add:
Add(document, path, value);
break;
case JsonPatchOperationType.remove:
Remove(document, path);
break;
case JsonPatchOperationType.replace:
Replace(document, path, value);
break;
case JsonPatchOperationType.move:
Move(document, path, from);
break;
case JsonPatchOperationType.copy:
Copy(document, path, from);
break;
case JsonPatchOperationType.test:
Test(document, path, value);
break;
}
}
private void Add(object document, string path, string value)
{
Type documentType = document.GetType();
PathInfo pathInfo = GetPathInfo(documentType, path);
object convertedValue = ConvertToType(value, pathInfo.PropertyInfo.PropertyType);
pathInfo.PropertyInfo.SetValue(document, convertedValue, pathInfo.Indexes);
}
private void Replace(object document, string path, string value)
{
Type documentType = document.GetType();
PathInfo pathInfo = GetPathInfo(documentType, path);
object convertedValue = ConvertToType(value, pathInfo.PropertyInfo.PropertyType);
try
{
pathInfo.PropertyInfo.SetValue(document, convertedValue, pathInfo.Indexes);
}
// gnarly hack for setting foreign key properties
catch (TargetInvocationException tie)
{
if (tie.InnerException is ForeignKeyReferenceAlreadyHasValueException)
{
PropertyInfo matchingProperty = documentType.GetProperties().Single(p => p.GetCustomAttributes(typeof(AssociationAttribute), true).Any(attr => ((AssociationAttribute)attr).ThisKey == pathInfo.PropertyInfo.Name));
matchingProperty.SetValue(document, GetForeignKeyObject(matchingProperty, convertedValue), null);
}
else
{
throw tie;
}
}
}
private void Remove(object document, string path)
{
Type documentType = document.GetType();
PathInfo pathInfo = GetPathInfo(documentType, path);
pathInfo.PropertyInfo.SetValue(document, GetDefaultValue(pathInfo.PropertyInfo.PropertyType), pathInfo.Indexes);
}
private void Copy(object document, string path, string from)
{
throw new NotImplementedException();
}
private void Move(object document, string path, string from)
{
throw new NotImplementedException();
}
private void Test(object document, string path, string value)
{
throw new NotImplementedException();
}
#region Util
private class PathInfo
{
public PropertyInfo PropertyInfo { get; set; }
public object[] Indexes { get; set; }
}
private PathInfo GetPathInfo(Type documentType, string path)
{
object[] indexes = null;
PropertyInfo propertyInfo = documentType.GetProperty(path);
return new PathInfo { PropertyInfo = propertyInfo, Indexes = indexes };
}
private object GetDefaultValue(Type t)
{
if (t.IsValueType)
return Activator.CreateInstance(t);
return null;
}
private object ConvertToType(string value, Type type)
{
TypeConverter typeConverter = TypeDescriptor.GetConverter(type);
return typeConverter.ConvertFromString(value);
}
#endregion
}
It should be obvious that this is not finished, mature or elegant. But it works.

Categories

Resources