Skip to content
This repository has been archived by the owner on Oct 17, 2018. It is now read-only.

Commit

Permalink
Ignore assembly version when activating DataProtection types from str…
Browse files Browse the repository at this point in the history
…ing name (#223)
  • Loading branch information
natemcmaster authored Apr 25, 2017
1 parent 5fe4807 commit 4dad47e
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<Import Project="..\..\build\common.props" />

<PropertyGroup>
<Description>Redis storrage support as key store.</Description>
<VersionPrefix>0.1.0</VersionPrefix>
<Description>Redis storage support as key store.</Description>
<VersionPrefix>0.3.0</VersionPrefix>
<TargetFrameworks>net46;netstandard1.5</TargetFrameworks>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static IDataProtectionBuilder AddDataProtection(this IServiceCollection s
throw new ArgumentNullException(nameof(services));
}

services.AddSingleton<IActivator, RC1ForwardingActivator>();
services.TryAddSingleton<IActivator, TypeForwardingActivator>();

This comment has been minimized.

Copy link
@davidfowl

davidfowl Jul 26, 2017

Member

Common to what?

services.AddOptions();
AddDataProtectionServices(services);

Expand Down
42 changes: 0 additions & 42 deletions src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs

This file was deleted.

73 changes: 73 additions & 0 deletions src/Microsoft.AspNetCore.DataProtection/TypeForwardingActivator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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.Text.RegularExpressions;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.DataProtection
{
internal class TypeForwardingActivator : SimpleActivator
{
private const string OldNamespace = "Microsoft.AspNet.DataProtection";
private const string CurrentNamespace = "Microsoft.AspNetCore.DataProtection";
private readonly ILogger _logger;
private static readonly Regex _versionPattern = new Regex(@",\s?Version=(\d+\.?)(\d+\.?)?(\d+\.?)?(\d+\.?)?", RegexOptions.Compiled, TimeSpan.FromSeconds(2));

public TypeForwardingActivator(IServiceProvider services)
: this(services, DataProtectionProviderFactory.GetDefaultLoggerFactory())
{
}

public TypeForwardingActivator(IServiceProvider services, ILoggerFactory loggerFactory)
: base(services)
{
_logger = loggerFactory.CreateLogger(typeof(TypeForwardingActivator));
}

public override object CreateInstance(Type expectedBaseType, string originalTypeName)
=> CreateInstance(expectedBaseType, originalTypeName, out var _);

// for testing
internal object CreateInstance(Type expectedBaseType, string originalTypeName, out bool forwarded)
{
var forwardedTypeName = originalTypeName;
var candidate = false;
if (originalTypeName.Contains(OldNamespace))
{
candidate = true;
forwardedTypeName = originalTypeName.Replace(OldNamespace, CurrentNamespace);
}

#if NET46
if (candidate || forwardedTypeName.Contains(CurrentNamespace))
{
candidate = true;
forwardedTypeName = RemoveVersionFromAssemblyName(forwardedTypeName);
}
#elif NETSTANDARD1_3
#else
#error Target framework needs to be updated
#endif

if (candidate)
{
var type = Type.GetType(forwardedTypeName, false);
if (type != null)
{
_logger.LogDebug("Forwarded activator type request from {FromType} to {ToType}",
originalTypeName,
forwardedTypeName);
forwarded = true;
return base.CreateInstance(expectedBaseType, forwardedTypeName);
}
}

forwarded = false;
return base.CreateInstance(expectedBaseType, originalTypeName);
}

protected string RemoveVersionFromAssemblyName(string forwardedTypeName)
=> _versionPattern.Replace(forwardedTypeName, "");
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// 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.Generic;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Microsoft.AspNetCore.DataProtection
{
public class TypeForwardingActivatorTests : MarshalByRefObject
{
[Fact]
public void CreateInstance_ForwardsToNewNamespaceIfExists()
{
// Arrange
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection();
var services = serviceCollection.BuildServiceProvider();
var activator = services.GetActivator();

// Act
var name = "Microsoft.AspNet.DataProtection.TypeForwardingActivatorTests+ClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test, Version=1.0.0.0";
var instance = activator.CreateInstance<object>(name);

// Assert
Assert.IsType<ClassWithParameterlessCtor>(instance);
}

[Fact]
public void CreateInstance_DoesNotForwardIfClassDoesNotExist()
{
// Arrange
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection();
var services = serviceCollection.BuildServiceProvider();
var activator = services.GetActivator();

// Act & Assert
var name = "Microsoft.AspNet.DataProtection.TypeForwardingActivatorTests+NonExistentClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test";
var exception = Assert.ThrowsAny<Exception>(() => activator.CreateInstance<object>(name));

Assert.Contains("Microsoft.AspNet.DataProtection.Test", exception.Message);
}

[Theory]
[InlineData(typeof(GenericType<GenericType<ClassWithParameterlessCtor>>))]
[InlineData(typeof(GenericType<ClassWithParameterlessCtor>))]
[InlineData(typeof(GenericType<GenericType<string>>))]
[InlineData(typeof(GenericType<GenericType<string, string>>))]
[InlineData(typeof(GenericType<string>))]
[InlineData(typeof(GenericType<int>))]
[InlineData(typeof(List<ClassWithParameterlessCtor>))]
public void CreateInstance_Generics(Type type)
{
// Arrange
var activator = new TypeForwardingActivator(null);
var name = type.AssemblyQualifiedName;

// Act & Assert
Assert.IsType(type, activator.CreateInstance<object>(name));
}

[Theory]
[InlineData(typeof(GenericType<>))]
[InlineData(typeof(GenericType<,>))]
public void CreateInstance_ThrowsForOpenGenerics(Type type)
{
// Arrange
var activator = new TypeForwardingActivator(null);
var name = type.AssemblyQualifiedName;

// Act & Assert
Assert.Throws<ArgumentException>(() => activator.CreateInstance<object>(name));
}

[Theory]
[InlineData(
"System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Culture=neutral]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[InlineData(
"Some.Type`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral",
"Some.Type`1[[System.Int32, mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.AspNetCore.DataProtection, Culture=neutral")]
[InlineData(
"System.Tuple`1[[System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Tuple`1[[System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Culture=neutral]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public void ParsesFullyQualifiedTypeName(string typeName, string expected)
{
Assert.Equal(expected, new MockTypeForwardingActivator().Parse(typeName));
}

[Theory]
[InlineData(typeof(List<string>))]
[InlineData(typeof(FactAttribute))]
public void CreateInstance_DoesNotForwardingTypesExternalTypes(Type type)
{
new TypeForwardingActivator(null).CreateInstance(typeof(object), type.AssemblyQualifiedName, out var forwarded);
Assert.False(forwarded, "Should not have forwarded types that are not in Microsoft.AspNetCore.DataProjection");
}

[Theory]
[MemberData(nameof(AssemblyVersions))]
public void CreateInstance_ForwardsAcrossVersionChanges(Version version)
{
#if NET46
// run this test in an appdomain without testhost's custom assembly resolution hooks
var setupInfo = new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
};
var domain = AppDomain.CreateDomain("TestDomain", null, setupInfo);
var wrappedTestClass = (TypeForwardingActivatorTests)domain.CreateInstanceAndUnwrap(GetType().Assembly.FullName, typeof(TypeForwardingActivatorTests).FullName);
wrappedTestClass.CreateInstance_ForwardsAcrossVersionChangesImpl(version);
#elif NETCOREAPP2_0
CreateInstance_ForwardsAcrossVersionChangesImpl(version);
#else
#error Target framework should be updated
#endif
}

private void CreateInstance_ForwardsAcrossVersionChangesImpl(Version newVersion)
{
var activator = new TypeForwardingActivator(null);

var typeInfo = typeof(ClassWithParameterlessCtor).GetTypeInfo();
var typeName = typeInfo.FullName;
var assemblyName = typeInfo.Assembly.GetName();

assemblyName.Version = newVersion;
var newName = $"{typeName}, {assemblyName}";

Assert.NotEqual(typeInfo.AssemblyQualifiedName, newName);
Assert.IsType<ClassWithParameterlessCtor>(activator.CreateInstance(typeof(object), newName, out var forwarded));
#if NET46
Assert.True(forwarded, "Should have forwarded this type to new version or namespace");
#elif NETCOREAPP2_0
Assert.False(forwarded, "Should not have forwarded this type to new version or namespace");
#else
#error Target framework should be updated
#endif
}

public static TheoryData<Version> AssemblyVersions
{
get
{
var current = typeof(ActivatorTests).Assembly.GetName().Version;
return new TheoryData<Version>
{
new Version(Math.Max(0, current.Major - 1), 0, 0, 0),
new Version(current.Major + 1, 0, 0, 0),
new Version(current.Major, current.Minor + 1, 0, 0),
new Version(current.Major, current.Minor, current.Revision + 1, 0),
};
}
}

private class MockTypeForwardingActivator : TypeForwardingActivator
{
public MockTypeForwardingActivator() : base(null) { }
public string Parse(string typeName) => RemoveVersionFromAssemblyName(typeName);
}

private class ClassWithParameterlessCtor
{
}

private class GenericType<T>
{
}

private class GenericType<T1, T2>
{
}
}
}

0 comments on commit 4dad47e

Please sign in to comment.