Use RazorViewAttribute \ RazorPageAttribute for view discovery

This commit is contained in:
Pranav K 2017-06-06 15:32:41 -07:00
parent 4bf20035e4
commit 8f883e8e13
11 changed files with 93 additions and 157 deletions

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.IO;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
@ -12,13 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
{ {
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews"; public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";
public static Type GetManifestType(AssemblyPart assemblyPart, string typeName) public static Assembly GetFeatureAssembly(AssemblyPart assemblyPart)
{
var assembly = GetFeatureAssembly(assemblyPart);
return assembly?.GetType(typeName);
}
private static Assembly GetFeatureAssembly(AssemblyPart assemblyPart)
{ {
if (assemblyPart.Assembly.IsDynamic || string.IsNullOrEmpty(assemblyPart.Assembly.Location)) if (assemblyPart.Assembly.IsDynamic || string.IsNullOrEmpty(assemblyPart.Assembly.Location))
{ {

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -34,20 +35,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
{ {
foreach (var assemblyPart in parts.OfType<AssemblyPart>()) foreach (var assemblyPart in parts.OfType<AssemblyPart>())
{ {
var viewContainer = GetManifest(assemblyPart); var viewAttributes = GetViewAttributes(assemblyPart);
if (viewContainer == null) foreach (var attribute in viewAttributes)
{ {
continue; var relativePath = ViewPath.NormalizePath(attribute.Path);
}
foreach (var item in viewContainer.ViewInfos)
{
var relativePath = ViewPath.NormalizePath(item.Path);
var viewDescriptor = new CompiledViewDescriptor var viewDescriptor = new CompiledViewDescriptor
{ {
ExpirationTokens = Array.Empty<IChangeToken>(), ExpirationTokens = Array.Empty<IChangeToken>(),
RelativePath = relativePath, RelativePath = relativePath,
ViewAttribute = new RazorViewAttribute(relativePath, item.Type), ViewAttribute = attribute,
IsPrecompiled = true, IsPrecompiled = true,
}; };
@ -57,19 +53,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
} }
/// <summary> /// <summary>
/// Gets the type of <see cref="ViewInfoContainer"/> for the specified <paramref name="assemblyPart"/>. /// Gets the sequence of <see cref="RazorViewAttribute"/> instances associated with the specified <paramref name="assemblyPart"/>.
/// </summary> /// </summary>
/// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param> /// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param>
/// <returns>The <see cref="ViewInfoContainer"/> <see cref="Type"/>.</returns> /// <returns>The sequence of <see cref="RazorViewAttribute"/> instances.</returns>
protected virtual ViewInfoContainer GetManifest(AssemblyPart assemblyPart) protected virtual IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
{ {
var type = CompiledViewManfiest.GetManifestType(assemblyPart, FullyQualifiedManifestTypeName); if (assemblyPart == null)
if (type != null)
{ {
return (ViewInfoContainer)Activator.CreateInstance(type); throw new ArgumentNullException(nameof(assemblyPart));
} }
return null; var featureAssembly = CompiledViewManfiest.GetFeatureAssembly(assemblyPart);
if (featureAssembly != null)
{
return featureAssembly.GetCustomAttributes<RazorViewAttribute>();
}
return Enumerable.Empty<RazorViewAttribute>();
} }
} }
} }

View File

@ -6,7 +6,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -188,10 +187,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
} }
var generatedAssembly = CompileAndEmit(codeDocument, cSharpDocument.GeneratedCode); var generatedAssembly = CompileAndEmit(codeDocument, cSharpDocument.GeneratedCode);
var exportedType = generatedAssembly.GetExportedTypes().FirstOrDefault(f => !f.IsNested); var viewAttribute = generatedAssembly.GetCustomAttribute<RazorViewAttribute>();
return new CompiledViewDescriptor return new CompiledViewDescriptor
{ {
ViewAttribute = new RazorViewAttribute(relativePath, exportedType), ViewAttribute = viewAttribute,
RelativePath = relativePath, RelativePath = relativePath,
}; };
} }

View File

@ -1,20 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Linq;
using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.ApplicationParts namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{ {
/// <summary> /// <summary>
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>. /// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>.
/// </summary> /// </summary>
public class CompiledPageFeatureProvider : IApplicationFeatureProvider<ViewsFeature> public class CompiledPageFeatureProvider
{ {
/// <summary> /// <summary>
/// Gets the namespace for the <see cref="ViewInfoContainer"/> type in the view assembly. /// Gets the namespace for the <see cref="ViewInfoContainer"/> type in the view assembly.
@ -28,47 +22,5 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
private static readonly string FullyQualifiedManifestTypeName = private static readonly string FullyQualifiedManifestTypeName =
CompiledPageManifestNamespace + "." + CompiledPageManifestTypeName; CompiledPageManifestNamespace + "." + CompiledPageManifestTypeName;
/// <inheritdoc />
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
{
foreach (var item in GetCompiledPageDescriptors(parts))
{
feature.ViewDescriptors.Add(item);
}
}
/// <summary>
/// Gets the sequence of <see cref="CompiledViewDescriptor"/> from <paramref name="parts"/>.
/// </summary>
/// <param name="parts">The <see cref="ApplicationPart"/>s</param>
/// <returns>The sequence of <see cref="CompiledViewDescriptor"/>.</returns>
public static IEnumerable<CompiledViewDescriptor> GetCompiledPageDescriptors(IEnumerable<ApplicationPart> parts)
{
var manifests = parts.OfType<AssemblyPart>()
.Select(part => CompiledViewManfiest.GetManifestType(part, FullyQualifiedManifestTypeName))
.Where(type => type != null)
.Select(type => (CompiledPageManifest)Activator.CreateInstance(type));
foreach (var page in manifests.SelectMany(m => m.CompiledPages))
{
var normalizedPath = ViewPath.NormalizePath(page.Path);
var pageAttribute = new RazorPageAttribute(
normalizedPath,
page.CompiledType,
page.RoutePrefix);
var viewDescriptor = new CompiledViewDescriptor
{
RelativePath = normalizedPath,
ViewAttribute = pageAttribute,
ExpirationTokens = Array.Empty<IChangeToken>(),
IsPrecompiled = true,
};
yield return viewDescriptor;
}
}
} }
} }

View File

@ -28,7 +28,6 @@ namespace Microsoft.Extensions.DependencyInjection
builder.AddRazorViewEngine(); builder.AddRazorViewEngine();
AddFeatureProviders(builder);
AddServices(builder.Services); AddServices(builder.Services);
return builder; return builder;
@ -50,7 +49,6 @@ namespace Microsoft.Extensions.DependencyInjection
builder.AddRazorViewEngine(); builder.AddRazorViewEngine();
AddFeatureProviders(builder);
AddServices(builder.Services); AddServices(builder.Services);
builder.Services.Configure(setupAction); builder.Services.Configure(setupAction);
@ -80,14 +78,6 @@ namespace Microsoft.Extensions.DependencyInjection
return builder; return builder;
} }
private static void AddFeatureProviders(IMvcCoreBuilder builder)
{
if (!builder.PartManager.FeatureProviders.OfType<CompiledPageFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new CompiledPageFeatureProvider());
}
}
// Internal for testing. // Internal for testing.
internal static void AddServices(IServiceCollection services) internal static void AddServices(IServiceCollection services)
{ {

View File

@ -3,9 +3,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -58,17 +61,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
var cachedApplicationModels = new List<PageApplicationModel>(); var cachedApplicationModels = new List<PageApplicationModel>();
foreach (var pageDescriptor in GetCompiledPageDescriptors()) foreach (var pageAttribute in GetRazorPageAttributes(_applicationManager.ApplicationParts))
{ {
var pageAttribute = (RazorPageAttribute)pageDescriptor.ViewAttribute; var normalizedPath = ViewPath.NormalizePath(pageAttribute.Path);
if (!normalizedPath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase))
if (!pageDescriptor.RelativePath.StartsWith(rootDirectory))
{ {
continue; continue;
} }
var viewEnginePath = GetViewEnginePath(rootDirectory, pageDescriptor.RelativePath); var viewEnginePath = GetViewEnginePath(rootDirectory, normalizedPath);
var model = new PageApplicationModel(pageDescriptor.RelativePath, viewEnginePath); var model = new PageApplicationModel(normalizedPath, viewEnginePath);
PageSelectorModel.PopulateDefaults(model, pageAttribute.RouteTemplate); PageSelectorModel.PopulateDefaults(model, pageAttribute.RouteTemplate);
cachedApplicationModels.Add(model); cachedApplicationModels.Add(model);
@ -78,8 +80,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
} }
protected virtual IEnumerable<CompiledViewDescriptor> GetCompiledPageDescriptors() /// <summary>
=> CompiledPageFeatureProvider.GetCompiledPageDescriptors(_applicationManager.ApplicationParts); /// Gets the sequence of <see cref="CompiledViewDescriptor"/> from <paramref name="parts"/>.
/// </summary>
/// <param name="parts">The <see cref="ApplicationPart"/>s</param>
/// <returns>The sequence of <see cref="CompiledViewDescriptor"/>.</returns>
protected virtual IEnumerable<RazorPageAttribute> GetRazorPageAttributes(IEnumerable<ApplicationPart> parts)
{
if (parts == null)
{
throw new ArgumentNullException(nameof(parts));
}
return _applicationManager.ApplicationParts
.OfType<AssemblyPart>()
.SelectMany(GetAttributes);
}
private static IEnumerable<RazorPageAttribute> GetAttributes(AssemblyPart assemblyPart)
{
var featureAssembly = CompiledViewManfiest.GetFeatureAssembly(assemblyPart);
if (featureAssembly != null)
{
return featureAssembly.GetCustomAttributes<RazorPageAttribute>();
}
return Enumerable.Empty<RazorPageAttribute>();
}
private string GetViewEnginePath(string rootDirectory, string path) private string GetViewEnginePath(string rootDirectory, string path)
{ {

View File

@ -28,12 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath); var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath);
var viewDescriptor = compileTask.GetAwaiter().GetResult(); var viewDescriptor = compileTask.GetAwaiter().GetResult();
var viewAttribute = viewDescriptor.ViewAttribute; var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
var pageAttribute = new RazorPageAttribute(
viewAttribute.Path,
viewAttribute.ViewType,
routeTemplate: null);
return CreateDescriptor(actionDescriptor, pageAttribute); return CreateDescriptor(actionDescriptor, pageAttribute);
} }

View File

@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(FilterScope.Controller, filter2.Scope); Assert.Equal(FilterScope.Controller, filter2.Scope);
var filter3 = descriptor.FilterDescriptors[2]; var filter3 = descriptor.FilterDescriptors[2];
Assert.Equal(3, Assert.IsType<MyFilterAttribute>(filter3.Filter).Value); ; Assert.Equal(3, Assert.IsType<MyFilterAttribute>(filter3.Filter).Value);
Assert.Equal(FilterScope.Action, filter3.Scope); Assert.Equal(FilterScope.Action, filter3.Scope);
} }

View File

@ -36,10 +36,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
// Arrange // Arrange
var part1 = new AssemblyPart(typeof(object).GetTypeInfo().Assembly); var part1 = new AssemblyPart(typeof(object).GetTypeInfo().Assembly);
var part2 = new AssemblyPart(GetType().GetTypeInfo().Assembly); var part2 = new AssemblyPart(GetType().GetTypeInfo().Assembly);
var featureProvider = new TestableViewsFeatureProvider(new Dictionary<AssemblyPart, Type> var featureProvider = new TestableViewsFeatureProvider(new Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>>
{ {
{ part1, typeof(ViewInfoContainer1) }, {
{ part2, typeof(ViewInfoContainer2) }, part1,
new[]
{
new RazorViewAttribute("/Views/test/Index.cshtml", typeof(object)),
}
},
{
part2,
new[]
{
new RazorViewAttribute("/Areas/Admin/Views/Index.cshtml", typeof(string)),
new RazorViewAttribute("/Areas/Admin/Views/About.cshtml", typeof(int)),
}
},
}); });
var applicationPartManager = new ApplicationPartManager(); var applicationPartManager = new ApplicationPartManager();
@ -109,40 +122,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
private class TestableViewsFeatureProvider : ViewsFeatureProvider private class TestableViewsFeatureProvider : ViewsFeatureProvider
{ {
private readonly Dictionary<AssemblyPart, Type> _containerLookup; private readonly Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> _attributeLookup;
public TestableViewsFeatureProvider(Dictionary<AssemblyPart, Type> containerLookup) public TestableViewsFeatureProvider(Dictionary<AssemblyPart, IEnumerable<RazorViewAttribute>> attributeLookup)
{ {
_containerLookup = containerLookup; _attributeLookup = attributeLookup;
} }
protected override ViewInfoContainer GetManifest(AssemblyPart assemblyPart) protected override IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
{
var type = _containerLookup[assemblyPart];
return (ViewInfoContainer)Activator.CreateInstance(type);
}
}
private class ViewInfoContainer1 : ViewInfoContainer
{
public ViewInfoContainer1()
: base(new[]
{
new ViewInfo("/Views/test/Index.cshtml", typeof(object))
})
{
}
}
private class ViewInfoContainer2 : ViewInfoContainer
{
public ViewInfoContainer2()
: base(new[]
{
new ViewInfo("/Areas/Admin/Views/Index.cshtml", typeof(string)),
new ViewInfo("/Areas/Admin/Views/About.cshtml", typeof(int))
})
{ {
return _attributeLookup[assemblyPart];
} }
} }

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Xunit; using Xunit;
@ -19,8 +18,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange // Arrange
var descriptors = new[] var descriptors = new[]
{ {
GetDescriptor("/Pages/About.cshtml"), GetAttribute("/Pages/About.cshtml"),
GetDescriptor("/Pages/Home.cshtml", "some-prefix"), GetAttribute("/Pages/Home.cshtml", "some-prefix"),
}; };
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions()); var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
var context = new PageApplicationModelProviderContext(); var context = new PageApplicationModelProviderContext();
@ -52,8 +51,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange // Arrange
var descriptors = new[] var descriptors = new[]
{ {
GetDescriptor("/Pages/Index.cshtml"), GetAttribute("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"), GetAttribute("/Pages/Admin/Index.cshtml", "some-template"),
}; };
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions { RootDirectory = "/" }); var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions { RootDirectory = "/" });
var context = new PageApplicationModelProviderContext(); var context = new PageApplicationModelProviderContext();
@ -87,8 +86,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange // Arrange
var descriptors = new[] var descriptors = new[]
{ {
GetDescriptor("/Pages/Index.cshtml"), GetAttribute("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Admin/Index.cshtml", "some-template"), GetAttribute("/Pages/Admin/Index.cshtml", "some-template"),
}; };
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions()); var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
var context = new PageApplicationModelProviderContext(); var context = new PageApplicationModelProviderContext();
@ -122,8 +121,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange // Arrange
var descriptors = new[] var descriptors = new[]
{ {
GetDescriptor("/Pages/Index.cshtml"), GetAttribute("/Pages/Index.cshtml"),
GetDescriptor("/Pages/Home.cshtml", "/some-prefix"), GetAttribute("/Pages/Home.cshtml", "/some-prefix"),
}; };
var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions()); var provider = new TestCompiledPageApplicationModelProvider(descriptors, new RazorPagesOptions());
var context = new PageApplicationModelProviderContext(); var context = new PageApplicationModelProviderContext();
@ -134,27 +133,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
ex.Message); ex.Message);
} }
private static CompiledViewDescriptor GetDescriptor(string path, string prefix = "") private static RazorPageAttribute GetAttribute(string path, string prefix = "") => new RazorPageAttribute(path, typeof(object), prefix);
{
return new CompiledViewDescriptor
{
RelativePath = path,
ViewAttribute = new RazorPageAttribute(path, typeof(object), prefix),
};
}
public class TestCompiledPageApplicationModelProvider : CompiledPageApplicationModelProvider public class TestCompiledPageApplicationModelProvider : CompiledPageApplicationModelProvider
{ {
private readonly IEnumerable<CompiledViewDescriptor> _info; private readonly IEnumerable<RazorPageAttribute> _attributes;
public TestCompiledPageApplicationModelProvider(IEnumerable<CompiledViewDescriptor> info, RazorPagesOptions options) public TestCompiledPageApplicationModelProvider(IEnumerable<RazorPageAttribute> attributes, RazorPagesOptions options)
: base(new ApplicationPartManager(), new TestOptionsManager<RazorPagesOptions>(options)) : base(new ApplicationPartManager(), new TestOptionsManager<RazorPagesOptions>(options))
{ {
_info = info; _attributes = attributes;
} }
protected override IEnumerable<RazorPageAttribute> GetRazorPageAttributes(IEnumerable<ApplicationPart> parts) => _attributes;
protected override IEnumerable<CompiledViewDescriptor> GetCompiledPageDescriptors() => _info;
} }
} }
} }

View File

@ -216,8 +216,7 @@ namespace Microsoft.AspNetCore.Mvc
feature => Assert.IsType<ViewComponentFeatureProvider>(feature), feature => Assert.IsType<ViewComponentFeatureProvider>(feature),
feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature), feature => Assert.IsType<MetadataReferenceFeatureProvider>(feature),
feature => Assert.IsType<TagHelperFeatureProvider>(feature), feature => Assert.IsType<TagHelperFeatureProvider>(feature),
feature => Assert.IsType<ViewsFeatureProvider>(feature), feature => Assert.IsType<ViewsFeatureProvider>(feature));
feature => Assert.IsType<CompiledPageFeatureProvider>(feature));
} }
[Fact] [Fact]