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

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.Extensions.Primitives;
@ -34,20 +35,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
{
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
{
var viewContainer = GetManifest(assemblyPart);
if (viewContainer == null)
var viewAttributes = GetViewAttributes(assemblyPart);
foreach (var attribute in viewAttributes)
{
continue;
}
foreach (var item in viewContainer.ViewInfos)
{
var relativePath = ViewPath.NormalizePath(item.Path);
var relativePath = ViewPath.NormalizePath(attribute.Path);
var viewDescriptor = new CompiledViewDescriptor
{
ExpirationTokens = Array.Empty<IChangeToken>(),
RelativePath = relativePath,
ViewAttribute = new RazorViewAttribute(relativePath, item.Type),
ViewAttribute = attribute,
IsPrecompiled = true,
};
@ -57,19 +53,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
}
/// <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>
/// <param name="assemblyPart">The <see cref="AssemblyPart"/>.</param>
/// <returns>The <see cref="ViewInfoContainer"/> <see cref="Type"/>.</returns>
protected virtual ViewInfoContainer GetManifest(AssemblyPart assemblyPart)
/// <returns>The sequence of <see cref="RazorViewAttribute"/> instances.</returns>
protected virtual IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
{
var type = CompiledViewManfiest.GetManifestType(assemblyPart, FullyQualifiedManifestTypeName);
if (type != null)
if (assemblyPart == 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@ -188,10 +187,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
}
var generatedAssembly = CompileAndEmit(codeDocument, cSharpDocument.GeneratedCode);
var exportedType = generatedAssembly.GetExportedTypes().FirstOrDefault(f => !f.IsNested);
var viewAttribute = generatedAssembly.GetCustomAttribute<RazorViewAttribute>();
return new CompiledViewDescriptor
{
ViewAttribute = new RazorViewAttribute(relativePath, exportedType),
ViewAttribute = viewAttribute,
RelativePath = relativePath,
};
}

View File

@ -1,20 +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.
using System;
using System.Collections.Generic;
using System.Linq;
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
{
/// <summary>
/// An <see cref="IApplicationFeatureProvider{TFeature}"/> for <see cref="ViewsFeature"/>.
/// </summary>
public class CompiledPageFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
public class CompiledPageFeatureProvider
{
/// <summary>
/// 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 =
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();
AddFeatureProviders(builder);
AddServices(builder.Services);
return builder;
@ -50,7 +49,6 @@ namespace Microsoft.Extensions.DependencyInjection
builder.AddRazorViewEngine();
AddFeatureProviders(builder);
AddServices(builder.Services);
builder.Services.Configure(setupAction);
@ -80,14 +78,6 @@ namespace Microsoft.Extensions.DependencyInjection
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 static void AddServices(IServiceCollection services)
{

View File

@ -3,9 +3,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Options;
@ -58,17 +61,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
var cachedApplicationModels = new List<PageApplicationModel>();
foreach (var pageDescriptor in GetCompiledPageDescriptors())
foreach (var pageAttribute in GetRazorPageAttributes(_applicationManager.ApplicationParts))
{
var pageAttribute = (RazorPageAttribute)pageDescriptor.ViewAttribute;
if (!pageDescriptor.RelativePath.StartsWith(rootDirectory))
var normalizedPath = ViewPath.NormalizePath(pageAttribute.Path);
if (!normalizedPath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var viewEnginePath = GetViewEnginePath(rootDirectory, pageDescriptor.RelativePath);
var model = new PageApplicationModel(pageDescriptor.RelativePath, viewEnginePath);
var viewEnginePath = GetViewEnginePath(rootDirectory, normalizedPath);
var model = new PageApplicationModel(normalizedPath, viewEnginePath);
PageSelectorModel.PopulateDefaults(model, pageAttribute.RouteTemplate);
cachedApplicationModels.Add(model);
@ -78,8 +80,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
}
protected virtual IEnumerable<CompiledViewDescriptor> GetCompiledPageDescriptors()
=> CompiledPageFeatureProvider.GetCompiledPageDescriptors(_applicationManager.ApplicationParts);
/// <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>
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)
{

View File

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

View File

@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(FilterScope.Controller, filter2.Scope);
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);
}

View File

@ -36,10 +36,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
// Arrange
var part1 = new AssemblyPart(typeof(object).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();
@ -109,40 +122,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
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)
{
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))
})
protected override IEnumerable<RazorViewAttribute> GetViewAttributes(AssemblyPart assemblyPart)
{
return _attributeLookup[assemblyPart];
}
}

View File

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

View File

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