-
-
Save galvesribeiro/4427521b66bc23dcc5216158ce306cba to your computer and use it in GitHub Desktop.
IMigratable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Newtonsoft.Json; | |
namespace Migratable { | |
[JsonConverter(typeof(MigratableConverter))] | |
public interface IMigratable { | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace Migratable { | |
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] | |
public class MigratableAttribute : Attribute { | |
public readonly Guid Id; | |
public MigratableAttribute(string guid) { | |
Id = Guid.Parse(guid); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace Apex.CQRSOrleans.Migratable { | |
public class MigratableConverter : JsonConverter { | |
[ThreadStatic] | |
static bool writeDisabled = false; | |
[ThreadStatic] | |
static bool readDisabled = false; | |
public override bool CanRead => !readDisabled; | |
public override bool CanWrite => !writeDisabled; | |
public override bool CanConvert(Type objectType) => typeof(IMigratable).IsAssignableFrom(objectType); | |
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { | |
try { | |
writeDisabled = true; | |
if (null == value) { | |
writer.WriteValue(value); | |
} else { | |
var jObject = JObject.FromObject(value); | |
jObject.Add("$typeId", MigratableTypes.GetTypeId(value.GetType())); | |
jObject.WriteTo(writer); | |
} | |
} finally { | |
writeDisabled = false; | |
} | |
} | |
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { | |
try { | |
readDisabled = true; | |
var jObject = JToken.ReadFrom(reader) as JObject; | |
if (null == jObject) return null; | |
var typeId = (Guid)jObject.GetValue("$typeId"); | |
var type = MigratableTypes.GetType(typeId); | |
return JsonConvert.DeserializeObject(jObject.ToString(), type); | |
} finally { | |
readDisabled = false; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Runtime.Serialization; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace Apex.CQRSOrleans.Migratable { | |
[Serializable] | |
public class MigratableTypeNotFoundException : Exception { | |
public readonly Guid TypeId; | |
public MigratableTypeNotFoundException(Guid typeId) : this(typeId, null) { } | |
public MigratableTypeNotFoundException(Guid typeId, Exception innerException) : base($"Migratable type with id '{typeId:N}' not found in any loaded assembly.", innerException) => TypeId = typeId; | |
public MigratableTypeNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.Serialization; | |
using System.Text; | |
using System.Threading.Tasks; | |
#if NETCOREAPP2_0 || NETSTANDARD2_0 | |
using Microsoft.Extensions.DependencyModel; | |
#endif | |
namespace Apex.CQRSOrleans.Migratable { | |
public static class MigratableTypes { | |
static readonly Dictionary<Guid, Type> Data = new Dictionary<Guid, Type>(); | |
static MigratableTypes() { | |
foreach (var type in GetIMigratableTypes()) { | |
CheckIMigratableRules(type); | |
Data[GetTypeId(type)] = type; | |
} | |
} | |
static IEnumerable<Type> GetIMigratableTypes() { | |
#if NETCOREAPP2_0 || NETSTANDARD2_0 | |
//http://www.michael-whelan.net/replacing-appdomain-in-dotnet-core/ | |
return DependencyContext.Default.CompileLibraries.Select(l => l.Name) | |
.Where(s => !s.StartsWith("Microsoft.")) | |
.Where(s => !s.StartsWith("System.")) | |
.Where(s => !s.StartsWith("runtime.")) | |
.OrderBy(s => s) | |
.SelectMany(assemblyName => { | |
try { | |
return Assembly.Load(assemblyName).DefinedTypes | |
.Where(t => !t.IsAbstract && typeof(IMigratable).IsAssignableFrom(t)) | |
.Select(t => t.AsType()); | |
} catch { // Some assemblies don't always play ball with loading. If we ever run into a point where one of them is needed, highly unlikely, we can look at improving this method. | |
return new Type[0]; | |
} | |
}); | |
#elif NET461 | |
return AppDomain.CurrentDomain.GetAssemblies() | |
.SelectMany(a => a.GetTypes() | |
.Where(t => typeof(IMigratable).IsAssignableFrom(t)) | |
.Where(t => !t.IsAbstract)); | |
#endif | |
} | |
static void CheckIMigratableRules(Type type) { | |
// Check for duplicate IMigratable identifiers | |
var id = GetTypeId(type); | |
if (Data.ContainsKey(id)) | |
throw new MigratableTypeValidationException($"Duplicate '{nameof(MigratableAttribute)}' value found on types '{type.FullName}' and '{Data[id].FullName}'."); | |
// [DataContract] attribute is required, on EVERY class, not just base classes | |
if (type.GetCustomAttributes(typeof(DataContractAttribute), false).Length == 0) | |
throw new MigratableTypeValidationException($"'{nameof(IMigratable)}' objects are required to use the '[DataContract]' attribute. Class: '{type.FullName}'."); | |
// Collect information about [DataMember] attributes on all fields and properties including inherited and private. | |
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; | |
var fields = type.GetFields(bindingFlags).Where(f => null != f.GetCustomAttribute(typeof(DataMemberAttribute))).ToArray(); | |
var properties = type.GetProperties(bindingFlags).Where(p => null != p.GetCustomAttribute(typeof(DataMemberAttribute))).ToArray(); | |
var members = fields.Cast<MemberInfo>().Concat(properties.Cast<MemberInfo>()) | |
.Select(m => new { | |
Member = m, | |
DataMemberAttribute = (DataMemberAttribute)m.GetCustomAttribute(typeof(DataMemberAttribute)) | |
}).ToArray(); | |
// Check that DataMember names are explicitly set eg [DataMember(Name = "xx")] | |
var noName = members.FirstOrDefault(m => !m.DataMemberAttribute.IsNameSetExplicitly); | |
if (null != noName) { | |
var message = $"'{nameof(IMigratable)}' objects are required to set DataMember names explicitly. Class: '{type.FullName}', Field: '{noName.Member.Name}'."; | |
throw new MigratableTypeValidationException(message); | |
} | |
// Check that DataMember names are not accidentally duplicated. | |
var duplicateName = members.GroupBy(m => m.DataMemberAttribute.Name).FirstOrDefault(g => g.Count() > 1); | |
if (null != duplicateName) { | |
throw new MigratableTypeValidationException($"Duplicate DataMemberName '{duplicateName.Key}' found on class '{type.FullName}'."); | |
} | |
} | |
public static Type GetType(Guid typeId) { | |
try { return Data[typeId]; } catch (KeyNotFoundException x) { throw new MigratableTypeNotFoundException(typeId, x); } | |
} | |
public static Guid GetTypeId(Type type) { | |
var a = type.GetCustomAttributes(typeof(MigratableAttribute), false) | |
.Cast<MigratableAttribute>() | |
.FirstOrDefault(); | |
if (null == a) | |
throw new MigratableTypeValidationException($"'{nameof(MigratableAttribute)}' attribute does not exist on type '{type.FullName}'."); | |
if (Guid.Empty == a.Id) | |
throw new MigratableTypeValidationException($"'{nameof(MigratableAttribute)}' attribute was not set to a proper value on type '{type.FullName}'."); | |
return a.Id; | |
} | |
public static IEnumerable<KeyValuePair<Guid, Type>> GetMigratableTypes() { | |
return Data; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Runtime.Serialization; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace Apex.CQRSOrleans.Migratable { | |
[Serializable] | |
public class MigratableTypeValidationException : Exception { | |
public MigratableTypeValidationException(String message) : base(message) { } | |
public MigratableTypeValidationException(String message, Exception innerException) : base(message, innerException) { } | |
public MigratableTypeValidationException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment