Merge pull request #9511 from umbraco/netcore/task/app_data

Cleans up usage of SystemDirectories, removes App_Data usages
This commit is contained in:
Bjarke Berg
2020-12-08 08:15:26 +01:00
committed by GitHub
18 changed files with 114 additions and 74 deletions

1
.gitignore vendored
View File

@@ -196,3 +196,4 @@ src/Umbraco.Tests.Integration/umbraco/logs/
src/Umbraco.Tests.Integration/Views/
src/Umbraco.Tests/TEMP/
/src/Umbraco.Web.UI.NetCore/Umbraco/Data/*

View File

@@ -4,7 +4,6 @@ namespace Umbraco.Core.Configuration
{
Unknown = 0,
Default,
AspNetTemp,
EnvironmentTemp
}
}

View File

@@ -1,15 +1,33 @@
namespace Umbraco.Core
namespace Umbraco.Core
{
public static partial class Constants
{
public static class SystemDirectories
{
/// <summary>
/// The aspnet bin folder
/// </summary>
public const string Bin = "~/bin";
// TODO: Shouldn't this exist underneath /Umbraco in the content root?
public const string Config = "~/config";
public const string Data = "~/App_Data";
/// <summary>
/// The Umbraco folder that exists at the content root.
/// </summary>
/// <remarks>
/// This is not the same as the Umbraco web folder which is configurable for serving front-end files.
/// </remarks>
public const string Umbraco = "~/Umbraco";
/// <summary>
/// The Umbraco data folder in the content root
/// </summary>
public const string Data = Umbraco + "/Data";
/// <summary>
/// The Umbraco temp data folder in the content root
/// </summary>
public const string TempData = Data + "/TEMP";
public const string TempFileUploads = TempData + "/FileUploads";
@@ -18,8 +36,6 @@
public const string Install = "~/install";
public const string AppCode = "~/App_Code";
public const string AppPlugins = "/App_Plugins";
public const string MvcViews = "~/Views";
@@ -32,7 +48,10 @@
public const string Preview = Data + "/preview";
public const string LogFiles= "~/Logs";
/// <summary>
/// The default folder where Umbraco log files are stored
/// </summary>
public const string LogFiles = Umbraco + "/Logs";
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
@@ -71,7 +71,6 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
//so these need to be tested differently
var pathsToCheckWithRestarts = new Dictionary<string, PermissionCheckRequirement>
{
{ Constants.SystemDirectories.AppCode, PermissionCheckRequirement.Optional },
{ Constants.SystemDirectories.Bin, PermissionCheckRequirement.Optional }
};

View File

@@ -5,6 +5,7 @@ namespace Umbraco.Core.Hosting
public interface IHostingEnvironment
{
string SiteName { get; }
string ApplicationId { get; }
/// <summary>
@@ -35,8 +36,6 @@ namespace Umbraco.Core.Hosting
/// <summary>
/// Maps a virtual path to a physical path to the application's web root
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
/// <remarks>
/// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however
/// in netcore the web root is /www therefore this will Map to a physical path within www.
@@ -46,8 +45,6 @@ namespace Umbraco.Core.Hosting
/// <summary>
/// Maps a virtual path to a physical path to the application's root (not always equal to the web root)
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
/// <remarks>
/// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however
/// in netcore the web root is /www therefore this will Map to a physical path within www.
@@ -58,7 +55,6 @@ namespace Umbraco.Core.Hosting
/// Converts a virtual path to an absolute URL path based on the application's web root
/// </summary>
/// <param name="virtualPath">The virtual path. Must start with either ~/ or / else an exception is thrown.</param>
/// <returns></returns>
/// <remarks>
/// This maps the virtual path syntax to the web root. For example when hosting in a virtual directory called "site" and the value "~/pages/test" is passed in, it will
/// map to "/site/pages/test" where "/site" is the value of <see cref="ApplicationVirtualPath"/>.

View File

@@ -1,10 +1,10 @@
namespace Umbraco.Core.Logging
namespace Umbraco.Core.Logging
{
public interface ILoggingConfiguration
{
/// <summary>
/// The physical path where logs are stored
/// Gets the physical path where logs are stored
/// </summary>
string LogDirectory { get; }
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
@@ -111,11 +111,14 @@ namespace Umbraco.Core.Diagnostics
// filter everywhere in our code = not!
var stacktrace = withException ? Environment.StackTrace : string.Empty;
var filepath = Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data/MiniDump");
if (Directory.Exists(filepath) == false)
Directory.CreateDirectory(filepath);
var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump");
var filename = Path.Combine(filepath, $"{DateTime.UtcNow:yyyyMMddTHHmmss}.{Guid.NewGuid().ToString("N").Substring(0, 4)}.dmp");
if (Directory.Exists(directory) == false)
{
Directory.CreateDirectory(directory);
}
var filename = Path.Combine(directory, $"{DateTime.UtcNow:yyyyMMddTHHmmss}.{Guid.NewGuid().ToString("N").Substring(0, 4)}.dmp");
using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
{
return Write(marchal, stream.SafeFileHandle, options, withException);
@@ -127,9 +130,12 @@ namespace Umbraco.Core.Diagnostics
{
lock (LockO)
{
var filepath = Path.Combine(hostingEnvironment.ApplicationPhysicalPath, "App_Data/MiniDump");
if (Directory.Exists(filepath) == false) return true;
var count = Directory.GetFiles(filepath, "*.dmp").Length;
var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump");
if (Directory.Exists(directory) == false)
{
return true;
}
var count = Directory.GetFiles(directory, "*.dmp").Length;
return count < 8;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Text;
using Serilog;
@@ -127,7 +127,7 @@ namespace Umbraco.Core.Logging.Serilog
/// Outputs a CLEF format JSON log at /App_Data/Logs/
/// </summary>
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
/// <param name="loggingConfiguration"></param>
/// <param name="loggingConfiguration">The logging configuration</param>
/// <param name="minimumLevel">The log level you wish the JSON file to collect - default is Verbose (highest)</param>
/// <param name="retainedFileCount">The number of days to keep log files. Default is set to null which means all logs are kept</param>
public static LoggerConfiguration OutputDefaultJsonFile(
@@ -135,13 +135,13 @@ namespace Umbraco.Core.Logging.Serilog
IHostingEnvironment hostingEnvironment,
ILoggingConfiguration loggingConfiguration, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
{
//.clef format (Compact log event format, that can be imported into local SEQ & will make searching/filtering logs easier)
//Ends with ..txt as Date is inserted before file extension substring
// .clef format (Compact log event format, that can be imported into local SEQ & will make searching/filtering logs easier)
// Ends with ..txt as Date is inserted before file extension substring
logConfig.WriteTo.File(new CompactJsonFormatter(),
Path.Combine(hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.LogFiles) ,$"UmbracoTraceLog.{Environment.MachineName}..json"),
shared: true,
rollingInterval: RollingInterval.Day, //Create a new JSON file every day
retainedFileCountLimit: retainedFileCount, //Setting to null means we keep all files - default is 31 days
rollingInterval: RollingInterval.Day, // Create a new JSON file every day
retainedFileCountLimit: retainedFileCount, // Setting to null means we keep all files - default is 31 days
restrictedToMinimumLevel: minimumLevel);
return logConfig;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
@@ -462,7 +462,7 @@ namespace Umbraco.Core.Migrations.Install
{
Message =
"The database configuration failed with the following message: " + ex.Message +
"\n Please check log file for additional information (can be found in '/App_Data/Logs/')",
$"\n Please check log file for additional information (can be found in '{Constants.SystemDirectories.LogFiles}')",
Success = false,
Percentage = "90"
};

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
@@ -48,7 +48,7 @@ namespace Umbraco.Tests.Common
public TypeLoader GetMockedTypeLoader()
{
return new TypeLoader(Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(GetHostingEnvironment().MapPathContentRoot("~/App_Data/TEMP")), Mock.Of<ILogger<TypeLoader>>(), Mock.Of<IProfilingLogger>());
return new TypeLoader(Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of<ILogger<TypeLoader>>(), Mock.Of<IProfilingLogger>());
}
// public Configs GetConfigs() => GetConfigsFactory().Create();

View File

@@ -68,7 +68,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components
private static TypeLoader MockTypeLoader()
{
var ioHelper = IOHelper;
return new TypeLoader(Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot("~/App_Data/TEMP")), Mock.Of<ILogger<TypeLoader>>(), Mock.Of<IProfilingLogger>());
return new TypeLoader(Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of<ILogger<TypeLoader>>(), Mock.Of<IProfilingLogger>());
}
@@ -390,7 +390,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components
public void AllComposers()
{
var typeFinder = TestHelper.GetTypeFinder();
var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot("~/App_Data/TEMP")), Mock.Of<ILogger<TypeLoader>>(), Mock.Of<IProfilingLogger>());
var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of<ILogger<TypeLoader>>(), Mock.Of<IProfilingLogger>());
var register = MockRegister();
var builder = new UmbracoBuilder(register, Mock.Of<IConfiguration>(), TestHelper.GetMockedTypeLoader());

View File

@@ -1,9 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
@@ -23,7 +24,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing
ProfilingLogger = new ProfilingLogger(Mock.Of<ILogger<ProfilingLogger>>(), Mock.Of<IProfiler>());
var typeFinder = TestHelper.GetTypeFinder();
TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot("~/App_Data/TEMP")), Mock.Of<ILogger<TypeLoader>>(), ProfilingLogger, false, AssembliesToScan);
TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of<ILogger<TypeLoader>>(), ProfilingLogger, false, AssembliesToScan);
}
protected virtual IEnumerable<Assembly> AssembliesToScan

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -28,7 +28,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing
// this ensures it's reset
var typeFinder = TestHelper.GetTypeFinder();
_typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance,
new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot("~/App_Data/TEMP")),
new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)),
Mock.Of<ILogger<TypeLoader>>(), new ProfilingLogger(Mock.Of<ILogger<ProfilingLogger>>(), Mock.Of<IProfiler>()), false,
// for testing, we'll specify which assemblies are scanned for the PluginTypeResolver

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -43,7 +43,7 @@ namespace Umbraco.Tests.TestHelpers
var logger = new ProfilingLogger(Mock.Of<ILogger<ProfilingLogger>>(), Mock.Of<IProfiler>());
var typeFinder = TestHelper.GetTypeFinder();
var typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance,
new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")),
new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.TempData)),
Mock.Of<ILogger<TypeLoader>>(),
logger,
false);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -643,7 +643,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
catch (Exception ex)
{
_logger.LogError(ex, "Error cleaning up temporary udt file in App_Data: {File}", filePath);
_logger.LogError(ex, "Error cleaning up temporary udt file in {File}", filePath);
}
return Ok();

View File

@@ -10,7 +10,7 @@ namespace Umbraco.Web.Common.AspNetCore
{
public class AspNetCoreHostingEnvironment : Core.Hosting.IHostingEnvironment
{
private IOptionsMonitor<HostingSettings> _hostingSettings;
private IOptionsMonitor<HostingSettings> _hostingSettings;
private readonly IWebHostEnvironment _webHostEnvironment;
private string _localTempPath;
@@ -27,59 +27,67 @@ namespace Umbraco.Web.Common.AspNetCore
IISVersion = new Version(0, 0); // TODO not necessary IIS
}
/// <inheritdoc/>
public bool IsHosted { get; } = true;
/// <inheritdoc/>
public string SiteName { get; }
/// <inheritdoc/>
public string ApplicationId { get; }
/// <inheritdoc/>
public string ApplicationPhysicalPath { get; }
public string ApplicationServerAddress { get; }
//TODO how to find this, This is a server thing, not application thing.
// TODO how to find this, This is a server thing, not application thing.
public string ApplicationVirtualPath => _hostingSettings.CurrentValue.ApplicationVirtualPath?.EnsureStartsWith('/') ?? "/";
/// <inheritdoc/>
public bool IsDebugMode => _hostingSettings.CurrentValue.Debug;
public Version IISVersion { get; }
public string LocalTempPath
{
get
{
if (_localTempPath != null)
{
return _localTempPath;
}
switch (_hostingSettings.CurrentValue.LocalTempStorageLocation)
{
case LocalTempStorage.AspNetTemp:
// TODO: I don't think this is correct? but also we probably can remove AspNetTemp as an option entirely
// since this is legacy and we shouldn't use it
return _localTempPath = System.IO.Path.Combine(Path.GetTempPath(), ApplicationId, "UmbracoData");
case LocalTempStorage.EnvironmentTemp:
// environment temp is unique, we need a folder per site
// use a hash
// combine site name and application id
// site name is a Guid on Cloud
// application id is eg /LM/W3SVC/123456/ROOT
// site name is a Guid on Cloud
// application id is eg /LM/W3SVC/123456/ROOT
// the combination is unique on one server
// and, if a site moves from worker A to B and then back to A...
// hopefully it gets a new Guid or new application id?
var hashString = SiteName + "::" + ApplicationId;
var hash = hashString.GenerateHash();
var siteTemp = System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", hash);
// hopefully it gets a new Guid or new application id?
string hashString = SiteName + "::" + ApplicationId;
string hash = hashString.GenerateHash();
string siteTemp = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", hash);
return _localTempPath = siteTemp;
//case LocalTempStorage.Default:
//case LocalTempStorage.Unknown:
default:
return _localTempPath = MapPathContentRoot("~/App_Data/TEMP");
return _localTempPath = MapPathContentRoot(Core.Constants.SystemDirectories.TempData);
}
}
}
/// <inheritdoc/>
public string MapPathWebRoot(string path) => MapPath(_webHostEnvironment.WebRootPath, path);
/// <inheritdoc/>
public string MapPathContentRoot(string path) => MapPath(_webHostEnvironment.ContentRootPath, path);
private string MapPath(string root, string path)
@@ -91,21 +99,29 @@ namespace Umbraco.Web.Common.AspNetCore
// however if you are requesting a path be mapped, it should always assume the path is relative to the root, not
// absolute in the file system. This error will help us find and fix improper uses, and should be removed once
// all those uses have been found and fixed
if (newPath.StartsWith(root)) throw new ArgumentException("The path appears to already be fully qualified. Please remove the call to MapPath");
if (newPath.StartsWith(root))
{
throw new ArgumentException("The path appears to already be fully qualified. Please remove the call to MapPath");
}
return Path.Combine(root, newPath.TrimStart('~', '/', '\\'));
}
/// <inheritdoc/>
public string ToAbsolute(string virtualPath)
{
if (!virtualPath.StartsWith("~/") && !virtualPath.StartsWith("/"))
{
throw new InvalidOperationException($"The value {virtualPath} for parameter {nameof(virtualPath)} must start with ~/ or /");
}
// will occur if it starts with "/"
if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute))
{
return virtualPath;
}
var fullPath = ApplicationVirtualPath.EnsureEndsWith('/') + virtualPath.TrimStart('~', '/');
string fullPath = ApplicationVirtualPath.EnsureEndsWith('/') + virtualPath.TrimStart('~', '/');
return fullPath;
}

View File

@@ -53,13 +53,20 @@ namespace Umbraco.Core.DependencyInjection
IConfiguration config)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (config is null)
{
throw new ArgumentNullException(nameof(config));
}
var loggingConfig = new LoggingConfiguration(Path.Combine(webHostEnvironment.ContentRootPath, "umbraco", "logs"));
IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config);
var loggingDir = tempHostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.LogFiles);
var loggingConfig = new LoggingConfiguration(loggingDir);
var tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config);
services.AddLogger(tempHostingEnvironment, loggingConfig, config);
IHttpContextAccessor httpContextAccessor = new HttpContextAccessor();
@@ -69,11 +76,11 @@ namespace Umbraco.Core.DependencyInjection
var appCaches = AppCaches.Create(requestCache);
services.AddUnique<AppCaches>(appCaches);
var profiler = GetWebProfiler(config);
IProfiler profiler = GetWebProfiler(config);
services.AddUnique<IProfiler>(profiler);
var loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false));
var typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler);
ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false));
TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler);
return new UmbracoBuilder(services, config, typeLoader, loggerFactory);
}

View File

@@ -11,6 +11,7 @@ using Umbraco.Core.Hosting;
namespace Umbraco.Web.Hosting
{
// TODO: This has been migrated to netcore
public class AspNetHostingEnvironment : IHostingEnvironment
{
@@ -67,9 +68,6 @@ namespace Umbraco.Web.Hosting
switch (_hostingSettings.LocalTempStorageLocation)
{
case LocalTempStorage.AspNetTemp:
return _localTempPath = System.IO.Path.Combine(HttpRuntime.CodegenDir, "UmbracoData");
case LocalTempStorage.EnvironmentTemp:
// environment temp is unique, we need a folder per site
@@ -88,10 +86,8 @@ namespace Umbraco.Web.Hosting
return _localTempPath = siteTemp;
//case LocalTempStorage.Default:
//case LocalTempStorage.Unknown:
default:
return _localTempPath = MapPathContentRoot("~/App_Data/TEMP");
return _localTempPath = MapPathContentRoot(Constants.SystemDirectories.TempData);
}
}
}