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:
Родитель
55dbbeade4
Коммит
4cf5452c51
|
@ -7,6 +7,7 @@ _ReSharper.*/
|
|||
packages/
|
||||
artifacts/
|
||||
PublishProfiles/
|
||||
project.lock.json
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
|
|
|
@ -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
|
|
@ -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
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": { }
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче