Compare commits

...

99 Commits

Author SHA1 Message Date
Sebastiaan Janssen 33ccf490ab Update nuspec to ask for a valid nuget pkg 2014-08-25 15:53:45 +02:00
Sebastiaan Janssen 03ba8c9e14 Update to 6.2.2, removes airinstallbadge 2014-08-25 13:46:21 +02:00
Shannon 13b57c7638 adds some logging 2014-08-25 16:40:45 +10:00
Shannon a8b297903d Ensures we don't get any ysods when changing doc type properties and the content re-creation since it's running on an async thread (U4-5388 , U4-5387 ) 2014-08-25 16:16:42 +10:00
Shannon 89b5774106 Fixes: U4-5380 Booting.aspx security issue 2014-08-21 14:39:06 -06:00
Shannon 774f201514 Adds a simple cache mechanism to hold reflected property info when cloning - reduces perf to 10% of what it used to be 2014-08-20 20:09:05 -06:00
Sebastiaan Janssen 12e2aa679b Merge pull request #455 from leekelleher/U4-4249
U4-4249 - PetaPocoExtensions CreateTable doesn't recreate the table
2014-08-20 10:45:56 +02:00
leekelleher 1978930fa2 Amends CreateTable, so if 'overwrite' is enabled, the table is dropped, then recreated. 2014-08-19 18:56:15 +01:00
Stephan c4e4f9579b U4-4931 - fix tests 2014-08-15 09:54:04 +02:00
Stephan 9782f66507 U4-5084 - fix commit 2014-08-14 22:24:12 +02:00
Stephan e9938afc83 Merge pull request #418 from zpqrtbnk/6.2-4931
U4-4931 - lock contention & potential application lockdown
2014-08-14 20:52:13 +02:00
Stephan fcda9821d0 Merge branch '6.2.2' into 6.2-4931 2014-08-14 20:50:37 +02:00
Stephan bdc234b18a U4-5084 - DynamicPublishedContentList ext methods confusion 2014-08-14 20:49:49 +02:00
Sebastiaan Janssen 908afbd264 #U4-2238 Fixed
Issue when saving/publishing special characters - '' hexadecimal value 0x03, is an invalid character
2014-08-14 10:26:38 +02:00
Sebastiaan Janssen c548ad6a98 Update failing unit test now that MaxInvalidPasswordAttempts is 20 by default 2014-08-14 10:16:02 +02:00
Sebastiaan Janssen 99b2a2da38 #U4-5337 fixed Due in version: 7.1.5,6.2.2
Too easy to lock yourself out of the backoffice
2014-08-12 11:06:24 +02:00
Sebastiaan Janssen 15ed458ad3 #U4-5258 Fixed
RelationService AreRelated should accepts two entity IDs and a relationTypeAlias
2014-08-12 10:25:33 +02:00
Shannon 6ce303a918 Completes: U4-5264 Add Count methods to IContentService, IMediaService, etc... 2014-08-05 12:37:20 -06:00
Shannon 62d3dc78fa Ensures all caching is disabled for editing templates (master pages and views) 2014-08-05 10:59:54 -06:00
Shannon 3374f9e023 Ensures that the batched distributed calls doesn't cache refresh the server executing the distributed calls since tht will already be done locally. 2014-08-05 10:32:07 -06:00
Stephan 2675992d82 Merge branch '6.2.2' into 6.2-4931 2014-08-05 18:20:04 +02:00
Stephan ce29f54663 U4-4837 - bugfix Umbraco.Core.EnumerableExtensions.InGroupsOf 2014-08-05 17:52:43 +02:00
Shannon 7261162d8a Fixes some of U4-2633 Bundle all cache refresher transmissions into a single call per request for much better performance. See last note on issue. 2014-08-04 17:50:19 -06:00
Shannon b5f900e789 added a couple of tests for member names 2014-07-28 19:37:44 -07:00
Shannon fdab6d5de5 Merge branch '6.2.2' of https://github.com/umbraco/Umbraco-CMS into 6.2.2 2014-07-24 09:39:12 -07:00
Shannon ff94eba3a7 Fixes: U4-5273 6.2: Umbraco.Field no longer rendering macro container 2014-07-24 09:39:05 -07:00
Sebastiaan Janssen e200e2fc48 Removes obsolete cachebrowser 2014-07-24 16:17:40 +02:00
Shannon 22dde5a421 U4-4738 Backoffice search should support node ID search 2014-07-23 14:07:21 -07:00
Shannon 7f510c107a updates proj's with correct specific version attributes 2014-07-23 12:31:38 -07:00
Shannon 2ebfd8f3d4 updates nuspec to have latest versions for all dependencies 2014-07-23 12:31:16 -07:00
Shannon fd7930256b Uninstalls package CodeSharp.Package.AspNetWebPage and removes refs to Nuget.Core and Webpages.Administration - neither of which are used anywhere... but we've been shipping Umbraco with them for some reason. 2014-07-23 10:53:30 -07:00
Shannon c24775adb0 re-installs mvc, webapi and ensures running the latest 2.0.x versions of webpages and razor, now all proj's are consistent with correct versions referenced and copy local set correctly. 2014-07-23 10:03:26 -07:00
Shannon 217f591db0 updates proj references to have SpecificVersion set to false for libs 2014-07-22 16:13:26 -07:00
Shannon d453aedd0d Microsoft.Net.Http required a reinstall command for the UI project as noted by it's persisted 'requiresReinstall' attribute in the packages.config - also ensures that ALL of the WebApi DLLs are copied local 2014-07-22 15:51:13 -07:00
Shannon 4693fb23c8 removes unneeded lock on master controller factory - must have been left over from some v5 stuff for some reason :( this will speed things up a bit! 2014-07-18 16:28:54 +10:00
Shannon 7848a3095d Fixes: U4-5207 User Type with a space in the name breaks User Content 2014-07-16 14:12:04 +10:00
Shannon 388eea95e5 fixes a single vs first check with render route handler when there might be duplicate routes assigned to a controller, fixes short/int parse 2014-07-16 13:46:15 +10:00
Shannon 6167d1584a Fixes an xss vulnerability 2014-07-15 11:37:31 +10:00
Stephan bfd0c5e309 U4-4931 - also for ObjectCacheRuntime provider 2014-07-10 10:25:51 +02:00
Stephan 43669e62b4 Merge branch '6.2.2' into 6.2-4931 2014-07-10 10:25:12 +02:00
Shannon 6b128ea7d4 Fixes: U4-5208 RenderActionInvoker needs to be able to run async so needs to inherit from AsyncControllerActionInvoker 2014-07-10 17:29:34 +10:00
Shannon d5993a4783 Backports fix from 7 for GetDimensions call to ensure the file exists 2014-07-10 12:09:26 +10:00
Shannon 86c2a6e8a9 updates physical file system, now that files are being disposed of properly we don't need to delete first 2014-07-10 11:51:48 +10:00
Shannon 874f464a2b Fixes: U4-5190 Overwrite Media File in 6.2.1 - process cannot access along with a bunch of other undisposed disposables :/ 2014-07-10 11:45:57 +10:00
Shannon b3c41e051f Fixes: U4-5199 Appending certain query strings to URLs cause InvalidOperationException (The UmbracoContext.Current is null) 2014-07-10 11:10:49 +10:00
Shannon a88637910f updates content xml bits so if it's called outside a web req it still executes.
Conflicts:
	src/Umbraco.Web/umbraco.presentation/content.cs
2014-07-10 10:45:26 +10:00
Shannon 431e93000e Fixes: U4-5157 Membership.GetUser() causes 13 SQL queries, including duplicates 2014-07-08 19:04:38 +10:00
Shannon 1a821b4073 super quick performance win in the case where people don't pass in distinct values 2014-07-01 17:41:22 +10:00
Shannon a89b9e9da6 ignores log4net tests 2014-07-01 11:16:57 +10:00
Shannon 8917701fa3 ignores failing test - need stephane to look 2014-07-01 11:03:47 +10:00
Shannon 68b17bff08 Ensures no membership user writing when checking public access 2014-07-01 10:52:29 +10:00
Shannon 7a12060c37 Potentially fixes U4-5069 Deadlock occurring in MemberService.GetByUsername
Conflicts:
	src/Umbraco.Web/Security/MembershipHelper.cs
2014-07-01 10:46:52 +10:00
Shannon 525280d466 fixed some tests 2014-06-30 14:09:36 +10:00
Shannon 496beca8f2 This Can_Find_Targetted_Migrations test needed a valid database to pass. This update ensures that the database is created with the old version 4.8 db schema. 2014-06-30 14:02:43 +10:00
Stephan 637a22293d U4-4931 - fixing & refactoring 2014-06-28 18:33:31 +02:00
Stephan 3c8517edcb Merge branch '6.2.2' into 6.2-4931 2014-06-28 16:26:44 +02:00
Stephan 45e6127d00 U4-4866 - fix the build 2014-06-28 16:26:27 +02:00
Stephan 6eb5d3f7ef Merge branch '6.2.2' into 6.2-4931 2014-06-28 16:06:30 +02:00
Stephan e6986432df U4-4931 - fixing & refactoring 2014-06-28 12:24:02 +02:00
Shannon 80103e6155 Fixes: U4-4866 Select content root when creating a package 2014-06-26 16:55:44 +10:00
Shannon 392634eb76 bumps nuspec versions 2014-06-26 11:44:28 +10:00
Shannon 259bc863f0 Removes Examine.dll deploy from nuspec 2014-06-26 11:32:43 +10:00
Shannon 582bafdefd Updates version 2014-06-26 10:02:09 +10:00
Shannon 9924ef2381 Merge branch '6.2.1' into 6.2.2 2014-06-26 09:57:16 +10:00
Shannon 859fbaaa05 Fixes: U4-5121 umbraco.content launches threadpool threads to reload the xml cache which causes lots of other issues
Conflicts:
	src/Umbraco.Web/umbraco.presentation/content.cs
2014-06-25 19:02:49 +10:00
Shannon 72991334c4 little bit of code cleanup 2014-06-25 15:55:52 +10:00
Shannon c659d5b85e fixes dll ref 2014-06-25 14:35:14 +10:00
Shannon a4bf4b0977 Fixes: U4-5118 Examine not storing XML fragments - updates to latest examine 2014-06-25 14:19:31 +10:00
Shannon bef6ce66f1 Updated core nuspec to include Examine 2014-06-25 14:19:06 +10:00
Shannon a18a2198a8 removes comment 2014-06-25 12:25:27 +10:00
Shannon 7cf7ac687c last issue with state/keep alive timer 2014-06-25 12:03:31 +10:00
Shannon 7ce0272169 updates state param with timers to use singleton 2014-06-25 11:56:36 +10:00
Shannon acb934e3f1 ensures timer's are not GCd 2014-06-25 11:52:48 +10:00
Shannon 3aa486a463 another log 2014-06-25 11:36:58 +10:00
Shannon 5aa0621c90 adds some notes 2014-06-25 11:36:14 +10:00
Shannon 625b8f7e65 Ensures the 'Index' portion of the path is used for scheduled publishing 2014-06-25 11:26:08 +10:00
Shannon e27163f3a7 adds logging to scheduled bits 2014-06-25 11:00:11 +10:00
Shannon 65ea6f94d3 adds authorization to the scheduled publishing , this new attribute can be used to secure scheduled tasks as well. 2014-06-23 15:02:23 +10:00
Shannon 9158ea1ace fixes url returned in GetCurrentServerUmbracoBaseUrl 2014-06-20 14:45:45 +10:00
Shannon e8f7f77bb6 Fixes: U4-581 Automatic publishing not working in load balanced setup - added some more convention and configuration to distributed calls so that servers are aware of the master and how to call into themselves for scheduled tasks, ping and scheduled publishing. Will need to update the docs on LB regarding this too. Cleaned up the code that does the scheduling and separates it into proper segments. Obsoletes the old presentation classes that were doing it. 2014-06-20 14:34:21 +10:00
Stephan 75e003185a U4-4931 - do not cache exceptions 2014-06-17 19:34:24 +02:00
Stephan 9d12e1c7df Merge branch '6.2.2' into 6.2-4931 2014-06-17 19:19:17 +02:00
Shannon b8575ef158 Started writing test implementation to mimic what developers might test in their controllers to expose the pitfalls of umbraco testing so we can make this simpler - identifying what needs to be public and how to re-strcuture some objects constructors, etc... to simplify and make this possible. 2014-06-17 18:53:23 +10:00
Shannon d3bb750f98 Updated to latest Examine with lock fixes 2014-06-17 16:07:08 +10:00
Morten Christensen 02eff33d29 Fixes U4-2640 LocalizationService throws exception when getting dictionary items and adds tests for the LocalizationService 2014-06-08 13:16:32 +02:00
Shannon b43f39a0cb disables dist call 2014-06-08 12:50:45 +02:00
Shannon d0cb27d1c1 Completes: U4-2633 Bundle all cache refresher transmissions into a single call per request for much better performance 2014-06-08 12:49:20 +02:00
Stephan 16a1720bf7 Merge remote-tracking branch 'origin/6.2.1' into 6.2.1 2014-05-28 17:11:56 +02:00
Stephan 74806984b3 U4-4931 - fixing & refactoring 2014-05-27 12:17:48 +02:00
Stephan c8c278c7bc Merge remote-tracking branch 'origin/6.2.1' into 6.2.1 2014-05-27 12:17:43 +02:00
Stephan a8e4c3eb9a Merge remote-tracking branch 'origin/6.2.1' into 6.2.1 2014-05-26 18:07:45 +02:00
Stephan 113b866485 U4-4931 - fixing & refactoring 2014-05-26 15:56:29 +02:00
Stephan 6c6ef8d144 Merge remote-tracking branch 'origin/6.2.1' into 6.2.1 2014-05-26 15:03:38 +02:00
Stephan 99250cefbb U4-4931 - fixing & refactoring 2014-05-23 10:46:41 +02:00
Stephan dc651a1c42 Merge remote-tracking branch 'origin/6.2.1' into 6.2.1 2014-05-23 10:46:13 +02:00
Stephan 4e9fd5a223 U4-4931 - fixing & refactoring 2014-05-22 13:43:55 +02:00
Stephan e2c36d7212 Merge remote-tracking branch 'origin/6.2.1' into HEAD 2014-05-22 13:40:58 +02:00
Stephan 695cc07912 Merge remote-tracking branch 'origin/6.2.1' into 6.2.1 2014-05-20 09:32:54 +02:00
Stephan 46f57a3115 U4-4931 - lock contention & potential application lockdown 2014-05-18 19:13:22 +02:00
144 changed files with 3802 additions and 1569 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
@ECHO OFF
SET release=6.2.1
SET release=6.2.2
SET comment=
SET version=%release%
+7 -5
View File
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>UmbracoCms.Core</id>
<version>6.2.0</version>
<version>6.2.2</version>
<title>Umbraco Cms Core Binaries</title>
<authors>Umbraco HQ</authors>
<owners>Umbraco HQ</owners>
@@ -15,11 +15,13 @@
<language>en-US</language>
<tags>umbraco</tags>
<dependencies>
<dependency id="Microsoft.AspNet.Mvc" version="[4.0.0,5.0.0)" />
<dependency id="Microsoft.AspNet.Mvc" version="[4.0.30506.0,5.0.0)" />
<dependency id="Microsoft.AspNet.WebApi" version="[4.0.30506,5.0.0)" />
<dependency id="Microsoft.AspNet.WebApi.WebHost" version="[4.0.30506, 5.0.0)" />
<dependency id="Microsoft.AspNet.WebApi.Core" version="[4.0.30506, 5.0.0)" />
<dependency id="Microsoft.AspNet.WebApi.Client" version="[4.0.30506, 5.0.0)" />
<dependency id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.0" />
<dependency id="Microsoft.Net.Http" version="[2.0.0, 3.0.0)" />
<dependency id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" />
<dependency id="Microsoft.Net.Http" version="[2.0.20710.0, 3.0.0)" />
<dependency id="MiniProfiler" version="[2.1.0, 3.0.0)" />
<dependency id="HtmlAgilityPack" version="[1.4.6, 2.0.0)" />
<dependency id="Lucene.Net" version="[2.9.4.1, 3.0.0.0)" />
@@ -29,6 +31,7 @@
<dependency id="ClientDependency" version="[1.7.1.2, 2.0.0)" />
<dependency id="ClientDependency-Mvc" version="[1.7.0.4, 2.0.0)" />
<dependency id="Newtonsoft.Json" version="[4.5.11, 6.0.0)" />
<dependency id="Examine" version="[0.1.57, 1.0.0)" />
</dependencies>
</metadata>
<files>
@@ -38,7 +41,6 @@
<file src="..\_BuildOutput\WebApp\bin\cms.xml" target="lib\cms.xml" />
<file src="..\_BuildOutput\WebApp\bin\controls.dll" target="lib\controls.dll" />
<file src="..\_BuildOutput\WebApp\bin\controls.xml" target="lib\controls.xml" />
<file src="..\_BuildOutput\WebApp\bin\Examine.dll" target="lib\Examine.dll" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.dll" target="lib\interfaces.dll" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.xml" target="lib\interfaces.xml" />
<file src="..\_BuildOutput\WebApp\bin\log4net.dll" target="lib\log4net.dll" />
+1 -1
View File
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>UmbracoCms</id>
<version>6.1.2</version>
<version>6.2.2</version>
<title>Umbraco Cms</title>
<authors>Umbraco HQ</authors>
<owners>Umbraco HQ</owners>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<UmbracoVersion>6.2.1</UmbracoVersion>
<UmbracoVersion>6.2.2</UmbracoVersion>
</PropertyGroup>
<Target Name="CopyUmbracoFilesToWebRoot" BeforeTargets="AfterBuild">
<PropertyGroup>
+1
View File
@@ -80,6 +80,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Sql\Total.sql" />
<None Include="app.config" />
<None Include="packages.config" />
<None Include="Sql\Version4_1_Upgrade.sql" />
</ItemGroup>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -1,218 +1,204 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
namespace Umbraco.Core.Cache
{
internal abstract class DictionaryCacheProviderBase : ICacheProvider
{
protected static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
protected abstract DictionaryCacheWrapper DictionaryCache { get; }
/// <summary>
/// Clears everything in umbraco's runtime cache
/// </summary>
/// <remarks>
/// Does not clear other stuff the user has put in httpruntime.cache!
/// </remarks>
public virtual void ClearAllCache()
{
using (new WriteLock(Locker))
{
var keysToRemove = DictionaryCache.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c => c.Key is string && ((string)c.Key).StartsWith(CacheItemPrefix) && DictionaryCache[c.Key.ToString()] != null)
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
{
DictionaryCache.Remove(k);
}
}
}
/// <summary>
/// Clears the item in umbraco's runtime cache with the given key
/// </summary>
/// <param name="key">Key</param>
public virtual void ClearCacheItem(string key)
{
using (new WriteLock(Locker))
{
if (DictionaryCache[GetCacheKey(key)] == null) return;
DictionaryCache.Remove(GetCacheKey(key)); ;
}
}
/// <summary>
/// Clears all objects in the System.Web.Cache with the System.Type name as the
/// input parameter. (using [object].GetType())
/// </summary>
/// <param name="typeName">The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument"</param>
public virtual void ClearCacheObjectTypes(string typeName)
{
using (new WriteLock(Locker))
{
var keysToRemove = DictionaryCache
.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c =>
{
var k = c.Key.ToString();
var v = DictionaryCache[k];
return v != null && v.GetType().ToString().InvariantEquals(typeName);
})
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
DictionaryCache.Remove(k);
}
}
public virtual void ClearCacheObjectTypes<T>()
{
using (new WriteLock(Locker))
{
var typeOfT = typeof(T);
var keysToRemove = DictionaryCache
.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c =>
{
var k = c.Key.ToString();
var v = DictionaryCache[k];
return v != null && v.GetType() == typeOfT;
})
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
DictionaryCache.Remove(k);
}
}
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{
using (new WriteLock(Locker))
{
var typeOfT = typeof(T);
var keysToRemove = DictionaryCache
.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c =>
{
var k = c.Key.ToString();
var v = DictionaryCache[k];
return v != null && v.GetType() == typeOfT && predicate(k, (T)v);
})
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
DictionaryCache.Remove(k);
}
}
/// <summary>
/// Clears all cache items that starts with the key passed.
/// </summary>
/// <param name="keyStartsWith">The start of the key</param>
public virtual void ClearCacheByKeySearch(string keyStartsWith)
{
var keysToRemove = DictionaryCache.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c => c.Key is string && ((string)c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith)))
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
{
DictionaryCache.Remove(k);
}
}
/// <summary>
/// Clears all cache items that have a key that matches the regular expression
/// </summary>
/// <param name="regexString"></param>
public virtual void ClearCacheByKeyExpression(string regexString)
{
var keysToRemove = new List<object>();
foreach (var item in DictionaryCache)
{
var c = new DictionaryItemWrapper(item);
var s = c.Key as string;
if (s != null)
{
var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
if (Regex.IsMatch(withoutPrefix, regexString))
{
keysToRemove.Add(c.Key);
}
}
}
foreach (var k in keysToRemove)
{
DictionaryCache.Remove(k);
}
}
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
{
return (from object item in DictionaryCache
select new DictionaryItemWrapper(item)
into c
where c.Key is string && ((string) c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith))
select c.Value).ToList();
}
public IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
{
var found = new List<object>();
foreach (var item in DictionaryCache)
{
var c = new DictionaryItemWrapper(item);
var s = c.Key as string;
if (s != null)
{
var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
if (Regex.IsMatch(withoutPrefix, regexString))
{
found.Add(c.Value);
}
}
}
return found;
}
/// <summary>
/// Returns a cache item by key, does not update the cache if it isn't there.
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
public virtual object GetCacheItem(string cacheKey)
{
var result = DictionaryCache.Get(GetCacheKey(cacheKey));
return result;
}
public abstract object GetCacheItem(string cacheKey, Func<object> getCacheItem);
/// <summary>
/// We prefix all cache keys with this so that we know which ones this class has created when
/// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create.
/// </summary>
// prefix cache keys so we know which one are ours
protected const string CacheItemPrefix = "umbrtmche";
// an object that represent a value that has not been created yet
protected readonly object ValueNotCreated = new object();
// manupulate the underlying cache entries
// these *must* be called from within the appropriate locks
// and use the full prefixed cache keys
protected abstract IEnumerable<DictionaryEntry> GetDictionaryEntries();
protected abstract void RemoveEntry(string key);
protected abstract object GetEntry(string key);
// read-write lock the underlying cache
protected abstract IDisposable ReadLock { get; }
protected abstract IDisposable WriteLock { get; }
protected string GetCacheKey(string key)
{
return string.Format("{0}-{1}", CacheItemPrefix, key);
}
protected object GetSafeLazyValue(Lazy<object> lazy, bool onlyIfValueIsCreated = false)
{
try
{
// if onlyIfValueIsCreated, do not trigger value creation
// must return something, though, to differenciate from null values
if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
return lazy.Value;
}
catch
{
return null;
}
}
#region Clear
public virtual void ClearAllCache()
{
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheItem(string key)
{
var cacheKey = GetCacheKey(key);
using (WriteLock)
{
RemoveEntry(cacheKey);
}
}
public virtual void ClearCacheObjectTypes(string typeName)
{
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
return value == null || value.GetType().ToString().InvariantEquals(typeName);
})
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheObjectTypes<T>()
{
var typeOfT = typeof(T);
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// compare on exact type, don't use "is"
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
return value == null || value.GetType() == typeOfT;
})
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{
var typeOfT = typeof(T);
var plen = CacheItemPrefix.Length + 1;
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// compare on exact type, don't use "is"
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
if (value == null) return true;
return value.GetType() == typeOfT
// run predicate on the 'public key' part only, ie without prefix
&& predicate(((string)x.Key).Substring(plen), (T)value);
}))
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheByKeySearch(string keyStartsWith)
{
var plen = CacheItemPrefix.Length + 1;
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheByKeyExpression(string regexString)
{
var plen = CacheItemPrefix.Length + 1;
using (WriteLock)
{
foreach (var entry in GetDictionaryEntries()
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
.ToArray())
RemoveEntry((string) entry.Key);
}
}
#endregion
#region Get
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
{
var plen = CacheItemPrefix.Length + 1;
IEnumerable<DictionaryEntry> entries;
using (ReadLock)
{
entries = GetDictionaryEntries()
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
.ToArray(); // evaluate while locked
}
return entries
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
.Where(x => x != null); // backward compat, don't store null values in the cache
}
public virtual IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
{
const string prefix = CacheItemPrefix + "-";
var plen = prefix.Length;
IEnumerable<DictionaryEntry> entries;
using (ReadLock)
{
entries = GetDictionaryEntries()
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
.ToArray(); // evaluate while locked
}
return entries
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
.Where(x => x != null); // backward compat, don't store null values in the cache
}
public virtual object GetCacheItem(string cacheKey)
{
cacheKey = GetCacheKey(cacheKey);
Lazy<object> result;
using (ReadLock)
{
result = GetEntry(cacheKey) as Lazy<object>; // null if key not found
}
return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
}
public abstract object GetCacheItem(string cacheKey, Func<object> getCacheItem);
#endregion
}
}
@@ -1,45 +0,0 @@
using System;
using System.Collections;
namespace Umbraco.Core.Cache
{
internal class DictionaryCacheWrapper : IEnumerable
{
private readonly IEnumerable _inner;
private readonly Func<object, object> _get;
private readonly Action<object> _remove;
public DictionaryCacheWrapper(
IEnumerable inner,
Func<object, object> get,
Action<object> remove)
{
_inner = inner;
_get = get;
_remove = remove;
}
public object this[object key]
{
get
{
return Get(key);
}
}
public object Get(object key)
{
return _get(key);
}
public void Remove(object key)
{
_remove(key);
}
public IEnumerator GetEnumerator()
{
return _inner.GetEnumerator();
}
}
}
@@ -1,14 +0,0 @@
namespace Umbraco.Core.Cache
{
internal class DictionaryItemWrapper
{
public DictionaryItemWrapper(dynamic item)
{
Key = item.Key;
Value = item.Value;
}
public object Key { get; private set; }
public object Value { get; private set; }
}
}
@@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
namespace Umbraco.Core.Cache
@@ -12,36 +11,115 @@ namespace Umbraco.Core.Cache
/// </summary>
internal class HttpRequestCacheProvider : DictionaryCacheProviderBase
{
private readonly Func<HttpContextBase> _context;
// context provider
// the idea is that there is only one, application-wide HttpRequestCacheProvider instance,
// that is initialized with a method that returns the "current" context.
// NOTE
// but then it is initialized with () => new HttpContextWrapper(HttpContent.Current)
// which is higly inefficient because it creates a new wrapper each time we refer to _context()
// so replace it with _context1 and _context2 below + a way to get context.Items.
//private readonly Func<HttpContextBase> _context;
public HttpRequestCacheProvider(HttpContext context)
// NOTE
// and then in almost 100% cases _context2 will be () => HttpContext.Current
// so why not bring that logic in here and fallback on to HttpContext.Current when
// _context1 is null?
//private readonly HttpContextBase _context1;
//private readonly Func<HttpContext> _context2;
private readonly HttpContextBase _context;
private IDictionary ContextItems
{
_context = () => new HttpContextWrapper(context);
//get { return _context1 != null ? _context1.Items : _context2().Items; }
get { return _context != null ? _context.Items : HttpContext.Current.Items; }
}
public HttpRequestCacheProvider(Func<HttpContextBase> context)
// for unit tests
public HttpRequestCacheProvider(HttpContextBase context)
{
_context = context;
}
protected override DictionaryCacheWrapper DictionaryCache
// main constructor
// will use HttpContext.Current
public HttpRequestCacheProvider(/*Func<HttpContext> context*/)
{
//_context2 = context;
}
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
return ContextItems.Cast<DictionaryEntry>()
.Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
}
protected override void RemoveEntry(string key)
{
ContextItems.Remove(key);
}
protected override object GetEntry(string key)
{
return ContextItems[key];
}
#region Lock
protected override IDisposable ReadLock
{
// there's no difference between ReadLock and WriteLock here
get { return WriteLock; }
}
protected override IDisposable WriteLock
{
// NOTE
// could think about just overriding base.Locker to return a different
// object but then we'd create a ReaderWriterLockSlim per request,
// which is less efficient than just using a basic monitor lock.
get
{
var ctx = _context();
return new DictionaryCacheWrapper(
ctx.Items,
o => ctx.Items[o],
o => ctx.Items.Remove(o));
return new MonitorLock(ContextItems.SyncRoot);
}
}
#endregion
#region Get
public override object GetCacheItem(string cacheKey, Func<object> getCacheItem)
{
var ctx = _context();
var ck = GetCacheKey(cacheKey);
return ctx.Items[ck] ?? (ctx.Items[ck] = getCacheItem());
cacheKey = GetCacheKey(cacheKey);
Lazy<object> result;
using (WriteLock)
{
result = ContextItems[cacheKey] as Lazy<object>; // null if key not found
// cannot create value within the lock, so if result.IsValueCreated is false, just
// do nothing here - means that if creation throws, a race condition could cause
// more than one thread to reach the return statement below and throw - accepted.
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
{
result = new Lazy<object>(getCacheItem);
ContextItems[cacheKey] = result;
}
}
// this may throw if getCacheItem throws, but this is the only place where
// it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
// and pretend exceptions were never inserted into cache to begin with.
return result.Value;
}
#endregion
#region Insert
#endregion
}
}
@@ -1,10 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Caching;
using Umbraco.Core.Logging;
using CacheItemPriority = System.Web.Caching.CacheItemPriority;
namespace Umbraco.Core.Cache
@@ -14,47 +13,51 @@ namespace Umbraco.Core.Cache
/// </summary>
internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider
{
// locker object that supports upgradeable read locking
// does not need to support recursion if we implement the cache correctly and ensure
// that methods cannot be reentrant, ie we do NOT create values while holding a lock.
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
private readonly System.Web.Caching.Cache _cache;
private readonly DictionaryCacheWrapper _wrapper;
public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache)
{
_cache = cache;
_wrapper = new DictionaryCacheWrapper(_cache, s => _cache.Get(s.ToString()), o => _cache.Remove(o.ToString()));
}
protected override DictionaryCacheWrapper DictionaryCache
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
{
get { return _wrapper; }
const string prefix = CacheItemPrefix + "-";
return _cache.Cast<DictionaryEntry>()
.Where(x => x.Key is string && ((string) x.Key).StartsWith(prefix));
}
/// <summary>
/// Clears all objects in the System.Web.Cache with the System.Type specified that satisfy the predicate
/// </summary>
public override void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
protected override void RemoveEntry(string key)
{
try
{
lock (Locker)
{
foreach (DictionaryEntry c in _cache)
{
var key = c.Key.ToString();
if (_cache[key] != null
&& _cache[key] is T
&& predicate(key, (T)_cache[key]))
{
_cache.Remove(c.Key.ToString());
}
}
}
}
catch (Exception e)
{
LogHelper.Error<CacheHelper>("Cache clearing error", e);
}
_cache.Remove(key);
}
protected override object GetEntry(string key)
{
return _cache.Get(key);
}
#region Lock
protected override IDisposable ReadLock
{
get { return new ReadLock(_locker); }
}
protected override IDisposable WriteLock
{
get { return new WriteLock(_locker); }
}
#endregion
#region Get
/// <summary>
/// Gets (and adds if necessary) an item from the cache with all of the default parameters
/// </summary>
@@ -81,25 +84,64 @@ namespace Umbraco.Core.Cache
{
cacheKey = GetCacheKey(cacheKey);
using (var lck = new UpgradeableReadLock(Locker))
// NOTE - because we don't know what getCacheItem does, how long it will take and whether it will hang,
// getCacheItem should run OUTSIDE of the global application lock else we run into lock contention and
// nasty performance issues.
// So.... we insert a Lazy<object> in the cache while holding the global application lock, and then rely
// on the Lazy lock to ensure that getCacheItem runs once and everybody waits on it, while the global
// application lock has been released.
// NOTE
// The Lazy value creation may produce a null value.
// Must make sure (for backward compatibility) that we pretend they are not in the cache.
// So if we find an entry in the cache that already has its value created and is null,
// pretend it was not there. If value is not already created, wait... and return null, that's
// what prior code did.
// NOTE
// The Lazy value creation may throw.
// So... the null value _will_ be in the cache but never returned
Lazy<object> result;
// Fast!
// Only one thread can enter an UpgradeableReadLock at a time, but it does not prevent other
// threads to enter a ReadLock in the meantime -- only upgrading to WriteLock will prevent all
// reads. We first try with a normal ReadLock for maximum concurrency and take the penalty of
// having to re-lock in case there's no value. Would need to benchmark to figure out whether
// it's worth it, though...
using (new ReadLock(_locker))
{
var result = DictionaryCache.Get(cacheKey);
if (result == null)
{
lck.UpgradeToWriteLock();
result = getCacheItem();
if (result != null)
{
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
}
}
return result;
result = _cache.Get(cacheKey) as Lazy<object>; // null if key not found
}
var value = result == null ? null : GetSafeLazyValue(result);
if (value != null) return value;
using (var lck = new UpgradeableReadLock(_locker))
{
result = _cache.Get(cacheKey) as Lazy<object>; // null if key not found
// cannot create value within the lock, so if result.IsValueCreated is false, just
// do nothing here - means that if creation throws, a race condition could cause
// more than one thread to reach the return statement below and throw - accepted.
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
{
result = new Lazy<object>(getCacheItem);
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
lck.UpgradeToWriteLock();
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
}
}
// this may throw if getCacheItem throws, but this is the only place where
// it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
// and pretend exceptions were never inserted into cache to begin with.
return result.Value;
}
public object GetCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
@@ -112,6 +154,10 @@ namespace Umbraco.Core.Cache
return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
}
#endregion
#region Insert
/// <summary>
/// This overload is here for legacy purposes
/// </summary>
@@ -124,15 +170,22 @@ namespace Umbraco.Core.Cache
/// <param name="dependency"></param>
internal void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null)
{
var result = getCacheItem();
if (result == null) return;
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
// and make sure we don't store a null value.
var result = new Lazy<object>(getCacheItem);
var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
if (value == null) return; // do not store null values (backward compat)
cacheKey = GetCacheKey(cacheKey);
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
using (new WriteLock(_locker))
{
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
}
}
public void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
@@ -144,5 +197,7 @@ namespace Umbraco.Core.Cache
}
InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
}
#endregion
}
}
@@ -16,17 +16,37 @@ namespace Umbraco.Core.Cache
/// </summary>
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
{
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
internal ObjectCache MemoryCache;
// an object that represent a value that has not been created yet
protected readonly object ValueNotCreated = new object();
public ObjectCacheRuntimeCacheProvider()
{
MemoryCache = new MemoryCache("in-memory");
}
protected object GetSafeLazyValue(Lazy<object> lazy, bool onlyIfValueIsCreated = false)
{
try
{
// if onlyIfValueIsCreated, do not trigger value creation
// must return something, though, to differenciate from null values
if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
return lazy.Value;
}
catch
{
return null;
}
}
#region Clear
public virtual void ClearAllCache()
{
using (new WriteLock(Locker))
using (new WriteLock(_locker))
{
MemoryCache.DisposeIfDisposable();
MemoryCache = new MemoryCache("in-memory");
@@ -35,7 +55,7 @@ namespace Umbraco.Core.Cache
public virtual void ClearCacheItem(string key)
{
using (new WriteLock(Locker))
using (new WriteLock(_locker))
{
if (MemoryCache[key] == null) return;
MemoryCache.Remove(key);
@@ -44,131 +64,179 @@ namespace Umbraco.Core.Cache
public virtual void ClearCacheObjectTypes(string typeName)
{
using (new WriteLock(Locker))
using (new WriteLock(_locker))
{
var keysToRemove = MemoryCache
.Where(c => c.Value != null && c.Value.GetType().ToString().InvariantEquals(typeName))
.Select(c => c.Key)
.ToArray();
foreach (var k in keysToRemove)
MemoryCache.Remove(k);
foreach (var key in MemoryCache
.Where(x =>
{
// x.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
return value == null || value.GetType().ToString().InvariantEquals(typeName);
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
}
}
public virtual void ClearCacheObjectTypes<T>()
{
using (new WriteLock(Locker))
using (new WriteLock(_locker))
{
var typeOfT = typeof (T);
var keysToRemove = MemoryCache
.Where(c => c.Value != null && c.Value.GetType() == typeOfT)
.Select(c => c.Key)
.ToArray();
foreach (var k in keysToRemove)
MemoryCache.Remove(k);
foreach (var key in MemoryCache
.Where(x =>
{
// x.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
return value == null || value.GetType() == typeOfT;
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
}
}
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{
using (new WriteLock(Locker))
using (new WriteLock(_locker))
{
var typeOfT = typeof(T);
var keysToRemove = MemoryCache
.Where(c => c.Value != null && c.Value.GetType() == typeOfT && predicate(c.Key, (T)c.Value))
.Select(c => c.Key)
.ToArray();
foreach (var k in keysToRemove)
MemoryCache.Remove(k);
foreach (var key in MemoryCache
.Where(x =>
{
// x.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
if (value == null) return true;
return value.GetType() == typeOfT
&& predicate(x.Key, (T) value);
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
}
}
public virtual void ClearCacheByKeySearch(string keyStartsWith)
{
using (new WriteLock(Locker))
using (new WriteLock(_locker))
{
var keysToRemove = (from c in MemoryCache where c.Key.InvariantStartsWith(keyStartsWith) select c.Key).ToList();
foreach (var k in keysToRemove)
{
MemoryCache.Remove(k);
}
foreach (var key in MemoryCache
.Where(x => x.Key.InvariantStartsWith(keyStartsWith))
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
}
}
public virtual void ClearCacheByKeyExpression(string regexString)
{
using (new WriteLock(Locker))
using (new WriteLock(_locker))
{
var keysToRemove = (from c in MemoryCache where Regex.IsMatch(c.Key, regexString) select c.Key).ToList();
foreach (var k in keysToRemove)
{
MemoryCache.Remove(k);
}
foreach (var key in MemoryCache
.Where(x => Regex.IsMatch(x.Key, regexString))
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
}
}
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
#endregion
#region Get
public IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
{
return (from c in MemoryCache
where c.Key.InvariantStartsWith(keyStartsWith)
select c.Value).ToList();
KeyValuePair<string, object>[] entries;
using (new ReadLock(_locker))
{
entries = MemoryCache
.Where(x => x.Key.InvariantStartsWith(keyStartsWith))
.ToArray(); // evaluate while locked
}
return entries
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
.Where(x => x != null) // backward compat, don't store null values in the cache
.ToList();
}
public IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
{
return (from c in MemoryCache
where Regex.IsMatch(c.Key, regexString)
select c.Value).ToList();
KeyValuePair<string, object>[] entries;
using (new ReadLock(_locker))
{
entries = MemoryCache
.Where(x => Regex.IsMatch(x.Key, regexString))
.ToArray(); // evaluate while locked
}
return entries
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
.Where(x => x != null) // backward compat, don't store null values in the cache
.ToList();
}
public virtual object GetCacheItem(string cacheKey)
public object GetCacheItem(string cacheKey)
{
var result = MemoryCache.Get(cacheKey);
return result;
Lazy<object> result;
using (new ReadLock(_locker))
{
result = MemoryCache.Get(cacheKey) as Lazy<object>; // null if key not found
}
return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
}
public virtual object GetCacheItem(string cacheKey, Func<object> getCacheItem)
public object GetCacheItem(string cacheKey, Func<object> getCacheItem)
{
return GetCacheItem(cacheKey, getCacheItem, null);
}
public object GetCacheItem(
string cacheKey,
Func<object> getCacheItem,
TimeSpan? timeout,
bool isSliding = false,
CacheItemPriority priority = CacheItemPriority.Normal,
CacheItemRemovedCallback removedCallback = null,
string[] dependentFiles = null)
public object GetCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
using (var lck = new UpgradeableReadLock(Locker))
{
var result = MemoryCache.Get(cacheKey);
if (result == null)
{
lck.UpgradeToWriteLock();
// see notes in HttpRuntimeCacheProvider
result = getCacheItem();
if (result != null)
{
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
MemoryCache.Set(cacheKey, result, policy);
}
Lazy<object> result;
using (var lck = new UpgradeableReadLock(_locker))
{
result = MemoryCache.Get(cacheKey) as Lazy<object>;
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
{
result = new Lazy<object>(getCacheItem);
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
lck.UpgradeToWriteLock();
MemoryCache.Set(cacheKey, result, policy);
}
return result;
}
return result.Value;
}
#endregion
#region Insert
public void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
object result = getCacheItem();
if (result != null)
{
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
MemoryCache.Set(cacheKey, result, policy);
}
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
// and make sure we don't store a null value.
var result = new Lazy<object>(getCacheItem);
var value = result.Value; // force evaluation now
if (value == null) return; // do not store null values (backward compat)
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
MemoryCache.Set(cacheKey, result, policy);
}
#endregion
private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
+2 -2
View File
@@ -44,7 +44,7 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(HttpRuntime.Cache),
new StaticCacheProvider(),
new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current)))
new HttpRequestCacheProvider())
{
}
@@ -56,7 +56,7 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(cache),
new StaticCacheProvider(),
new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current)))
new HttpRequestCacheProvider())
{
}
@@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("6.2.1");
private static readonly Version Version = new Version("6.2.2");
/// <summary>
/// Gets the current version of Umbraco.
+6 -36
View File
@@ -15,43 +15,13 @@ namespace Umbraco.Core
public static IEnumerable<IEnumerable<T>> InGroupsOf<T>(this IEnumerable<T> source, int groupSize)
{
if (source == null)
throw new NullReferenceException("source");
throw new ArgumentNullException("source");
if (groupSize <= 0)
throw new ArgumentException("Must be greater than zero.", "groupSize");
// enumerate the source only once!
return new InGroupsEnumerator<T>(source, groupSize).Groups();
}
// this class makes sure that the source is enumerated only ONCE
// which means that when it is enumerated, the actual groups content
// has to be evaluated at the same time, and stored in an array.
private class InGroupsEnumerator<T>
{
private readonly IEnumerator<T> _source;
private readonly int _count;
private bool _mightHaveNext;
public InGroupsEnumerator(IEnumerable<T> source, int count)
{
_source = source.GetEnumerator();
_count = count;
_mightHaveNext = true;
}
public IEnumerable<IEnumerable<T>> Groups()
{
while (_mightHaveNext && _source.MoveNext())
yield return Group().ToArray(); // see note above
}
private IEnumerable<T> Group()
{
var c = 0;
do
{
yield return _source.Current;
} while (++c < _count && _source.MoveNext());
_mightHaveNext = c == _count;
}
return source
.Select((x, i) => Tuple.Create(i / groupSize, x))
.GroupBy(t => t.Item1, t => t.Item2);
}
/// <summary>The distinct by.</summary>
+4 -1
View File
@@ -19,7 +19,10 @@ namespace Umbraco.Core.IO
public static void CopyFile(this IFileSystem fs, string path, string newPath)
{
fs.AddFile(newPath, fs.OpenFile(path));
using (var stream = fs.OpenFile(path))
{
fs.AddFile(newPath, stream);
}
}
public static string GetExtension(this IFileSystem fs, string path)
+3 -2
View File
@@ -93,8 +93,9 @@ namespace Umbraco.Core.IO
public void AddFile(string path, Stream stream, bool overrideIfExists)
{
if (FileExists(path) && !overrideIfExists) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
var exists = FileExists(path);
if (exists && overrideIfExists == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
EnsureDirectory(Path.GetDirectoryName(path));
if (stream.CanSeek)
+15 -8
View File
@@ -133,16 +133,23 @@ namespace Umbraco.Core.IO
{
if (_size == null)
{
EnsureFileSupportsResizing();
if (_fs.FileExists(Path))
{
EnsureFileSupportsResizing();
var fs = _fs.OpenFile(Path);
var image = Image.FromStream(fs);
var fileWidth = image.Width;
var fileHeight = image.Height;
fs.Close();
image.Dispose();
using (var fs = _fs.OpenFile(Path))
using (var image = Image.FromStream(fs))
{
_size = new Size(fileWidth, fileHeight);
var fileWidth = image.Width;
var fileHeight = image.Height;
_size = new Size(fileWidth, fileHeight);
}
}
else
{
_size = new Size(-1, -1);
}
}
return _size.Value;
}
@@ -222,6 +222,28 @@ namespace Umbraco.Core.Models
}
#endregion
/// <summary>
/// Removes characters that are not valide XML characters from all entity properties
/// of type string. See: http://stackoverflow.com/a/961504/5018
/// </summary>
/// <returns></returns>
/// <remarks>
/// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it.
/// </remarks>
/// <param name="entity"></param>
public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity)
{
entity.Name = entity.Name.ToValidXmlString();
foreach (var property in entity.Properties)
{
if (property.Value is string)
{
var value = (string)property.Value;
property.Value = value.ToValidXmlString();
}
}
}
/// <summary>
/// Checks if the IContentBase has children
/// </summary>
+17 -9
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -27,6 +28,11 @@ namespace Umbraco.Core.Models
public static class DeepCloneHelper
{
/// <summary>
/// Used to avoid constant reflection (perf)
/// </summary>
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropCache = new ConcurrentDictionary<Type, PropertyInfo[]>();
/// <summary>
/// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output')
/// </summary>
@@ -43,16 +49,18 @@ namespace Umbraco.Core.Models
throw new InvalidOperationException("Both the input and output types must be the same");
}
var refProperties = inputType.GetProperties()
.Where(x =>
//is not attributed with the ignore clone attribute
var refProperties = PropCache.GetOrAdd(inputType, type =>
inputType.GetProperties()
.Where(x =>
//is not attributed with the ignore clone attribute
Attribute.GetCustomAttribute(x, typeof(DoNotCloneAttribute)) == null
//reference type but not string
&& x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
//settable
&& x.CanWrite
//non-indexed
&& x.GetIndexParameters().Any() == false);
//reference type but not string
&& x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
//settable
&& x.CanWrite
//non-indexed
&& x.GetIndexParameters().Any() == false)
.ToArray());
foreach (var propertyInfo in refProperties)
{
+3 -6
View File
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
@@ -23,5 +20,5 @@ namespace Umbraco.Core.Models
var dirty = (IRememberBeingDirty)entity;
return dirty.WasPropertyDirty("Id");
}
}
}
}
}
+48 -9
View File
@@ -1,27 +1,55 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Models.Membership
{
/// <summary>
/// Represents the Type for a Backoffice User
/// </summary>
/// <remarks>
/// Should be internal until a proper user/membership implementation
/// is part of the roadmap.
/// </remarks>
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class UserType : Entity, IUserType
{
[DataMember]
public string Alias { get; set; }
private string _alias;
private string _name;
private IEnumerable<string> _permissions;
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Name);
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Alias);
private static readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo<UserType, IEnumerable<string>>(x => x.Permissions);
[DataMember]
public string Name { get; set; }
public string Alias
{
get { return _alias; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase);
return _alias;
}, _alias, AliasSelector);
}
}
[DataMember]
public string Name
{
get { return _name; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
/// The set of default permissions for the user type
@@ -30,6 +58,17 @@ namespace Umbraco.Core.Models.Membership
/// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future.
/// </remarks>
[DataMember]
public IEnumerable<string> Permissions { get; set; }
public IEnumerable<string> Permissions
{
get { return _permissions; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_permissions = value;
return _permissions;
}, _permissions, PermissionsSelector);
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
namespace Umbraco.Core
{
/// <summary>
/// Provides an equivalent to the c# lock statement, to be used in a using block.
/// </summary>
/// <remarks>Ie replace <c>lock (o) {...}</c> by <c>using (new MonitorLock(o)) { ... }</c></remarks>
public class MonitorLock : IDisposable
{
private readonly object _locker;
private readonly bool _entered;
/// <summary>
/// Initializes a new instance of the <see cref="MonitorLock"/> class with an object to lock.
/// </summary>
/// <param name="locker">The object to lock.</param>
/// <remarks>Should always be used within a using block.</remarks>
public MonitorLock(object locker)
{
_locker = locker;
_entered = false;
System.Threading.Monitor.Enter(_locker, ref _entered);
}
void IDisposable.Dispose()
{
if (_entered)
System.Threading.Monitor.Exit(_locker);
}
}
}
@@ -63,6 +63,11 @@ namespace Umbraco.Core.ObjectResolution
}
}
// NOTE - the ugly code below exists only because of umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers
// which wants to re-register actions and handlers instead of properly restarting the application. Don't even think
// about using it for anything else. Also, while the backdoor is open, the resolution system is locked so nothing
// can work properly => deadlocks. Therefore, open the backdoor, do resolution changes EXCLUSIVELY, and close the door!
/// <summary>
/// Returns a disposable object that reprents dirty access to temporarily unfrozen resolution configuration.
/// </summary>
@@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Factories
ComputerName = entity.ComputerName
};
if (entity.HasIdentity)
dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
dto.Id = int.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
return dto;
}
@@ -221,6 +221,7 @@ namespace Umbraco.Core.Persistence
if (overwrite && tableExist)
{
db.DropTable(tableName);
tableExist = false;
}
if (tableExist == false)
@@ -245,6 +245,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
//Ensure that strings don't contain characters that are invalid in XML
entity.SanitizeEntityPropertiesForXmlStorage();
var factory = new ContentFactory(NodeObjectTypeId, entity.Id);
var dto = factory.BuildDto(entity);
@@ -350,6 +353,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
//Ensure that strings don't contain characters that are invalid in XML
entity.SanitizeEntityPropertiesForXmlStorage();
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId"))
{
@@ -144,6 +144,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
((DictionaryItem)entity).AddingEntity();
foreach (var translation in entity.Translations)
translation.Value = translation.Value.ToValidXmlString();
var factory = new DictionaryItemFactory();
var dto = factory.BuildDto(entity);
@@ -165,6 +168,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
((Entity)entity).UpdatingEntity();
foreach (var translation in entity.Translations)
translation.Value = translation.Value.ToValidXmlString();
var factory = new DictionaryItemFactory();
var dto = factory.BuildDto(entity);
@@ -12,6 +12,17 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IRepositoryVersionable<TId, TEntity> : IRepositoryQueryable<TId, TEntity>
where TEntity : IAggregateRoot
{
/// <summary>
/// Get the total count of entities
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <returns></returns>
int Count(string contentTypeAlias = null);
int CountChildren(int parentId, string contentTypeAlias = null);
int CountDescendants(int parentId, string contentTypeAlias = null);
/// <summary>
/// Gets a list of all versions for an <see cref="TEntity"/>.
/// </summary>
@@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false);
sql.Where(GetBaseWhereClause(), new { Id = id });
var languageDto = Database.First<LanguageDto>(sql);
var languageDto = Database.FirstOrDefault<LanguageDto>(sql);
if (languageDto == null)
return null;
@@ -213,6 +213,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
//Ensure that strings don't contain characters that are invalid in XML
entity.SanitizeEntityPropertiesForXmlStorage();
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
var dto = factory.BuildDto(entity);
@@ -280,6 +283,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
//Ensure that strings don't contain characters that are invalid in XML
entity.SanitizeEntityPropertiesForXmlStorage();
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId"))
{
@@ -413,4 +419,4 @@ namespace Umbraco.Core.Persistence.Repositories
return currentName;
}
}
}
}
@@ -204,6 +204,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
((Member)entity).AddingEntity();
//Ensure that strings don't contain characters that are invalid in XML
entity.SanitizeEntityPropertiesForXmlStorage();
var factory = new MemberFactory(NodeObjectTypeId, entity.Id);
var dto = factory.BuildDto(entity);
@@ -277,6 +280,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Updates Modified date
((Member)entity).UpdatingEntity();
//Ensure that strings don't contain characters that are invalid in XML
entity.SanitizeEntityPropertiesForXmlStorage();
var dirtyEntity = (ICanBeDirty)entity;
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
@@ -130,6 +130,9 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns></returns>
public IEnumerable<TEntity> GetAll(params TId[] ids)
{
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
ids = ids.Distinct().ToArray();
if (ids.Any())
{
var entities = _cache.GetByIds(
@@ -64,6 +64,9 @@ namespace Umbraco.Core.Persistence.Repositories
public override IEnumerable<Script> GetAll(params string[] ids)
{
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
ids = ids.Distinct().ToArray();
if (ids.Any())
{
foreach (var id in ids)
@@ -85,6 +85,9 @@ namespace Umbraco.Core.Persistence.Repositories
public override IEnumerable<Stylesheet> GetAll(params string[] ids)
{
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
ids = ids.Distinct().ToArray();
if (ids.Any())
{
foreach (var id in ids)
@@ -93,6 +93,90 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
public int CountDescendants(int parentId, string contentTypeAlias = null)
{
var pathMatch = parentId == -1
? "-1,"
: "," + parentId + ",";
var sql = new Sql();
if (contentTypeAlias.IsNullOrWhiteSpace())
{
sql.Select("COUNT(*)")
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
.Where<NodeDto>(x => x.Path.Contains(pathMatch));
}
else
{
sql.Select("COUNT(*)")
.From<NodeDto>()
.InnerJoin<ContentDto>()
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentTypeDto>()
.On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
.Where<NodeDto>(x => x.Path.Contains(pathMatch))
.Where<ContentTypeDto>(x => x.Alias == contentTypeAlias);
}
return Database.ExecuteScalar<int>(sql);
}
public int CountChildren(int parentId, string contentTypeAlias = null)
{
var sql = new Sql();
if (contentTypeAlias.IsNullOrWhiteSpace())
{
sql.Select("COUNT(*)")
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
.Where<NodeDto>(x => x.ParentId == parentId);
}
else
{
sql.Select("COUNT(*)")
.From<NodeDto>()
.InnerJoin<ContentDto>()
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentTypeDto>()
.On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
.Where<NodeDto>(x => x.ParentId == parentId)
.Where<ContentTypeDto>(x => x.Alias == contentTypeAlias);
}
return Database.ExecuteScalar<int>(sql);
}
/// <summary>
/// Get the total count of entities
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <returns></returns>
public int Count(string contentTypeAlias = null)
{
var sql = new Sql();
if (contentTypeAlias.IsNullOrWhiteSpace())
{
sql.Select("COUNT(*)")
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
}
else
{
sql.Select("COUNT(*)")
.From<NodeDto>()
.InnerJoin<ContentDto>()
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentTypeDto>()
.On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
.Where<ContentTypeDto>(x => x.Alias == contentTypeAlias);
}
return Database.ExecuteScalar<int>(sql);
}
/// <summary>
/// This is a fix for U4-1407 - when property types are added to a content type - the property of the entity are not actually created
/// and we get YSODs
@@ -0,0 +1,61 @@
using System;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace Umbraco.Core.Publishing
{
/// <summary>
/// Used to perform scheduled publishing/unpublishing
/// </summary>
internal class ScheduledPublisher
{
private readonly IContentService _contentService;
public ScheduledPublisher(IContentService contentService)
{
_contentService = contentService;
}
public void CheckPendingAndProcess()
{
foreach (var d in _contentService.GetContentForRelease())
{
try
{
d.ReleaseDate = null;
var result = _contentService.SaveAndPublishWithStatus(d, (int)d.GetWriterProfile().Id);
if (result.Success == false)
{
if (result.Exception != null)
{
LogHelper.Error<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception);
}
else
{
LogHelper.Warn<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType);
}
}
}
catch (Exception ee)
{
LogHelper.Error<ScheduledPublisher>(string.Format("Error publishing node {0}", d.Id), ee);
throw;
}
}
foreach (var d in _contentService.GetContentForExpiration())
{
try
{
d.ExpireDate = null;
_contentService.UnPublish(d, (int)d.GetWriterProfile().Id);
}
catch (Exception ee)
{
LogHelper.Error<ScheduledPublisher>(string.Format("Error unpublishing node {0}", d.Id), ee);
throw;
}
}
}
}
}
@@ -211,7 +211,7 @@ namespace Umbraco.Core.Security
_enablePasswordReset = config.GetValue("enablePasswordReset", false);
_requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false);
_requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true);
_maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
_maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 20, false, 0);
_passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
_minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80);
_minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80);
@@ -62,7 +62,12 @@ namespace Umbraco.Core.Security
return Membership.Providers[UmbracoSettings.DefaultBackofficeProvider];
}
public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider)
/// <summary>
/// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login)
/// </summary>
/// <param name="membershipProvider"></param>
/// <returns></returns>
public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider)
{
var username = membershipProvider.GetCurrentUserName();
return username.IsNullOrWhiteSpace()
@@ -70,6 +75,19 @@ namespace Umbraco.Core.Security
: membershipProvider.GetUser(username, true);
}
/// <summary>
/// Returns the currently logged in MembershipUser
/// </summary>
/// <param name="membershipProvider"></param>
/// <returns></returns>
public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider)
{
var username = membershipProvider.GetCurrentUserName();
return username.IsNullOrWhiteSpace()
? null
: membershipProvider.GetUser(username, false);
}
/// <summary>
/// Just returns the current user's login name (just a wrapper).
/// </summary>
@@ -74,6 +74,33 @@ namespace Umbraco.Core.Services
_dataTypeService = dataTypeService;
}
public int Count(string contentTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
return repository.Count(contentTypeAlias);
}
}
public int CountChildren(int parentId, string contentTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
return repository.CountChildren(parentId, contentTypeAlias);
}
}
public int CountDescendants(int parentId, string contentTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateContentRepository(uow))
{
return repository.CountDescendants(parentId, contentTypeAlias);
}
}
/// <summary>
/// Assigns a single permission to the current content item for the specified user ids
/// </summary>
@@ -11,6 +11,10 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IContentService : IService
{
int Count(string contentTypeAlias = null);
int CountChildren(int parentId, string contentTypeAlias = null);
int CountDescendants(int parentId, string contentTypeAlias = null);
/// <summary>
/// Assigns a single permission to the current content item for the specified user ids
/// </summary>
@@ -9,6 +9,10 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IMediaService : IService
{
int Count(string contentTypeAlias = null);
int CountChildren(int parentId, string contentTypeAlias = null);
int CountDescendants(int parentId, string contentTypeAlias = null);
IEnumerable<IMedia> GetByIds(IEnumerable<int> ids);
/// <summary>
@@ -11,6 +11,8 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IMemberService : IMembershipMemberService
{
int Count(string contentTypeAlias = null);
IMember CreateMember(string username, string email, string name, string memberTypeAlias);
IMember CreateMember(string username, string email, string name, IMemberType memberType);
IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias);
@@ -237,6 +237,15 @@ namespace Umbraco.Core.Services
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
/// <summary>
/// Checks whether two items are related
/// </summary>
/// <param name="parentId">Id of the Parent relation</param>
/// <param name="childId">Id of the Child relation</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
bool AreRelated(int parentId, int childId, string relationTypeAlias);
/// <summary>
/// Saves a <see cref="Relation"/>
/// </summary>
+27
View File
@@ -251,6 +251,33 @@ namespace Umbraco.Core.Services
}
}
public int Count(string contentTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
{
return repository.Count(contentTypeAlias);
}
}
public int CountChildren(int parentId, string contentTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
{
return repository.CountChildren(parentId, contentTypeAlias);
}
}
public int CountDescendants(int parentId, string contentTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
{
return repository.CountDescendants(parentId, contentTypeAlias);
}
}
/// <summary>
/// Gets an <see cref="IMedia"/> object by Id
/// </summary>
@@ -642,6 +642,15 @@ namespace Umbraco.Core.Services
}
}
public int Count(string contentTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMemberRepository(uow))
{
return repository.Count(contentTypeAlias);
}
}
/// <summary>
/// Creates a member object
/// </summary>
@@ -773,6 +773,7 @@ namespace Umbraco.Core.Services
var items = new List<IDictionaryItem>();
foreach (var dictionaryItemElement in dictionaryItemElementList.Elements("DictionaryItem"))
items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId));
return items;
}
@@ -788,6 +789,7 @@ namespace Umbraco.Core.Services
dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages, parentId);
_localizationService.Save(dictionaryItem);
items.Add(dictionaryItem);
items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, dictionaryItem.Key));
return items;
}
@@ -817,7 +819,8 @@ namespace Umbraco.Core.Services
private static bool DictionaryValueIsNew(IEnumerable<IDictionaryTranslation> translations, XElement valueElement)
{
return translations.All(t =>
String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value, StringComparison.InvariantCultureIgnoreCase) != 0
String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value,
StringComparison.InvariantCultureIgnoreCase) != 0
);
}
+73 -48
View File
@@ -30,13 +30,14 @@ namespace Umbraco.Core
private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray();
private static readonly char[] ToCSharpEscapeChars;
static StringExtensions()
{
var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" };
ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1];
foreach (var escape in escapes)
ToCSharpEscapeChars[escape[0]] = escape[1];
}
internal static string ReplaceNonAlphanumericChars(this string input, char replacement)
@@ -73,65 +74,66 @@ namespace Umbraco.Core
return sb.ToString();
}
/// <summary>
/// Encrypt the string using the MachineKey in medium trust
/// </summary>
/// <param name="value">The string value to be encrypted.</param>
/// <returns>The encrypted string.</returns>
public static string EncryptWithMachineKey(this string value)
/// <summary>
/// Encrypt the string using the MachineKey in medium trust
/// </summary>
/// <param name="value">The string value to be encrypted.</param>
/// <returns>The encrypted string.</returns>
public static string EncryptWithMachineKey(this string value)
{
if (value == null)
return null;
if (value == null)
return null;
string valueToEncrypt = value;
List<string> parts = new List<string>();
string valueToEncrypt = value;
List<string> parts = new List<string>();
const int EncrpytBlockSize = 500;
const int EncrpytBlockSize = 500;
while (valueToEncrypt.Length > EncrpytBlockSize)
{
parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize));
valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize);
}
while (valueToEncrypt.Length > EncrpytBlockSize)
{
parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize));
valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize);
}
if (valueToEncrypt.Length > 0)
{
parts.Add(valueToEncrypt);
}
if (valueToEncrypt.Length > 0)
{
parts.Add(valueToEncrypt);
}
StringBuilder encrpytedValue = new StringBuilder();
StringBuilder encrpytedValue = new StringBuilder();
foreach (var part in parts)
{
var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part));
encrpytedValue.AppendLine(encrpytedBlock);
}
foreach (var part in parts)
{
var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part));
encrpytedValue.AppendLine(encrpytedBlock);
}
return encrpytedValue.ToString().TrimEnd();
return encrpytedValue.ToString().TrimEnd();
}
/// <summary>
/// Decrypt the encrypted string using the Machine key in medium trust
/// </summary>
/// <param name="value">The string value to be decrypted</param>
/// <returns>The decrypted string.</returns>
public static string DecryptWithMachineKey(this string value)
/// Decrypt the encrypted string using the Machine key in medium trust
/// </summary>
/// <param name="value">The string value to be decrypted</param>
/// <returns>The decrypted string.</returns>
public static string DecryptWithMachineKey(this string value)
{
if (value == null)
return null;
if (value == null)
return null;
string[] parts = value.Split('\n');
string[] parts = value.Split('\n');
StringBuilder decryptedValue = new StringBuilder();
StringBuilder decryptedValue = new StringBuilder();
foreach (var part in parts)
{
decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData);
}
foreach (var part in parts)
{
decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData);
}
return decryptedValue.ToString();
return decryptedValue.ToString();
}
//this is from SqlMetal and just makes it a bit of fun to allow pluralisation
public static string MakePluralName(this string name)
{
@@ -877,8 +879,8 @@ namespace Umbraco.Core
{
var helper = ShortStringHelper;
var legacy = helper as LegacyShortStringHelper;
return legacy != null
? legacy.LegacyToUrlAlias(value, charReplacements, replaceDoubleDashes, stripNonAscii, urlEncode)
return legacy != null
? legacy.LegacyToUrlAlias(value, charReplacements, replaceDoubleDashes, stripNonAscii, urlEncode)
: helper.CleanStringForUrlSegment(value);
}
@@ -972,7 +974,7 @@ namespace Umbraco.Core
}
// the new methods to get a url segment
/// <summary>
/// Cleans a string to produce a string that can safely be used in an url segment.
/// </summary>
@@ -1110,7 +1112,7 @@ namespace Umbraco.Core
{
return ShortStringHelper.CleanStringForSafeFileName(text, culture);
}
/// <summary>
/// An extension method that returns a new string in which all occurrences of a
/// specified string in the current instance are replaced with another specified string.
@@ -1129,7 +1131,7 @@ namespace Umbraco.Core
int index = -1 * newString.Length;
// Determine if there are any matches left in source, starting from just after the result of replacing the last match.
while((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0)
while ((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0)
{
// Remove the old text.
source = source.Remove(index, oldString.Length);
@@ -1200,5 +1202,28 @@ namespace Umbraco.Core
var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
return idCheckList.Contains(value);
}
// From: http://stackoverflow.com/a/961504/5018
// filters control characters but allows only properly-formed surrogate sequences
private static readonly Regex InvalidXmlChars =
new Regex(
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
RegexOptions.Compiled);
/// <summary>
/// An extension method that returns a new string in which all occurrences of an
/// unicode characters that are invalid in XML files are replaced with an empty string.
/// </summary>
/// <param name="text">Current instance of the string</param>
/// <returns>Updated string</returns>
///
/// <summary>
/// removes any unusual unicode characters that can't be encoded into XML
/// </summary>
internal static string ToValidXmlString(this string text)
{
return string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Replace(text, "");
}
}
}
@@ -0,0 +1,28 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// The current status of the server in the Umbraco environment
/// </summary>
internal enum CurrentServerEnvironmentStatus
{
/// <summary>
/// If the current server is detected as the 'master' server when configured in a load balanced scenario
/// </summary>
Master,
/// <summary>
/// If the current server is detected as a 'slave' server when configured in a load balanced scenario
/// </summary>
Slave,
/// <summary>
/// If the current server cannot be detected as a 'slave' or 'master' when configured in a load balanced scenario
/// </summary>
Unknown,
/// <summary>
/// If load balancing is not enabled and this is the only server in the umbraco environment
/// </summary>
Single
}
}
+20 -29
View File
@@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Web;
using System.Web.Script.Serialization;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
@@ -19,18 +20,19 @@ namespace Umbraco.Core.Sync
{
private readonly Func<Tuple<string, string>> _getUserNamePasswordDelegate;
private volatile bool _hasResolvedDelegate = false;
private readonly object _locker = new object();
private bool _useDistributedCalls;
private readonly object _locker = new object();
protected string Login { get; private set; }
protected string Password{ get; private set; }
protected bool UseDistributedCalls { get; private set; }
/// <summary>
/// Without a username/password all distribuion will be disabled
/// </summary>
internal DefaultServerMessenger()
{
_useDistributedCalls = false;
UseDistributedCalls = false;
}
/// <summary>
@@ -54,7 +56,7 @@ namespace Umbraco.Core.Sync
if (login == null) throw new ArgumentNullException("login");
if (password == null) throw new ArgumentNullException("password");
_useDistributedCalls = useDistributedCalls;
UseDistributedCalls = useDistributedCalls;
Login = login;
Password = password;
}
@@ -220,13 +222,13 @@ namespace Umbraco.Core.Sync
{
Login = null;
Password = null;
_useDistributedCalls = false;
UseDistributedCalls = false;
}
else
{
Login = result.Item1;
Password = result.Item2;
_useDistributedCalls = UmbracoSettings.UseDistributedCalls;
UseDistributedCalls = UmbracoSettings.UseDistributedCalls;
}
}
catch (Exception ex)
@@ -234,7 +236,7 @@ namespace Umbraco.Core.Sync
LogHelper.Error<DefaultServerMessenger>("Could not resolve username/password delegate, server distribution will be disabled", ex);
Login = null;
Password = null;
_useDistributedCalls = false;
UseDistributedCalls = false;
}
}
}
@@ -313,7 +315,7 @@ namespace Umbraco.Core.Sync
//Now, check if we are using Distrubuted calls. If there are no servers in the list then we
// can definitely not distribute.
if (!_useDistributedCalls || !servers.Any())
if (!UseDistributedCalls || !servers.Any())
{
//if we are not, then just invoke the call on the cache refresher
InvokeMethodOnRefresherInstance(refresher, dispatchType, getId, instances);
@@ -324,7 +326,7 @@ namespace Umbraco.Core.Sync
MessageSeversForIdsOrJson(servers, refresher, dispatchType, instances.Select(getId));
}
private void MessageSeversForIdsOrJson(
protected virtual void MessageSeversForIdsOrJson(
IEnumerable<IServerAddress> servers,
ICacheRefresher refresher,
MessageType dispatchType,
@@ -344,7 +346,7 @@ namespace Umbraco.Core.Sync
//Now, check if we are using Distrubuted calls. If there are no servers in the list then we
// can definitely not distribute.
if (!_useDistributedCalls || !servers.Any())
if (!UseDistributedCalls || !servers.Any())
{
//if we are not, then just invoke the call on the cache refresher
InvokeMethodOnRefresherInstance(refresher, dispatchType, ids, jsonPayload);
@@ -455,16 +457,16 @@ namespace Umbraco.Core.Sync
}
}
List<WaitHandle> waitHandlesList;
var asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList);
var waitHandlesList = asyncResultsList.Select(x => x.AsyncWaitHandle).ToArray();
var errorCount = 0;
// Once for each WaitHandle that we have, wait for a response and log it
// We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis
foreach (var t in asyncResults)
//Wait for all requests to complete
WaitHandle.WaitAll(waitHandlesList.ToArray());
foreach (var t in asyncResultsList)
{
var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));
//var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));
try
{
@@ -519,18 +521,7 @@ namespace Umbraco.Core.Sync
LogDispatchBatchError(ee);
}
}
private IEnumerable<IAsyncResult> GetAsyncResults(List<IAsyncResult> asyncResultsList, out List<WaitHandle> waitHandlesList)
{
var asyncResults = asyncResultsList.ToArray();
waitHandlesList = new List<WaitHandle>();
foreach (var asyncResult in asyncResults)
{
waitHandlesList.Add(asyncResult.AsyncWaitHandle);
}
return asyncResults;
}
private void LogDispatchBatchError(Exception ee)
{
LogHelper.Error<DefaultServerMessenger>("Error refreshing distributed list", ee);
+2
View File
@@ -8,5 +8,7 @@ namespace Umbraco.Core.Sync
internal interface IServerAddress
{
string ServerAddress { get; }
//TODO : Should probably add things like port, protocol, server name, app id
}
}
@@ -0,0 +1,63 @@
using System;
namespace Umbraco.Core.Sync
{
[Serializable]
public class RefreshInstruction
{
public RefreshMethodType RefreshType { get; set; }
public Guid RefresherId { get; set; }
public Guid GuidId { get; set; }
public int IntId { get; set; }
public string JsonIds { get; set; }
public string JsonPayload { get; set; }
[Serializable]
public enum RefreshMethodType
{
RefreshAll,
RefreshByGuid,
RefreshById,
RefreshByIds,
RefreshByJson,
RemoveById
}
protected bool Equals(RefreshInstruction other)
{
return RefreshType == other.RefreshType && RefresherId.Equals(other.RefresherId) && GuidId.Equals(other.GuidId) && IntId == other.IntId && string.Equals(JsonIds, other.JsonIds) && string.Equals(JsonPayload, other.JsonPayload);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((RefreshInstruction) obj);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = (int) RefreshType;
hashCode = (hashCode*397) ^ RefresherId.GetHashCode();
hashCode = (hashCode*397) ^ GuidId.GetHashCode();
hashCode = (hashCode*397) ^ IntId;
hashCode = (hashCode*397) ^ (JsonIds != null ? JsonIds.GetHashCode() : 0);
hashCode = (hashCode*397) ^ (JsonPayload != null ? JsonPayload.GetHashCode() : 0);
return hashCode;
}
}
public static bool operator ==(RefreshInstruction left, RefreshInstruction right)
{
return Equals(left, right);
}
public static bool operator !=(RefreshInstruction left, RefreshInstruction right)
{
return !Equals(left, right);
}
}
}
@@ -0,0 +1,116 @@
using System;
using System.Linq;
using System.Web;
using System.Xml;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
namespace Umbraco.Core.Sync
{
/// <summary>
/// A helper used to determine the current server environment status
/// </summary>
internal static class ServerEnvironmentHelper
{
/// <summary>
/// Returns the current umbraco base url for the current server depending on it's environment
/// status. This will attempt to determine the internal umbraco base url that can be used by the current
/// server to send a request to itself if it is in a load balanced environment.
/// </summary>
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco )</returns>
public static string GetCurrentServerUmbracoBaseUrl()
{
var status = GetStatus();
if (status == CurrentServerEnvironmentStatus.Single)
{
//if it's a single install, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
}
var servers = UmbracoSettings.DistributionServers;
var nodes = servers.SelectNodes("./server");
if (nodes == null)
{
//cannot be determined, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
}
var xmlNodes = nodes.Cast<XmlNode>().ToList();
foreach (var xmlNode in xmlNodes)
{
var appId = xmlNode.AttributeValue<string>("appId");
var serverName = xmlNode.AttributeValue<string>("serverName");
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
{
continue;
}
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
//match by appId or computer name! return the url configured
return string.Format("{0}://{1}:{2}/{3}",
xmlNode.AttributeValue<string>("forceProtocol").IsNullOrWhiteSpace() ? "http" : xmlNode.AttributeValue<string>("forceProtocol"),
xmlNode.InnerText,
xmlNode.AttributeValue<string>("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue<string>("forcePortnumber"),
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
}
}
//cannot be determined, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
}
/// <summary>
/// Returns the current environment status for the current server
/// </summary>
/// <returns></returns>
public static CurrentServerEnvironmentStatus GetStatus()
{
if (UmbracoSettings.UseDistributedCalls == false)
{
return CurrentServerEnvironmentStatus.Single;
}
var servers = UmbracoSettings.DistributionServers;
var nodes = servers.SelectNodes("./server");
if (nodes == null)
{
return CurrentServerEnvironmentStatus.Unknown;
}
var master = nodes.Cast<XmlNode>().FirstOrDefault();
if (master == null)
{
return CurrentServerEnvironmentStatus.Unknown;
}
//we determine master/slave based on the first server registered
//TODO: In v7 we have publicized ServerRegisterResolver - we won't be able to determine this based on that
// but we'd need to change the IServerAddress interfaces which is breaking.
var appId = master.AttributeValue<string>("appId");
var serverName = master.AttributeValue<string>("serverName");
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
{
return CurrentServerEnvironmentStatus.Unknown;
}
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
//match by appdid or server name!
return CurrentServerEnvironmentStatus.Master;
}
return CurrentServerEnvironmentStatus.Slave;
}
}
}
@@ -4,7 +4,6 @@ using Umbraco.Core.IO;
namespace Umbraco.Core.Sync
{
/// <summary>
/// The client Soap service for making distrubuted cache calls between servers
/// </summary>
@@ -21,6 +20,33 @@ namespace Umbraco.Core.Sync
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/BulkRefresh", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void BulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password)
{
this.Invoke("BulkRefresh", new object[] {
instructions,
appId,
login,
password});
}
/// <remarks/>
public System.IAsyncResult BeginBulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password, System.AsyncCallback callback, object asyncState)
{
return this.BeginInvoke("BulkRefresh", new object[] {
instructions,
appId,
login,
password}, callback, asyncState);
}
/// <remarks/>
public void EndBulkRefresh(System.IAsyncResult asyncResult)
{
this.EndInvoke(asyncResult);
}
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void RefreshAll(System.Guid uniqueIdentifier, string Login, string Password)
+9 -3
View File
@@ -82,7 +82,9 @@
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll</HintPath>
</Reference>
<Reference Include="System.Web.Services" />
<Reference Include="System.Web.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll</HintPath>
@@ -121,8 +123,6 @@
<Compile Include="Cache\CacheRefresherBase.cs" />
<Compile Include="Cache\CacheRefresherEventArgs.cs" />
<Compile Include="Cache\DictionaryCacheProviderBase.cs" />
<Compile Include="Cache\DictionaryCacheWrapper.cs" />
<Compile Include="Cache\DictionaryItemWrapper.cs" />
<Compile Include="Cache\HttpRequestCacheProvider.cs" />
<Compile Include="Cache\HttpRuntimeCacheProvider.cs" />
<Compile Include="Cache\ICacheProvider.cs" />
@@ -217,6 +217,7 @@
<Compile Include="Models\PublishedContent\PublishedContentModel.cs" />
<Compile Include="Models\PublishedContent\PublishedContentModelFactoryResolver.cs" />
<Compile Include="Models\TemplateNode.cs" />
<Compile Include="MonitorLock.cs" />
<Compile Include="Packaging\Models\InstallAction.cs" />
<Compile Include="Packaging\Models\InstallationSummary.cs" />
<Compile Include="Packaging\Models\MetaData.cs" />
@@ -770,6 +771,7 @@
<Compile Include="Publishing\PublishingStrategy.cs" />
<Compile Include="Publishing\PublishStatus.cs" />
<Compile Include="Publishing\PublishStatusType.cs" />
<Compile Include="Publishing\ScheduledPublisher.cs" />
<Compile Include="RenderingEngine.cs" />
<Compile Include="Security\AuthenticationExtensions.cs" />
<Compile Include="Security\IUsersMembershipProvider.cs" />
@@ -830,6 +832,9 @@
<Compile Include="Standalone\StandaloneCoreBootManager.cs" />
<Compile Include="Strings\ContentBaseExtensions.cs" />
<Compile Include="Strings\Diff.cs" />
<Compile Include="Sync\CurrentServerEnvironmentStatus.cs" />
<Compile Include="Sync\RefreshInstruction.cs" />
<Compile Include="Sync\ServerEnvironmentHelper.cs" />
<Compile Include="TopologicalSorter.cs" />
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
<Compile Include="Strings\IUrlSegmentProvider.cs" />
@@ -924,6 +929,7 @@
<Compile Include="Xml\XPathVariable.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
+5 -5
View File
@@ -134,7 +134,7 @@ namespace Umbraco.Core
}
/// <summary>
/// This is a performance tweak to check if this is a .css, .js or .ico, .jpg, .jpeg, .png, .gif file request since
/// This is a performance tweak to check if this not an ASP.Net server file
/// .Net will pass these requests through to the module when in integrated mode.
/// We want to ignore all of these requests immediately.
/// </summary>
@@ -142,10 +142,10 @@ namespace Umbraco.Core
/// <returns></returns>
internal static bool IsClientSideRequest(this Uri url)
{
// fixme - IsClientSideRequest should not use an hard-coded list of extensions
// a client-side request is anything that has an extension that is not .aspx?
var toIgnore = new[] { ".js", ".css", ".ico", ".png", ".jpg", ".jpeg", ".gif", ".html", ".svg" };
return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x));
var ext = Path.GetExtension(url.LocalPath);
if (ext.IsNullOrWhiteSpace()) return false;
var toInclude = new[] { ".aspx", ".ashx", ".asmx", ".axd", ".svc" };
return toInclude.Any(ext.InvariantEquals) == false;
}
/// <summary>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+8
View File
@@ -121,6 +121,14 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NuGet.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.11220.104" newVersion="1.0.11220.104" />
</dependentAssembly>
</assemblyBinding>
</runtime>
@@ -15,6 +15,8 @@ using log4net.Repository;
namespace Umbraco.Tests
{
//Ignore this test, it fails sometimes on the build server - pretty sure it's a threading issue with this test class
[Ignore]
[TestFixture]
public class AsynchronousRollingFileAppenderTests
{
@@ -14,7 +14,7 @@ namespace Umbraco.Tests.Cache
{
base.Setup();
_ctx = new FakeHttpContextFactory("http://localhost/test");
_provider = new HttpRequestCacheProvider(() => _ctx.HttpContext);
_provider = new HttpRequestCacheProvider(_ctx.HttpContext);
}
internal override ICacheProvider Provider
@@ -51,24 +51,20 @@ namespace Umbraco.Tests.Membership
{
var date = DateTime.Now;
var m = Mock.Of<MembershipUser>(
user => user.UserName == "test username"
&& user.Comment == "test comment"
&& user.IsApproved == true
&& user.IsLockedOut == false
&& user.CreationDate == date
&& user.Email == "test@email.com"
&& user.LastActivityDate == date.AddMinutes(1)
&& user.LastLockoutDate == date.AddMinutes(2)
&& user.LastLoginDate == date.AddMinutes(3)
&& user.LastPasswordChangedDate == date.AddMinutes(4)
&& user.PasswordQuestion == "test question");
var member = new Member("test name", "test@email.com", "test username", "test password",
GetMemberType());
member.Comments = "test comment";
member.IsApproved = true;
member.IsLockedOut = false;
member.CreateDate = date;
member.LastLoginDate = date.AddMinutes(1);
member.LastLockoutDate = date.AddMinutes(2);
//NOTE: Last activity date is always the same as last login date since we don't have a place to store that data
//member.LastLoginDate = date.AddMinutes(3);
member.LastPasswordChangeDate = date.AddMinutes(4);
member.PasswordQuestion = "test question";
var mpc = new MemberPublishedContent(
new Member("test name", "test@email.com", "test username", "test password",
Mock.Of<IMemberType>(type => type.Alias == "Member")),
m);
var mpc = new MemberPublishedContent(member);
var d = mpc.AsDynamic();
@@ -79,7 +75,7 @@ namespace Umbraco.Tests.Membership
Assert.AreEqual(false, d.IsLockedOut);
Assert.AreEqual(date.AddMinutes(1), d.LastActivityDate);
Assert.AreEqual(date.AddMinutes(2), d.LastLockoutDate);
Assert.AreEqual(date.AddMinutes(3), d.LastLoginDate);
Assert.AreEqual(date.AddMinutes(1), d.LastLoginDate);
Assert.AreEqual(date.AddMinutes(4), d.LastPasswordChangedDate);
Assert.AreEqual("test name", d.Name);
Assert.AreEqual("test question", d.PasswordQuestion);
@@ -92,24 +88,20 @@ namespace Umbraco.Tests.Membership
{
var date = DateTime.Now;
var m = Mock.Of<MembershipUser>(
user => user.UserName == "test username"
&& user.Comment == "test comment"
&& user.IsApproved == true
&& user.IsLockedOut == false
&& user.CreationDate == date
&& user.Email == "test@email.com"
&& user.LastActivityDate == date.AddMinutes(1)
&& user.LastLockoutDate == date.AddMinutes(2)
&& user.LastLoginDate == date.AddMinutes(3)
&& user.LastPasswordChangedDate == date.AddMinutes(4)
&& user.PasswordQuestion == "test question");
var member = new Member("test name", "test@email.com", "test username", "test password",
GetMemberType());
member.Comments = "test comment";
member.IsApproved = true;
member.IsLockedOut = false;
member.CreateDate = date;
member.LastLoginDate = date.AddMinutes(1);
member.LastLockoutDate = date.AddMinutes(2);
//NOTE: Last activity date is always the same as last login date since we don't have a place to store that data
//member.LastLoginDate = date.AddMinutes(3);
member.LastPasswordChangeDate = date.AddMinutes(4);
member.PasswordQuestion = "test question";
var mpc = new MemberPublishedContent(
new Member("test name", "test@email.com", "test username", "test password",
Mock.Of<IMemberType>(type => type.Alias == "Member")) ,
m);
var mpc = new MemberPublishedContent(member);
var d = mpc.AsDynamic();
@@ -120,7 +112,7 @@ namespace Umbraco.Tests.Membership
Assert.AreEqual(false, d.isLockedOut);
Assert.AreEqual(date.AddMinutes(1), d.lastActivityDate);
Assert.AreEqual(date.AddMinutes(2), d.lastLockoutDate);
Assert.AreEqual(date.AddMinutes(3), d.lastLoginDate);
Assert.AreEqual(date.AddMinutes(1), d.lastLoginDate);
Assert.AreEqual(date.AddMinutes(4), d.lastPasswordChangedDate);
Assert.AreEqual("test name", d.name);
Assert.AreEqual("test question", d.passwordQuestion);
@@ -133,25 +125,23 @@ namespace Umbraco.Tests.Membership
{
var date = DateTime.Now;
var m = Mock.Of<MembershipUser>(
user => user.UserName == "test username"
&& user.Comment == "test comment"
&& user.IsApproved == true
&& user.IsLockedOut == false
&& user.CreationDate == date
&& user.Email == "test@email.com"
&& user.LastActivityDate == date.AddMinutes(1)
&& user.LastLockoutDate == date.AddMinutes(2)
&& user.LastLoginDate == date.AddMinutes(3)
&& user.LastPasswordChangedDate == date.AddMinutes(4)
&& user.PasswordQuestion == "test question");
var memberType = MockedContentTypes.CreateSimpleMemberType("Member", "Member");
var member = MockedMember.CreateSimpleMember(memberType, "test name", "test@email.com", "test password", "test username");
member.Comments = "test comment";
member.IsApproved = true;
member.IsLockedOut = false;
member.CreateDate = date;
member.LastLoginDate = date.AddMinutes(1);
member.LastLockoutDate = date.AddMinutes(2);
//NOTE: Last activity date is always the same as last login date since we don't have a place to store that data
//member.LastLoginDate = date.AddMinutes(3);
member.LastPasswordChangeDate = date.AddMinutes(4);
member.PasswordQuestion = "test question";
member.Properties["title"].Value = "Test Value 1";
member.Properties["bodyText"].Value = "Test Value 2";
member.Properties["author"].Value = "Test Value 3";
var mpc = new MemberPublishedContent(member, m);
var mpc = new MemberPublishedContent(member);
var d = mpc.AsDynamic();
@@ -165,6 +155,21 @@ namespace Umbraco.Tests.Membership
}
private IMemberType GetMemberType()
{
var entity = new MemberType(-1)
{
Alias = "Member"
};
entity.AddPropertyGroup(Umbraco.Core.Constants.Conventions.Member.StandardPropertiesGroupName);
var standardPropertyTypes = Umbraco.Core.Constants.Conventions.Member.GetStandardPropertyTypeStubs();
foreach (var standardPropertyType in standardPropertyTypes)
{
entity.AddPropertyType(standardPropertyType.Value, Umbraco.Core.Constants.Conventions.Member.StandardPropertiesGroupName);
}
return entity;
}
}
}
@@ -148,7 +148,7 @@ namespace Umbraco.Tests.Membership
Assert.AreEqual(false, provider.EnablePasswordReset);
Assert.AreEqual(false, provider.RequiresQuestionAndAnswer);
Assert.AreEqual(true, provider.RequiresUniqueEmail);
Assert.AreEqual(5, provider.MaxInvalidPasswordAttempts);
Assert.AreEqual(20, provider.MaxInvalidPasswordAttempts);
Assert.AreEqual(10, provider.PasswordAttemptWindow);
Assert.AreEqual(provider.DefaultMinPasswordLength, provider.MinRequiredPasswordLength);
Assert.AreEqual(provider.DefaultMinNonAlphanumericChars, provider.MinRequiredNonAlphanumericCharacters);
@@ -1,7 +1,11 @@
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Linq;
using System.Text.RegularExpressions;
using NUnit.Framework;
using SQLCE4Umbraco;
using Umbraco.Core;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Migrations;
@@ -13,13 +17,20 @@ using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings;
namespace Umbraco.Tests.Migrations
{
[TestFixture]
public class TargetVersionSixthMigrationsTest
public class TargetVersionSixthMigrationsTest : BaseDatabaseFactoryTest
{
/// <summary>Regular expression that finds multiline block comments.</summary>
private static readonly Regex FindComments = new Regex(@"\/\*.*?\*\/", RegexOptions.Singleline | RegexOptions.Compiled);
[SetUp]
public void Initialize()
public override void Initialize()
{
TestHelper.SetupLog4NetForTests();
TestHelper.InitializeContentDirectories();
Path = TestHelper.CurrentAssemblyDirectory;
AppDomain.CurrentDomain.SetData("DataDirectory", Path);
MigrationResolver.Current = new MigrationResolver(() => new List<Type>
{
typeof (Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero.RemoveUmbracoAppConstraints),
@@ -36,22 +47,39 @@ namespace Umbraco.Tests.Migrations
typeof (UpdateCmsPropertyTypeGroupTable)
}.OrderByDescending(x => x.Name));
Resolution.Freeze();
Resolution.Freeze();
SqlSyntaxContext.SqlSyntaxProvider = SqlCeSyntax.Provider;
var engine = new SqlCeEngine("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;");
engine.CreateDatabase();
}
[Test]
public void Can_Find_Targetted_Migrations()
{
var db = GetConfiguredDatabase();
//Create db schema and data from old Total.sql file for Sql Ce
string statements = GetDatabaseSpecificSqlScript();
// replace block comments by whitespace
statements = FindComments.Replace(statements, " ");
// execute all non-empty statements
foreach (string statement in statements.Split(";".ToCharArray()))
{
string rawStatement = statement.Replace("GO", "").Trim();
if (rawStatement.Length > 0)
db.Execute(new Sql(rawStatement));
}
var configuredVersion = new Version("4.8.0");
var targetVersion = new Version("6.0.0");
var foundMigrations = MigrationResolver.Current.Migrations;
var foundMigrations = MigrationResolver.Current.Migrations;
var migrationRunner = new MigrationRunner(configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName);
var migrations = migrationRunner.OrderedUpgradeMigrations(foundMigrations).ToList();
var context = new MigrationContext(DatabaseProviders.SqlServerCE, null);
var context = new MigrationContext(DatabaseProviders.SqlServerCE, db);
foreach (MigrationBase migration in migrations)
{
migration.GetUpExpressions(context);
@@ -66,9 +94,37 @@ namespace Umbraco.Tests.Migrations
}
[TearDown]
public void TearDown()
public override void TearDown()
{
base.TearDown();
PluginManager.Current = null;
SqlSyntaxContext.SqlSyntaxProvider = null;
MigrationResolver.Reset();
TestHelper.CleanContentDirectories();
Path = TestHelper.CurrentAssemblyDirectory;
AppDomain.CurrentDomain.SetData("DataDirectory", null);
SqlCeContextGuardian.CloseBackgroundConnection();
}
public string Path { get; set; }
public UmbracoDatabase GetConfiguredDatabase()
{
return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0");
}
public DatabaseProviders GetDatabaseProvider()
{
return DatabaseProviders.SqlServerCE;
}
public string GetDatabaseSpecificSqlScript()
{
return SqlScripts.SqlResources.SqlCeTotal_480;
}
}
}
@@ -0,0 +1,151 @@
using System.CodeDom;
using System.Web;
using System.Web.Mvc;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.ObjectResolution;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.Mvc;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Tests.Mvc
{
[TestFixture]
public class SurfaceControllerTests
{
[Test]
public void Can_Construct_And_Get_Result()
{
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper());
ApplicationContext.EnsureContext(appCtx, true);
var umbCtx = UmbracoContext.EnsureContext(
new Mock<HttpContextBase>().Object,
appCtx,
true);
var ctrl = new TestSurfaceController(umbCtx);
var result = ctrl.Index();
Assert.IsNotNull(result);
}
[Test]
public void Umbraco_Context_Not_Null()
{
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper());
ApplicationContext.EnsureContext(appCtx, true);
var umbCtx = UmbracoContext.EnsureContext(
new Mock<HttpContextBase>().Object,
appCtx,
true);
var ctrl = new TestSurfaceController(umbCtx);
Assert.IsNotNull(ctrl.UmbracoContext);
}
[Test]
public void Umbraco_Helper_Not_Null()
{
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper());
ApplicationContext.EnsureContext(appCtx, true);
var umbCtx = UmbracoContext.EnsureContext(
new Mock<HttpContextBase>().Object,
appCtx,
true);
var ctrl = new TestSurfaceController(umbCtx);
Assert.IsNotNull(ctrl.Umbraco);
}
[Test]
public void Can_Lookup_Content()
{
//init app context
var appCtx = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper());
//TODO: Need to either make this public or make all methods on the UmbracoHelper or
// in v7 the PublishedContentQuery object virtual so we can just mock the methods
var contentCaches = new Mock<IPublishedCaches>();
//init content resolver
//TODO: This is not public so people cannot actually do this!
PublishedCachesResolver.Current = new PublishedCachesResolver(contentCaches.Object);
//init umb context
var umbCtx = UmbracoContext.EnsureContext(
new Mock<HttpContextBase>().Object,
appCtx,
true);
//setup the mock
contentCaches.Setup(caches => caches.CreateContextualContentCache(It.IsAny<UmbracoContext>()))
.Returns(new ContextualPublishedContentCache(
Mock.Of<IPublishedContentCache>(cache =>
cache.GetById(It.IsAny<UmbracoContext>(), false, It.IsAny<int>()) ==
//return mock of IPublishedContent for any call to GetById
Mock.Of<IPublishedContent>(content => content.Id == 2)),
umbCtx));
using (var uTest = new DisposableUmbracoTest(appCtx))
{
var ctrl = new TestSurfaceController(uTest.UmbracoContext);
var result = ctrl.GetContent(2) as PublishedContentResult;
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Content.Id);
}
}
public class TestSurfaceController : SurfaceController
{
public TestSurfaceController(UmbracoContext umbracoContext) : base(umbracoContext)
{
}
public ActionResult Index()
{
return View();
}
public ActionResult GetContent(int id)
{
var content = Umbraco.TypedContent(id);
return new PublishedContentResult(content);
}
}
public class PublishedContentResult : ActionResult
{
public IPublishedContent Content { get; set; }
public PublishedContentResult(IPublishedContent content)
{
Content = content;
}
public override void ExecuteResult(ControllerContext context)
{
}
}
}
}
@@ -90,9 +90,9 @@ namespace Umbraco.Tests.Routing
}
//test all template name styles to match the ActionName
[TestCase("home-\\234^^*32page")]
//[TestCase("home-\\234^^*32page")] //TODO: This fails!
[TestCase("home-page")]
[TestCase("home-\\234^^*32page")]
[TestCase("home-page")]
[TestCase("home-page")]
[TestCase("Home-Page")]
@@ -38,7 +38,83 @@ namespace Umbraco.Tests.Services
//TODO Add test to verify there is only ONE newest document/content in cmsDocument table after updating.
//TODO Add test to delete specific version (with and without deleting prior versions) and versions by date.
[Test]
public void Count_All()
{
// Arrange
var contentService = ServiceContext.ContentService;
// Act
for (int i = 0; i < 20; i++)
{
contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0);
}
// Assert
Assert.AreEqual(24, contentService.Count());
}
[Test]
public void Count_By_Content_Type()
{
// Arrange
var contentService = ServiceContext.ContentService;
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type");
contentTypeService.Save(contentType);
// Act
for (int i = 0; i < 20; i++)
{
contentService.CreateContentWithIdentity("Test", -1, "umbBlah", 0);
}
// Assert
Assert.AreEqual(20, contentService.Count(contentTypeAlias: "umbBlah"));
}
[Test]
public void Count_Children()
{
// Arrange
var contentService = ServiceContext.ContentService;
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type");
contentTypeService.Save(contentType);
var parent = contentService.CreateContentWithIdentity("Test", -1, "umbBlah", 0);
// Act
for (int i = 0; i < 20; i++)
{
contentService.CreateContentWithIdentity("Test", parent, "umbBlah");
}
// Assert
Assert.AreEqual(20, contentService.CountChildren(parent.Id));
}
[Test]
public void Count_Descendants()
{
// Arrange
var contentService = ServiceContext.ContentService;
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateSimpleContentType("umbBlah", "test Doc Type");
contentTypeService.Save(contentType);
var parent = contentService.CreateContentWithIdentity("Test", -1, "umbBlah", 0);
// Act
IContent current = parent;
for (int i = 0; i < 20; i++)
{
current = contentService.CreateContentWithIdentity("Test", current, "umbBlah");
}
// Assert
Assert.AreEqual(20, contentService.CountDescendants(parent.Id));
}
[Test]
public void Can_Remove_Property_Type()
{
@@ -1,4 +1,8 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Services
@@ -8,10 +12,17 @@ namespace Umbraco.Tests.Services
/// This is more of an integration test as it involves multiple layers
/// as well as configuration.
/// </summary>
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)]
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture, RequiresSTA]
public class LocalizationServiceTests : BaseServiceTest
{
private Guid _parentItemGuidId;
private int _parentItemIntId;
private Guid _childItemGuidId;
private int _childItemIntId;
private int _danishLangId;
private int _englishLangId;
[SetUp]
public override void Initialize()
{
@@ -23,5 +34,194 @@ namespace Umbraco.Tests.Services
{
base.TearDown();
}
[Test]
public void LocalizationService_Can_Get_Root_Dictionary_Items()
{
var rootItems = ServiceContext.LocalizationService.GetRootDictionaryItems();
Assert.NotNull(rootItems);
Assert.IsTrue(rootItems.Any());
}
[Test]
public void LocalizationService_Can_Determint_If_DictionaryItem_Exists()
{
var exists = ServiceContext.LocalizationService.DictionaryItemExists("Parent");
Assert.IsTrue(exists);
}
[Test]
public void LocalizationService_Can_Get_All_Languages()
{
var languages = ServiceContext.LocalizationService.GetAllLanguages();
Assert.NotNull(languages);
Assert.IsTrue(languages.Any());
Assert.That(languages.Count(), Is.EqualTo(3));
}
[Test]
public void LocalizationService_Can_Get_Dictionary_Item_By_Int_Id()
{
var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemIntId);
Assert.NotNull(parentItem);
var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemIntId);
Assert.NotNull(childItem);
}
[Test]
public void LocalizationService_Can_Get_Dictionary_Item_By_Guid_Id()
{
var parentItem = ServiceContext.LocalizationService.GetDictionaryItemById(_parentItemGuidId);
Assert.NotNull(parentItem);
var childItem = ServiceContext.LocalizationService.GetDictionaryItemById(_childItemGuidId);
Assert.NotNull(childItem);
}
[Test]
public void LocalizationService_Can_Get_Dictionary_Item_By_Key()
{
var parentItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Parent");
Assert.NotNull(parentItem);
var childItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child");
Assert.NotNull(childItem);
}
[Test]
public void LocalizationService_Can_Get_Dictionary_Item_Children()
{
var item = ServiceContext.LocalizationService.GetDictionaryItemChildren(_parentItemGuidId);
Assert.NotNull(item);
Assert.That(item.Count(), Is.EqualTo(1));
foreach (var dictionaryItem in item)
{
Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey));
}
}
[Test]
public void LocalizationService_Can_Get_Language_By_Culture_Code()
{
var danish = ServiceContext.LocalizationService.GetLanguageByCultureCode("Danish");
var english = ServiceContext.LocalizationService.GetLanguageByCultureCode("English");
Assert.NotNull(danish);
Assert.NotNull(english);
}
[Test]
public void LocalizationService_Can_GetLanguageById()
{
var danish = ServiceContext.LocalizationService.GetLanguageById(_danishLangId);
var english = ServiceContext.LocalizationService.GetLanguageById(_englishLangId);
Assert.NotNull(danish);
Assert.NotNull(english);
}
[Test]
public void LocalizationService_Can_GetLanguageByIsoCode()
{
var danish = ServiceContext.LocalizationService.GetLanguageByIsoCode("da-DK");
var english = ServiceContext.LocalizationService.GetLanguageByIsoCode("en-GB");
Assert.NotNull(danish);
Assert.NotNull(english);
}
[Test]
public void LocalizationService_Does_Not_Fail_When_Language_Doesnt_Exist()
{
var language = ServiceContext.LocalizationService.GetLanguageByIsoCode("sv-SE");
Assert.Null(language);
}
[Test]
public void LocalizationService_Does_Not_Fail_When_DictionaryItem_Doesnt_Exist()
{
var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("RandomKey");
Assert.Null(item);
}
[Test]
public void LocalizationService_Can_Delete_Language()
{
var norwegian = new Language("nb-NO") { CultureName = "Norwegian" };
ServiceContext.LocalizationService.Save(norwegian, 0);
Assert.That(norwegian.HasIdentity, Is.True);
var languageId = norwegian.Id;
ServiceContext.LocalizationService.Delete(norwegian);
var language = ServiceContext.LocalizationService.GetLanguageById(languageId);
Assert.Null(language);
}
[Test]
public void LocalizationService_Can_Delete_DictionaryItem()
{
var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child");
Assert.NotNull(item);
ServiceContext.LocalizationService.Delete(item);
var deletedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child");
Assert.Null(deletedItem);
}
[Test]
public void LocalizationService_Can_Update_Existing_DictionaryItem()
{
var item = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child");
foreach (var translation in item.Translations)
{
translation.Value = translation.Value + "UPDATED";
}
ServiceContext.LocalizationService.Save(item);
var updatedItem = ServiceContext.LocalizationService.GetDictionaryItemByKey("Child");
Assert.NotNull(updatedItem);
foreach (var translation in updatedItem.Translations)
{
Assert.That(translation.Value.EndsWith("UPDATED"), Is.True);
}
}
public override void CreateTestData()
{
var danish = new Language("da-DK") { CultureName = "Danish" };
var english = new Language("en-GB") { CultureName = "English" };
ServiceContext.LocalizationService.Save(danish, 0);
ServiceContext.LocalizationService.Save(english, 0);
_danishLangId = danish.Id;
_englishLangId = english.Id;
var parentItem = new DictionaryItem("Parent")
{
Translations = new List<IDictionaryTranslation>
{
new DictionaryTranslation(english, "ParentValue"),
new DictionaryTranslation(danish, "ForældreVærdi")
}
};
ServiceContext.LocalizationService.Save(parentItem);
_parentItemGuidId = parentItem.Key;
_parentItemIntId = parentItem.Id;
var childItem = new DictionaryItem(parentItem.Key, "Child")
{
Translations = new List<IDictionaryTranslation>
{
new DictionaryTranslation(english, "ChildValue"),
new DictionaryTranslation(danish, "BørnVærdi")
}
};
ServiceContext.LocalizationService.Save(childItem);
_childItemGuidId = childItem.Key;
_childItemIntId = childItem.Id;
}
}
}
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
@@ -352,6 +353,34 @@ namespace Umbraco.Tests.Services
Assert.IsNull(ServiceContext.MemberService.GetByEmail("do@not.find"));
}
[Test]
public void Get_Member_Name()
{
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
ServiceContext.MemberTypeService.Save(memberType);
IMember member = MockedMember.CreateSimpleMember(memberType, "Test Real Name", "test@test.com", "pass", "testUsername");
ServiceContext.MemberService.Save(member);
Assert.AreEqual("Test Real Name", member.Name);
}
[Test]
public void Get_Member_Name_In_Created_Event()
{
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
ServiceContext.MemberTypeService.Save(memberType);
TypedEventHandler<IMemberService, NewEventArgs<IMember>> callback = (sender, args) =>
{
Assert.AreEqual("Test Real Name", args.Entity.Name);
};
MemberService.Created += callback;
var member = ServiceContext.MemberService.CreateMember("testUsername", "test@test.com", "Test Real Name", memberType);
MemberService.Created -= callback;
}
[Test]
public void Get_By_Username()
{
@@ -38,7 +38,7 @@ namespace Umbraco.Tests.Sync
[Test]
public void RefreshIntId()
{
for (var i = 0; i < 10; i++)
for (var i = 1; i < 11; i++)
{
DistributedCache.Instance.Refresh(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), i);
}
@@ -71,7 +71,7 @@ namespace Umbraco.Tests.Sync
[Test]
public void RemoveIds()
{
for (var i = 0; i < 12; i++)
for (var i = 1; i < 13; i++)
{
DistributedCache.Instance.Remove(Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), i);
}
@@ -0,0 +1,52 @@
using System.Web;
using Moq;
using Umbraco.Core;
using Umbraco.Core.ObjectResolution;
using Umbraco.Web;
namespace Umbraco.Tests.TestHelpers
{
//NOTE: This is just a POC! Looking at the simplest way to expose some code so people can very easily test
// their Umbraco controllers, etc....
public class DisposableUmbracoTest : DisposableObject
{
public ApplicationContext ApplicationContext { get; set; }
public UmbracoContext UmbracoContext { get; set; }
public DisposableUmbracoTest(ApplicationContext applicationContext)
{
//init umb context
var umbctx = UmbracoContext.EnsureContext(
new Mock<HttpContextBase>().Object,
applicationContext,
true);
Init(applicationContext, umbctx);
}
public DisposableUmbracoTest(ApplicationContext applicationContext, UmbracoContext umbracoContext)
{
Init(applicationContext, umbracoContext);
}
private void Init(ApplicationContext applicationContext, UmbracoContext umbracoContext)
{
TestHelper.SetupLog4NetForTests();
ApplicationContext = applicationContext;
UmbracoContext = umbracoContext;
ApplicationContext.Current = applicationContext;
UmbracoContext.Current = umbracoContext;
Resolution.Freeze();
}
protected override void DisposeResources()
{
ApplicationContext.Current = null;
UmbracoContext.Current = null;
Resolution.Reset();
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
@@ -94,7 +95,9 @@ namespace Umbraco.Tests.TestHelpers
var httpContextMock = new Mock<HttpContextBase>();
httpContextMock.Setup(x => x.Cache).Returns(HttpRuntime.Cache);
httpContextMock.Setup(x => x.Items).Returns(new Dictionary<object, object>());
//note: foreach on Items should return DictionaryEntries!
//httpContextMock.Setup(x => x.Items).Returns(new Dictionary<object, object>());
httpContextMock.Setup(x => x.Items).Returns(new Hashtable());
httpContextMock.Setup(x => x.Request).Returns(requestMock.Object);
httpContextMock.Setup(x => x.Server).Returns(serverMock.Object);
httpContextMock.Setup(x => x.Response).Returns(responseMock.Object);
+2 -1
View File
@@ -9,6 +9,7 @@ using Umbraco.Core;
using Umbraco.Tests.PartialTrust;
using Umbraco.Web;
using Umbraco.Web.Cache;
using Umbraco.Web.Scheduling;
using UmbracoExamine;
using umbraco;
using umbraco.presentation;
@@ -67,7 +68,7 @@ namespace Umbraco.Tests
Assert.AreEqual(typeof(UmbracoEventManager), t5.Result);
var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler),
typeof (LegacyScheduledTasks),
typeof (Scheduler),
typeof(CacheRefresherEventHandler));
Assert.IsTrue(t6.Success);
Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result);
+9 -8
View File
@@ -48,9 +48,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Examine">
<Reference Include="Examine, Version=0.1.57.2941, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.55.2941\lib\Examine.dll</HintPath>
<HintPath>..\packages\Examine.0.1.57.2941\lib\Examine.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -122,17 +122,16 @@
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web.Razor">
<HintPath>..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll</HintPath>
<Reference Include="System.Web.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages">
<Reference Include="System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Deployment">
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll</HintPath>
<Reference Include="System.Web.WebPages.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll</HintPath>
</Reference>
<Reference Include="System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
@@ -184,6 +183,7 @@
<Compile Include="Models\UmbracoEntityTests.cs" />
<Compile Include="Models\UserTests.cs" />
<Compile Include="Models\UserTypeTests.cs" />
<Compile Include="Mvc\SurfaceControllerTests.cs" />
<Compile Include="Mvc\UmbracoViewPageTests.cs" />
<Compile Include="Persistence\Auditing\AuditTests.cs" />
<Compile Include="BootManagers\CoreBootManagerTests.cs" />
@@ -417,6 +417,7 @@
<Compile Include="TestHelpers\Entities\MockedContentTypes.cs" />
<Compile Include="TestHelpers\Entities\MockedEntity.cs" />
<Compile Include="TestHelpers\Entities\MockedMedia.cs" />
<Compile Include="TestHelpers\DisposableUmbracoTest.cs" />
<Compile Include="Trees\BaseContentTreeTests.cs" />
<Compile Include="Trees\BaseMediaTreeTests.cs" />
<Compile Include="UmbracoExamine\ContentServiceTest.cs" />
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Examine" version="0.1.55.2941" targetFramework="net40" />
<package id="Examine" version="0.1.57.2941" targetFramework="net40" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
+9 -8
View File
@@ -109,9 +109,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll</HintPath>
</Reference>
<Reference Include="Examine">
<Reference Include="Examine, Version=0.1.57.2941, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.55.2941\lib\Examine.dll</HintPath>
<HintPath>..\packages\Examine.0.1.57.2941\lib\Examine.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net, Version=1.2.11.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -158,12 +159,14 @@
</Reference>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Private>True</Private>
</Reference>
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.EnterpriseServices" />
@@ -1608,7 +1611,6 @@
<Content Include="Umbraco\Dashboard\Images\starterkit32x32.png" />
<Content Include="Umbraco\Dashboard\Images\TV.png" />
<Content Include="Umbraco\Dashboard\Images\ZipFile.png" />
<Content Include="Umbraco\Dashboard\Swfs\AIRInstallBadge.swf" />
<Content Include="Umbraco\Dashboard\Swfs\expressinstall.swf" />
<Content Include="Umbraco\Images\Editor\insMacroSB.png" />
<Content Include="Umbraco\Images\Editor\insRazorMacro.png" />
@@ -2100,7 +2102,6 @@
<Content Include="Install\Steps\welcome.ascx">
<SubType>UserControl</SubType>
</Content>
<Content Include="Umbraco\cacheBrowser.aspx" />
<Content Include="Umbraco\Config\Create\UI.xml" />
<Content Include="Umbraco\Config\Lang\en.xml">
<SubType>Designer</SubType>
@@ -2674,9 +2675,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>6210</DevelopmentServerPort>
<DevelopmentServerPort>6220</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:6210</IISUrl>
<IISUrl>http://localhost:6220</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -223,16 +223,47 @@
<!-- <task log="true" alias="test60" interval="60" url="http://localhost/umbraco/test.aspx"/>-->
</scheduledTasks>
<!-- distributed calls make umbraco use webservices to handle cache refreshing -->
<!-- distributed calls must be enabled when using Umbraco in a load balanced environment -->
<distributedCall enable="false">
<!-- the id of the user who's making the calls -->
<!-- needed for security, umbraco will automatically look up correct login and passwords -->
<user>0</user>
<!--
When distributed call is enabled, you need to add all of the servers part taking in load balancing
to the server list below.
-->
<servers>
<!-- add ip number or hostname, make sure that it can be reached from all servers -->
<!-- you can also add optional attributes to force a protocol or port number (see #2) -->
<!-- <server>127.0.0.1</server>-->
<!-- <server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>-->
<!--
Add ip number or hostname, make sure that it can be reached from all servers
you can also add optional attributes to force a protocol or port number.
Examples:
<server>127.0.0.1</server>
<server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>
Generally when setting up load balancing you will designate a 'master' server,
Umbraco will always assume that the FIRST server listed in this list is the 'master'.
(NOTE: Not all load balancing scenarios have a 'master', depends on how you are setting it up)
In order for scheduled tasks (including scheduled publishing) to work properly when load balancing, each
server in the load balanced environment needs to know if it is the 'master'. In order for servers
to know this or not, they need to compare some values against the servers listed. These values
are either: serverName or appId. You should not enter both values but appId will always supersede serverName.
The serverName is the easiest and will work so long as you are not load balancing your site on the same server.
If you are doing this, then you will need to use appId which is equivalent to the value returned from
HttpRuntime.AppDomainAppId. It is recommended that you set either the serverName or appId for all servers
registered here if possible, not just the first one.
Examples:
<server serverName="MyServer">server1.mysite.com</server>
<server appId="/LM/W3SVC/69/ROOT">server1.mysite.com</server>
-->
</servers>
</distributedCall>
@@ -223,16 +223,48 @@
<!-- <task log="true" alias="test60" interval="60" url="http://localhost/umbraco/test.aspx"/>-->
</scheduledTasks>
<!-- distributed calls make umbraco use webservices to handle cache refreshing -->
<!-- distributed calls must be enabled when using Umbraco in a load balanced environment -->
<distributedCall enable="false">
<!-- the id of the user who's making the calls -->
<!-- needed for security, umbraco will automatically look up correct login and passwords -->
<user>0</user>
<!--
When distributed call is enabled, you need to add all of the servers part taking in load balancing
to the server list below.
-->
<servers>
<!-- add ip number or hostname, make sure that it can be reached from all servers -->
<!-- you can also add optional attributes to force a protocol or port number (see #2) -->
<!-- <server>127.0.0.1</server>-->
<!-- <server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>-->
<!--
Add ip number or hostname, make sure that it can be reached from all servers
you can also add optional attributes to force a protocol or port number.
Examples:
<server>127.0.0.1</server>
<server forceProtocol="http|https" forcePortnumber="80|443">127.0.0.1</server>
Generally when setting up load balancing you will designate a 'master' server,
Umbraco will always assume that the FIRST server listed in this list is the 'master'.
(NOTE: Not all load balancing scenarios have a 'master', depends on how you are setting it up)
In order for scheduled tasks (including scheduled publishing) to work properly when load balancing, each
server in the load balanced environment needs to know if it is the 'master'. In order for servers
to know this or not, they need to compare some values against the servers listed. These values
are either: serverName or appId. You should not enter both values but appId will always supersede serverName.
The serverName is the easiest and will work so long as you are not load balancing your site on the same server.
If you are doing this, then you will need to use appId which is equivalent to the value returned from
HttpRuntime.AppDomainAppId. It is recommended that you set either the serverName or appId for all servers
registered here if possible, not just the first one.
Examples:
<server serverName="MyServer">server1.mysite.com</server>
<server appId="/LM/W3SVC/69/ROOT">server1.mysite.com</server>
-->
</servers>
</distributedCall>
+2 -2
View File
@@ -2,7 +2,7 @@
<packages>
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="ClientDependency-Mvc" version="1.7.0.4" targetFramework="net40" />
<package id="Examine" version="0.1.55.2941" targetFramework="net40" />
<package id="Examine" version="0.1.57.2941" targetFramework="net40" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
@@ -13,7 +13,7 @@
<package id="Microsoft.AspNet.WebApi.Core" version="4.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="4.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net40" requireReinstallation="True" />
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net40" />
<package id="Microsoft.Web.Helpers" version="1.0.0" targetFramework="net40" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
<package id="MiniProfiler" version="2.1.0" targetFramework="net40" />
@@ -1,16 +0,0 @@
<%@ Page language="c#" Codebehind="cacheBrowser.aspx.cs" AutoEventWireup="True" Inherits="umbraco.cms.presentation.cacheBrowser" trace="true" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>cacheBrowser</title>
<meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR">
<meta content="C#" name="CODE_LANGUAGE">
<meta content="JavaScript" name="vs_defaultClientScript">
<meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<asp:Button id="Button1" runat="server" Text="Button" onclick="Button1_Click"></asp:Button>
</form>
</body>
</HTML>
@@ -1,50 +1,25 @@
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DesktopMediaUploader.ascx.cs" Inherits="umbraco.presentation.umbraco.dashboard.DesktopMediaUploader" %>
<%@ Register Assembly="controls" Namespace="umbraco.uicontrols" TagPrefix="umb" %>
<%@ Register Assembly="ClientDependency.Core" Namespace="ClientDependency.Core.Controls" TagPrefix="umb" %>
<umb:CssInclude runat="server" FilePath="propertypane/style.css" PathNameAlias="UmbracoClient" />
<umb:JsInclude runat="server" FilePath="dashboard/scripts/swfobject.js" PathNameAlias="UmbracoRoot" />
<div class="dashboardWrapper">
<h2>Desktop Media Uploader</h2>
<img src="./dashboard/images/dmu.png" alt="Umbraco" class="dashboardIcon" />
<p><strong>Desktop Media Uploader</strong> is a small desktop application that you can install on your computer which allows you to easily upload media items directly to the media section.</p>
<p>The badge below will auto configure itself based upon whether you already have <strong>Desktop Media Uploader</strong> installed or not.</p>
<p>Just click the <strong>Install Now / Upgrade Now / Launch Now</strong> link to perform that action.</p>
<div class="dashboardColWrapper">
<div class="dashboardCols">
<div class="dashboardCol">
<asp:Panel ID="Panel1" runat="server"></asp:Panel>
<asp:Panel ID="Panel2" runat="server">
<p>
<div id="dmu-badge">
Download <a href="<%= FullyQualifiedAppPath %>umbraco/dashboard/air/desktopmediauploader.air">Desktop Media Uploader</a> now.<br /><br /><span id="Span1">This application requires Adobe&#174;&nbsp;AIR&#8482; to be installed for <a href="http://airdownload.adobe.com/air/mac/download/latest/AdobeAIR.dmg">Mac OS</a> or <a href="http://airdownload.adobe.com/air/win/download/latest/AdobeAIRInstaller.exe">Windows</a>.
</div>
</p>
<script type="text/javascript">
// <![CDATA[
var flashvars = {
appid: "org.umbraco.DesktopMediaUploader",
appname: "Desktop Media Uploader",
appversion: "v2.1.0",
appurl: "<%= FullyQualifiedAppPath %>umbraco/dashboard/air/desktopmediauploader.air",
applauncharg: "<%= AppLaunchArg %>",
image: "/umbraco/dashboard/images/dmu-badge.jpg?2.1.0",
airversion: "2.0"
};
var params = {
menu: "false",
wmode: "opaque"
};
var attributes = {
style: "margin-bottom:10px;"
};
swfobject.embedSWF("/umbraco/dashboard/swfs/airinstallbadge.swf", "dmu-badge", "215", "180", "9.0.115", "/umbraco/dashboard/swfs/expressinstall.swf", flashvars, params, attributes);
// ]]>
</script>
<div id="dmu-badge">
<p>
<span id="Span1">This application requires Adobe&#174;&nbsp;AIR&#8482; to be installed for <a href="http://airdownload.adobe.com/air/mac/download/latest/AdobeAIR.dmg">Mac OS</a> or <a href="http://airdownload.adobe.com/air/win/download/latest/AdobeAIRInstaller.exe">Windows</a></span>.<br />
After Air is installed you can install the <a href="<%= FullyQualifiedAppPath %>umbraco/Dashboard/air/DesktopMediaUploader.air">Desktop Media Uploader by clicking here</a>.
</p>
</div>
</asp:Panel>
</div>
</div>
</div>
</div>
</div>
@@ -5,9 +5,57 @@
</head>
<body>
<script type="text/javascript">
top.right.document.location = window.location.search.substring(1);
</script>
<script type="text/javascript">
//This is a genius way of parsing a uri
//https://gist.github.com/jlong/2428561
try {
var parser = document.createElement('a');
parser.href = window.location.search.substring(1);
// => "http:"
if (!parser.protocol || (parser.protocol.toLowerCase() != "http:" && parser.protocol.toLowerCase() != "https:")) {
throw "invalid protocol";
};
// => "example.com"
if (!parser.hostname || parser.hostname == "") {
throw "invalid hostname";
}
//parser.port; // => "3000"
// => "/pathname/"
if (!parser.pathname || ((parser.pathname.length - parser.pathname.indexOf("/developer/packages/installer.aspx")) != "/developer/packages/installer.aspx".length))
{
throw "invalid pathname";
}
// => "?search=test"
if (!parser.search || parser.search.indexOf("?repoGuid") != 0) {
throw "invalid search";
}
// => "#hash"
if (parser.hash && parser.hash != "") {
throw "invalid hash";
}
//parser.host; // => "example.com:3000"
if (!top.right) {
throw "invalid document";
}
top.right.document.location = window.location.search.substring(1);
} catch (e) {
alert(e);
}
</script>
</body>
</html>
@@ -1,5 +1,8 @@
<%@ Page MasterPageFile="../masterpages/umbracoPage.Master" Language="c#" CodeBehind="EditTemplate.aspx.cs"
ValidateRequest="false" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.Settings.EditTemplate" %>
<%@ OutputCache Location="None" %>
<%@ Import Namespace="Umbraco.Core" %>
<%@ Import Namespace="Umbraco.Core.IO" %>
<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
@@ -2,6 +2,8 @@
CodeBehind="EditView.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.Settings.Views.EditView"
ValidateRequest="False" %>
<%@ OutputCache Location="None" %>
<%@ Import Namespace="Umbraco.Core" %>
<%@ Import Namespace="Umbraco.Core.IO" %>
<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
+312
View File
@@ -0,0 +1,312 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.UI.WebControls;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
using umbraco.interfaces;
namespace Umbraco.Web
{
internal class BatchedServerMessenger : DefaultServerMessenger
{
internal BatchedServerMessenger()
{
UmbracoModule.EndRequest += UmbracoModule_EndRequest;
}
internal BatchedServerMessenger(string login, string password) : base(login, password)
{
UmbracoModule.EndRequest += UmbracoModule_EndRequest;
}
internal BatchedServerMessenger(string login, string password, bool useDistributedCalls) : base(login, password, useDistributedCalls)
{
UmbracoModule.EndRequest += UmbracoModule_EndRequest;
}
public BatchedServerMessenger(Func<Tuple<string, string>> getUserNamePasswordDelegate) : base(getUserNamePasswordDelegate)
{
UmbracoModule.EndRequest += UmbracoModule_EndRequest;
}
void UmbracoModule_EndRequest(object sender, EventArgs e)
{
if (HttpContext.Current == null)
{
return;
}
var items = HttpContext.Current.Items[typeof(BatchedServerMessenger).Name] as List<Message>;
if (items != null)
{
var copied = new Message[items.Count];
items.CopyTo(copied);
//now set to null so it get's cleaned up on this request
HttpContext.Current.Items[typeof (BatchedServerMessenger).Name] = null;
SendMessages(copied);
}
}
private class Message
{
public IEnumerable<IServerAddress> Servers { get; set; }
public ICacheRefresher Refresher { get; set; }
public MessageType DispatchType { get; set; }
public IEnumerable<object> Ids { get; set; }
public Type IdArrayType { get; set; }
public string JsonPayload { get; set; }
}
/// <summary>
/// We need to check if distributed calls are enabled, if they are we also want to make sure
/// that the current server's cache is updated internally in real time instead of at the end of
/// the call. This is because things like the URL cache, etc... might need to be updated during
/// the request that is making these calls.
/// </summary>
/// <param name="servers"></param>
/// <param name="refresher"></param>
/// <param name="dispatchType"></param>
/// <param name="ids"></param>
/// <param name="jsonPayload"></param>
/// <remarks>
/// See: http://issues.umbraco.org/issue/U4-2633#comment=67-15604
/// </remarks>
protected override void MessageSeversForIdsOrJson(IEnumerable<IServerAddress> servers, ICacheRefresher refresher, MessageType dispatchType, IEnumerable<object> ids = null, string jsonPayload = null)
{
//do all the normal stuff
base.MessageSeversForIdsOrJson(servers, refresher, dispatchType, ids, jsonPayload);
//Now, check if we are using Distrubuted calls
if (UseDistributedCalls && servers.Any())
{
//invoke on the current server - we will basically be double cache refreshing for the calling
// server but that just needs to be done currently, see the link above for details.
InvokeMethodOnRefresherInstance(refresher, dispatchType, ids, jsonPayload);
}
}
/// <summary>
/// This adds the call to batched list
/// </summary>
/// <param name="servers"></param>
/// <param name="refresher"></param>
/// <param name="dispatchType"></param>
/// <param name="ids"></param>
/// <param name="idArrayType"></param>
/// <param name="jsonPayload"></param>
protected override void PerformDistributedCall(
IEnumerable<IServerAddress> servers,
ICacheRefresher refresher,
MessageType dispatchType,
IEnumerable<object> ids = null,
Type idArrayType = null,
string jsonPayload = null)
{
//NOTE: we use UmbracoContext instead of HttpContext.Current because when some web methods run async, the
// HttpContext.Current is null but the UmbracoContext.Current won't be since we manually assign it.
if (UmbracoContext.Current == null || UmbracoContext.Current.HttpContext == null)
{
throw new NotSupportedException("This messenger cannot execute without a valid/current UmbracoContext with an HttpContext assigned");
}
if (UmbracoContext.Current.HttpContext.Items[typeof(BatchedServerMessenger).Name] == null)
{
UmbracoContext.Current.HttpContext.Items[typeof(BatchedServerMessenger).Name] = new List<Message>();
}
var list = (List<Message>)UmbracoContext.Current.HttpContext.Items[typeof(BatchedServerMessenger).Name];
list.Add(new Message
{
DispatchType = dispatchType,
IdArrayType = idArrayType,
Ids = ids,
JsonPayload = jsonPayload,
Refresher = refresher,
Servers = servers
});
}
private RefreshInstruction[] ConvertToInstruction(Message msg)
{
switch (msg.DispatchType)
{
case MessageType.RefreshAll:
return new[]
{
new RefreshInstruction
{
RefreshType = RefreshInstruction.RefreshMethodType.RefreshAll,
RefresherId = msg.Refresher.UniqueIdentifier
}
};
case MessageType.RefreshById:
if (msg.IdArrayType == null)
{
throw new InvalidOperationException("Cannot refresh by id if the idArrayType is null");
}
if (msg.IdArrayType == typeof(int))
{
var serializer = new JavaScriptSerializer();
var jsonIds = serializer.Serialize(msg.Ids.Cast<int>().ToArray());
return new[]
{
new RefreshInstruction
{
JsonIds = jsonIds,
RefreshType = RefreshInstruction.RefreshMethodType.RefreshByIds,
RefresherId = msg.Refresher.UniqueIdentifier
}
};
}
return msg.Ids.Select(x => new RefreshInstruction
{
GuidId = (Guid)x,
RefreshType = RefreshInstruction.RefreshMethodType.RefreshById,
RefresherId = msg.Refresher.UniqueIdentifier
}).ToArray();
case MessageType.RefreshByJson:
return new[]
{
new RefreshInstruction
{
RefreshType = RefreshInstruction.RefreshMethodType.RefreshByJson,
RefresherId = msg.Refresher.UniqueIdentifier,
JsonPayload = msg.JsonPayload
}
};
case MessageType.RemoveById:
return msg.Ids.Select(x => new RefreshInstruction
{
IntId = (int)x,
RefreshType = RefreshInstruction.RefreshMethodType.RemoveById,
RefresherId = msg.Refresher.UniqueIdentifier
}).ToArray();
case MessageType.RefreshByInstance:
case MessageType.RemoveByInstance:
default:
throw new ArgumentOutOfRangeException();
}
}
private void SendMessages(IEnumerable<Message> messages)
{
var batchedMsg = new List<Tuple<Message, RefreshInstruction[]>>();
foreach (var msg in messages)
{
var instructions = ConvertToInstruction(msg);
batchedMsg.Add(new Tuple<Message, RefreshInstruction[]>(msg, instructions));
}
var servers = batchedMsg.SelectMany(x => x.Item1.Servers).Distinct();
try
{
//TODO: We should try to figure out the current server's address and if it matches any of the ones
// in the ServerAddress list, then just refresh directly on this server and exclude that server address
// from the list, this will save an internal request.
using (var cacheRefresher = new ServerSyncWebServiceClient())
{
var asyncResultsList = new List<IAsyncResult>();
LogStartDispatch();
// Go through each configured node submitting a request asynchronously
//NOTE: 'asynchronously' in this case does not mean that it will continue while we give the page back to the user!
foreach (var server in servers)
{
//set the server address
cacheRefresher.Url = server.ServerAddress;
var instructions = batchedMsg
.Where(x => x.Item1.Servers.Contains(server))
.SelectMany(x => x.Item2)
//only execute distinct instructions - no sense in running the same one.
.Distinct()
.ToArray();
asyncResultsList.Add(
cacheRefresher.BeginBulkRefresh(
instructions,
HttpRuntime.AppDomainAppId,
Login, Password, null, null));
}
var waitHandlesList = asyncResultsList.Select(x => x.AsyncWaitHandle).ToArray();
var errorCount = 0;
//Wait for all requests to complete
WaitHandle.WaitAll(waitHandlesList.ToArray());
foreach (var t in asyncResultsList)
{
//var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));
try
{
cacheRefresher.EndBulkRefresh(t);
}
catch (WebException ex)
{
LogDispatchNodeError(ex);
errorCount++;
}
catch (Exception ex)
{
LogDispatchNodeError(ex);
errorCount++;
}
}
LogDispatchBatchResult(errorCount);
}
}
catch (Exception ee)
{
LogDispatchBatchError(ee);
}
}
private void LogDispatchBatchError(Exception ee)
{
LogHelper.Error<BatchedServerMessenger>("Error refreshing distributed list", ee);
}
private void LogDispatchBatchResult(int errorCount)
{
LogHelper.Debug<BatchedServerMessenger>(string.Format("Distributed server push completed with {0} nodes reporting an error", errorCount == 0 ? "no" : errorCount.ToString(CultureInfo.InvariantCulture)));
}
private void LogDispatchNodeError(Exception ex)
{
LogHelper.Error<BatchedServerMessenger>("Error refreshing a node in the distributed list", ex);
}
private void LogDispatchNodeError(WebException ex)
{
string url = (ex.Response != null) ? ex.Response.ResponseUri.ToString() : "invalid url (responseUri null)";
LogHelper.Error<BatchedServerMessenger>("Error refreshing a node in the distributed list, URI attempted: " + url, ex);
}
private void LogStartDispatch()
{
LogHelper.Info<BatchedServerMessenger>("Submitting calls to distributed servers");
}
}
}
+14 -17
View File
@@ -60,23 +60,6 @@ namespace Umbraco.Web.Cache
private static readonly DistributedCache InstanceObject = new DistributedCache();
///// <summary>
///// Fired when any cache refresher method has fired
///// </summary>
///// <remarks>
///// This could be used by developers to know when a cache refresher has executed on the local server.
///// Similarly to the content.AfterUpdateDocumentCache which fires locally on each machine.
///// </remarks>
//public event EventHandler<CacheUpdatedEventArgs> CacheChanged;
//private void OnCacheChanged(CacheUpdatedEventArgs args)
//{
// if (CacheChanged != null)
// {
// CacheChanged(this, args);
// }
//}
/// <summary>
/// Constructor
/// </summary>
@@ -109,6 +92,8 @@ namespace Umbraco.Web.Cache
/// </remarks>
public void Refresh<T>(Guid factoryGuid, Func<T, int> getNumericId, params T[] instances)
{
if (factoryGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return;
ServerMessengerResolver.Current.Messenger.PerformRefresh<T>(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
@@ -124,6 +109,8 @@ namespace Umbraco.Web.Cache
/// <param name="id">The id of the node.</param>
public void Refresh(Guid factoryGuid, int id)
{
if (factoryGuid == Guid.Empty || id == default(int)) return;
ServerMessengerResolver.Current.Messenger.PerformRefresh(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
@@ -138,6 +125,8 @@ namespace Umbraco.Web.Cache
/// <param name="id">The guid of the node.</param>
public void Refresh(Guid factoryGuid, Guid id)
{
if (factoryGuid == Guid.Empty || id == Guid.Empty) return;
ServerMessengerResolver.Current.Messenger.PerformRefresh(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
@@ -152,6 +141,8 @@ namespace Umbraco.Web.Cache
/// <param name="jsonPayload"></param>
public void RefreshByJson(Guid factoryGuid, string jsonPayload)
{
if (factoryGuid == Guid.Empty || jsonPayload.IsNullOrWhiteSpace()) return;
ServerMessengerResolver.Current.Messenger.PerformRefresh(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
@@ -165,6 +156,8 @@ namespace Umbraco.Web.Cache
/// <param name="factoryGuid">The unique identifier.</param>
public void RefreshAll(Guid factoryGuid)
{
if (factoryGuid == Guid.Empty) return;
RefreshAll(factoryGuid, true);
}
@@ -178,6 +171,8 @@ namespace Umbraco.Web.Cache
/// </param>
public void RefreshAll(Guid factoryGuid, bool allServers)
{
if (factoryGuid == Guid.Empty) return;
ServerMessengerResolver.Current.Messenger.PerformRefreshAll(
allServers
? ServerRegistrarResolver.Current.Registrar.Registrations
@@ -193,6 +188,8 @@ namespace Umbraco.Web.Cache
/// <param name="id">The id.</param>
public void Remove(Guid factoryGuid, int id)
{
if (factoryGuid == Guid.Empty || id == default(int)) return;
ServerMessengerResolver.Current.Messenger.PerformRemove(
ServerRegistrarResolver.Current.Registrar.Registrations,
GetRefresherById(factoryGuid),
-107
View File
@@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.Caching;
using Umbraco.Core;
using Umbraco.Core.Logging;
using global::umbraco.BusinessLogic;
namespace Umbraco.Web
{
// note: has to be public to be detected by the resolver
// if it's made internal, which would make more sense, then it's not detected
// and it needs to be manually registered - which we want to avoid, in order
// to be as unobtrusive as possible
internal sealed class LegacyScheduledTasks : ApplicationEventHandler
{
Timer _pingTimer;
Timer _publishingTimer;
CacheItemRemovedCallback _onCacheRemove;
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, Core.ApplicationContext applicationContext)
{
if (umbracoApplication.Context == null)
return;
// time to setup the tasks
// these are the legacy tasks
// just copied over here for backward compatibility
// of course we should have a proper scheduler, see #U4-809
// ping/keepalive
_pingTimer = new Timer(new TimerCallback(global::umbraco.presentation.keepAliveService.PingUmbraco), applicationContext, 60000, 300000);
// (un)publishing _and_ also run scheduled tasks (!)
_publishingTimer = new Timer(new TimerCallback(global::umbraco.presentation.publishingService.CheckPublishing), applicationContext, 30000, 60000);
// log scrubbing
AddTask(LOG_SCRUBBER_TASK_NAME, GetLogScrubbingInterval());
}
#region Log Scrubbing
// this is a raw copy of the legacy code in all its uglyness
const string LOG_SCRUBBER_TASK_NAME = "ScrubLogs";
private static int GetLogScrubbingInterval()
{
int interval = 24 * 60 * 60; //24 hours
try
{
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<LegacyScheduledTasks>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
}
return interval;
}
private static int GetLogScrubbingMaximumAge()
{
int maximumAge = 24 * 60 * 60;
try
{
if (global::umbraco.UmbracoSettings.MaxLogAge > -1)
maximumAge = global::umbraco.UmbracoSettings.MaxLogAge;
}
catch (Exception e)
{
LogHelper.Error<LegacyScheduledTasks>("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e);
}
return maximumAge;
}
private void AddTask(string name, int seconds)
{
_onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
HttpRuntime.Cache.Insert(name, seconds, null,
DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, _onCacheRemove);
}
public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
{
if (k.Equals(LOG_SCRUBBER_TASK_NAME))
{
ScrubLogs();
}
AddTask(k, Convert.ToInt32(v));
}
private static void ScrubLogs()
{
Log.CleanLogs(GetLogScrubbingMaximumAge());
}
#endregion
}
}
@@ -15,7 +15,7 @@ namespace Umbraco.Web.Models
/// <summary>
/// Represents a collection of DynamicPublishedContent items.
/// </summary>
public class DynamicPublishedContentList : DynamicObject, IEnumerable<DynamicPublishedContent>, IEnumerable<IPublishedContent>
public class DynamicPublishedContentList : DynamicObject, IEnumerable<DynamicPublishedContent>
{
private readonly List<IPublishedContent> _content;
private readonly PublishedContentSet<IPublishedContent> _contentSet;
@@ -574,11 +574,6 @@ namespace Umbraco.Web.Models
#region Enumerate inner IPublishedContent items
IEnumerator<IPublishedContent> IEnumerable<IPublishedContent>.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<DynamicPublishedContent> GetEnumerator()
{
return Items.GetEnumerator();
@@ -0,0 +1,108 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Mvc
{
/// <summary>
/// Used for authorizing scheduled tasks
/// </summary>
public sealed class AdminTokenAuthorizeAttribute : AuthorizeAttribute
{
private readonly ApplicationContext _applicationContext;
/// <summary>
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
/// </summary>
/// <param name="appContext"></param>
public AdminTokenAuthorizeAttribute(ApplicationContext appContext)
{
if (appContext == null) throw new ArgumentNullException("appContext");
_applicationContext = appContext;
}
public AdminTokenAuthorizeAttribute()
{
}
private ApplicationContext GetApplicationContext()
{
return _applicationContext ?? ApplicationContext.Current;
}
/// <summary>
/// Used to return the value that needs to go in the Authorization header
/// </summary>
/// <param name="appContext"></param>
/// <returns></returns>
public static string GetAuthHeaderTokenVal(ApplicationContext appContext)
{
var admin = appContext.Services.UserService.GetUserById(0);
var token = string.Format("{0}u____u{1}u____u{2}", admin.Email, admin.Username, admin.RawPasswordValue);
var encrypted = token.EncryptWithMachineKey();
var bytes = Encoding.UTF8.GetBytes(encrypted);
var base64 = Convert.ToBase64String(bytes);
return "AToken val=\"" + base64 + "\"";
}
/// <summary>
/// Ensures that the user must be in the Administrator or the Install role
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
var appContext = GetApplicationContext();
//we need to that the app is configured and that a user is logged in
if (appContext.IsConfigured == false) return false;
//need the auth header
if (httpContext.Request.Headers["Authorization"] == null || httpContext.Request.Headers["Authorization"].IsNullOrWhiteSpace()) return false;
var header = httpContext.Request.Headers["Authorization"];
if (header.StartsWith("AToken ") == false) return false;
var keyVal = Regex.Matches(header, "AToken val=(.*?)(?:$|\\s)");
if (keyVal.Count != 1) return false;
if (keyVal[0].Groups.Count != 2) return false;
var admin = appContext.Services.UserService.GetUserById(0);
if (admin == null) return false;
try
{
//get the token value from the header
var val = keyVal[0].Groups[1].Value.Trim("\"");
//un-base 64 the string
var bytes = Convert.FromBase64String(val);
var encrypted = Encoding.UTF8.GetString(bytes);
//decrypt the string
var text = encrypted.DecryptWithMachineKey();
//split
var split = text.Split(new[] {"u____u"}, StringSplitOptions.RemoveEmptyEntries);
if (split.Length != 3) return false;
//compare
return
split[0] == admin.Email
&& split[1] == admin.Username
&& split[2] == admin.RawPasswordValue;
}
catch (Exception ex)
{
LogHelper.Error<AdminTokenAuthorizeAttribute>("Failed to format passed in token value", ex);
return false;
}
}
}
}
@@ -18,9 +18,6 @@ namespace Umbraco.Web.Mvc
internal class MasterControllerFactory : DefaultControllerFactory
{
private readonly FilteredControllerFactoriesResolver _slaveFactories;
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private readonly ConcurrentDictionary<string, Type> _controllerCache = new ConcurrentDictionary<string, Type>();
public MasterControllerFactory(FilteredControllerFactoriesResolver factoryResolver)
{
@@ -87,9 +84,7 @@ namespace Umbraco.Web.Mvc
/// <remarks></remarks>
public override void ReleaseController(IController controller)
{
using (new WriteLock(_locker))
{
bool released = false;
bool released = false;
if (controller is Controller)
{
var requestContext = ((Controller)controller).ControllerContext.RequestContext;
@@ -101,7 +96,6 @@ namespace Umbraco.Web.Mvc
}
}
if (!released) base.ReleaseController(controller);
}
}
}
}
+2 -1
View File
@@ -1,12 +1,13 @@
using System.Linq;
using System.Web.Mvc;
using System.Web.Mvc.Async;
namespace Umbraco.Web.Mvc
{
/// <summary>
/// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute
/// </summary>
public class RenderActionInvoker : ControllerActionInvoker
public class RenderActionInvoker : AsyncControllerActionInvoker
{
/// <summary>
+3 -2
View File
@@ -228,8 +228,9 @@ namespace Umbraco.Web.Mvc
// If more than one route is found, find one with a matching action
if (surfaceRoutes.Count() > 1)
{
surfaceRoute = surfaceRoutes.SingleOrDefault(x =>
x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName));
surfaceRoute = surfaceRoutes.FirstOrDefault(x =>
x.Defaults["action"] != null &&
x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName));
}
else
{
@@ -7,7 +7,7 @@ using umbraco.BasePages;
namespace Umbraco.Web.Mvc
{
/// <summary>
/// <summary>
/// Ensures authorization is successful for a back office user
/// </summary>
public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute
@@ -34,3 +34,5 @@ using System.Security;
[assembly: InternalsVisibleTo("umbraco.webservices")]
[assembly: InternalsVisibleTo("Concorde.Sync")]
[assembly: InternalsVisibleTo("Umbraco.Belle")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
@@ -0,0 +1,69 @@
using System;
using System.Text;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Macros;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
/// <summary>
/// Ensures macro syntax is parsed for the macro container which will work when getting the field
/// values in any way (i.e. dynamically, using Field(), or IPublishedContent)
/// </summary>
[PropertyValueType(typeof (IHtmlString))]
[PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)]
public class MacroContainerValueConverter : PropertyValueConverterBase
{
public override bool IsConverter(PublishedPropertyType propertyType)
{
return Guid.Parse(Constants.PropertyEditors.MacroContainer).Equals(propertyType.PropertyEditorGuid);
}
// NOT thread-safe over a request because it modifies the
// global UmbracoContext.Current.InPreviewMode status. So it
// should never execute in // over the same UmbracoContext with
// different preview modes.
static string RenderMacros(string source, bool preview)
{
// save and set for macro rendering
var inPreviewMode = UmbracoContext.Current.InPreviewMode;
UmbracoContext.Current.InPreviewMode = preview;
var sb = new StringBuilder();
try
{
var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
MacroTagParser.ParseMacros(
source,
//callback for when text block is found
textBlock => sb.Append(textBlock),
//callback for when macro syntax is found
(macroAlias, macroAttributes) => sb.Append(umbracoHelper.RenderMacro(
macroAlias,
//needs to be explicitly casted to Dictionary<string, object>
macroAttributes.ConvertTo(x => (string)x, x => x)).ToString()));
}
finally
{
// restore
UmbracoContext.Current.InPreviewMode = inPreviewMode;
}
return sb.ToString();
}
public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
{
if (source == null) return null;
var sourceString = source.ToString();
// ensure string is parsed for macros and macros are executed correctly
sourceString = RenderMacros(sourceString, preview);
return sourceString;
}
}
}
@@ -11,7 +11,7 @@ using Umbraco.Web.Templates;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
/// <summary>
/// <summary>
/// A value converter for TinyMCE that will ensure any macro content is rendered properly even when
/// used dynamically.
/// </summary>
@@ -5,6 +5,7 @@ using System.Text;
using System.Web.Security;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Models;
@@ -17,17 +18,16 @@ namespace Umbraco.Web.PublishedCache
{
private readonly IMember _member;
private readonly MembershipUser _membershipUser;
private readonly IMembershipUser _membershipUser;
private readonly IPublishedContentProperty[] _properties;
private readonly PublishedContentType _publishedMemberType;
public MemberPublishedContent(IMember member, MembershipUser membershipUser)
public MemberPublishedContent(IMember member)
{
if (member == null) throw new ArgumentNullException("member");
if (membershipUser == null) throw new ArgumentNullException("membershipUser");
if (member == null) throw new ArgumentNullException("member");
_member = member;
_membershipUser = membershipUser;
_membershipUser = member;
_publishedMemberType = PublishedContentType.Get(PublishedItemType.Member, _member.ContentTypeAlias);
if (_publishedMemberType == null)
{
@@ -45,7 +45,7 @@ namespace Umbraco.Web.PublishedCache
}
public string UserName
{
get { return _membershipUser.UserName; }
get { return _membershipUser.Username; }
}
public string PasswordQuestion
{
@@ -53,7 +53,7 @@ namespace Umbraco.Web.PublishedCache
}
public string Comments
{
get { return _membershipUser.Comment; }
get { return _membershipUser.Comments; }
}
public bool IsApproved
{
@@ -69,7 +69,7 @@ namespace Umbraco.Web.PublishedCache
}
public DateTime CreationDate
{
get { return _membershipUser.CreationDate; }
get { return _membershipUser.CreateDate; }
}
public DateTime LastLoginDate
{
@@ -77,11 +77,11 @@ namespace Umbraco.Web.PublishedCache
}
public DateTime LastActivityDate
{
get { return _membershipUser.LastActivityDate; }
get { return _membershipUser.LastLoginDate; }
}
public DateTime LastPasswordChangedDate
{
get { return _membershipUser.LastPasswordChangedDate; }
get { return _membershipUser.LastPasswordChangeDate; }
}
#endregion
+34
View File
@@ -0,0 +1,34 @@
using System;
using System.Net;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
namespace Umbraco.Web.Scheduling
{
internal class KeepAlive
{
public static void Start(object sender)
{
using (DisposableTimer.DebugDuration<KeepAlive>(() => "Keep alive executing", () => "Keep alive complete"))
{
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
var url = string.Format("{0}/ping.aspx", umbracoBaseUrl);
try
{
using (var wc = new WebClient())
{
wc.DownloadString(url);
}
}
catch (Exception ee)
{
LogHelper.Error<KeepAlive>("Error in ping", ee);
}
}
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using System;
using System.Web;
using System.Web.Caching;
using umbraco.BusinessLogic;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Scheduling
{
//TODO: Refactor this to use a normal scheduling processor!
internal class LogScrubber
{
// this is a raw copy of the legacy code in all its uglyness
CacheItemRemovedCallback _onCacheRemove;
const string LogScrubberTaskName = "ScrubLogs";
public void Start()
{
// log scrubbing
AddTask(LogScrubberTaskName, GetLogScrubbingInterval());
}
private static int GetLogScrubbingInterval()
{
int interval = 24 * 60 * 60; //24 hours
try
{
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
}
return interval;
}
private static int GetLogScrubbingMaximumAge()
{
int maximumAge = 24 * 60 * 60;
try
{
if (global::umbraco.UmbracoSettings.MaxLogAge > -1)
maximumAge = global::umbraco.UmbracoSettings.MaxLogAge;
}
catch (Exception e)
{
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e);
}
return maximumAge;
}
private void AddTask(string name, int seconds)
{
_onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
HttpRuntime.Cache.Insert(name, seconds, null,
DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, _onCacheRemove);
}
public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
{
if (k.Equals(LogScrubberTaskName))
{
ScrubLogs();
}
AddTask(k, Convert.ToInt32(v));
}
private static void ScrubLogs()
{
Log.CleanLogs(GetLogScrubbingMaximumAge());
}
}
}
@@ -0,0 +1,52 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Publishing;
using Umbraco.Core.Sync;
using Umbraco.Web.Mvc;
namespace Umbraco.Web.Scheduling
{
internal class ScheduledPublishing
{
private static bool _isPublishingRunning = false;
public void Start(ApplicationContext appContext)
{
if (appContext == null) return;
using (DisposableTimer.DebugDuration<ScheduledPublishing>(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
try
{
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
var url = string.Format("{0}/RestServices/ScheduledPublish/Index", umbracoBaseUrl);
using (var wc = new WebClient())
{
//pass custom the authorization header
wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(appContext));
var result = wc.UploadString(url, "");
}
}
catch (Exception ee)
{
LogHelper.Error<ScheduledPublishing>("An error occurred with the scheduled publishing", ee);
}
finally
{
_isPublishingRunning = false;
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More