Adding support for activating view properties

Fixes #700
This commit is contained in:
Pranav K 2014-06-27 11:59:51 -07:00
parent 7e7c56ce48
commit 21bb8cb9fb
26 changed files with 591 additions and 113 deletions

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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;
@ -19,10 +17,9 @@ 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.
@ -30,9 +27,11 @@ namespace Microsoft.AspNet.Mvc
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>
@ -59,7 +58,7 @@ namespace Microsoft.AspNet.Mvc
}
}
protected virtual ReadOnlyDictionary<Type, Func<ActionContext, object>> CreateValueAccessorLookup()
protected virtual IReadOnlyDictionary<Type, Func<ActionContext, object>> CreateValueAccessorLookup()
{
var dictionary = new Dictionary<Type, Func<ActionContext, object>>
{
@ -78,20 +77,11 @@ namespace Microsoft.AspNet.Mvc
}
}
};
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))
@ -103,29 +93,7 @@ namespace Microsoft.AspNet.Mvc
};
}
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);
}
}
}

View File

@ -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
@ -25,7 +28,14 @@ namespace Microsoft.AspNet.Mvc.Razor
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,

View File

@ -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; }
}
}

View File

@ -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" />
@ -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)' != ''" />

View File

@ -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;
@ -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.
@ -25,6 +32,8 @@ namespace Microsoft.AspNet.Mvc.Razor
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)
{
@ -46,26 +55,18 @@ namespace Microsoft.AspNet.Mvc.Razor
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(";");
}
}
}
}
}

View File

@ -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;
@ -24,6 +26,8 @@ namespace Microsoft.AspNet.Mvc.Razor
"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;
@ -36,8 +40,11 @@ namespace Microsoft.AspNet.Mvc.Razor
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",
@ -73,7 +80,34 @@ namespace Microsoft.AspNet.Mvc.Razor
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));
}
}
}
}

View File

@ -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.Collections.Generic;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Represents configuration options for the Razor Host
/// </summary>
public class MvcRazorHostOptions
{
public MvcRazorHostOptions()
{
DefaultModel = "dynamic";
ActivateAttributeName = "Microsoft.AspNet.Mvc.ActivateAttribute";
DefaultInjectedProperties = new List<InjectDescriptor>()
{
new InjectDescriptor("Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel>", "Html")
};
}
/// <summary>
/// Gets or sets the model that is used by default for generated views
/// when no model is explicily specified. Defaults to dynamic.
/// </summary>
public string DefaultModel { get; set; }
/// <summary>
/// Gets or sets the attribue that is used to decorate properties that are injected and need to
/// be activated.
/// </summary>
public string ActivateAttributeName { get; set; }
/// <summary>
/// Gets the list of properties that are injected by default.
/// </summary>
public IList<InjectDescriptor> DefaultInjectedProperties { get; private set; }
}
}

View File

@ -10,6 +10,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Host
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Mvc.Razor.Host.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Argument cannot be null or empty.
/// </summary>
internal static string ArgumentCannotBeNullOrEmpy
{
get { return GetString("ArgumentCannotBeNullOrEmpy"); }
}
/// <summary>
/// Argument cannot be null or empty.
/// </summary>
internal static string FormatArgumentCannotBeNullOrEmpy()
{
return GetString("ArgumentCannotBeNullOrEmpy");
}
/// <summary>
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
/// </summary>

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ArgumentCannotBeNullOrEmpy" xml:space="preserve">
<value>Argument cannot be null or empty.</value>
</data>
<data name="MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword" xml:space="preserve">
<value>The 'inherits' keyword is not allowed when a '{0}' keyword is used.</value>
</data>

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// Provides methods to activate properties on a view instance.
/// </summary>
public interface IRazorViewActivator
{
/// <summary>
/// When implemented in a type, activates an instantiated view.
/// </summary>
/// <param name="view">The view to activate.</param>
/// <param name="context">The <see cref="ViewContext"/> for the view.</param>
void Activate(RazorView view, ViewContext context);
}
}

View File

@ -30,9 +30,11 @@
<Compile Include="Compilation\RoslynCompilationService.cs" />
<Compile Include="Extensions\DictionaryExtensions.cs" />
<Compile Include="HelperResult.cs" />
<Compile Include="IRazorViewActivator.cs" />
<Compile Include="PositionTagged.cs" />
<Compile Include="Properties\Resources.Designer.cs" />
<Compile Include="RazorView.cs" />
<Compile Include="RazorViewActivator.cs" />
<Compile Include="RazorViewOfT.cs" />
<Compile Include="Razor\IRazorCompilationService.cs" />
<Compile Include="Razor\RazorCompilationService.cs" />
@ -42,4 +44,4 @@
<Compile Include="ViewEngine\VirtualPathViewFactory.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

View File

@ -186,6 +186,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0);
}
/// <summary>
/// View of type '{0}' cannot be instatiated by '{1}'.
/// </summary>
internal static string ViewCannotBeActivated
{
get { return GetString("ViewCannotBeActivated"); }
}
/// <summary>
/// View of type '{0}' cannot be instatiated by '{1}'.
/// </summary>
internal static string FormatViewCannotBeActivated(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ViewCannotBeActivated"), p0, p1);
}
/// <summary>
/// View '{0}' must have extension '{1}' when the view represents a full path.
/// </summary>

View File

@ -0,0 +1,125 @@
// 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.Concurrent;
using System.Reflection;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Razor
{
/// <inheritdoc />
public class RazorViewActivator : IRazorViewActivator
{
private readonly ITypeActivator _typeActivator;
private readonly ConcurrentDictionary<Type, ViewActivationInfo> _activationInfo;
/// <summary>
/// Initializes a new instance of the RazorViewActivator class.
/// </summary>
public RazorViewActivator(ITypeActivator typeActivator)
{
_typeActivator = typeActivator;
_activationInfo = new ConcurrentDictionary<Type, ViewActivationInfo>();
}
/// <summary>
/// Activates the specified view by using the specified ViewContext.
/// </summary>
/// <param name="view">The view to activate.</param>
/// <param name="context">The ViewContext for the executing view.</param>
public void Activate([NotNull] RazorView view, [NotNull] ViewContext context)
{
var activationInfo = _activationInfo.GetOrAdd(view.GetType(),
CreateViewActivationInfo);
context.ViewData = CreateViewDataDictionary(context, activationInfo);
for (var i = 0; i < activationInfo.PropertyActivators.Length; i++)
{
var activateInfo = activationInfo.PropertyActivators[i];
activateInfo.Activate(view, context);
}
}
private ViewDataDictionary CreateViewDataDictionary(ViewContext context, ViewActivationInfo activationInfo)
{
// Create a ViewDataDictionary<TModel> if the ViewContext.ViewData is not set or the type of
// ViewContext.ViewData is an incompatibile type.
if (context.ViewData == null)
{
// Create ViewDataDictionary<TModel>(metadataProvider);
return (ViewDataDictionary)_typeActivator.CreateInstance(context.HttpContext.RequestServices,
activationInfo.ViewDataDictionaryType);
}
else if (context.ViewData.GetType() != activationInfo.ViewDataDictionaryType)
{
// Create ViewDataDictionary<TModel>(ViewDataDictionary);
return (ViewDataDictionary)_typeActivator.CreateInstance(context.HttpContext.RequestServices,
activationInfo.ViewDataDictionaryType,
context.ViewData);
}
return context.ViewData;
}
private ViewActivationInfo CreateViewActivationInfo(Type type)
{
var typeInfo = type.GetTypeInfo();
Type viewDataType;
if (!typeInfo.IsGenericType || typeInfo.GenericTypeArguments.Length != 1)
{
// Ensure that the view is of the type RazorView<TModel>.
var message = Resources.FormatViewCannotBeActivated(type.FullName, GetType().FullName);
throw new InvalidOperationException(message);
}
var modelType = typeInfo.GenericTypeArguments[0];
viewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
return new ViewActivationInfo
{
ViewDataDictionaryType = viewDataType,
PropertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(type,
typeof(ActivateAttribute),
CreateActivateInfo)
};
}
private PropertyActivator<ViewContext> CreateActivateInfo(PropertyInfo property)
{
Func<ViewContext, object> valueAccessor;
if (property.PropertyType.IsAssignableFrom(typeof(ViewDataDictionary)))
{
valueAccessor = context => context.ViewData;
}
else
{
valueAccessor = context =>
{
var serviceProvider = context.HttpContext.RequestServices;
var value = serviceProvider.GetService(property.PropertyType);
var canHasViewContext = value as ICanHasViewContext;
if (canHasViewContext != null)
{
canHasViewContext.Contextualize(context);
}
return value;
};
}
return new PropertyActivator<ViewContext>(property, valueAccessor);
}
private class ViewActivationInfo
{
public PropertyActivator<ViewContext>[] PropertyActivators { get; set; }
public Type ViewDataDictionaryType { get; set; }
public Action<object, object> ViewDataDictionarySetter { get; set; }
}
}
}

View File

@ -1,10 +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.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Razor
@ -19,43 +16,15 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
public ViewDataDictionary<TModel> ViewData { get; private set; }
public IHtmlHelper<TModel> Html { get; set; }
[Activate]
public ViewDataDictionary<TModel> ViewData { get; set; }
public override Task RenderAsync([NotNull] ViewContext context)
{
ViewData = context.ViewData as ViewDataDictionary<TModel>;
if (ViewData == null)
{
if (context.ViewData != null)
{
ViewData = new ViewDataDictionary<TModel>(context.ViewData);
}
else
{
var metadataProvider = context.HttpContext.RequestServices.GetService<IModelMetadataProvider>();
ViewData = new ViewDataDictionary<TModel>(metadataProvider);
}
// Have new ViewDataDictionary; make sure it's visible everywhere.
context.ViewData = ViewData;
}
InitHelpers(context);
var viewActivator = context.HttpContext.RequestServices.GetService<IRazorViewActivator>();
viewActivator.Activate(this, context);
return base.RenderAsync(context);
}
private void InitHelpers(ViewContext context)
{
Html = context.HttpContext.RequestServices.GetService<IHtmlHelper<TModel>>();
var contextable = Html as ICanHasViewContext;
if (contextable != null)
{
contextable.Contextualize(context);
}
}
}
}

View File

@ -150,6 +150,9 @@
<data name="SectionsNotRendered" xml:space="preserve">
<value>The following sections have been defined but have not been rendered: '{0}'.</value>
</data>
<data name="ViewCannotBeActivated" xml:space="preserve">
<value>View of type '{0}' cannot be instatiated by '{1}'.</value>
</data>
<data name="ViewMustEndInExtension" xml:space="preserve">
<value>View '{0}' must have extension '{1}' when the view represents a full path.</value>
</data>

View File

@ -43,6 +43,7 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Scoped<ICompositeViewEngine, CompositeViewEngine>();
yield return describe.Transient<IRazorCompilationService, RazorCompilationService>();
yield return describe.Transient<IVirtualPathViewFactory, VirtualPathViewFactory>();
yield return describe.Singleton<IRazorViewActivator, RazorViewActivator>();
yield return describe.Transient<INestedProvider<ActionDescriptorProviderContext>,
ReflectedActionDescriptorProvider>();

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var writer = new CSharpCodeWriter();
var context = CreateContext();
var visitor = new InjectChunkVisitor(writer, context);
var visitor = new InjectChunkVisitor(writer, context, "ActivateAttribute");
// Act
visitor.Accept(new Chunk[]
@ -41,13 +41,15 @@ namespace Microsoft.AspNet.Mvc.Razor
{
// Arrange
var expected =
@"public MyType1 MyPropertyName1 { get; private set; }
@"[ActivateAttribute]
public MyType1 MyPropertyName1 { get; private set; }
[ActivateAttribute]
public MyType2 @MyPropertyName2 { get; private set; }
";
var writer = new CSharpCodeWriter();
var context = CreateContext();
var visitor = new InjectChunkVisitor(writer, context);
var visitor = new InjectChunkVisitor(writer, context, "ActivateAttribute");
var factory = SpanFactory.CreateCsHtml();
var node = (Span)factory.Code("Some code")
.As(new InjectParameterGenerator("MyType", "MyPropertyName"));
@ -69,13 +71,15 @@ public MyType2 @MyPropertyName2 { get; private set; }
public void Visit_WithDesignTimeHost_GeneratesPropertiesAndLinePragmas_ForInjectChunks()
{
// Arrange
var expected = @"public
var expected = @"[Microsoft.AspNet.Mvc.ActivateAttribute]
public
#line 1 """"
MyType1 MyPropertyName1
#line default
#line hidden
{ get; private set; }
[Microsoft.AspNet.Mvc.ActivateAttribute]
public
#line 1 """"
MyType2 @MyPropertyName2
@ -88,7 +92,7 @@ MyType2 @MyPropertyName2
var context = CreateContext();
context.Host.DesignTimeMode = true;
var visitor = new InjectChunkVisitor(writer, context);
var visitor = new InjectChunkVisitor(writer, context, "Microsoft.AspNet.Mvc.ActivateAttribute");
var factory = SpanFactory.CreateCsHtml();
var node = (Span)factory.Code("Some code")
.As(new InjectParameterGenerator("MyType", "MyPropertyName"));
@ -121,7 +125,40 @@ MyType2 @MyPropertyName2
var expectedLineMappings = new List<LineMapping>
{
BuildLineMapping(1, 0, 1, 32, 3, 0, 17),
BuildLineMapping(28, 1, 8, 442, 21, 8, 20)
BuildLineMapping(28, 1, 8, 573, 26, 8, 20)
};
// Act
GeneratorResults results;
using (var buffer = new StringTextBuffer(source))
{
results = engine.GenerateCode(buffer);
}
// Assert
Assert.True(results.Success);
Assert.Equal(expectedCode, results.GeneratedCode);
Assert.Empty(results.ParserErrors);
Assert.Equal(expectedLineMappings, results.DesignTimeLineMappings);
}
[Fact]
public void InjectVisitorWithModel_GeneratesCorrectLineMappings()
{
// Arrange
var host = new MvcRazorHost("RazorView")
{
DesignTimeMode = true
};
host.NamespaceImports.Clear();
var engine = new RazorTemplateEngine(host);
var source = ReadResource("TestFiles/Input/InjectWithModel.cshtml");
var expectedCode = ReadResource("TestFiles/Output/InjectWithModel.cs");
var expectedLineMappings = new List<LineMapping>
{
BuildLineMapping(7, 0, 7, 126, 6, 7, 7),
BuildLineMapping(24, 1, 8, 562, 26, 8, 20),
BuildLineMapping(54, 2, 8, 732, 34, 8, 22)
};
// Act

View File

@ -37,6 +37,8 @@
<Compile Include="SpanFactory\SpanFactoryExtensions.cs" />
<Compile Include="SpanFactory\UnclassifiedSpanConstructor.cs" />
<Compile Include="StringTextBuffer.cs" />
<Compile Include="TestFiles\Input\InjectWithModel.cshtml" />
<Compile Include="TestFiles\Output\InjectWithModel.cs" />
<Compile Include="TestFiles\Output\Model.cs" />
<Compile Include="TestFiles\Output\Inject.cs" />
</ItemGroup>

View File

@ -0,0 +1,3 @@
@model MyModel
@inject MyApp MyPropertyName
@inject MyService<TModel> Html

View File

@ -17,6 +17,11 @@ using MyNamespace
#pragma warning restore 219
}
#line hidden
public __CompiledTemplate()
{
}
#line hidden
[Microsoft.AspNet.Mvc.ActivateAttribute]
public
#line 2 ""
MyApp MyPropertyName
@ -24,12 +29,10 @@ using MyNamespace
#line default
#line hidden
{ get; private set; }
[Microsoft.AspNet.Mvc.ActivateAttribute]
public Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel> Html { get; private set; }
#line hidden
public __CompiledTemplate(MyApp MyPropertyName)
{
this.MyPropertyName = MyPropertyName;
}
#pragma warning disable 1998
public override async Task ExecuteAsync()

View File

@ -0,0 +1,49 @@
namespace Razor
{
using System.Threading.Tasks;
public class __CompiledTemplate : RazorView<
#line 1 ""
MyModel
#line default
#line hidden
>
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
#pragma warning restore 219
}
#line hidden
public __CompiledTemplate()
{
}
#line hidden
[Microsoft.AspNet.Mvc.ActivateAttribute]
public
#line 2 ""
MyApp MyPropertyName
#line default
#line hidden
{ get; private set; }
[Microsoft.AspNet.Mvc.ActivateAttribute]
public
#line 3 ""
MyService<TModel> Html
#line default
#line hidden
{ get; private set; }
#line hidden
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
}
#pragma warning restore 1998
}
}

View File

@ -16,12 +16,15 @@
#pragma warning disable 219
#pragma warning restore 219
}
#line hidden
#line hidden
public __CompiledTemplate()
{
}
#line hidden
[Microsoft.AspNet.Mvc.ActivateAttribute]
public Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<TModel> Html { get; private set; }
#line hidden
#pragma warning disable 1998
public override async Task ExecuteAsync()

View File

@ -23,9 +23,10 @@
<ItemGroup>
<Compile Include="MvcRazorCodeParserTest.cs" />
<Compile Include="RazorCompilationServiceTest.cs" />
<Compile Include="RazorViewActivatorTest.cs" />
<Compile Include="RazorViewEngineTest.cs" />
<Compile Include="RazorViewTest.cs" />
<Compile Include="SpanFactory.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

View File

@ -0,0 +1,80 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Razor
{
public class RazorViewActivatorTest
{
[Fact]
public void Activate_ActivatesAndContextualizesPropertiesOnViews()
{
// Arrange
var activator = new RazorViewActivator();
var instance = new TestView();
var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(p => p.GetService(typeof(MyService)))
.Returns(myService);
serviceProvider.Setup(p => p.GetService(typeof(IHtmlHelper<object>)))
.Returns(helper);
var httpContext = new Mock<HttpContext>();
httpContext.SetupGet(c => c.RequestServices)
.Returns(serviceProvider.Object);
var routeContext = new RouteContext(httpContext.Object);
var actionContext = new ActionContext(routeContext, new ActionDescriptor());
var viewContext = new ViewContext(actionContext,
instance,
new ViewDataDictionary(Mock.Of<IModelMetadataProvider>()),
TextWriter.Null);
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.Same(helper, instance.Html);
Assert.Same(myService, instance.MyService);
Assert.Same(viewContext, myService.ViewContext);
Assert.Null(instance.MyService2);
}
private abstract class TestViewBase : RazorView
{
[Activate]
public MyService MyService { get; set; }
public MyService MyService2 { get; set; }
}
private class TestView : TestViewBase
{
[Activate]
internal IHtmlHelper<object> Html { get; private set; }
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class MyService : ICanHasViewContext
{
public ViewContext ViewContext { get; private set; }
public void Contextualize(ViewContext viewContext)
{
ViewContext = viewContext;
}
}
}
}