Add a feature to disable file watching in Razor pages (#8369)
* Add a feature to disable file watching in Razor pages Fixes https://github.com/aspnet/Mvc/issues/8362
This commit is contained in:
parent
b156dee4f1
commit
07cc9e66c6
|
|
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
||||||
|
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Transient<IPostConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
|
||||||
|
|
||||||
services.TryAddSingleton<
|
services.TryAddSingleton<
|
||||||
IRazorViewEngineFileProviderAccessor,
|
IRazorViewEngineFileProviderAccessor,
|
||||||
DefaultRazorViewEngineFileProviderAccessor>();
|
DefaultRazorViewEngineFileProviderAccessor>();
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AllowRecompilingViewsOnFileChange { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<CompiledViewDescriptor> CompileAsync(string relativePath)
|
public Task<CompiledViewDescriptor> CompileAsync(string relativePath)
|
||||||
{
|
{
|
||||||
|
|
@ -254,16 +256,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
// Used to validate and recompile
|
// Used to validate and recompile
|
||||||
NormalizedPath = normalizedPath,
|
NormalizedPath = normalizedPath,
|
||||||
ExpirationTokens = new List<IChangeToken>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
var checksums = precompiledView.Item.GetChecksumMetadata();
|
ExpirationTokens = GetExpirationTokens(precompiledView),
|
||||||
for (var i = 0; i < checksums.Count; i++)
|
};
|
||||||
{
|
|
||||||
// We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job,
|
|
||||||
// so it probably will.
|
|
||||||
item.ExpirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We also need to create a new descriptor, because the original one doesn't have expiration tokens on
|
// We also need to create a new descriptor, because the original one doesn't have expiration tokens on
|
||||||
// it. These will be used by the view location cache, which is like an L1 cache for views (this class is
|
// it. These will be used by the view location cache, which is like an L1 cache for views (this class is
|
||||||
|
|
@ -282,10 +277,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath)
|
private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath)
|
||||||
{
|
{
|
||||||
var expirationTokens = new List<IChangeToken>()
|
IList<IChangeToken> expirationTokens = Array.Empty<IChangeToken>();
|
||||||
|
|
||||||
|
if (AllowRecompilingViewsOnFileChange)
|
||||||
{
|
{
|
||||||
_fileProvider.Watch(normalizedPath),
|
var changeToken = _fileProvider.Watch(normalizedPath);
|
||||||
};
|
expirationTokens = new List<IChangeToken> { changeToken };
|
||||||
|
}
|
||||||
|
|
||||||
var projectItem = _projectEngine.FileSystem.GetItem(normalizedPath);
|
var projectItem = _projectEngine.FileSystem.GetItem(normalizedPath);
|
||||||
if (!projectItem.Exists)
|
if (!projectItem.Exists)
|
||||||
|
|
@ -313,9 +311,46 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
_logger.ViewCompilerFoundFileToCompile(normalizedPath);
|
_logger.ViewCompilerFoundFileToCompile(normalizedPath);
|
||||||
|
|
||||||
|
GetChangeTokensFromImports(expirationTokens, projectItem);
|
||||||
|
|
||||||
|
return new ViewCompilerWorkItem()
|
||||||
|
{
|
||||||
|
SupportsCompilation = true,
|
||||||
|
|
||||||
|
NormalizedPath = normalizedPath,
|
||||||
|
ExpirationTokens = expirationTokens,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IList<IChangeToken> GetExpirationTokens(CompiledViewDescriptor precompiledView)
|
||||||
|
{
|
||||||
|
if (!AllowRecompilingViewsOnFileChange)
|
||||||
|
{
|
||||||
|
return Array.Empty<IChangeToken>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var checksums = precompiledView.Item.GetChecksumMetadata();
|
||||||
|
var expirationTokens = new List<IChangeToken>(checksums.Count);
|
||||||
|
|
||||||
|
for (var i = 0; i < checksums.Count; i++)
|
||||||
|
{
|
||||||
|
// We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job,
|
||||||
|
// so it probably will.
|
||||||
|
expirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expirationTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetChangeTokensFromImports(IList<IChangeToken> expirationTokens, RazorProjectItem projectItem)
|
||||||
|
{
|
||||||
|
if (!AllowRecompilingViewsOnFileChange)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// OK this means we can do compilation. For now let's just identify the other files we need to watch
|
// OK this means we can do compilation. For now let's just identify the other files we need to watch
|
||||||
// so we can create the cache entry. Compilation will happen after we release the lock.
|
// so we can create the cache entry. Compilation will happen after we release the lock.
|
||||||
|
|
||||||
var importFeature = _projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault();
|
var importFeature = _projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault();
|
||||||
|
|
||||||
// There should always be an import feature unless someone has misconfigured their RazorProjectEngine.
|
// There should always be an import feature unless someone has misconfigured their RazorProjectEngine.
|
||||||
|
|
@ -330,14 +365,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
expirationTokens.Add(_fileProvider.Watch(physicalImport.FilePath));
|
expirationTokens.Add(_fileProvider.Watch(physicalImport.FilePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ViewCompilerWorkItem()
|
|
||||||
{
|
|
||||||
SupportsCompilation = true,
|
|
||||||
|
|
||||||
NormalizedPath = normalizedPath,
|
|
||||||
ExpirationTokens = expirationTokens,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath)
|
protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath)
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
feature.ViewDescriptors,
|
feature.ViewDescriptors,
|
||||||
_compilationMemoryCacheProvider.CompilationMemoryCache,
|
_compilationMemoryCacheProvider.CompilationMemoryCache,
|
||||||
_logger);
|
_logger)
|
||||||
|
{
|
||||||
|
AllowRecompilingViewsOnFileChange = _viewEngineOptions.AllowRecompilingViewsOnFileChange,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
// 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;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
@ -13,10 +15,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides programmatic configuration for the <see cref="RazorViewEngine"/>.
|
/// Provides programmatic configuration for the <see cref="RazorViewEngine"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RazorViewEngineOptions
|
public class RazorViewEngineOptions : IEnumerable<ICompatibilitySwitch>
|
||||||
{
|
{
|
||||||
|
private readonly ICompatibilitySwitch[] _switches;
|
||||||
|
private readonly CompatibilitySwitch<bool> _allowRecompilingViewsOnFileChange;
|
||||||
private Action<RoslynCompilationContext> _compilationCallback = c => { };
|
private Action<RoslynCompilationContext> _compilationCallback = c => { };
|
||||||
|
|
||||||
|
public RazorViewEngineOptions()
|
||||||
|
{
|
||||||
|
_allowRecompilingViewsOnFileChange = new CompatibilitySwitch<bool>(nameof(AllowRecompilingViewsOnFileChange));
|
||||||
|
_switches = new[]
|
||||||
|
{
|
||||||
|
_allowRecompilingViewsOnFileChange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a <see cref="IList{IViewLocationExpander}"/> used by the <see cref="RazorViewEngine"/>.
|
/// Gets a <see cref="IList{IViewLocationExpander}"/> used by the <see cref="RazorViewEngine"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -181,5 +194,52 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
_compilationCallback = value;
|
_compilationCallback = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that determines if Razor files (Razor Views and Razor Pages) are recompiled and updated
|
||||||
|
/// if files change on disk.
|
||||||
|
/// <para>
|
||||||
|
/// When <see langword="true"/>, MVC will use <see cref="IFileProvider.Watch(string)"/> to watch for changes to
|
||||||
|
/// Razor files in configured <see cref="IFileProvider"/> instances.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The default value is <see langword="true"/> if the version is <see cref = "CompatibilityVersion.Version_2_1" />
|
||||||
|
/// or earlier. If the version is later and <see cref= "IHostingEnvironment.EnvironmentName" /> is <c>Development</c>,
|
||||||
|
/// the default value is <see langword="true"/>. Otherwise, the default value is <see langword="false" />.
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This property is associated with a compatibility switch and can provide a different behavior depending on
|
||||||
|
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
|
||||||
|
/// guidance and examples of setting the application's compatibility version.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
|
||||||
|
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
|
||||||
|
/// lower then this setting will have the value <see langword="true"/> unless explicitly configured.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
|
||||||
|
/// higher then this setting will have the value <see langword="false"/> unless
|
||||||
|
/// <see cref="IHostingEnvironment.EnvironmentName"/> is <c>Development</c> or the value is explicitly configured.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public bool AllowRecompilingViewsOnFileChange
|
||||||
|
{
|
||||||
|
// Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property.
|
||||||
|
get => _allowRecompilingViewsOnFileChange.Value;
|
||||||
|
set => _allowRecompilingViewsOnFileChange.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
|
||||||
|
{
|
||||||
|
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,45 @@
|
||||||
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
{
|
{
|
||||||
/// <summary>
|
internal class RazorViewEngineOptionsSetup :
|
||||||
/// Sets up default options for <see cref="RazorViewEngineOptions"/>.
|
ConfigureCompatibilityOptions<RazorViewEngineOptions>,
|
||||||
/// </summary>
|
IConfigureOptions<RazorViewEngineOptions>
|
||||||
public class RazorViewEngineOptionsSetup : IConfigureOptions<RazorViewEngineOptions>
|
|
||||||
{
|
{
|
||||||
private readonly IHostingEnvironment _hostingEnvironment;
|
private readonly IHostingEnvironment _hostingEnvironment;
|
||||||
|
|
||||||
/// <summary>
|
public RazorViewEngineOptionsSetup(
|
||||||
/// Initializes a new instance of <see cref="RazorViewEngineOptions"/>.
|
IHostingEnvironment hostingEnvironment,
|
||||||
/// </summary>
|
ILoggerFactory loggerFactory,
|
||||||
/// <param name="hostingEnvironment"><see cref="IHostingEnvironment"/> for the application.</param>
|
IOptions<MvcCompatibilityOptions> compatibilityOptions)
|
||||||
public RazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment)
|
: base(loggerFactory, compatibilityOptions)
|
||||||
{
|
{
|
||||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDictionary<string, object> DefaultValues
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var values = new Dictionary<string, object>();
|
||||||
|
if (Version < CompatibilityVersion.Version_2_2)
|
||||||
|
{
|
||||||
|
// Default to true in 2.1 or earlier. In 2.2, we have to conditionally enable this
|
||||||
|
// and consequently this switch has no default value.
|
||||||
|
values[nameof(RazorViewEngineOptions.AllowRecompilingViewsOnFileChange)] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Configure(RazorViewEngineOptions options)
|
public void Configure(RazorViewEngineOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
if (options == null)
|
||||||
|
|
@ -41,6 +59,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
|
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
|
||||||
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
||||||
options.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
options.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
|
||||||
|
|
||||||
|
if (_hostingEnvironment.IsDevelopment())
|
||||||
|
{
|
||||||
|
options.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
@ -18,11 +19,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
private readonly IFileProvider _fileProvider;
|
private readonly IFileProvider _fileProvider;
|
||||||
private readonly string[] _searchPatterns;
|
private readonly string[] _searchPatterns;
|
||||||
private readonly string[] _additionalFilesToTrack;
|
private readonly string[] _additionalFilesToTrack;
|
||||||
|
private readonly bool _watchForChanges;
|
||||||
|
|
||||||
public PageActionDescriptorChangeProvider(
|
public PageActionDescriptorChangeProvider(
|
||||||
RazorTemplateEngine templateEngine,
|
RazorTemplateEngine templateEngine,
|
||||||
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
IRazorViewEngineFileProviderAccessor fileProviderAccessor,
|
||||||
IOptions<RazorPagesOptions> razorPagesOptions)
|
IOptions<RazorPagesOptions> razorPagesOptions,
|
||||||
|
IOptions<RazorViewEngineOptions> razorViewEngineOptions)
|
||||||
{
|
{
|
||||||
if (templateEngine == null)
|
if (templateEngine == null)
|
||||||
{
|
{
|
||||||
|
|
@ -39,6 +42,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
throw new ArgumentNullException(nameof(razorPagesOptions));
|
throw new ArgumentNullException(nameof(razorPagesOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_watchForChanges = razorViewEngineOptions.Value.AllowRecompilingViewsOnFileChange;
|
||||||
|
if (!_watchForChanges)
|
||||||
|
{
|
||||||
|
// No need to do any additional work if we aren't going to be watching for file changes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_fileProvider = fileProviderAccessor.FileProvider;
|
_fileProvider = fileProviderAccessor.FileProvider;
|
||||||
|
|
||||||
var rootDirectory = razorPagesOptions.Value.RootDirectory;
|
var rootDirectory = razorPagesOptions.Value.RootDirectory;
|
||||||
|
|
@ -84,6 +94,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
public IChangeToken GetChangeToken()
|
public IChangeToken GetChangeToken()
|
||||||
{
|
{
|
||||||
|
if (!_watchForChanges)
|
||||||
|
{
|
||||||
|
return NullChangeToken.Singleton;
|
||||||
|
}
|
||||||
|
|
||||||
var changeTokens = new IChangeToken[_additionalFilesToTrack.Length + _searchPatterns.Length];
|
var changeTokens = new IChangeToken[_additionalFilesToTrack.Length + _searchPatterns.Length];
|
||||||
for (var i = 0; i < _additionalFilesToTrack.Length; i++)
|
for (var i = 0; i < _additionalFilesToTrack.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
// 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.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
// Verifies that updating Razor files (views and pages) with AllowRecompilingViewsOnFileChange=true works
|
||||||
|
public class RazorFileUpdateTests : IClassFixture<MvcTestFixture<RazorWebSite.Startup>>
|
||||||
|
{
|
||||||
|
public RazorFileUpdateTests(MvcTestFixture<RazorWebSite.Startup> fixture)
|
||||||
|
{
|
||||||
|
var factory = fixture.WithWebHostBuilder(builder =>
|
||||||
|
{
|
||||||
|
builder.UseStartup<RazorWebSite.Startup>();
|
||||||
|
builder.ConfigureTestServices(services =>
|
||||||
|
{
|
||||||
|
services.Configure<RazorViewEngineOptions>(options => options.AllowRecompilingViewsOnFileChange = true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Client = factory.CreateDefaultClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RazorViews_AreUpdatedOnChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected1 = "Original content";
|
||||||
|
var expected2 = "New content";
|
||||||
|
var path = "/Views/UpdateableShared/_Partial.cshtml";
|
||||||
|
|
||||||
|
// Act - 1
|
||||||
|
var body = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
await UpdateFile(path, expected2);
|
||||||
|
body = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RazorViews_AreUpdatedWhenViewImportsChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var content = "@GetType().Assembly.FullName";
|
||||||
|
await UpdateFile("/Views/UpdateableIndex/Index.cshtml", content);
|
||||||
|
var initial = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Trigger a change in ViewImports
|
||||||
|
await UpdateFile("/Views/UpdateableIndex/_ViewImports.cshtml", string.Empty);
|
||||||
|
var updated = await Client.GetStringAsync("/UpdateableFileProvider");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotEqual(initial, updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RazorPages_AreUpdatedOnChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected1 = "Original content";
|
||||||
|
var expected2 = "New content";
|
||||||
|
|
||||||
|
// Act - 1
|
||||||
|
var body = await Client.GetStringAsync("/UpdateablePage");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
await UpdateRazorPages();
|
||||||
|
await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + expected2);
|
||||||
|
body = await Client.GetStringAsync("/UpdateablePage");
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RazorPages_AreUpdatedWhenViewImportsChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var content = "@GetType().Assembly.FullName";
|
||||||
|
await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + content);
|
||||||
|
var initial = await Client.GetStringAsync("/UpdateablePage");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
// Trigger a change in ViewImports
|
||||||
|
await UpdateRazorPages();
|
||||||
|
await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + content);
|
||||||
|
var updated = await Client.GetStringAsync("/UpdateablePage");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotEqual(initial, updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateFile(string path, string content)
|
||||||
|
{
|
||||||
|
var updateContent = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "path", path },
|
||||||
|
{ "content", content },
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await Client.PostAsync($"/UpdateableFileProvider/Update", updateContent);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateRazorPages()
|
||||||
|
{
|
||||||
|
var response = await Client.PostAsync($"/UpdateableFileProvider/UpdateRazorPages", new StringContent(string.Empty));
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -270,28 +270,6 @@ Hello from Shared/_EmbeddedPartial
|
||||||
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RazorViewEngine_UpdatesViewsReferencedViaRelativePathsOnChange()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var expected1 = "Original content";
|
|
||||||
var expected2 = "New content";
|
|
||||||
|
|
||||||
// Act - 1
|
|
||||||
var body = await Client.GetStringAsync("/UpdateableFileProvider");
|
|
||||||
|
|
||||||
// Assert - 1
|
|
||||||
Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true);
|
|
||||||
|
|
||||||
// Act - 2
|
|
||||||
var response = await Client.PostAsync("/UpdateableFileProvider/Update", new StringContent(string.Empty));
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
body = await Client.GetStringAsync("/UpdateableFileProvider");
|
|
||||||
|
|
||||||
// Assert - 1
|
|
||||||
Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task LayoutValueIsPassedBetweenNestedViewStarts()
|
public async Task LayoutValueIsPassedBetweenNestedViewStarts()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,25 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var result1 = await viewCompiler.CompileAsync(path);
|
var result1 = await viewCompiler.CompileAsync(path);
|
||||||
var result2 = await viewCompiler.CompileAsync(path);
|
var result2 = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(result1, result2);
|
||||||
|
Assert.Null(result1.ViewAttribute);
|
||||||
|
Assert.Empty(result1.ExpirationTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CompileAsync_ReturnsResultWithExpirationToken_WhenWatchingForFileChanges()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/file/does-not-exist";
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var viewCompiler = GetViewCompiler(fileProvider);
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result1 = await viewCompiler.CompileAsync(path);
|
||||||
|
var result2 = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Same(result1, result2);
|
Assert.Same(result1, result2);
|
||||||
Assert.Null(result1.ViewAttribute);
|
Assert.Null(result1.ViewAttribute);
|
||||||
|
|
@ -57,6 +76,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
// Act
|
// Act
|
||||||
var result = await viewCompiler.CompileAsync(path);
|
var result = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result.ViewAttribute);
|
||||||
|
Assert.Empty(result.ExpirationTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CompileAsync_AddsChangeTokensForViewStartsIfFileExists_WhenWatchingForFileChanges()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/file/exists/FilePath.cshtml";
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
fileProvider.AddFile(path, "Content");
|
||||||
|
var viewCompiler = GetViewCompiler(fileProvider);
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(result.ViewAttribute);
|
Assert.NotNull(result.ViewAttribute);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
|
|
@ -92,13 +129,40 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CompileAsync_InvalidatesCache_IfChangeTokenExpires()
|
public async Task CompileAsync_DoesNotInvalidCache_IfChangeTokenChanges()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var path = "/Views/Home/Index.cshtml";
|
var path = "/Views/Home/Index.cshtml";
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||||
var viewCompiler = GetViewCompiler(fileProvider);
|
var viewCompiler = GetViewCompiler(fileProvider);
|
||||||
|
var changeToken = fileProvider.Watch(path);
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var result1 = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
Assert.NotNull(result1.ViewAttribute);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
fileProvider.DeleteFile(path);
|
||||||
|
fileProvider.GetChangeToken(path).HasChanged = true;
|
||||||
|
viewCompiler.Compile = _ => throw new Exception("Can't call me");
|
||||||
|
var result2 = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.Same(result1, result2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CompileAsync_InvalidatesCache_IfChangeTokenExpires_WhenWatchingForFileChanges()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Views/Home/Index.cshtml";
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||||
|
var viewCompiler = GetViewCompiler(fileProvider);
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
var result1 = await viewCompiler.CompileAsync(path);
|
var result1 = await viewCompiler.CompileAsync(path);
|
||||||
|
|
@ -125,6 +189,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||||
var viewCompiler = GetViewCompiler(fileProvider);
|
var viewCompiler = GetViewCompiler(fileProvider);
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
var expected2 = new CompiledViewDescriptor();
|
var expected2 = new CompiledViewDescriptor();
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
|
|
@ -151,6 +216,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var fileProvider = new TestFileProvider();
|
var fileProvider = new TestFileProvider();
|
||||||
var fileInfo = fileProvider.AddFile(path, "some content");
|
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||||
var viewCompiler = GetViewCompiler(fileProvider);
|
var viewCompiler = GetViewCompiler(fileProvider);
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
var expected2 = new CompiledViewDescriptor();
|
var expected2 = new CompiledViewDescriptor();
|
||||||
|
|
||||||
// Act 1
|
// Act 1
|
||||||
|
|
@ -327,6 +393,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
};
|
};
|
||||||
|
|
||||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await viewCompiler.CompileAsync(path);
|
var result = await viewCompiler.CompileAsync(path);
|
||||||
|
|
@ -371,7 +438,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompile()
|
public async Task CompileAsync_PrecompiledViewWithChecksum_DoesNotAddExpirationTokens()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var path = "/Views/Home/Index.cshtml";
|
var path = "/Views/Home/Index.cshtml";
|
||||||
|
|
@ -392,11 +459,43 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
|
|
||||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(precompiledView.Item, result.Item);
|
||||||
|
Assert.Empty(result.ExpirationTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompile()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = "/Views/Home/Index.cshtml";
|
||||||
|
|
||||||
|
var fileProvider = new TestFileProvider();
|
||||||
|
var fileInfo = fileProvider.AddFile(path, "some content");
|
||||||
|
|
||||||
|
var expected2 = new CompiledViewDescriptor();
|
||||||
|
|
||||||
|
var precompiledView = new CompiledViewDescriptor
|
||||||
|
{
|
||||||
|
RelativePath = path,
|
||||||
|
Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[]
|
||||||
|
{
|
||||||
|
new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
var result = await viewCompiler.CompileAsync(path);
|
var result = await viewCompiler.CompileAsync(path);
|
||||||
|
|
||||||
// Assert - 1
|
// Assert - 1
|
||||||
Assert.Same(precompiledView.Item, result.Item);
|
Assert.Same(precompiledView.Item, result.Item);
|
||||||
|
Assert.NotEmpty(result.ExpirationTokens);
|
||||||
|
|
||||||
// Act - 2
|
// Act - 2
|
||||||
fileInfo.Content = "some other content";
|
fileInfo.Content = "some other content";
|
||||||
|
|
@ -427,6 +526,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
};
|
};
|
||||||
|
|
||||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
var result = await viewCompiler.CompileAsync(path);
|
var result = await viewCompiler.CompileAsync(path);
|
||||||
|
|
@ -463,6 +563,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
};
|
};
|
||||||
|
|
||||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
viewCompiler.Compile = _ => expected1;
|
viewCompiler.Compile = _ => expected1;
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
|
|
@ -504,6 +605,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
};
|
};
|
||||||
|
|
||||||
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView });
|
||||||
|
viewCompiler.AllowRecompilingViewsOnFileChange = true;
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
var result = await viewCompiler.CompileAsync(path);
|
var result = await viewCompiler.CompileAsync(path);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
// 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 Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -19,7 +23,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var hostingEnv = new Mock<IHostingEnvironment>();
|
var hostingEnv = new Mock<IHostingEnvironment>();
|
||||||
hostingEnv.SetupGet(e => e.ContentRootFileProvider)
|
hostingEnv.SetupGet(e => e.ContentRootFileProvider)
|
||||||
.Returns(expected);
|
.Returns(expected);
|
||||||
var optionsSetup = new RazorViewEngineOptionsSetup(hostingEnv.Object);
|
|
||||||
|
var optionsSetup = GetSetup(hostingEnvironment: hostingEnv.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
optionsSetup.Configure(options);
|
optionsSetup.Configure(options);
|
||||||
|
|
@ -28,5 +33,128 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
var fileProvider = Assert.Single(options.FileProviders);
|
var fileProvider = Assert.Single(options.FileProviders);
|
||||||
Assert.Same(expected, fileProvider);
|
Assert.Same(expected, fileProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PostConfigure_SetsAllowRecompilingViewsOnFileChange_For21()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new RazorViewEngineOptions();
|
||||||
|
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_1);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
optionsSetup.PostConfigure(string.Empty, options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(options.AllowRecompilingViewsOnFileChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(CompatibilityVersion.Version_2_2)]
|
||||||
|
[InlineData(CompatibilityVersion.Latest)]
|
||||||
|
public void PostConfigure_SetsAllowRecompilingViewsOnFileChange_InDevelopmentMode(CompatibilityVersion compatibilityVersion)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new RazorViewEngineOptions();
|
||||||
|
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Development);
|
||||||
|
var optionsSetup = GetSetup(compatibilityVersion, hostingEnv);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
optionsSetup.PostConfigure(string.Empty, options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(options.AllowRecompilingViewsOnFileChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(CompatibilityVersion.Version_2_2)]
|
||||||
|
[InlineData(CompatibilityVersion.Latest)]
|
||||||
|
public void PostConfigure_DoesNotSetAllowRecompilingViewsOnFileChange_WhenNotInDevelopment(CompatibilityVersion compatibilityVersion)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var options = new RazorViewEngineOptions();
|
||||||
|
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Staging);
|
||||||
|
var optionsSetup = GetSetup(compatibilityVersion, hostingEnv);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
optionsSetup.Configure(options);
|
||||||
|
optionsSetup.PostConfigure(string.Empty, options);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(options.AllowRecompilingViewsOnFileChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RazorViewEngineOptionsSetup_DoesNotOverwriteAllowRecompilingViewsOnFileChange_In21CompatMode()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Staging);
|
||||||
|
var compatibilityVersion = new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_1 };
|
||||||
|
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_1, hostingEnv);
|
||||||
|
var serviceProvider = new ServiceCollection()
|
||||||
|
.AddOptions()
|
||||||
|
.AddSingleton<IConfigureOptions<RazorViewEngineOptions>>(optionsSetup)
|
||||||
|
.Configure<RazorViewEngineOptions>(o => o.AllowRecompilingViewsOnFileChange = false)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var options = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(options.Value.AllowRecompilingViewsOnFileChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RazorViewEngineOptionsSetup_ConfiguresAllowRecompilingViewsOnFileChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Production);
|
||||||
|
var compatibilityVersion = new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_2 };
|
||||||
|
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_2, hostingEnv);
|
||||||
|
var serviceProvider = new ServiceCollection()
|
||||||
|
.AddOptions()
|
||||||
|
.AddSingleton<IConfigureOptions<RazorViewEngineOptions>>(optionsSetup)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var options = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(options.Value.AllowRecompilingViewsOnFileChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RazorViewEngineOptionsSetup_DoesNotOverwriteAllowRecompilingViewsOnFileChange()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var hostingEnv = Mock.Of<IHostingEnvironment>(env => env.EnvironmentName == EnvironmentName.Production);
|
||||||
|
var optionsSetup = GetSetup(CompatibilityVersion.Version_2_2, hostingEnv);
|
||||||
|
var serviceProvider = new ServiceCollection()
|
||||||
|
.AddOptions()
|
||||||
|
.AddSingleton<IConfigureOptions<RazorViewEngineOptions>>(optionsSetup)
|
||||||
|
.Configure<RazorViewEngineOptions>(o => o.AllowRecompilingViewsOnFileChange = true)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var options = serviceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(options.Value.AllowRecompilingViewsOnFileChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RazorViewEngineOptionsSetup GetSetup(
|
||||||
|
CompatibilityVersion compatibilityVersion = CompatibilityVersion.Latest,
|
||||||
|
IHostingEnvironment hostingEnvironment = null)
|
||||||
|
{
|
||||||
|
hostingEnvironment = hostingEnvironment ?? Mock.Of<IHostingEnvironment>();
|
||||||
|
var compatibilityOptions = new MvcCompatibilityOptions { CompatibilityVersion = compatibilityVersion };
|
||||||
|
|
||||||
|
return new RazorViewEngineOptionsSetup(
|
||||||
|
hostingEnvironment,
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(compatibilityOptions));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
|
@ -1979,7 +1980,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
IEnumerable<string> areaViewLocationFormats = null,
|
IEnumerable<string> areaViewLocationFormats = null,
|
||||||
IEnumerable<string> pageViewLocationFormats = null)
|
IEnumerable<string> pageViewLocationFormats = null)
|
||||||
{
|
{
|
||||||
var optionsSetup = new RazorViewEngineOptionsSetup(Mock.Of<IHostingEnvironment>());
|
var optionsSetup = new RazorViewEngineOptionsSetup(
|
||||||
|
Mock.Of<IHostingEnvironment>(),
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(new MvcCompatibilityOptions()));
|
||||||
|
|
||||||
var options = new RazorViewEngineOptions();
|
var options = new RazorViewEngineOptions();
|
||||||
optionsSetup.Configure(options);
|
optionsSetup.Configure(options);
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
var templateEngine = new RazorTemplateEngine(
|
var templateEngine = new RazorTemplateEngine(
|
||||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||||
fileSystem);
|
fileSystem);
|
||||||
var options = Options.Create(new RazorPagesOptions());
|
var razorPageOptions = Options.Create(new RazorPagesOptions());
|
||||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
|
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||||
|
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, razorPageOptions, razorViewEngineOptions);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var changeToken = changeProvider.GetChangeToken();
|
var changeToken = changeProvider.GetChangeToken();
|
||||||
|
|
@ -57,8 +58,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
fileSystem);
|
fileSystem);
|
||||||
var options = Options.Create(new RazorPagesOptions());
|
var options = Options.Create(new RazorPagesOptions());
|
||||||
options.Value.RootDirectory = rootDirectory;
|
options.Value.RootDirectory = rootDirectory;
|
||||||
|
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||||
|
|
||||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
|
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var changeToken = changeProvider.GetChangeToken();
|
var changeToken = changeProvider.GetChangeToken();
|
||||||
|
|
@ -81,7 +83,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||||
fileSystem);
|
fileSystem);
|
||||||
var options = Options.Create(new RazorPagesOptions { AllowAreas = true });
|
var options = Options.Create(new RazorPagesOptions { AllowAreas = true });
|
||||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
|
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||||
|
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var changeToken = changeProvider.GetChangeToken();
|
var changeToken = changeProvider.GetChangeToken();
|
||||||
|
|
@ -104,8 +107,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
||||||
var options = Options.Create(new RazorPagesOptions());
|
var options = Options.Create(new RazorPagesOptions());
|
||||||
options.Value.RootDirectory = "/dir1/dir2";
|
options.Value.RootDirectory = "/dir1/dir2";
|
||||||
|
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||||
|
|
||||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
|
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
||||||
|
|
@ -131,7 +135,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
options.Value.RootDirectory = "/dir1/dir2";
|
options.Value.RootDirectory = "/dir1/dir2";
|
||||||
options.Value.AllowAreas = true;
|
options.Value.AllowAreas = true;
|
||||||
|
|
||||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
|
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||||
|
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
||||||
|
|
@ -155,8 +160,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
fileSystem);
|
fileSystem);
|
||||||
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
||||||
var options = Options.Create(new RazorPagesOptions { AllowAreas = false });
|
var options = Options.Create(new RazorPagesOptions { AllowAreas = false });
|
||||||
|
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true });
|
||||||
|
|
||||||
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options);
|
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
var compositeChangeToken = Assert.IsType<CompositeChangeToken>(changeProvider.GetChangeToken());
|
||||||
|
|
@ -164,5 +170,27 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), changeToken),
|
changeToken => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), changeToken),
|
||||||
changeToken => Assert.Same(fileProvider.GetChangeToken("/Pages/**/*.cshtml"), changeToken));
|
changeToken => Assert.Same(fileProvider.GetChangeToken("/Pages/**/*.cshtml"), changeToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetChangeToken_DoesNotWatch_WhenOptionIsReset()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileProvider = new Mock<IFileProvider>(MockBehavior.Strict);
|
||||||
|
var accessor = Mock.Of<IRazorViewEngineFileProviderAccessor>(a => a.FileProvider == fileProvider.Object);
|
||||||
|
|
||||||
|
var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment);
|
||||||
|
var templateEngine = new RazorTemplateEngine(
|
||||||
|
RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine,
|
||||||
|
fileSystem);
|
||||||
|
templateEngine.Options.ImportsFileName = "_ViewImports.cshtml";
|
||||||
|
var options = Options.Create(new RazorPagesOptions());
|
||||||
|
var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions());
|
||||||
|
|
||||||
|
var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var compositeChangeToken = Assert.IsType<NullChangeToken>(changeProvider.GetChangeToken());
|
||||||
|
fileProvider.Verify(f => f.Watch(It.IsAny<string>()), Times.Never());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
// 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 Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -186,7 +187,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
private static RazorViewEngineOptions GetViewEngineOptions()
|
private static RazorViewEngineOptions GetViewEngineOptions()
|
||||||
{
|
{
|
||||||
var defaultSetup = new RazorViewEngineOptionsSetup(Mock.Of<IHostingEnvironment>());
|
var defaultSetup = new RazorViewEngineOptionsSetup(
|
||||||
|
Mock.Of<IHostingEnvironment>(),
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
|
Options.Create(new MvcCompatibilityOptions()));
|
||||||
var options = new RazorViewEngineOptions();
|
var options = new RazorViewEngineOptions();
|
||||||
defaultSetup.Configure(options);
|
defaultSetup.Configure(options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +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 Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
|
|
@ -32,6 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.False(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -44,6 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.Null(mvcOptions.MaxValidationDepth);
|
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||||
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
|
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
|
||||||
|
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -61,6 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -73,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.Null(mvcOptions.MaxValidationDepth);
|
Assert.Null(mvcOptions.MaxValidationDepth);
|
||||||
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
|
Assert.True(apiBehaviorOptions.SuppressMapClientErrors);
|
||||||
|
Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -90,6 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -102,6 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
||||||
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
|
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
|
||||||
|
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -119,6 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
var jsonOptions = services.GetRequiredService<IOptions<MvcJsonOptions>>().Value;
|
||||||
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
var razorPagesOptions = services.GetRequiredService<IOptions<RazorPagesOptions>>().Value;
|
||||||
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
var apiBehaviorOptions = services.GetRequiredService<IOptions<ApiBehaviorOptions>>().Value;
|
||||||
|
var razorViewEngineOptions = services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
Assert.True(mvcOptions.AllowCombiningAuthorizeFilters);
|
||||||
|
|
@ -131,11 +141,13 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest
|
||||||
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
Assert.Equal(32, mvcOptions.MaxValidationDepth);
|
||||||
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses);
|
||||||
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
|
Assert.False(apiBehaviorOptions.SuppressMapClientErrors);
|
||||||
|
Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This just does the minimum needed to be able to resolve these options.
|
// This just does the minimum needed to be able to resolve these options.
|
||||||
private static void AddHostingServices(IServiceCollection serviceCollection)
|
private static void AddHostingServices(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
|
serviceCollection.AddSingleton(Mock.Of<IHostingEnvironment>());
|
||||||
serviceCollection.AddLogging();
|
serviceCollection.AddLogging();
|
||||||
serviceCollection.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
serviceCollection.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace RazorPagesWebSite
|
||||||
|
{
|
||||||
|
public class NonWatchingPhysicalFileProvider : PhysicalFileProvider, IFileProvider
|
||||||
|
{
|
||||||
|
public NonWatchingPhysicalFileProvider(string root) : base(root)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IChangeToken IFileProvider.Watch(string filter) => throw new ArgumentException("This method should not be called.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using RazorPagesWebSite.Conventions;
|
using RazorPagesWebSite.Conventions;
|
||||||
|
|
@ -11,11 +12,18 @@ namespace RazorPagesWebSite
|
||||||
{
|
{
|
||||||
public class StartupWithBasePath
|
public class StartupWithBasePath
|
||||||
{
|
{
|
||||||
|
private readonly IHostingEnvironment _hostingEnvironment;
|
||||||
|
|
||||||
|
public StartupWithBasePath(IHostingEnvironment hostingEnvironment)
|
||||||
|
{
|
||||||
|
_hostingEnvironment = hostingEnvironment;
|
||||||
|
}
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||||
.AddCookie(options => options.LoginPath = "/Login");
|
.AddCookie(options => options.LoginPath = "/Login");
|
||||||
services.AddMvc()
|
var builder = services.AddMvc()
|
||||||
.AddCookieTempDataProvider()
|
.AddCookieTempDataProvider()
|
||||||
.AddRazorPagesOptions(options =>
|
.AddRazorPagesOptions(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -27,6 +35,14 @@ namespace RazorPagesWebSite
|
||||||
options.Conventions.Add(new CustomModelTypeConvention());
|
options.Conventions.Add(new CustomModelTypeConvention());
|
||||||
})
|
})
|
||||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||||
|
|
||||||
|
// Ensure we don't have code paths that call IFileProvider.Watch in the default code path.
|
||||||
|
// Comment this code block if you happen to run this site in Development.
|
||||||
|
builder.AddRazorOptions(options =>
|
||||||
|
{
|
||||||
|
options.FileProviders.Clear();
|
||||||
|
options.FileProviders.Add(new NonWatchingPhysicalFileProvider(_hostingEnvironment.ContentRootPath));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,16 @@ namespace RazorWebSite
|
||||||
public IActionResult Index() => View("/Views/UpdateableIndex/Index.cshtml");
|
public IActionResult Index() => View("/Views/UpdateableIndex/Index.cshtml");
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Update([FromServices] UpdateableFileProvider fileProvider)
|
public IActionResult Update([FromServices] UpdateableFileProvider fileProvider, string path, string content)
|
||||||
{
|
{
|
||||||
fileProvider.UpdateContent("/Views/UpdateableShared/_Partial.cshtml", "New content");
|
fileProvider.UpdateContent(path, content);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult UpdateRazorPages([FromServices] UpdateableFileProvider fileProvider)
|
||||||
|
{
|
||||||
|
fileProvider.CancelRazorPages();
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// 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;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -13,8 +14,14 @@ namespace RazorWebSite
|
||||||
{
|
{
|
||||||
public class UpdateableFileProvider : IFileProvider
|
public class UpdateableFileProvider : IFileProvider
|
||||||
{
|
{
|
||||||
|
public CancellationTokenSource _pagesTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
private readonly Dictionary<string, TestFileInfo> _content = new Dictionary<string, TestFileInfo>()
|
private readonly Dictionary<string, TestFileInfo> _content = new Dictionary<string, TestFileInfo>()
|
||||||
{
|
{
|
||||||
|
{
|
||||||
|
"/Views/UpdateableIndex/_ViewImports.cshtml",
|
||||||
|
new TestFileInfo(string.Empty)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"/Views/UpdateableIndex/Index.cshtml",
|
"/Views/UpdateableIndex/Index.cshtml",
|
||||||
new TestFileInfo(@"@Html.Partial(""../UpdateableShared/_Partial.cshtml"")")
|
new TestFileInfo(@"@Html.Partial(""../UpdateableShared/_Partial.cshtml"")")
|
||||||
|
|
@ -23,9 +30,21 @@ namespace RazorWebSite
|
||||||
"/Views/UpdateableShared/_Partial.cshtml",
|
"/Views/UpdateableShared/_Partial.cshtml",
|
||||||
new TestFileInfo("Original content")
|
new TestFileInfo("Original content")
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"/Pages/UpdateablePage.cshtml",
|
||||||
|
new TestFileInfo("@page" + Environment.NewLine + "Original content")
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
public IDirectoryContents GetDirectoryContents(string subpath) => new NotFoundDirectoryContents();
|
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||||
|
{
|
||||||
|
if (subpath == "/Pages")
|
||||||
|
{
|
||||||
|
return new PagesDirectoryContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NotFoundDirectoryContents();
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateContent(string subpath, string content)
|
public void UpdateContent(string subpath, string content)
|
||||||
{
|
{
|
||||||
|
|
@ -34,10 +53,16 @@ namespace RazorWebSite
|
||||||
_content[subpath] = new TestFileInfo(content);
|
_content[subpath] = new TestFileInfo(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CancelRazorPages()
|
||||||
|
{
|
||||||
|
var oldToken = _pagesTokenSource;
|
||||||
|
_pagesTokenSource = new CancellationTokenSource();
|
||||||
|
oldToken.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
public IFileInfo GetFileInfo(string subpath)
|
public IFileInfo GetFileInfo(string subpath)
|
||||||
{
|
{
|
||||||
TestFileInfo fileInfo;
|
if (!_content.TryGetValue(subpath, out var fileInfo))
|
||||||
if (!_content.TryGetValue(subpath, out fileInfo))
|
|
||||||
{
|
{
|
||||||
fileInfo = new TestFileInfo(null);
|
fileInfo = new TestFileInfo(null);
|
||||||
}
|
}
|
||||||
|
|
@ -47,8 +72,12 @@ namespace RazorWebSite
|
||||||
|
|
||||||
public IChangeToken Watch(string filter)
|
public IChangeToken Watch(string filter)
|
||||||
{
|
{
|
||||||
TestFileInfo fileInfo;
|
if (filter == "/Pages/**/*.cshtml")
|
||||||
if (_content.TryGetValue(filter, out fileInfo))
|
{
|
||||||
|
return new CancellationChangeToken(_pagesTokenSource.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_content.TryGetValue(filter, out var fileInfo))
|
||||||
{
|
{
|
||||||
return fileInfo.ChangeToken;
|
return fileInfo.ChangeToken;
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +100,7 @@ namespace RazorWebSite
|
||||||
public bool IsDirectory => false;
|
public bool IsDirectory => false;
|
||||||
public DateTimeOffset LastModified => DateTimeOffset.MinValue;
|
public DateTimeOffset LastModified => DateTimeOffset.MinValue;
|
||||||
public long Length => -1;
|
public long Length => -1;
|
||||||
public string Name => null;
|
public string Name { get; set; }
|
||||||
public string PhysicalPath => null;
|
public string PhysicalPath => null;
|
||||||
public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource();
|
public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource();
|
||||||
public CancellationChangeToken ChangeToken { get; }
|
public CancellationChangeToken ChangeToken { get; }
|
||||||
|
|
@ -81,5 +110,23 @@ namespace RazorWebSite
|
||||||
return new MemoryStream(Encoding.UTF8.GetBytes(_content));
|
return new MemoryStream(Encoding.UTF8.GetBytes(_content));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PagesDirectoryContents : IDirectoryContents
|
||||||
|
{
|
||||||
|
public bool Exists => true;
|
||||||
|
|
||||||
|
public IEnumerator<IFileInfo> GetEnumerator()
|
||||||
|
{
|
||||||
|
var file = new TestFileInfo("@page" + Environment.NewLine + "Original content")
|
||||||
|
{
|
||||||
|
Name = "UpdateablePage.cshtml"
|
||||||
|
};
|
||||||
|
|
||||||
|
var files = new List<IFileInfo> { file };
|
||||||
|
return files.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue