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

Remove IViewsProvider and make view lookup to the feature provider #5262

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
public class AssemblyPart :
ApplicationPart,
IApplicationPartTypeProvider,
ICompilationReferencesProvider,
IViewsProvider
ICompilationReferencesProvider
{
/// <summary>
/// Gets the suffix for the view assembly.
/// </summary>
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";

/// <summary>
/// Gets the namespace for the <see cref="ViewInfoContainer"/> type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerNamespace = "AspNetCore";

/// <summary>
/// Gets the type name for the view collection type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";

/// <summary>
/// Initalizes a new <see cref="AssemblyPart"/> instance.
/// </summary>
Expand Down Expand Up @@ -60,27 +44,6 @@ public AssemblyPart(Assembly assembly)
/// <inheritdoc />
public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;

/// <inheritdoc />
public IEnumerable<ViewInfo> Views
{
get
{
var precompiledAssemblyName = new AssemblyName(Assembly.FullName);
precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;

var typeName = $"{ViewInfoContainerNamespace}.{ViewInfoContainerTypeName},{precompiledAssemblyName}";
var viewInfoContainerTypeName = Type.GetType(typeName);

if (viewInfoContainerTypeName == null)
{
return null;
}

var precompiledViews = (ViewInfoContainer)Activator.CreateInstance(viewInfoContainerTypeName);
return precompiledViews.ViewInfos;
}
}

/// <inheritdoc />
public IEnumerable<string> GetReferencePaths()
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// 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 System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;

namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
Expand All @@ -12,20 +14,53 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
/// </summary>
public class ViewsFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
{
/// <summary>
/// Gets the suffix for the view assembly.
/// </summary>
public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews";

/// <summary>
/// Gets the namespace for the <see cref="ViewInfoContainer"/> type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerNamespace = "AspNetCore";

/// <summary>
/// Gets the type name for the view collection type in the view assembly.
/// </summary>
public static readonly string ViewInfoContainerTypeName = "__PrecompiledViewCollection";

/// <inheritdoc />
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
{
foreach (var provider in parts.OfType<IViewsProvider>())
foreach (var assemblyPart in parts.OfType<AssemblyPart>())
{
var precompiledViews = provider.Views;
if (precompiledViews != null)
var viewInfoContainerTypeName = GetViewInfoContainerType(assemblyPart);
if (viewInfoContainerTypeName == null)
{
foreach (var viewInfo in precompiledViews)
{
feature.Views[viewInfo.Path] = viewInfo.Type;
}
continue;
}

var viewContainer = (ViewInfoContainer)Activator.CreateInstance(viewInfoContainerTypeName);

foreach (var item in viewContainer.ViewInfos)
{
feature.Views[item.Path] = item.Type;
}
}
}

/// <summary>
/// Gets the type of <see cref="ViewInfoContainer"/> for 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 Type GetViewInfoContainerType(AssemblyPart assemblyPart)
{
var precompiledAssemblyName = new AssemblyName(assemblyPart.Assembly.FullName);
precompiledAssemblyName.Name = precompiledAssemblyName.Name + PrecompiledViewsAssemblySuffix;

var typeName = $"{ViewInfoContainerNamespace}.{ViewInfoContainerTypeName},{precompiledAssemblyName}";
return Type.GetType(typeName);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// 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 System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Moq;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
Expand All @@ -18,49 +20,89 @@ public void PopulateFeature_ReturnsEmptySequenceIfNoAssemblyPartHasViewAssembly(
applicationPartManager.ApplicationParts.Add(
new AssemblyPart(typeof(ViewsFeatureProviderTest).GetTypeInfo().Assembly));
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
var feature = new MetadataReferenceFeature();
var feature = new ViewsFeature();

// Act
applicationPartManager.PopulateFeature(feature);

// Assert
Assert.Empty(feature.MetadataReferences);
Assert.Empty(feature.Views);
}

[Fact]
public void PopulateFeature_ReturnsViewsFromAllAvailableApplicationParts()
{
// Arrange
var applicationPart1 = new Mock<ApplicationPart>();
var viewsProvider1 = applicationPart1
.As<IViewsProvider>()
.SetupGet(p => p.Views)
.Returns(new[]
{
new ViewInfo("/Views/test/Index.cshtml", typeof(object))
});
var applicationPart2 = new Mock<ApplicationPart>();
var viewsProvider2 = applicationPart2
.As<IViewsProvider>()
.SetupGet(p => p.Views)
.Returns(new[]
{
new ViewInfo("/Areas/Admin/Views/Index.cshtml", typeof(string)),
new ViewInfo("/Areas/Admin/Views/About.cshtml", typeof(int))
});

var part1 = new AssemblyPart(typeof(object).GetTypeInfo().Assembly);
var part2 = new AssemblyPart(GetType().GetTypeInfo().Assembly);
var featureProvider = new TestableViewsFeatureProvider(new Dictionary<AssemblyPart, Type>
{
{ part1, typeof(ViewInfoContainer1) },
{ part2, typeof(ViewInfoContainer2) },
});

var applicationPartManager = new ApplicationPartManager();
applicationPartManager.ApplicationParts.Add(applicationPart1.Object);
applicationPartManager.ApplicationParts.Add(applicationPart2.Object);
applicationPartManager.FeatureProviders.Add(new ViewsFeatureProvider());
var feature = new MetadataReferenceFeature();
applicationPartManager.ApplicationParts.Add(part1);
applicationPartManager.ApplicationParts.Add(part2);
applicationPartManager.FeatureProviders.Add(featureProvider);
var feature = new ViewsFeature();

// Act
applicationPartManager.PopulateFeature(feature);

// Assert
Assert.Empty(feature.MetadataReferences);
Assert.Collection(feature.Views.OrderBy(f => f.Key, StringComparer.Ordinal),
view =>
{
Assert.Equal("/Areas/Admin/Views/About.cshtml", view.Key);
Assert.Equal(typeof(int), view.Value);
},
view =>
{
Assert.Equal("/Areas/Admin/Views/Index.cshtml", view.Key);
Assert.Equal(typeof(string), view.Value);
},
view =>
{
Assert.Equal("/Views/test/Index.cshtml", view.Key);
Assert.Equal(typeof(object), view.Value);
});
}

private class TestableViewsFeatureProvider : ViewsFeatureProvider
{
private readonly Dictionary<AssemblyPart, Type> _containerLookup;

public TestableViewsFeatureProvider(Dictionary<AssemblyPart, Type> containerLookup)
{
_containerLookup = containerLookup;
}

protected override Type GetViewInfoContainerType(AssemblyPart assemblyPart)
=> _containerLookup[assemblyPart];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Do we put the arrow on a new line? It kinda looks weird 😵

}

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))
})
{
}
}
}
}