Move Pages filter creation to application model provider

Fixes #6198
This commit is contained in:
Pranav K 2017-05-22 11:38:31 -07:00
parent 083f55e337
commit 39f1f5c933
11 changed files with 147 additions and 105 deletions

View File

@ -92,8 +92,6 @@ namespace Microsoft.Extensions.DependencyInjection
internal static void AddServices(IServiceCollection services)
{
// Options
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<RazorPagesOptions>, RazorPagesOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorPagesRazorViewEngineOptionsSetup>());
@ -105,6 +103,8 @@ namespace Microsoft.Extensions.DependencyInjection
ServiceDescriptor.Singleton<IPageApplicationModelProvider, RazorProjectPageApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, CompiledPageApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPageApplicationModelProvider, PageFilterApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());

View File

@ -0,0 +1,41 @@
// 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 Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageFilterApplicationModelProvider : IPageApplicationModelProvider
{
/// <remarks>This order ensures that <see cref="PageFilterApplicationModelProvider"/> runs after
/// <see cref="RazorProjectPageApplicationModelProvider"/> and <see cref="CompiledPageApplicationModelProvider"/>.
/// </remarks>
public int Order => -1000 + 10;
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
{
// Do nothing
}
public void OnProvidersExecuting(PageApplicationModelProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
for (var i = 0; i < context.Results.Count; i++)
{
var pageApplicationModel = context.Results[i];
// Support for [TempData] on properties
pageApplicationModel.Filters.Add(new PageSaveTempDataPropertyFilterFactory());
// Always require an antiforgery token on post
pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}
}
}
}

View File

@ -1,29 +0,0 @@
// 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 Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class RazorPagesOptionsSetup : IConfigureOptions<RazorPagesOptions>
{
public void Configure(RazorPagesOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
// Support for [TempData] on properties
options.ConfigureFilter(page => new PageSaveTempDataPropertyFilterFactory());
// Always require an antiforgery token on post
options.ConfigureFilter(new AutoValidateAntiforgeryTokenAttribute());
options.RootDirectory = "/Pages";
}
}
}

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary>
public class RazorPagesOptions
{
private string _root = "/";
private string _root = "/Pages";
/// <summary>
/// Gets a list of <see cref="IPageApplicationModelConvention"/> instances that will be applied to
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <summary>
/// Application relative path used as the root of discovery for Razor Page files.
/// Defaults to the <c>/Pages</c> directory under application root.
/// </summary>
public string RootDirectory
{

View File

@ -186,8 +186,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
// Arrange
var options = new MvcOptions();
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
var filterProvider = new PageFilterApplicationModelProvider();
var provider = new PageActionDescriptorProvider(
new[] { applicationModelProvider },
new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
GetAccessor(options),
GetRazorPagesOptions());
var context = new ActionDescriptorProviderContext();
@ -222,8 +223,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
options.Filters.Add(filter1);
options.Filters.Add(filter2);
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
var filterProvider = new PageFilterApplicationModelProvider();
var provider = new PageActionDescriptorProvider(
new[] { applicationModelProvider },
new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
GetAccessor(options),
GetRazorPagesOptions());
var context = new ActionDescriptorProviderContext();
@ -275,8 +277,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
var razorOptions = GetRazorPagesOptions();
razorOptions.Value.Conventions.Add(convention.Object);
var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel());
var filterProvider = new PageFilterApplicationModelProvider();
var provider = new PageActionDescriptorProvider(
new[] { applicationModelProvider },
new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider },
GetAccessor(options),
razorOptions);
var context = new ActionDescriptorProviderContext();
@ -337,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
private static IOptions<RazorPagesOptions> GetRazorPagesOptions()
{
return new OptionsManager<RazorPagesOptions>(new[] { new RazorPagesOptionsSetup() });
return new TestOptionsManager<RazorPagesOptions>();
}
private static RazorProjectItem GetProjectItem(string basePath, string path, string content)
@ -359,7 +362,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
_models = models ?? Array.Empty<PageApplicationModel>();
}
public int Order => 0;
public int Order => -1000;
public void OnProvidersExecuted(PageApplicationModelProviderContext context)
{

View File

@ -31,16 +31,51 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
result =>
{
Assert.Equal("/Pages/About.cshtml", result.RelativePath);
Assert.Equal("/Pages/About", result.ViewEnginePath);
Assert.Equal("/About", result.ViewEnginePath);
Assert.Collection(result.Selectors,
selector => Assert.Equal("Pages/About", selector.AttributeRouteModel.Template));
selector => Assert.Equal("About", selector.AttributeRouteModel.Template));
},
result =>
{
Assert.Equal("/Pages/Home.cshtml", result.RelativePath);
Assert.Equal("/Pages/Home", result.ViewEnginePath);
Assert.Equal("/Home", result.ViewEnginePath);
Assert.Collection(result.Selectors,
selector => Assert.Equal("Pages/Home/some-prefix", selector.AttributeRouteModel.Template));
selector => Assert.Equal("Home/some-prefix", selector.AttributeRouteModel.Template));
});
}
[Fact]
public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPage_WithIndexAtRoot()
{
// Arrange
var info = new[]
{
new CompiledPageInfo("/Pages/Index.cshtml", typeof(object), routePrefix: string.Empty),
new CompiledPageInfo("/Pages/Admin/Index.cshtml", typeof(object), "some-template"),
};
var provider = new TestCompiledPageApplicationModelProvider(info, new RazorPagesOptions { RootDirectory = "/" });
var context = new PageApplicationModelProviderContext();
// Act
provider.OnProvidersExecuting(context);
// Assert
Assert.Collection(context.Results,
result =>
{
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
Assert.Equal("/Pages/Index", result.ViewEnginePath);
Assert.Collection(result.Selectors,
selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template),
selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template));
},
result =>
{
Assert.Equal("/Pages/Admin/Index.cshtml", result.RelativePath);
Assert.Equal("/Pages/Admin/Index", result.ViewEnginePath);
Assert.Collection(result.Selectors,
selector => Assert.Equal("Pages/Admin/Index/some-template", selector.AttributeRouteModel.Template),
selector => Assert.Equal("Pages/Admin/some-template", selector.AttributeRouteModel.Template));
});
}
@ -64,18 +99,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
result =>
{
Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
Assert.Equal("/Pages/Index", result.ViewEnginePath);
Assert.Equal("/Index", result.ViewEnginePath);
Assert.Collection(result.Selectors,
selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template),
selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template));
selector => Assert.Equal("Index", selector.AttributeRouteModel.Template),
selector => Assert.Equal("", selector.AttributeRouteModel.Template));
},
result =>
{
Assert.Equal("/Pages/Admin/Index.cshtml", result.RelativePath);
Assert.Equal("/Pages/Admin/Index", result.ViewEnginePath);
Assert.Equal("/Admin/Index", result.ViewEnginePath);
Assert.Collection(result.Selectors,
selector => Assert.Equal("Pages/Admin/Index/some-template", selector.AttributeRouteModel.Template),
selector => Assert.Equal("Pages/Admin/some-template", selector.AttributeRouteModel.Template));
selector => Assert.Equal("Admin/Index/some-template", selector.AttributeRouteModel.Template),
selector => Assert.Equal("Admin/some-template", selector.AttributeRouteModel.Template));
});
}

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var changeToken = changeProvider.GetChangeToken();
// Assert
fileProvider.Verify(f => f.Watch("/**/*.cshtml"));
fileProvider.Verify(f => f.Watch("/Pages/**/*.cshtml"));
}
[Theory]

View File

@ -182,7 +182,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
loader.Object,
CreateActionDescriptorCollection(descriptor),
razorPageFactoryProvider: razorPageFactoryProvider.Object,
razorProject: defaultRazorProject);
razorProject: defaultRazorProject,
razorPagesOptions: new RazorPagesOptions { RootDirectory = "/" });
var context = new ActionInvokerProviderContext(new ActionContext()
{
@ -346,7 +347,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
pageProvider: null,
modelProvider: null,
razorPageFactoryProvider: razorPageFactoryProvider,
razorProject: razorProject);
razorProject: razorProject,
razorPagesOptions: new RazorPagesOptions { RootDirectory = "/" });
var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor);
@ -458,7 +460,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
pageProvider: null,
modelProvider: null,
razorPageFactoryProvider: pageFactory.Object,
razorProject: razorProject);
razorProject: razorProject,
razorPagesOptions: new RazorPagesOptions { RootDirectory = "/" });
var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor);

View File

@ -0,0 +1,41 @@
// 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 Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageFilterApplicationModelProviderTest
{
[Fact]
public void OnProvidersExecuting_AddsFiltersToModels()
{
// Arrange
var applicationModel1 = new PageApplicationModel("/Home.cshtml", "/Home.cshtml");
var applicationModel2 = new PageApplicationModel("/About.cshtml", "/About.cshtml");
var modelProvider = new PageFilterApplicationModelProvider();
var context = new PageApplicationModelProviderContext
{
Results =
{
applicationModel1,
applicationModel2,
}
};
// Act
modelProvider.OnProvidersExecuting(context);
// Assert
Assert.Collection(applicationModel1.Filters,
filter => Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter),
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
Assert.Collection(applicationModel2.Filters,
filter => Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter),
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
}
}
}

View File

@ -1,47 +0,0 @@
// 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 Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class RazorPagesOptionsSetupTest
{
[Fact]
public void Configure_AddsGlobalFilters()
{
// Arrange
var options = new RazorPagesOptions();
var setup = new RazorPagesOptionsSetup();
var applicationModel = new PageApplicationModel("/Home.cshtml", "/Home.cshtml");
// Act
setup.Configure(options);
foreach (var convention in options.Conventions)
{
convention.Apply(applicationModel);
}
// Assert
Assert.Collection(applicationModel.Filters,
filter => Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter),
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
}
[Fact]
public void Configure_SetsRazorPagesRoot()
{
// Arrange
var options = new RazorPagesOptions();
var setup = new RazorPagesOptionsSetup();
// Act
setup.Configure(options);
// Assert
Assert.Equal("/Pages", options.RootDirectory);
}
}
}

View File

@ -364,13 +364,6 @@ namespace Microsoft.AspNetCore.Mvc
typeof(RazorPagesRazorViewEngineOptionsSetup),
}
},
{
typeof(IConfigureOptions<RazorPagesOptions>),
new[]
{
typeof(RazorPagesOptionsSetup),
}
},
{
typeof(IActionConstraintProvider),
new Type[]
@ -433,6 +426,7 @@ namespace Microsoft.AspNetCore.Mvc
{
typeof(CompiledPageApplicationModelProvider),
typeof(RazorProjectPageApplicationModelProvider),
typeof(PageFilterApplicationModelProvider),
}
},
};