diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
index 10d5b10955..4010252571 100644
--- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
+++ b/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
@@ -1,3 +1,5 @@
+using System;
+
namespace Umbraco.Net
{
public interface IUmbracoApplicationLifetime
@@ -10,5 +12,7 @@ namespace Umbraco.Net
/// Terminates the current application. The application restarts the next time a request is received for it.
///
void Restart();
+
+ event EventHandler ApplicationInit;
}
}
diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs
index 4321aefd7f..e7be0b6500 100644
--- a/src/Umbraco.Core/UriExtensions.cs
+++ b/src/Umbraco.Core/UriExtensions.cs
@@ -146,7 +146,7 @@ namespace Umbraco.Core
///
///
///
- internal static bool IsClientSideRequest(this Uri url)
+ public static bool IsClientSideRequest(this Uri url)
{
try
{
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 29d69db0d2..7dd0d6592b 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -99,7 +99,7 @@
-
+
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs
index 20cfef352d..f597c40252 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
@@ -14,6 +15,11 @@ namespace Umbraco.Web.AspNet
{
_httpContextAccessor = httpContextAccessor;
_hostApplicationLifetime = hostApplicationLifetime;
+
+ hostApplicationLifetime.ApplicationStarted.Register(() =>
+ {
+ ApplicationInit?.Invoke(this, EventArgs.Empty);
+ });
}
public bool IsRestarting { get; set; }
@@ -32,5 +38,7 @@ namespace Umbraco.Web.AspNet
Thread.CurrentPrincipal = null;
_hostApplicationLifetime.StopApplication();
}
+
+ public event EventHandler ApplicationInit;
}
}
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs
new file mode 100644
index 0000000000..110c09e1a9
--- /dev/null
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoRequestApplicationBuilderExtensions.cs
@@ -0,0 +1,21 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Umbraco.Web.Common.Middleware;
+
+namespace Umbraco.Web.Common.Extensions
+{
+ public static class UmbracoRequestApplicationBuilderExtensions
+ {
+ public static IApplicationBuilder UseUmbracoRequest(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ app.UseMiddleware();
+
+ return app;
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..2a16b8b4f9
--- /dev/null
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoRequestServiceCollectionExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Web.Common.Middleware;
+
+namespace Umbraco.Web.Common.Extensions
+{
+ public static class UmbracoRequestServiceCollectionExtensions
+ {
+ public static IServiceCollection AddUmbracoRequest(this IServiceCollection services)
+ {
+ var umbracoRequestLifetime = new UmbracoRequestLifetime();
+
+ services.AddSingleton(umbracoRequestLifetime);
+ services.AddSingleton(umbracoRequestLifetime);
+
+ return services;
+ }
+
+ }
+
+}
diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs
new file mode 100644
index 0000000000..60b6463152
--- /dev/null
+++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Umbraco.Web.Common.Middleware
+{
+ public class UmbracoRequestMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly IUmbracoRequestLifetimeManager _umbracoRequestLifetimeManager;
+ public UmbracoRequestMiddleware(RequestDelegate next, IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager)
+ {
+ _next = next;
+ _umbracoRequestLifetimeManager = umbracoRequestLifetimeManager;
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ _umbracoRequestLifetimeManager.InitRequest(context);
+ await _next(context);
+ _umbracoRequestLifetimeManager.EndRequest(context);
+ }
+ }
+
+ public interface IUmbracoRequestLifetime
+ {
+ event EventHandler RequestStart;
+ event EventHandler RequestEnd;
+ }
+
+ public class UmbracoRequestLifetime : IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager
+ {
+ public event EventHandler RequestStart;
+ public event EventHandler RequestEnd;
+
+ public void InitRequest(HttpContext context)
+ {
+ RequestStart?.Invoke(this, context);
+ }
+
+ public void EndRequest(HttpContext context)
+ {
+ RequestEnd?.Invoke(this, context);
+ }
+ }
+
+ public interface IUmbracoRequestLifetimeManager
+ {
+ void InitRequest(HttpContext context);
+ void EndRequest(HttpContext context);
+ }
+}
diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs
new file mode 100644
index 0000000000..4e1a7c7d84
--- /dev/null
+++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Threading;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using StackExchange.Profiling;
+using Umbraco.Core;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Logging;
+using Umbraco.Web.Common.Middleware;
+
+namespace Umbraco.Web.Common.Runtime.Profiler
+{
+ public class WebProfiler : IProfiler
+ {
+ private const string BootRequestItemKey = "Umbraco.Core.Logging.WebProfiler__isBootRequest";
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly WebProfilerProvider _provider;
+ private int _first;
+
+ public WebProfiler(IHttpContextAccessor httpContextAccessor)
+ {
+ // create our own provider, which can provide a profiler even during boot
+ _provider = new WebProfilerProvider();
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ public string Render() => MiniProfiler.Current
+ .RenderIncludes(_httpContextAccessor.HttpContext, RenderPosition.Right).ToString();
+
+ public IDisposable Step(string name) => MiniProfiler.Current?.Step(name);
+
+ public void Start()
+ {
+ MiniProfiler.StartNew();
+ }
+
+ public void Stop(bool discardResults = false)
+ {
+ MiniProfiler.Current?.Stop(discardResults);
+ }
+
+ public void UmbracoApplicationBeginRequest(HttpContext context)
+ {
+ // if this is the first request, notify our own provider that this request is the boot request
+ var first = Interlocked.Exchange(ref _first, 1) == 0;
+ if (first)
+ {
+ _provider.BeginBootRequest();
+ context.Items[BootRequestItemKey] = true;
+ // and no need to start anything, profiler is already there
+ }
+ // else start a profiler, the normal way
+ else if (ShouldProfile(context.Request))
+ Start();
+ }
+
+ public void UmbracoApplicationEndRequest(HttpContext context)
+ {
+ // if this is the boot request, or if we should profile this request, stop
+ // (the boot request is always profiled, no matter what)
+ if (context.Items.TryGetValue(BootRequestItemKey, out var isBoot) && isBoot.Equals(true))
+ {
+ _provider.EndBootRequest();
+ Stop();
+ }
+ else if (ShouldProfile(context.Request))
+ {
+ Stop();
+ }
+ }
+
+ private static bool ShouldProfile(HttpRequest request)
+ {
+ if (new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) return false;
+ if (bool.TryParse(request.Query["umbDebug"], out var umbDebug)) return umbDebug;
+ if (bool.TryParse(request.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug;
+ if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug;
+ return false;
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs
new file mode 100644
index 0000000000..425996a765
--- /dev/null
+++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs
@@ -0,0 +1,55 @@
+using System;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Logging;
+using Umbraco.Net;
+using Umbraco.Web.Common.Middleware;
+
+namespace Umbraco.Web.Common.Runtime.Profiler
+{
+ internal sealed class WebProfilerComponent : IComponent
+ {
+ private readonly bool _profile;
+ private readonly WebProfiler _profiler;
+ private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
+ private readonly IUmbracoRequestLifetime _umbracoRequestLifetime;
+
+ public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime,
+ IUmbracoApplicationLifetime umbracoApplicationLifetime)
+ {
+ _umbracoRequestLifetime = umbracoRequestLifetime;
+ _umbracoApplicationLifetime = umbracoApplicationLifetime;
+ _profile = true;
+
+ // although registered in WebRuntime.Compose, ensure that we have not
+ // been replaced by another component, and we are still "the" profiler
+ _profiler = profiler as WebProfiler;
+ if (_profiler != null) return;
+
+ // if VoidProfiler was registered, let it be known
+ if (profiler is VoidProfiler)
+ logger.Info(
+ "Profiler is VoidProfiler, not profiling (must run debug mode to profile).");
+ _profile = false;
+ }
+
+ public void Initialize()
+ {
+ if (!_profile) return;
+
+ // bind to ApplicationInit - ie execute the application initialization for *each* application
+ // it would be a mistake to try and bind to the current application events
+ _umbracoApplicationLifetime.ApplicationInit += InitializeApplication;
+ }
+
+ public void Terminate()
+ {
+ }
+
+ private void InitializeApplication(object sender, EventArgs args)
+ {
+ _umbracoRequestLifetime.RequestStart +=
+ (sender, context) => _profiler.UmbracoApplicationBeginRequest(context);
+ _umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context);
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs
new file mode 100644
index 0000000000..688a3e5c28
--- /dev/null
+++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs
@@ -0,0 +1,8 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.Common.Runtime.Profiler
+{
+ internal class WebProfilerComposer : ComponentComposer, ICoreComposer
+ {
+ }
+}
diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs
new file mode 100644
index 0000000000..99ad6f724c
--- /dev/null
+++ b/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerProvider.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Threading;
+using StackExchange.Profiling;
+using StackExchange.Profiling.Internal;
+using Umbraco.Core.Cache;
+
+namespace Umbraco.Web.Common.Runtime.Profiler
+{
+ public class WebProfilerProvider : DefaultProfilerProvider
+ {
+ private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
+ private volatile BootPhase _bootPhase;
+ private int _first;
+ private MiniProfiler _startupProfiler;
+
+ public WebProfilerProvider()
+ {
+ // booting...
+ _bootPhase = BootPhase.Boot;
+ }
+
+
+ ///
+ /// Gets the current profiler.
+ ///
+ ///
+ /// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise
+ /// returns the base class
+ ///
+ public override MiniProfiler CurrentProfiler
+ {
+ get
+ {
+ // if not booting then just use base (fast)
+ // no lock, _bootPhase is volatile
+ if (_bootPhase == BootPhase.Booted)
+ return base.CurrentProfiler;
+
+ // else
+ try
+ {
+ var current = base.CurrentProfiler;
+ return current ?? _startupProfiler;
+ }
+ catch
+ {
+ return _startupProfiler;
+ }
+ }
+ }
+
+ public void BeginBootRequest()
+ {
+ _locker.EnterWriteLock();
+ try
+ {
+ if (_bootPhase != BootPhase.Boot)
+ throw new InvalidOperationException("Invalid boot phase.");
+ _bootPhase = BootPhase.BootRequest;
+
+ // assign the profiler to be the current MiniProfiler for the request
+ // is's already active, starting and all
+ CurrentProfiler = _startupProfiler;
+ }
+ finally
+ {
+ _locker.ExitWriteLock();
+ }
+ }
+
+ public void EndBootRequest()
+ {
+ _locker.EnterWriteLock();
+ try
+ {
+ if (_bootPhase != BootPhase.BootRequest)
+ throw new InvalidOperationException("Invalid boot phase.");
+ _bootPhase = BootPhase.Booted;
+
+ _startupProfiler = null;
+ }
+ finally
+ {
+ _locker.ExitWriteLock();
+ }
+ }
+
+ ///
+ /// Starts a new MiniProfiler.
+ ///
+ ///
+ ///
+ /// This is called when WebProfiler calls MiniProfiler.Start() so,
+ /// - as a result of WebRuntime starting the WebProfiler, and
+ /// - assuming profiling is enabled, on every BeginRequest that should be profiled,
+ /// - except for the very first one which is the boot request.
+ ///
+ ///
+ public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options)
+ {
+ var first = Interlocked.Exchange(ref _first, 1) == 0;
+ if (first == false) return base.Start(profilerName, options);
+
+ _startupProfiler = new MiniProfiler("StartupProfiler", options);
+ CurrentProfiler = _startupProfiler;
+ return _startupProfiler;
+ }
+
+ ///
+ /// Indicates the boot phase.
+ ///
+ private enum BootPhase
+ {
+ Boot = 0, // boot phase (before the 1st request even begins)
+ BootRequest = 1, // request boot phase (during the 1st request)
+ Booted = 2 // done booting
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
new file mode 100644
index 0000000000..cb9ca9156a
--- /dev/null
+++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
@@ -0,0 +1,22 @@
+
+
+
+ netcoreapp3.1
+ Library
+ 8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index 9ef4985aea..be81261d2a 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Umbraco.Web.BackOffice.AspNetCore;
+using Umbraco.Web.Common.Extensions;
using Umbraco.Web.Website.AspNetCore;
@@ -21,7 +22,9 @@ namespace Umbraco.Web.UI.BackOffice
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
+
services.AddUmbracoConfiguration();
+ services.AddUmbracoRequest();
services.AddUmbracoWebsite();
services.AddUmbracoBackOffice();
}
@@ -29,6 +32,7 @@ namespace Umbraco.Web.UI.BackOffice
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
+ app.UseUmbracoRequest();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj
index ca7c3e26fa..abfb172763 100644
--- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj
+++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj
@@ -7,6 +7,7 @@
+
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index fe11a8d9aa..0c0e6aee50 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -101,7 +101,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
3.4.0
diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs
index 245e8ea374..f5ebc03cd0 100644
--- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs
+++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading;
using System.Web;
using Umbraco.Net;
@@ -11,6 +12,8 @@ namespace Umbraco.Web.AspNet
public AspNetUmbracoApplicationLifetime(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
+
+ UmbracoApplicationBase.ApplicationInit += ApplicationInit;
}
public bool IsRestarting { get; set; }
@@ -30,5 +33,7 @@ namespace Umbraco.Web.AspNet
Thread.CurrentPrincipal = null;
HttpRuntime.UnloadAppDomain();
}
+
+ public event EventHandler ApplicationInit;
}
}
diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs
index f5a2ca8ac8..3f81c0d589 100644
--- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs
+++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs
@@ -56,20 +56,7 @@ namespace Umbraco.Web.Install.InstallSteps
throw new InvalidOperationException("Could not find the super user!");
}
- var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager();
- var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId);
- if (membershipUser == null)
- {
- throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}.");
- }
-
- //To change the password here we actually need to reset it since we don't have an old one to use to change
- var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id);
- var resetResult = await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim());
- if (!resetResult.Succeeded)
- {
- throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors));
- }
+ await ResetAdminPassword(user.Password);
admin.Email = user.Email.Trim();
admin.Name = user.Name.Trim();
@@ -95,6 +82,26 @@ namespace Umbraco.Web.Install.InstallSteps
return null;
}
+ private async Task ResetAdminPassword(string clearTextPassword)
+ {
+ var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager();
+ var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId);
+ if (membershipUser == null)
+ {
+ throw new InvalidOperationException(
+ $"No user found in membership provider with id of {Constants.Security.SuperUserId}.");
+ }
+
+ //To change the password here we actually need to reset it since we don't have an old one to use to change
+ var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id);
+ var resetResult =
+ await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, clearTextPassword.Trim());
+ if (!resetResult.Succeeded)
+ {
+ throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors));
+ }
+ }
+
///
/// Return a custom view model for this step
///
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index e8aaecdf4d..b9892e1c31 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -92,7 +92,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/src/umbraco.sln b/src/umbraco.sln
index be363ef2e6..11098abdec 100644
--- a/src/umbraco.sln
+++ b/src/umbraco.sln
@@ -123,6 +123,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.Website", "Umbr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Common", "Umbraco.Tests.Common\Umbraco.Tests.Common.csproj", "{A499779C-1B3B-48A8-B551-458E582E6E96}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.Common", "Umbraco.Web.Common\Umbraco.Web.Common.csproj", "{839D3048-9718-4907-BDE0-7CD63D364383}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -195,6 +197,10 @@ Global
{A499779C-1B3B-48A8-B551-458E582E6E96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.Build.0 = Release|Any CPU
+ {839D3048-9718-4907-BDE0-7CD63D364383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {839D3048-9718-4907-BDE0-7CD63D364383}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {839D3048-9718-4907-BDE0-7CD63D364383}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {839D3048-9718-4907-BDE0-7CD63D364383}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE