Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Adding support for activating view properties
Browse files Browse the repository at this point in the history
Fixes #700
  • Loading branch information
pranavkm committed Jul 14, 2014
1 parent 7e7c56c commit 21bb8cb
Show file tree
Hide file tree
Showing 26 changed files with 591 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="Encodings.cs" />
<Compile Include="NotNullArgument.cs" />
<Compile Include="PlatformHelper.cs" />
<Compile Include="PropertyActivator.cs" />
<Compile Include="PropertyHelper.cs" />
<Compile Include="TypeExtensions.cs" />
</ItemGroup>
Expand Down
51 changes: 51 additions & 0 deletions src/Microsoft.AspNet.Mvc.Common/PropertyActivator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
using System.Reflection;

namespace Microsoft.AspNet.Mvc
{
internal class PropertyActivator<TContext>
{
private readonly Func<TContext, object> _valueAccessor;
private readonly Action<object, object> _fastPropertySetter;

public PropertyActivator(PropertyInfo propertyInfo,
Func<TContext, object> valueAccessor)
{
PropertyInfo = propertyInfo;
_valueAccessor = valueAccessor;
_fastPropertySetter = PropertyHelper.MakeFastPropertySetter(propertyInfo);
}

public PropertyInfo PropertyInfo { get; private set; }

public object Activate(object view, TContext context)
{
var value = _valueAccessor(context);
_fastPropertySetter(view, value);
return value;
}

/// <summary>
/// Returns a list of properties on a type that are decorated with
/// the specified activateAttributeType and have setters.
/// </summary>
public static PropertyActivator<TContext>[] GetPropertiesToActivate(
Type type,
Type activateAttributeType,
Func<PropertyInfo, PropertyActivator<TContext>> createActivateInfo)
{
return type.GetRuntimeProperties()
.Where(property =>
property.IsDefined(activateAttributeType) &&
property.GetIndexParameters().Length == 0 &&
property.SetMethod != null &&
!property.SetMethod.IsStatic)
.Select(createActivateInfo)
.ToArray();
}
}
}
58 changes: 13 additions & 45 deletions src/Microsoft.AspNet.Mvc.Core/DefaultControllerActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Core;
Expand All @@ -19,20 +17,21 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public class DefaultControllerActivator : IControllerActivator
{
private readonly Func<Type, PropertyActivator[]> _getPropertiesToActivate;
private readonly Func<PropertyInfo, PropertyActivator> _createActivateInfo;
private readonly ReadOnlyDictionary<Type, Func<ActionContext, object>> _valueAccessorLookup;
private readonly ConcurrentDictionary<Type, PropertyActivator[]> _injectActions;
private readonly Func<Type, PropertyActivator<ActionContext>[]> _getPropertiesToActivate;
private readonly IReadOnlyDictionary<Type, Func<ActionContext, object>> _valueAccessorLookup;
private readonly ConcurrentDictionary<Type, PropertyActivator<ActionContext>[]> _injectActions;

/// <summary>
/// Initializes a new instance of the DefaultControllerActivator class.
/// </summary>
public DefaultControllerActivator()
{
_valueAccessorLookup = CreateValueAccessorLookup();
_getPropertiesToActivate = GetPropertiesToActivate;
_createActivateInfo = CreateActivateInfo;
_injectActions = new ConcurrentDictionary<Type, PropertyActivator[]>();
_injectActions = new ConcurrentDictionary<Type, PropertyActivator<ActionContext>[]>();
_getPropertiesToActivate = type =>
PropertyActivator<ActionContext>.GetPropertiesToActivate(type,
typeof(ActivateAttribute),
CreateActivateInfo);
}

/// <summary>
Expand All @@ -59,7 +58,7 @@ public void Activate([NotNull] object controller, [NotNull] ActionContext contex
}
}

protected virtual ReadOnlyDictionary<Type, Func<ActionContext, object>> CreateValueAccessorLookup()
protected virtual IReadOnlyDictionary<Type, Func<ActionContext, object>> CreateValueAccessorLookup()
{
var dictionary = new Dictionary<Type, Func<ActionContext, object>>
{
Expand All @@ -78,20 +77,11 @@ protected virtual ReadOnlyDictionary<Type, Func<ActionContext, object>> CreateVa
}
}
};
return new ReadOnlyDictionary<Type, Func<ActionContext, object>>(dictionary);
return dictionary;
}

private PropertyActivator[] GetPropertiesToActivate(Type controllerType)
{
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
return controllerType.GetProperties(bindingFlags)
.Where(property => property.IsDefined(typeof(ActivateAttribute)) &&
property.GetSetMethod(nonPublic: true) != null)
.Select(_createActivateInfo)
.ToArray();
}

private PropertyActivator CreateActivateInfo(PropertyInfo property)
private PropertyActivator<ActionContext> CreateActivateInfo(
PropertyInfo property)
{
Func<ActionContext, object> valueAccessor;
if (!_valueAccessorLookup.TryGetValue(property.PropertyType, out valueAccessor))
Expand All @@ -103,29 +93,7 @@ private PropertyActivator CreateActivateInfo(PropertyInfo property)
};
}

return new PropertyActivator(property,
valueAccessor);
}

private sealed class PropertyActivator
{
private readonly PropertyInfo _propertyInfo;
private readonly Func<ActionContext, object> _valueAccessor;
private readonly Action<object, object> _fastPropertySetter;

public PropertyActivator(PropertyInfo propertyInfo,
Func<ActionContext, object> valueAccessor)
{
_propertyInfo = propertyInfo;
_valueAccessor = valueAccessor;
_fastPropertySetter = PropertyHelper.MakeFastPropertySetter(propertyInfo);
}

public void Activate(object instance, ActionContext context)
{
var value = _valueAccessor(context);
_fastPropertySetter(instance, value);
}
return new PropertyActivator<ActionContext>(property, valueAccessor);
}
}
}
14 changes: 12 additions & 2 deletions src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ namespace Microsoft.AspNet.Mvc.Razor
public class InjectChunkVisitor : MvcCSharpCodeVisitor
{
private readonly List<InjectChunk> _injectChunks = new List<InjectChunk>();
private readonly string _activateAttribute;

public InjectChunkVisitor([NotNull] CSharpCodeWriter writer,
[NotNull] CodeGeneratorContext context)
[NotNull] CodeGeneratorContext context,
[NotNull] string activateAttributeName)
: base(writer, context)
{
_activateAttribute = '[' + activateAttributeName + ']';
}

public List<InjectChunk> InjectChunks
Expand All @@ -25,7 +28,14 @@ public List<InjectChunk> InjectChunks

protected override void Visit([NotNull] InjectChunk chunk)
{
if (Context.Host.DesignTimeMode)
Writer.WriteLine(_activateAttribute);

// Some of the chunks that we visit are either InjectDescriptors that are added by default or
// are chunks from _ViewStart files and are not associated with any Spans. Invoking
// CreateExpressionMapping to produce line mappings on these chunks would fail. We'll skip
// generating code mappings for these chunks. This makes sense since the chunks do not map
// to any code in the current view.
if (Context.Host.DesignTimeMode && chunk.Association != null)
{
Writer.WriteLine("public");
var code = string.Format(CultureInfo.InvariantCulture,
Expand Down
40 changes: 40 additions & 0 deletions src/Microsoft.AspNet.Mvc.Razor.Host/InjectDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNet.Mvc.Razor.Host;

namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Represents information about an injected property.
/// </summary>
public class InjectDescriptor
{
public InjectDescriptor(string typeName, string memberName)
{
if (string.IsNullOrEmpty(typeName))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpy, "typeName");
}

if (string.IsNullOrEmpty(memberName))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpy, "memberName");
}

TypeName = typeName;
MemberName = memberName;
}

/// <summary>
/// Gets the type name of the injected property
/// </summary>
public string TypeName { get; private set; }

/// <summary>
/// Gets the name of the injected property.
/// </summary>
public string MemberName { get; private set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<Compile Include="IMvcRazorHost.cs" />
<Compile Include="InjectChunk.cs" />
<Compile Include="InjectChunkVisitor.cs" />
<Compile Include="InjectDescriptor.cs" />
<Compile Include="InjectParameterGenerator.cs" />
<Compile Include="ModelChunk.cs" />
<Compile Include="ModelChunkVisitor.cs" />
Expand All @@ -33,6 +34,7 @@
<Compile Include="MvcCSharpCodeVistor.cs" />
<Compile Include="MvcRazorCodeParser.cs" />
<Compile Include="MvcRazorHost.cs" />
<Compile Include="MvcRazorHostOptions.cs" />
<Compile Include="Properties\Resources.Designer.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
Expand Down
31 changes: 16 additions & 15 deletions src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
using System.Linq;
Expand All @@ -12,11 +13,17 @@ namespace Microsoft.AspNet.Mvc.Razor
{
public class MvcCSharpCodeBuilder : CSharpCodeBuilder
{
public MvcCSharpCodeBuilder([NotNull] CodeGeneratorContext context)
private readonly MvcRazorHostOptions _hostOptions;

public MvcCSharpCodeBuilder([NotNull] CodeGeneratorContext context,
[NotNull] MvcRazorHostOptions hostOptions)
: base(context)
{
_hostOptions = hostOptions;
}

private string Model { get; set; }

protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer)
{
// Grab the last model chunk so it gets intellisense.
Expand All @@ -25,6 +32,8 @@ protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter
var modelChunk = Context.CodeTreeBuilder.CodeTree.Chunks.OfType<ModelChunk>()
.LastOrDefault();

Model = modelChunk != null ? modelChunk.ModelType : _hostOptions.DefaultModel;

// If there were any model chunks then we need to modify the class declaration signature.
if (modelChunk != null)
{
Expand All @@ -46,26 +55,18 @@ protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter

protected override void BuildConstructor([NotNull] CSharpCodeWriter writer)
{
// TODO: Move this to a proper extension point. Right now, we don't have a place to print out properties
// in the generated view.
// Tracked by #773
base.BuildConstructor(writer);

writer.WriteLineHiddenDirective();

var injectVisitor = new InjectChunkVisitor(writer, Context);
var injectVisitor = new InjectChunkVisitor(writer, Context, _hostOptions.ActivateAttributeName);
injectVisitor.Accept(Context.CodeTreeBuilder.CodeTree.Chunks);

writer.WriteLine();
writer.WriteLineHiddenDirective();

var arguments = injectVisitor.InjectChunks
.Select(chunk => new KeyValuePair<string, string>(chunk.TypeName,
chunk.MemberName));
using (writer.BuildConstructor("public", Context.ClassName, arguments))
{
foreach (var inject in injectVisitor.InjectChunks)
{
writer.WriteStartAssignment("this." + inject.MemberName)
.Write(inject.MemberName)
.WriteLine(";");
}
}
}
}
}
38 changes: 36 additions & 2 deletions src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// 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.IO;
using System.Linq;
using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Generator.Compiler;
Expand All @@ -24,6 +26,8 @@ public class MvcRazorHost : RazorEngineHost, IMvcRazorHost
"Microsoft.AspNet.Mvc.Rendering"
};

private readonly MvcRazorHostOptions _hostOptions;

// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
// This field holds the type name without the generic decoration (MyBaseType)
private readonly string _baseType;
Expand All @@ -36,8 +40,11 @@ public MvcRazorHost(Type baseType)
public MvcRazorHost(string baseType)
: base(new CSharpRazorCodeLanguage())
{
// TODO: this needs to flow from the application rather than being initialized here.
// Tracked by #774
_hostOptions = new MvcRazorHostOptions();
_baseType = baseType;
DefaultBaseClass = baseType + "<dynamic>";
DefaultBaseClass = baseType + '<' + _hostOptions.DefaultModel + '>';
GeneratedClassContext = new GeneratedClassContext(
executeMethodName: "ExecuteAsync",
writeMethodName: "Write",
Expand Down Expand Up @@ -73,7 +80,34 @@ public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)

public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeGeneratorContext context)
{
return new MvcCSharpCodeBuilder(context);
UpdateCodeBuilder(context);
return new MvcCSharpCodeBuilder(context, _hostOptions);
}

private void UpdateCodeBuilder(CodeGeneratorContext context)
{
var currentChunks = context.CodeTreeBuilder.CodeTree.Chunks;
var existingInjects = new HashSet<string>(currentChunks.OfType<InjectChunk>()
.Select(c => c.MemberName),
StringComparer.Ordinal);

var modelChunk = currentChunks.OfType<ModelChunk>()
.LastOrDefault();
var model = _hostOptions.DefaultModel;
if (modelChunk != null)
{
model = modelChunk.ModelType;
}
model = '<' + model + '>';

// Locate properties by name that haven't already been injected in to the View.
var propertiesToAdd = _hostOptions.DefaultInjectedProperties
.Where(c => !existingInjects.Contains(c.MemberName));
foreach (var property in propertiesToAdd)
{
var memberName = property.MemberName.Replace("<TModel>", model);
currentChunks.Add(new InjectChunk(property.TypeName, memberName));
}
}
}
}
Loading

0 comments on commit 21bb8cb

Please sign in to comment.