Initial checkin of Notification service

This implementation uses Reflection.Emit to generate proxies for
duck-typing of event data on net45 and dnxcore50.

For now, the code that does a 'splat' of the top-level event data is still
using reflection instead of code-generation. The next update will address
this and add more test coverage for this part of the code.
This commit is contained in:
Ryan Nowak 2015-05-13 16:39:00 -07:00
Родитель 55dbbeade4
Коммит 4cf5452c51
21 изменённых файлов: 1570 добавлений и 0 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -7,6 +7,7 @@ _ReSharper.*/
packages/ packages/
artifacts/ artifacts/
PublishProfiles/ PublishProfiles/
project.lock.json
*.user *.user
*.suo *.suo
*.cache *.cache

36
EventNotification.sln Normal file
Просмотреть файл

@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22808.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8B66E199-1AFE-4B68-AC71-4521C46EC4CD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D75011A4-DEEE-48DE-BB83-CE042F2AC05B}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Framework.Notification", "src\Microsoft.Framework.Notification\Microsoft.Framework.Notification.xproj", "{4C660D0B-32C5-43D0-899D-73FF60352172}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Framework.Notification.Test", "test\Microsoft.Framework.Notification.Test\Microsoft.Framework.Notification.Test.xproj", "{51E95E1C-FE88-4DAF-8E03-3FC3153A03AF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4C660D0B-32C5-43D0-899D-73FF60352172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C660D0B-32C5-43D0-899D-73FF60352172}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C660D0B-32C5-43D0-899D-73FF60352172}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C660D0B-32C5-43D0-899D-73FF60352172}.Release|Any CPU.Build.0 = Release|Any CPU
{51E95E1C-FE88-4DAF-8E03-3FC3153A03AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51E95E1C-FE88-4DAF-8E03-3FC3153A03AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51E95E1C-FE88-4DAF-8E03-3FC3153A03AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51E95E1C-FE88-4DAF-8E03-3FC3153A03AF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4C660D0B-32C5-43D0-899D-73FF60352172} = {8B66E199-1AFE-4B68-AC71-4521C46EC4CD}
{51E95E1C-FE88-4DAF-8E03-3FC3153A03AF} = {D75011A4-DEEE-48DE-BB83-CE042F2AC05B}
EndGlobalSection
EndGlobal

3
global.json Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"projects": ["src"]
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Framework.Notification
{
public interface INotifier
{
void EnlistTarget(object target);
bool ShouldNotify(string notificationName);
void Notify(string notificationName, object parameters);
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Framework.Notification
{
public interface INotifyParameterAdapter
{
object Adapt(object inputParameter, Type outputType);
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45 || DNX451 || DNXCORE50
using System;
using System.Reflection.Emit;
namespace Microsoft.Framework.Notification.Internal
{
public class CacheResult
{
public static CacheResult FromError(Tuple<Type, Type> key, string error)
{
return new CacheResult()
{
Key = key,
Error = error,
};
}
public static CacheResult FromTypeBuilder(
Tuple<Type, Type> key,
TypeBuilder typeBuilder,
ConstructorBuilder constructorBuilder)
{
return new CacheResult()
{
Key = key,
TypeBuilder = typeBuilder,
ConstructorBuilder = constructorBuilder,
};
}
public ConstructorBuilder ConstructorBuilder { get; private set; }
public string Error { get; private set; }
public bool IsError => Error != null;
public Tuple<Type, Type> Key { get; private set; }
public TypeBuilder TypeBuilder { get; private set; }
}
}
#endif

Просмотреть файл

@ -0,0 +1,376 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45 || DNX451 || DNXCORE50
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Microsoft.Framework.Notification.Internal
{
public static class Converter
{
private static int _counter = 0;
private static AssemblyBuilder AssemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ProxyHolderAssembly"), AssemblyBuilderAccess.Run);
private static ModuleBuilder ModuleBuilder = AssemblyBuilder.DefineDynamicModule("Main Module");
public static object Convert(ConverterCache cache, Type outputType, Type inputType, object input)
{
if (input == null)
{
return null;
}
if (inputType == outputType)
{
return input;
}
if (outputType.IsAssignableFrom(inputType))
{
return input;
}
// If we get to this point, all of the trivial conversions have been tried. We don't attempt value
// conversions such as int -> double. The only thing left is proxy generation, which requires that
// the destination type be an interface.
//
// We should always end up with a proxy type or an exception
var proxyType = GetProxyType(cache, outputType, inputType);
Debug.Assert(proxyType != null);
return Activator.CreateInstance(proxyType, input);
}
private static Type GetProxyType(ConverterCache cache, Type tout, Type tin)
{
var key = new Tuple<Type, Type>(tin, tout);
CacheResult result;
if (!cache.TryGetValue(key, out result))
{
var context = new ProxyBuilderContext(cache, tout, tin);
// Check that all required types are proxy-able - this will create the TypeBuilder, Constructor,
// and property mappings.
//
// We need to create the TypeBuilder and Constructor up front to deal with cycles that can occur
// when generating the proxy properties.
if (!VerifyProxySupport(context, context.Key))
{
var error = cache[key];
Debug.Assert(error != null && error.IsError);
throw new InvalidOperationException(error.Error);
}
Debug.Assert(context.Visited.ContainsKey(context.Key));
// Now that we've generated all of the constructors for the proxies, we can generate the rest
// of the type.
foreach (var verificationResult in context.Visited)
{
AddProperties(
context,
verificationResult.Value.TypeBuilder,
verificationResult.Value.Mappings);
verificationResult.Value.TypeBuilder.CreateTypeInfo().AsType();
}
// We only want to publish the results after all of the proxies are totally generated.
foreach (var verificationResult in context.Visited)
{
cache[verificationResult.Key] = CacheResult.FromTypeBuilder(
verificationResult.Key,
verificationResult.Value.TypeBuilder,
verificationResult.Value.ConstructorBuilder);
}
return context.Visited[context.Key].TypeBuilder.CreateTypeInfo().AsType();
}
else if (result.IsError)
{
throw new InvalidOperationException(result.Error);
}
else if (result.TypeBuilder == null)
{
// This is an identity convertion
return null;
}
else
{
return result.TypeBuilder.CreateTypeInfo().AsType();
}
}
private static bool VerifyProxySupport(ProxyBuilderContext context, Tuple<Type, Type> key)
{
var sourceType = key.Item1;
var targetType = key.Item2;
if (context.Visited.ContainsKey(key))
{
// We've already seen this combination and so far so good.
return true;
}
CacheResult cacheResult;
if (context.Cache.TryGetValue(key, out cacheResult))
{
// If we get here we've got a published conversion or error, so we can stop searching.
return !cacheResult.IsError;
}
if (targetType == sourceType || targetType.IsAssignableFrom(sourceType))
{
// If we find a trivial conversion, then that will work.
return true;
}
if (!targetType.GetTypeInfo().IsInterface)
{
var message = Resources.FormatConverter_TypeMustBeInterface(targetType.FullName, sourceType.FullName);
context.Cache[key] = CacheResult.FromError(key, message);
return false;
}
// This is a combination we haven't seen before, and it *might* support proxy generation, so let's
// start trying.
var verificationResult = new VerificationResult();
context.Visited.Add(key, verificationResult);
var propertyMappings = new List<KeyValuePair<PropertyInfo, PropertyInfo>>();
var sourceProperties = sourceType.GetRuntimeProperties();
foreach (var targetProperty in targetType.GetRuntimeProperties())
{
if (!targetProperty.CanRead)
{
var message = Resources.FormatConverter_PropertyMustHaveGetter(
targetProperty.Name,
targetType.FullName);
context.Cache[key] = CacheResult.FromError(key, message);
return false;
}
if (targetProperty.CanWrite)
{
var message = Resources.FormatConverter_PropertyMustNotHaveSetter(
targetProperty.Name,
targetType.FullName);
context.Cache[key] = CacheResult.FromError(key, message);
return false;
}
if (targetProperty.GetIndexParameters()?.Length > 0)
{
var message = Resources.FormatConverter_PropertyMustNotHaveIndexParameters(
targetProperty.Name,
targetType.FullName);
context.Cache[key] = CacheResult.FromError(key, message);
return false;
}
// To allow for flexible versioning, we want to allow missing properties in the source.
//
// For now we'll just store null, and later generate a stub getter that returns default(T).
var sourceProperty = sourceProperties.Where(p => p.Name == targetProperty.Name).FirstOrDefault();
if (sourceProperty != null)
{
var propertyKey = new Tuple<Type, Type>(sourceProperty.PropertyType, targetProperty.PropertyType);
if (!VerifyProxySupport(context, propertyKey))
{
// There's an error here, so bubble it up and cache it.
var error = context.Cache[propertyKey];
Debug.Assert(error != null && error.IsError);
context.Cache[key] = CacheResult.FromError(key, error.Error);
return false;
}
}
propertyMappings.Add(new KeyValuePair<PropertyInfo, PropertyInfo>(targetProperty, sourceProperty));
}
verificationResult.Mappings = propertyMappings;
var baseType = typeof(ProxyBase<>).MakeGenericType(sourceType);
var typeBuilder = ModuleBuilder.DefineType(
"ProxyType" + _counter++ + " wrapping:" + sourceType.Name + " to look like:" + targetType.Name,
TypeAttributes.Class,
baseType,
new Type[] { targetType });
var constructorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
new Type[] { sourceType });
var il = constructorBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Castclass, sourceType);
il.Emit(OpCodes.Call, baseType.GetConstructor(new Type[] { sourceType }));
il.Emit(OpCodes.Ret);
verificationResult.ConstructorBuilder = constructorBuilder;
verificationResult.TypeBuilder = typeBuilder;
return true;
}
private static void AddProperties(
ProxyBuilderContext context,
TypeBuilder typeBuilder,
IEnumerable<KeyValuePair<PropertyInfo, PropertyInfo>> properties)
{
foreach (var property in properties)
{
var targetProperty = property.Key;
var sourceProperty = property.Value;
var propertyBuilder = typeBuilder.DefineProperty(
targetProperty.Name,
PropertyAttributes.None,
property.Key.PropertyType,
Type.EmptyTypes);
var methodBuilder = typeBuilder.DefineMethod(
targetProperty.GetMethod.Name,
targetProperty.GetMethod.Attributes & ~MethodAttributes.Abstract,
targetProperty.GetMethod.CallingConvention,
targetProperty.GetMethod.ReturnType,
Type.EmptyTypes);
propertyBuilder.SetGetMethod(methodBuilder);
typeBuilder.DefineMethodOverride(methodBuilder, targetProperty.GetMethod);
var il = methodBuilder.GetILGenerator();
if (sourceProperty == null)
{
// Return a default(T) value.
il.Emit(OpCodes.Initobj, targetProperty.PropertyType);
il.Emit(OpCodes.Ret);
}
else
{
// Push 'this' and get the underlying instance.
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld,
typeBuilder.BaseType.GetField(
"Instance",
BindingFlags.Instance | BindingFlags.Public));
// Call the source property.
il.EmitCall(OpCodes.Callvirt, sourceProperty.GetMethod, null);
// Create a proxy for the value returned by source property (if necessary).
EmitProxy(context, il, targetProperty.PropertyType, sourceProperty.PropertyType);
il.Emit(OpCodes.Ret);
}
}
}
private static void EmitProxy(ProxyBuilderContext context, ILGenerator il, Type targetType, Type sourceType)
{
if (sourceType == targetType)
{
// Do nothing.
return;
}
else if (targetType.IsAssignableFrom(sourceType))
{
il.Emit(OpCodes.Castclass, targetType);
return;
}
// If we get here, then we actually need a proxy.
var key = new Tuple<Type, Type>(sourceType, targetType);
ConstructorBuilder constructorBuilder = null;
CacheResult cacheResult;
VerificationResult verificationResult;
if (context.Cache.TryGetValue(key, out cacheResult))
{
Debug.Assert(!cacheResult.IsError);
Debug.Assert(cacheResult.ConstructorBuilder != null);
// This means we've got a fully-built (published) type.
constructorBuilder = cacheResult.ConstructorBuilder;
}
else if (context.Visited.TryGetValue(key, out verificationResult))
{
Debug.Assert(verificationResult.ConstructorBuilder != null);
constructorBuilder = verificationResult.ConstructorBuilder;
}
Debug.Assert(constructorBuilder != null);
var endLabel = il.DefineLabel();
var createProxyLabel = il.DefineLabel();
// If the 'source' value is null, then just return it.
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Brfalse_S, endLabel);
// If the 'source' value isn't a proxy then we need to create one.
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Isinst, typeof(ProxyBase));
il.Emit(OpCodes.Brfalse_S, createProxyLabel);
// If the 'source' value is-a proxy then get the wrapped value.
il.Emit(OpCodes.Isinst, typeof(ProxyBase));
il.EmitCall(OpCodes.Callvirt, typeof(ProxyBase).GetMethod("get_UnderlyingInstanceAsObject"), null);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Isinst, targetType);
il.Emit(OpCodes.Brtrue_S, endLabel);
il.MarkLabel(createProxyLabel);
// Create the proxy.
il.Emit(OpCodes.Newobj, constructorBuilder);
il.MarkLabel(endLabel);
}
private class ProxyBuilderContext
{
public ProxyBuilderContext(ConverterCache cache, Type targetType, Type sourceType)
{
Cache = cache;
Key = new Tuple<Type, Type>(sourceType, targetType);
Visited = new Dictionary<Tuple<Type, Type>, VerificationResult>();
}
public ConverterCache Cache { get; }
public Tuple<Type, Type> Key { get; }
public Type SourceType => Key.Item1;
public Type TargetType => Key.Item2;
public Dictionary<Tuple<Type, Type>, VerificationResult> Visited { get; }
}
private class VerificationResult
{
public ConstructorBuilder ConstructorBuilder { get; set; }
public IEnumerable<KeyValuePair<PropertyInfo, PropertyInfo>> Mappings { get; set; }
public TypeBuilder TypeBuilder { get; set; }
}
}
}
#endif

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45 || DNX451 || DNXCORE50
using System;
using System.Collections.Concurrent;
namespace Microsoft.Framework.Notification.Internal
{
public class ConverterCache : ConcurrentDictionary<Tuple<Type, Type>, CacheResult>
{
}
}
#endif

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45 || DNX451 || DNXCORE50
using System;
namespace Microsoft.Framework.Notification.Internal
{
public abstract class ProxyBase
{
public readonly Type WrappedType;
protected ProxyBase(Type wrappedType)
{
WrappedType = wrappedType;
}
// Used by reflection, don't rename.
public abstract object UnderlyingInstanceAsObject
{
get;
}
}
}
#endif

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45 || DNX451 || DNXCORE50
using Microsoft.Framework.Internal;
namespace Microsoft.Framework.Notification.Internal
{
public class ProxyBase<T> : ProxyBase where T : class
{
// Used by reflection, don't rename.
public readonly T Instance;
public ProxyBase([NotNull] T instance)
: base(typeof(T))
{
Instance = instance;
}
public T UnderlyingInstance
{
get
{
return Instance;
}
}
public override object UnderlyingInstanceAsObject
{
get
{
return Instance;
}
}
}
}
#endif

Просмотреть файл

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>4c660d0b-32c5-43d0-899d-73ff60352172</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AssemblyName>Microsoft.Framework.Notification</AssemblyName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<RootNamespace>Microsoft.Framework.Notification</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Framework.Notification
{
public class NotificationNameAttribute : Attribute
{
public NotificationNameAttribute(string name)
{
Name = name;
}
public string Name { get; }
}
}

Просмотреть файл

@ -0,0 +1,109 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.Framework.Notification
{
public class Notifier : INotifier
{
private readonly ConcurrentDictionary<string, List<Entry>> _notificationNames = new ConcurrentDictionary<string, List<Entry>>(StringComparer.Ordinal);
private readonly INotifyParameterAdapter _parameterAdapter;
public Notifier(INotifyParameterAdapter parameterAdapter)
{
_parameterAdapter = parameterAdapter;
}
public void EnlistTarget(object target)
{
var typeInfo = target.GetType().GetTypeInfo();
var methodInfos = typeInfo.DeclaredMethods;
foreach (var methodInfo in methodInfos)
{
var notificationNameAttribute = methodInfo.GetCustomAttribute<NotificationNameAttribute>();
if (notificationNameAttribute != null)
{
Enlist(notificationNameAttribute.Name, target, methodInfo);
}
}
}
private void Enlist(string notificationName, object target, MethodInfo methodInfo)
{
var entries = _notificationNames.GetOrAdd(
notificationName,
_ => new List<Entry>());
entries.Add(new Entry(target, methodInfo));
}
public bool ShouldNotify(string notificationName)
{
return _notificationNames.ContainsKey(notificationName);
}
public void Notify(string notificationName, object parameters)
{
List<Entry> entries;
if (_notificationNames.TryGetValue(notificationName, out entries))
{
foreach (var entry in entries)
{
entry.Send(parameters, _parameterAdapter);
}
}
}
internal class Entry
{
private MethodInfo _methodInfo;
private object _target;
public Entry(object target, MethodInfo methodInfo)
{
_target = target;
_methodInfo = methodInfo;
}
internal void Send(object parameters, INotifyParameterAdapter parameterAdapter)
{
var methodParameterInfos = _methodInfo.GetParameters();
var methodParameterCount = methodParameterInfos.Length;
var methodParameterValues = new object[methodParameterCount];
var objectTypeInfo = parameters.GetType().GetTypeInfo();
for (var index = 0; index != methodParameterCount; ++index)
{
var objectPropertyInfo = objectTypeInfo.GetDeclaredProperty(methodParameterInfos[index].Name);
if (objectPropertyInfo == null)
{
continue;
}
var objectPropertyValue = objectPropertyInfo.GetValue(parameters);
if (objectPropertyValue == null)
{
continue;
}
var methodParameterInfo = methodParameterInfos[index];
if (methodParameterInfo.ParameterType.GetTypeInfo().IsAssignableFrom(objectPropertyInfo.PropertyType.GetTypeInfo()))
{
methodParameterValues[index] = objectPropertyValue;
}
else
{
methodParameterValues[index] = parameterAdapter.Adapt(objectPropertyValue, methodParameterInfo.ParameterType);
}
}
_methodInfo.Invoke(_target, methodParameterValues);
}
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Framework.Notification
{
public class NotifierParameterAdapter : INotifyParameterAdapter
{
#if NET45 || DNX451 || DNXCORE50
private readonly Internal.ConverterCache _cache = new Internal.ConverterCache();
#endif
public object Adapt(object inputParameter, Type outputType)
{
if (inputParameter == null)
{
return null;
}
#if NET45 || DNX451 || DNXCORE50
return Internal.Converter.Convert(_cache, outputType, inputParameter.GetType(), inputParameter);
#else
return inputParameter;
#endif
}
}
}

94
src/Microsoft.Framework.Notification/Properties/Resources.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,94 @@
// <auto-generated />
namespace Microsoft.Framework.Notification
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Framework.Notification.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The property '{0}' on type '{1}' must define a getter to support proxy generation.
/// </summary>
internal static string Converter_PropertyMustHaveGetter
{
get { return GetString("Converter_PropertyMustHaveGetter"); }
}
/// <summary>
/// The property '{0}' on type '{1}' must define a getter to support proxy generation.
/// </summary>
internal static string FormatConverter_PropertyMustHaveGetter(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Converter_PropertyMustHaveGetter"), p0, p1);
}
/// <summary>
/// The property '{0}' on type '{1}' must not use index parameters to support proxy generation.
/// </summary>
internal static string Converter_PropertyMustNotHaveIndexParameters
{
get { return GetString("Converter_PropertyMustNotHaveIndexParameters"); }
}
/// <summary>
/// The property '{0}' on type '{1}' must not use index parameters to support proxy generation.
/// </summary>
internal static string FormatConverter_PropertyMustNotHaveIndexParameters(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Converter_PropertyMustNotHaveIndexParameters"), p0, p1);
}
/// <summary>
/// The property '{0}' on type '{1}' must not define a setter to support proxy generation.
/// </summary>
internal static string Converter_PropertyMustNotHaveSetter
{
get { return GetString("Converter_PropertyMustNotHaveSetter"); }
}
/// <summary>
/// The property '{0}' on type '{1}' must not define a setter to support proxy generation.
/// </summary>
internal static string FormatConverter_PropertyMustNotHaveSetter(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Converter_PropertyMustNotHaveSetter"), p0, p1);
}
/// <summary>
/// Type '{0}' must be an interface in order to support proxy generation from source type '{1}'.
/// </summary>
internal static string Converter_TypeMustBeInterface
{
get { return GetString("Converter_TypeMustBeInterface"); }
}
/// <summary>
/// Type '{0}' must be an interface in order to support proxy generation from source type '{1}'.
/// </summary>
internal static string FormatConverter_TypeMustBeInterface(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Converter_TypeMustBeInterface"), p0, p1);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

Просмотреть файл

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Converter_PropertyMustHaveGetter" xml:space="preserve">
<value>The property '{0}' on type '{1}' must define a getter to support proxy generation. </value>
</data>
<data name="Converter_PropertyMustNotHaveIndexParameters" xml:space="preserve">
<value>The property '{0}' on type '{1}' must not use index parameters to support proxy generation.</value>
</data>
<data name="Converter_PropertyMustNotHaveSetter" xml:space="preserve">
<value>The property '{0}' on type '{1}' must not define a setter to support proxy generation.</value>
</data>
<data name="Converter_TypeMustBeInterface" xml:space="preserve">
<value>Type '{0}' must be an interface in order to support proxy generation from source type '{1}'.</value>
</data>
</root>

Просмотреть файл

@ -0,0 +1,57 @@
{
"version": "1.0.0-*",
"description": "Logging infrastructure.",
"dependencies": {
"Microsoft.Framework.DependencyInjection.Interfaces": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type" : "build" },
"System.Runtime": "4.0.20-beta-*"
},
"compilationOptions": {
"define": [
"TRACE"
]
},
"frameworks": {
"net45": {
"frameworkAssemblies": {
"System.Linq": "",
"System.Collections.Concurrent": ""
}
},
"dnx451": {
"frameworkAssemblies": {
"System.Linq": "",
"System.Collections.Concurrent": ""
}
},
"dnxcore50": {
"dependencies": {
"System.Collections.Concurrent": "4.0.10-beta-*",
"System.Collections": "4.0.10-beta-*",
"System.Diagnostics.Debug": "4.0.10-beta-*",
"System.Globalization": "4.0.10-beta-*",
"System.Linq": "4.0.0-beta-*",
"System.Threading": "4.0.10-beta-*",
"System.Reflection.Emit": "4.0.0-beta-*",
"System.Reflection.Extensions": "4.0.0-beta-*",
"System.Reflection.TypeExtensions": "4.0.0-beta-*",
"System.Resources.ResourceManager": "4.0.0-beta-*",
"System.Runtime.Extensions": "4.0.10-beta-*"
}
},
".NETPortable,Version=v4.6,Profile=Profile151": {
"frameworkAssemblies": {
"System.Collections": "",
"System.Collections.Concurrent": "",
"System.Diagnostics.Debug": "",
"System.Globalization": "",
"System.Reflection": "",
"System.Reflection.Extensions": "",
"System.Resources.ResourceManager": "",
"System.Runtime.Extensions": "",
"System.Linq": "",
"System.Threading": ""
}
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>51e95e1c-fe88-4daf-8e03-3fc3153a03af</ProjectGuid>
<RootNamespace>Microsoft.Framework.Notification.Test</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AssemblyName>Microsoft.Framework.Notification.Test</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

Просмотреть файл

@ -0,0 +1,291 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.Framework.Notification
{
public class NotifierParameterAdapterTest
{
[Fact]
public void Adapt_Null()
{
// Arrange
var value = (object)null;
var adapter = new NotifierParameterAdapter();
// Act
var result = adapter.Adapt(value, typeof(string));
// Assert
Assert.Null(result);
}
public static TheoryData<object, Type> Identity_ReferenceTypes_Data
{
get
{
return new TheoryData<object, Type>()
{
{ "Hello, world!", typeof(string) },
{ new Person(), typeof(Person) },
};
}
}
[Theory]
[MemberData(nameof(Identity_ReferenceTypes_Data))]
public void Adapt_Identity_ReferenceTypes(object value, Type outputType)
{
// Arrange
var adapter = new NotifierParameterAdapter();
// Act
var result = adapter.Adapt(value, outputType);
// Assert
Assert.IsType(outputType, result);
Assert.Same(value, result);
}
public static TheoryData<object, Type> Identity_ValueTypes_Data
{
get
{
return new TheoryData<object, Type>()
{
{ 19, typeof(int) },
{ new SomeValueType(17), typeof(SomeValueType) },
};
}
}
[Theory]
[MemberData(nameof(Identity_ValueTypes_Data))]
public void Adapt_Identity_ValueTypes(object value, Type outputType)
{
// Arrange
var adapter = new NotifierParameterAdapter();
// Act
var result = adapter.Adapt(value, outputType);
// Assert
Assert.IsType(outputType, result);
Assert.Same(value, result); // This works because of boxing
}
public static TheoryData<object, Type> Assignable_Data
{
get
{
return new TheoryData<object, Type>()
{
{ 5, typeof(IConvertible) }, // Interface assignment
{ new DerivedPerson(), typeof(Person) }, // Base-class assignment
{ 5.8m, typeof(decimal?) }, // value-type to nullable assignment
};
}
}
[Theory]
[MemberData(nameof(Assignable_Data))]
public void Adapt_Assignable(object value, Type outputType)
{
// Arrange
var adapter = new NotifierParameterAdapter();
// Act
var result = adapter.Adapt(value, outputType);
// Assert
Assert.IsType(value.GetType(), result);
Assert.IsAssignableFrom(outputType, result);
Assert.Same(value, result);
}
[Fact]
public void Adapt_Proxy_DestinationIsNotInterface()
{
// Arrange
var value = new Person();
var outputType = typeof(string);
var expectedMessage = string.Format(
"Type '{0}' must be an interface in order to support proxy generation from source type '{1}'.",
outputType.FullName,
value.GetType().FullName);
var adapter = new NotifierParameterAdapter();
// Act
var exception = Assert.Throws<InvalidOperationException>(() => adapter.Adapt(value, outputType));
// Assert
Assert.Equal(expectedMessage, exception.Message);
}
[Fact]
public void Adapt_Proxy_InvalidProperty_DestinationIsNotInterface()
{
// Arrange
var value = new Person();
var outputType = typeof(IBadPerson);
var expectedMessage = string.Format(
"Type '{0}' must be an interface in order to support proxy generation from source type '{1}'.",
typeof(string),
typeof(Address).FullName);
var adapter = new NotifierParameterAdapter();
// Act
var exception = Assert.Throws<InvalidOperationException>(() => adapter.Adapt(value, outputType));
// Assert
Assert.Equal(expectedMessage, exception.Message);
}
[Fact]
public void Adapt_Proxy()
{
// Arrange
var value = new Person()
{
Address = new Address()
{
City = "Redmond",
State = "WA",
Zip = 98002,
},
FirstName = "Bill",
LastName = "Gates",
};
var outputType = typeof(IPerson);
var adapter = new NotifierParameterAdapter();
// Act
var result = adapter.Adapt(value, outputType);
// Assert
var person = Assert.IsAssignableFrom<IPerson>(result);
Assert.Same(value.Address.City, person.Address.City);
Assert.Same(value.Address.State, person.Address.State);
Assert.Equal(value.Address.Zip, person.Address.Zip);
// IPerson doesn't define the FirstName property.
Assert.Same(value.LastName, person.LastName);
}
[Fact]
public void Adapt_Proxy_WithTypeCycle()
{
// Arrange
var value = new C1()
{
C2 = new C2()
{
C1 = new C1()
{
C2 = new C2(),
Tag = "C1.C2.C1",
},
Tag = "C1.C2",
},
Tag = "C1",
};
var outputType = typeof(IC1);
var adapter = new NotifierParameterAdapter();
// Act
var result = adapter.Adapt(value, outputType);
// Assert
var c1 = Assert.IsAssignableFrom<IC1>(result);
Assert.Equal(value.C2.Tag, c1.C2.Tag);
Assert.Equal(value.C2.C1.Tag, c1.C2.C1.Tag);
Assert.Equal(value.C2.C1.C2.Tag, c1.C2.C1.C2.Tag);
Assert.Null(value.C2.C1.C2.C1);
}
public interface IC1
{
IC2 C2 { get; }
string Tag { get; }
}
public interface IC2
{
IC1 C1 { get; }
string Tag { get; }
}
public class C1
{
public C2 C2 { get; set; }
public string Tag { get; set; }
}
public class C2
{
public C1 C1 { get; set; }
public string Tag { get; set; }
}
public interface IPerson
{
string FirstName { get; }
string LastName { get; }
IAddress Address { get; }
}
public interface IAddress
{
string City { get; }
string State { get; }
int Zip { get; }
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
public interface IBadPerson
{
string FirstName { get; }
string LastName { get; }
string Address { get; } // doesn't match with Person
}
public class DerivedPerson : Person
{
public double CoolnessFactor { get; set; }
}
public class Address
{
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
}
public class SomeValueType
{
public SomeValueType(int value)
{
Value = value;
}
public int Value { get; private set; }
}
}
}

Просмотреть файл

@ -0,0 +1,206 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.Framework.Notification
{
public class NotifierTest
{
INotifier NewNotifier()
{
return new Notifier(new NotifierParameterAdapter());
}
public class OneTarget
{
public int OneCallCount { get; private set; }
[NotificationName("One")]
public void One()
{
++OneCallCount;
}
}
[Fact]
public void ShouldNotifyBecomesTrueAfterEnlisting()
{
var notifier = NewNotifier();
Assert.False(notifier.ShouldNotify("One"));
Assert.False(notifier.ShouldNotify("Two"));
notifier.EnlistTarget(new OneTarget());
Assert.True(notifier.ShouldNotify("One"));
Assert.False(notifier.ShouldNotify("Two"));
}
[Fact]
public void CallingNotifyWillInvokeMethod()
{
var notifier = NewNotifier();
var target = new OneTarget();
notifier.EnlistTarget(target);
Assert.Equal(0, target.OneCallCount);
notifier.Notify("One", new { });
Assert.Equal(1, target.OneCallCount);
}
[Fact]
public void CallingNotifyForNonEnlistedNameIsHarmless()
{
var notifier = new Notifier(new NotifierParameterAdapter());
var target = new OneTarget();
notifier.EnlistTarget(target);
Assert.Equal(0, target.OneCallCount);
notifier.Notify("Two", new { });
Assert.Equal(0, target.OneCallCount);
}
private class TwoTarget
{
public string Alpha { get; private set; }
public string Beta { get; private set; }
public int Delta { get; private set; }
[NotificationName("Two")]
public void Two(string alpha, string beta, int delta)
{
Alpha = alpha;
Beta = beta;
Delta = delta;
}
}
[Fact]
public void ParametersWillSplatFromObjectByName()
{
var notifier = NewNotifier();
var target = new TwoTarget();
notifier.EnlistTarget(target);
notifier.Notify("Two", new { alpha = "ALPHA", beta = "BETA", delta = -1 });
Assert.Equal("ALPHA", target.Alpha);
Assert.Equal("BETA", target.Beta);
Assert.Equal(-1, target.Delta);
}
[Fact]
public void ExtraParametersAreHarmless()
{
var notifier = NewNotifier();
var target = new TwoTarget();
notifier.EnlistTarget(target);
notifier.Notify("Two", new { alpha = "ALPHA", beta = "BETA", delta = -1, extra = this });
Assert.Equal("ALPHA", target.Alpha);
Assert.Equal("BETA", target.Beta);
Assert.Equal(-1, target.Delta);
}
[Fact]
public void MissingParametersArriveAsNull()
{
var notifier = NewNotifier();
var target = new TwoTarget();
notifier.EnlistTarget(target);
notifier.Notify("Two", new { alpha = "ALPHA", delta = -1 });
Assert.Equal("ALPHA", target.Alpha);
Assert.Null(target.Beta);
Assert.Equal(-1, target.Delta);
}
[Fact]
public void NotificationCanDuckType()
{
var notifier = NewNotifier();
var target = new ThreeTarget();
notifier.EnlistTarget(target);
notifier.Notify("Three", new
{
person = new Person
{
FirstName = "Alpha",
Address = new Address
{
City = "Beta",
State = "Gamma",
Zip = 98028
}
}
});
Assert.Equal("Alpha", target.Person.FirstName);
Assert.Equal("Beta", target.Person.Address.City);
Assert.Equal("Gamma", target.Person.Address.State);
Assert.Equal(98028, target.Person.Address.Zip);
}
public class ThreeTarget
{
public IPerson Person { get; private set; }
[NotificationName("Three")]
public void Three(IPerson person)
{
Person = person;
}
}
public interface IPerson
{
string FirstName { get; }
string LastName { get; }
IAddress Address { get; }
}
public interface IAddress
{
string City { get; }
string State { get; }
int Zip { get; }
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
public class DerivedPerson : Person
{
public double CoolnessFactor { get; set; }
}
public class Address
{
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
}
public class SomeValueType
{
public SomeValueType(int value)
{
Value = value;
}
public int Value { get; private set; }
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
{
"dependencies": {
"Microsoft.Framework.Notification": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"commands": {
"run": "xunit.runner.aspnet",
"test": "xunit.runner.aspnet"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
}
}