Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 173ac65eb5 | |||
| 7c28e1aeba | |||
| 5989d19f7f | |||
| da1c33a034 | |||
| 2111a5e31e | |||
| 5478de911b | |||
| 7765acb130 | |||
| 5640daff32 | |||
| a1cae3f286 | |||
| d22dbb4654 | |||
| 4d5a8298b0 | |||
| 9e2733ce69 | |||
| fbfdd8d398 | |||
| 2552dffdad | |||
| 52540165f1 | |||
| 2b408df24c | |||
| d559411187 | |||
| 3a4a6579d3 |
@@ -81,3 +81,6 @@ build/_BuildOutput/
|
||||
tools/NDepend/
|
||||
src/*.vspx
|
||||
src/*.psess
|
||||
NDependOut/*
|
||||
QueryResult.htm
|
||||
*.ndproj
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
@ECHO OFF
|
||||
SET release=6.1.4
|
||||
SET release=6.1.5
|
||||
SET comment=
|
||||
SET version=%release%
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public class UmbracoVersion
|
||||
{
|
||||
private static readonly Version Version = new Version("6.1.4");
|
||||
private static readonly Version Version = new Version("6.1.5");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
{
|
||||
@@ -107,6 +108,12 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
var constraintAttribute = propertyInfo.FirstAttribute<ConstraintAttribute>();
|
||||
if (constraintAttribute != null)
|
||||
{
|
||||
//Special case for MySQL as it can't have multiple default DateTime values, which
|
||||
//is what the umbracoServer table definition is trying to create
|
||||
if (SqlSyntaxContext.SqlSyntaxProvider is MySqlSyntaxProvider && definition.TableName == "umbracoServer" &&
|
||||
definition.TableName.ToLowerInvariant() == "lastNotifiedDate".ToLowerInvariant())
|
||||
return definition;
|
||||
|
||||
definition.ConstraintName = constraintAttribute.Name ?? string.Empty;
|
||||
definition.DefaultValue = constraintAttribute.Default ?? string.Empty;
|
||||
}
|
||||
|
||||
@@ -170,11 +170,31 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return content;
|
||||
}
|
||||
|
||||
public override void DeleteVersion(Guid versionId)
|
||||
{
|
||||
var sql = new Sql()
|
||||
.Select("*")
|
||||
.From<DocumentDto>()
|
||||
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, DocumentDto>(left => left.VersionId, right => right.VersionId)
|
||||
.Where<ContentVersionDto>(x => x.VersionId == versionId)
|
||||
.Where<DocumentDto>(x => x.Newest == true);
|
||||
var dto = Database.Fetch<DocumentDto, ContentVersionDto>(sql).FirstOrDefault();
|
||||
|
||||
if(dto == null) return;
|
||||
|
||||
using (var transaction = Database.GetTransaction())
|
||||
{
|
||||
PerformDeleteVersion(dto.NodeId, versionId);
|
||||
|
||||
transaction.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PerformDeleteVersion(int id, Guid versionId)
|
||||
{
|
||||
Database.Delete<PreviewXmlDto>("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<PropertyDataDto>("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE nodeId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<PropertyDataDto>("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<DocumentDto>("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
}
|
||||
|
||||
@@ -340,19 +360,18 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
//Look up (newest) entries by id in cmsDocument table to set newest = false
|
||||
var documentDtos = Database.Fetch<DocumentDto>("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true });
|
||||
foreach (var documentDto in documentDtos)
|
||||
{
|
||||
var docDto = documentDto;
|
||||
docDto.Newest = false;
|
||||
Database.Update(docDto);
|
||||
}
|
||||
|
||||
var contentVersionDto = dto.ContentVersionDto;
|
||||
if (shouldCreateNewVersion)
|
||||
{
|
||||
//Look up (newest) entries by id in cmsDocument table to set newest = false
|
||||
//NOTE: This is only relevant when a new version is created, which is why its done inside this if-statement.
|
||||
var documentDtos = Database.Fetch<DocumentDto>("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true });
|
||||
foreach (var documentDto in documentDtos)
|
||||
{
|
||||
var docDto = documentDto;
|
||||
docDto.Newest = false;
|
||||
Database.Update(docDto);
|
||||
}
|
||||
|
||||
//Create a new version - cmsContentVersion
|
||||
//Assumes a new Version guid and Version date (modified date) has been set
|
||||
Database.Insert(contentVersionDto);
|
||||
|
||||
@@ -176,8 +176,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
protected override void PerformDeleteVersion(int id, Guid versionId)
|
||||
{
|
||||
Database.Delete<PreviewXmlDto>("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<PropertyDataDto>("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE nodeId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<PropertyDataDto>("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
Database.Delete<ContentVersionDto>("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public virtual void DeleteVersion(Guid versionId)
|
||||
{
|
||||
var dto = Database.FirstOrDefault<ContentVersionDto>("WHERE versionId = @VersionId AND newest = @Newest", new { VersionId = versionId, Newest = false });
|
||||
Mandate.That<Exception>(dto != null);
|
||||
var dto = Database.FirstOrDefault<ContentVersionDto>("WHERE versionId = @VersionId", new { VersionId = versionId });
|
||||
if(dto == null) return;
|
||||
|
||||
using (var transaction = Database.GetTransaction())
|
||||
{
|
||||
@@ -56,8 +56,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public virtual void DeleteVersions(int id, DateTime versionDate)
|
||||
{
|
||||
var list = Database.Fetch<ContentVersionDto>("WHERE nodeId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate });
|
||||
Mandate.That<Exception>(list.Any());
|
||||
var list = Database.Fetch<ContentVersionDto>("WHERE ContentId = @Id AND VersionDate < @VersionDate", new { Id = id, VersionDate = versionDate });
|
||||
if (list.Any() == false) return;
|
||||
|
||||
using (var transaction = Database.GetTransaction())
|
||||
{
|
||||
|
||||
+367
-342
@@ -19,54 +19,56 @@ using Umbraco.Core.IO;
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A utility class to find all classes of a certain type by reflection in the current bin folder
|
||||
/// of the web application.
|
||||
/// </summary>
|
||||
public static class TypeFinder
|
||||
{
|
||||
|
||||
private static readonly ConcurrentBag<Assembly> LocalFilteredAssemblyCache = new ConcurrentBag<Assembly>();
|
||||
private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim();
|
||||
private static ReadOnlyCollection<Assembly> _allAssemblies = null;
|
||||
private static ReadOnlyCollection<Assembly> _binFolderAssemblies = null;
|
||||
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
|
||||
/// <summary>
|
||||
/// A utility class to find all classes of a certain type by reflection in the current bin folder
|
||||
/// of the web application.
|
||||
/// </summary>
|
||||
public static class TypeFinder
|
||||
{
|
||||
private static readonly HashSet<Assembly> LocalFilteredAssemblyCache = new HashSet<Assembly>();
|
||||
private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim();
|
||||
private static HashSet<Assembly> _allAssemblies = null;
|
||||
private static HashSet<Assembly> _binFolderAssemblies = null;
|
||||
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
|
||||
|
||||
/// <summary>
|
||||
/// lazily load a reference to all assemblies and only local assemblies.
|
||||
/// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been
|
||||
/// loaded in the CLR, not all assemblies.
|
||||
/// See these threads:
|
||||
/// http://issues.umbraco.org/issue/U5-198
|
||||
/// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app
|
||||
/// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl
|
||||
/// </remarks>
|
||||
internal static IEnumerable<Assembly> GetAllAssemblies()
|
||||
{
|
||||
if (_allAssemblies == null)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
List<Assembly> assemblies = null;
|
||||
try
|
||||
{
|
||||
var isHosted = HttpContext.Current != null;
|
||||
/// <summary>
|
||||
/// lazily load a reference to all assemblies and only local assemblies.
|
||||
/// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been
|
||||
/// loaded in the CLR, not all assemblies.
|
||||
/// See these threads:
|
||||
/// http://issues.umbraco.org/issue/U5-198
|
||||
/// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app
|
||||
/// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl
|
||||
/// </remarks>
|
||||
internal static HashSet<Assembly> GetAllAssemblies()
|
||||
{
|
||||
using (var lck = new UpgradeableReadLock(Locker))
|
||||
{
|
||||
if (_allAssemblies == null)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (isHosted)
|
||||
{
|
||||
assemblies = new List<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>());
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
if (!(e.InnerException is SecurityException))
|
||||
throw;
|
||||
}
|
||||
lck.UpgradeToWriteLock();
|
||||
|
||||
HashSet<Assembly> assemblies = null;
|
||||
try
|
||||
{
|
||||
var isHosted = HttpContext.Current != null;
|
||||
|
||||
try
|
||||
{
|
||||
if (isHosted)
|
||||
{
|
||||
assemblies = new HashSet<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>());
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
if (!(e.InnerException is SecurityException))
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
if (assemblies == null)
|
||||
@@ -77,7 +79,7 @@ namespace Umbraco.Core
|
||||
var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||
//var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
|
||||
//var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||
assemblies = new List<Assembly>();
|
||||
assemblies = new HashSet<Assembly>();
|
||||
foreach (var a in binAssemblyFiles)
|
||||
{
|
||||
try
|
||||
@@ -99,151 +101,161 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies.
|
||||
if (!assemblies.Any())
|
||||
{
|
||||
assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().ToList());
|
||||
}
|
||||
if (!assemblies.Any())
|
||||
{
|
||||
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
assemblies.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
//here we are trying to get the App_Code assembly
|
||||
var fileExtensions = new[] {".cs", ".vb"}; //only vb and cs files are supported
|
||||
var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code")));
|
||||
//check if the folder exists and if there are any files in it with the supported file extensions
|
||||
if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())))
|
||||
{
|
||||
var appCodeAssembly = Assembly.Load("App_Code");
|
||||
if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already
|
||||
assemblies.Add(appCodeAssembly);
|
||||
}
|
||||
//here we are trying to get the App_Code assembly
|
||||
var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported
|
||||
var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code")));
|
||||
//check if the folder exists and if there are any files in it with the supported file extensions
|
||||
if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())))
|
||||
{
|
||||
var appCodeAssembly = Assembly.Load("App_Code");
|
||||
if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already
|
||||
assemblies.Add(appCodeAssembly);
|
||||
}
|
||||
|
||||
//now set the _allAssemblies
|
||||
_allAssemblies = new ReadOnlyCollection<Assembly>(assemblies);
|
||||
//now set the _allAssemblies
|
||||
_allAssemblies = new HashSet<Assembly>(assemblies);
|
||||
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
if (!(e.InnerException is SecurityException))
|
||||
throw;
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
if (!(e.InnerException is SecurityException))
|
||||
throw;
|
||||
|
||||
_binFolderAssemblies = _allAssemblies;
|
||||
}
|
||||
}
|
||||
}
|
||||
_binFolderAssemblies = _allAssemblies;
|
||||
}
|
||||
}
|
||||
|
||||
return _allAssemblies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns only assemblies found in the bin folder that have been loaded into the app domain.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used.
|
||||
/// </remarks>
|
||||
internal static IEnumerable<Assembly> GetBinAssemblies()
|
||||
{
|
||||
return _allAssemblies;
|
||||
}
|
||||
}
|
||||
|
||||
if (_binFolderAssemblies == null)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var assemblies = GetAssembliesWithKnownExclusions().ToArray();
|
||||
var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
|
||||
var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||
var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName);
|
||||
var safeDomainAssemblies = new List<Assembly>();
|
||||
var binFolderAssemblies = new List<Assembly>();
|
||||
/// <summary>
|
||||
/// Returns only assemblies found in the bin folder that have been loaded into the app domain.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used.
|
||||
/// </remarks>
|
||||
internal static HashSet<Assembly> GetBinAssemblies()
|
||||
{
|
||||
|
||||
foreach (var a in assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
//do a test to see if its queryable in med trust
|
||||
var assemblyFile = a.GetAssemblyFile();
|
||||
safeDomainAssemblies.Add(a);
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
//we will just ignore this because this will fail
|
||||
//in medium trust for system assemblies, we get an exception but we just want to continue until we get to
|
||||
//an assembly that is ok.
|
||||
}
|
||||
}
|
||||
if (_binFolderAssemblies == null)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var assemblies = GetAssembliesWithKnownExclusions().ToArray();
|
||||
var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
|
||||
var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||
var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName);
|
||||
var safeDomainAssemblies = new HashSet<Assembly>();
|
||||
var binFolderAssemblies = new HashSet<Assembly>();
|
||||
|
||||
foreach (var assemblyName in domainAssemblyNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var foundAssembly =
|
||||
safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile());
|
||||
if (foundAssembly != null)
|
||||
{
|
||||
binFolderAssemblies.Add(foundAssembly);
|
||||
}
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
//we will just ignore this because if we are trying to do a call to:
|
||||
// AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName)))
|
||||
//in medium trust for system assemblies, we get an exception but we just want to continue until we get to
|
||||
//an assembly that is ok.
|
||||
}
|
||||
}
|
||||
foreach (var a in assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
//do a test to see if its queryable in med trust
|
||||
var assemblyFile = a.GetAssemblyFile();
|
||||
safeDomainAssemblies.Add(a);
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
//we will just ignore this because this will fail
|
||||
//in medium trust for system assemblies, we get an exception but we just want to continue until we get to
|
||||
//an assembly that is ok.
|
||||
}
|
||||
}
|
||||
|
||||
_binFolderAssemblies = new ReadOnlyCollection<Assembly>(binFolderAssemblies);
|
||||
}
|
||||
}
|
||||
return _binFolderAssemblies;
|
||||
}
|
||||
foreach (var assemblyName in domainAssemblyNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var foundAssembly =
|
||||
safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile());
|
||||
if (foundAssembly != null)
|
||||
{
|
||||
binFolderAssemblies.Add(foundAssembly);
|
||||
}
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
//we will just ignore this because if we are trying to do a call to:
|
||||
// AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName)))
|
||||
//in medium trust for system assemblies, we get an exception but we just want to continue until we get to
|
||||
//an assembly that is ok.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of found local Assemblies excluding the known assemblies we don't want to scan
|
||||
/// and exluding the ones passed in and excluding the exclusion list filter, the results of this are
|
||||
/// cached for perforance reasons.
|
||||
/// </summary>
|
||||
/// <param name="excludeFromResults"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<Assembly> GetAssembliesWithKnownExclusions(
|
||||
IEnumerable<Assembly> excludeFromResults = null)
|
||||
{
|
||||
if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache;
|
||||
using (new WriteLock(LocalFilteredAssemblyCacheLocker))
|
||||
{
|
||||
var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter);
|
||||
assemblies.ForEach(LocalFilteredAssemblyCache.Add);
|
||||
}
|
||||
return LocalFilteredAssemblyCache;
|
||||
}
|
||||
_binFolderAssemblies = new HashSet<Assembly>(binFolderAssemblies);
|
||||
}
|
||||
}
|
||||
return _binFolderAssemblies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter
|
||||
/// </summary>
|
||||
/// <param name="excludeFromResults"></param>
|
||||
/// <param name="exclusionFilter"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<Assembly> GetFilteredAssemblies(
|
||||
IEnumerable<Assembly> excludeFromResults = null,
|
||||
string[] exclusionFilter = null)
|
||||
{
|
||||
if (excludeFromResults == null)
|
||||
excludeFromResults = new List<Assembly>();
|
||||
if (exclusionFilter == null)
|
||||
exclusionFilter = new string[] { };
|
||||
/// <summary>
|
||||
/// Return a list of found local Assemblies excluding the known assemblies we don't want to scan
|
||||
/// and exluding the ones passed in and excluding the exclusion list filter, the results of this are
|
||||
/// cached for perforance reasons.
|
||||
/// </summary>
|
||||
/// <param name="excludeFromResults"></param>
|
||||
/// <returns></returns>
|
||||
internal static HashSet<Assembly> GetAssembliesWithKnownExclusions(
|
||||
IEnumerable<Assembly> excludeFromResults = null)
|
||||
{
|
||||
using (var lck = new UpgradeableReadLock(LocalFilteredAssemblyCacheLocker))
|
||||
{
|
||||
if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache;
|
||||
|
||||
return GetAllAssemblies()
|
||||
.Where(x => !excludeFromResults.Contains(x)
|
||||
&& !x.GlobalAssemblyCache
|
||||
&& !exclusionFilter.Any(f => x.FullName.StartsWith(f)));
|
||||
}
|
||||
lck.UpgradeToWriteLock();
|
||||
|
||||
/// <summary>
|
||||
/// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match
|
||||
/// </remarks>
|
||||
internal static readonly string[] KnownAssemblyExclusionFilter = new[]
|
||||
var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter);
|
||||
foreach (var a in assemblies)
|
||||
{
|
||||
LocalFilteredAssemblyCache.Add(a);
|
||||
}
|
||||
|
||||
return LocalFilteredAssemblyCache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a distinct list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter
|
||||
/// </summary>
|
||||
/// <param name="excludeFromResults"></param>
|
||||
/// <param name="exclusionFilter"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<Assembly> GetFilteredAssemblies(
|
||||
IEnumerable<Assembly> excludeFromResults = null,
|
||||
string[] exclusionFilter = null)
|
||||
{
|
||||
if (excludeFromResults == null)
|
||||
excludeFromResults = new HashSet<Assembly>();
|
||||
if (exclusionFilter == null)
|
||||
exclusionFilter = new string[] { };
|
||||
|
||||
return GetAllAssemblies()
|
||||
.Where(x => !excludeFromResults.Contains(x)
|
||||
&& !x.GlobalAssemblyCache
|
||||
&& !exclusionFilter.Any(f => x.FullName.StartsWith(f)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match
|
||||
/// </remarks>
|
||||
internal static readonly string[] KnownAssemblyExclusionFilter = new[]
|
||||
{
|
||||
"mscorlib,",
|
||||
"System.",
|
||||
@@ -287,11 +299,11 @@ namespace Umbraco.Core
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TAttribute"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>()
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return FindClassesOfTypeWithAttribute<T, TAttribute>(GetAssembliesWithKnownExclusions(), true);
|
||||
}
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>()
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return FindClassesOfTypeWithAttribute<T, TAttribute>(GetAssembliesWithKnownExclusions(), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds any classes derived from the type T that contain the attribute TAttribute
|
||||
@@ -300,11 +312,11 @@ namespace Umbraco.Core
|
||||
/// <typeparam name="TAttribute"></typeparam>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>(IEnumerable<Assembly> assemblies)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return FindClassesOfTypeWithAttribute<T, TAttribute>(assemblies, true);
|
||||
}
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>(IEnumerable<Assembly> assemblies)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return FindClassesOfTypeWithAttribute<T, TAttribute>(assemblies, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds any classes derived from the type T that contain the attribute TAttribute
|
||||
@@ -314,13 +326,13 @@ namespace Umbraco.Core
|
||||
/// <param name="assemblies"></param>
|
||||
/// <param name="onlyConcreteClasses"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>(
|
||||
IEnumerable<Assembly> assemblies,
|
||||
bool onlyConcreteClasses)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return FindClassesOfTypeWithAttribute<TAttribute>(typeof (T), assemblies, onlyConcreteClasses);
|
||||
}
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<T, TAttribute>(
|
||||
IEnumerable<Assembly> assemblies,
|
||||
bool onlyConcreteClasses)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return FindClassesOfTypeWithAttribute<TAttribute>(typeof(T), assemblies, onlyConcreteClasses);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute
|
||||
@@ -330,85 +342,85 @@ namespace Umbraco.Core
|
||||
/// <param name="assemblies"></param>
|
||||
/// <param name="onlyConcreteClasses"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<TAttribute>(
|
||||
Type assignTypeFrom,
|
||||
IEnumerable<Assembly> assemblies,
|
||||
public static IEnumerable<Type> FindClassesOfTypeWithAttribute<TAttribute>(
|
||||
Type assignTypeFrom,
|
||||
IEnumerable<Assembly> assemblies,
|
||||
bool onlyConcreteClasses)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
if (assemblies == null) throw new ArgumentNullException("assemblies");
|
||||
|
||||
return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses,
|
||||
{
|
||||
if (assemblies == null) throw new ArgumentNullException("assemblies");
|
||||
|
||||
return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses,
|
||||
//the additional filter will ensure that any found types also have the attribute applied.
|
||||
t => t.GetCustomAttributes<TAttribute>(false).Any());
|
||||
}
|
||||
t => t.GetCustomAttributes<TAttribute>(false).Any());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches all filtered local assemblies specified for classes of the type passed in.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfType<T>()
|
||||
{
|
||||
return FindClassesOfType<T>(GetAssembliesWithKnownExclusions(), true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Searches all filtered local assemblies specified for classes of the type passed in.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfType<T>()
|
||||
{
|
||||
return FindClassesOfType<T>(GetAssembliesWithKnownExclusions(), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all types found of in the assemblies specified of type T
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <param name="onlyConcreteClasses"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfType<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses)
|
||||
{
|
||||
if (assemblies == null) throw new ArgumentNullException("assemblies");
|
||||
/// <summary>
|
||||
/// Returns all types found of in the assemblies specified of type T
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <param name="onlyConcreteClasses"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfType<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses)
|
||||
{
|
||||
if (assemblies == null) throw new ArgumentNullException("assemblies");
|
||||
|
||||
return GetClasses(typeof(T), assemblies, onlyConcreteClasses);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all types found of in the assemblies specified of type T
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfType<T>(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return FindClassesOfType<T>(assemblies, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns all types found of in the assemblies specified of type T
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesOfType<T>(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
return FindClassesOfType<T>(assemblies, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the classes with attribute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies">The assemblies.</param>
|
||||
/// <param name="onlyConcreteClasses">if set to <c>true</c> only concrete classes.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses)
|
||||
where T : Attribute
|
||||
{
|
||||
return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses);
|
||||
}
|
||||
/// <summary>
|
||||
/// Finds the classes with attribute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies">The assemblies.</param>
|
||||
/// <param name="onlyConcreteClasses">if set to <c>true</c> only concrete classes.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses)
|
||||
where T : Attribute
|
||||
{
|
||||
return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds any classes with the attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeType">The attribute type </param>
|
||||
/// <param name="assemblies">The assemblies.</param>
|
||||
/// <param name="onlyConcreteClasses">if set to <c>true</c> only concrete classes.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute(
|
||||
Type attributeType,
|
||||
IEnumerable<Assembly> assemblies,
|
||||
bool onlyConcreteClasses)
|
||||
{
|
||||
if (assemblies == null) throw new ArgumentNullException("assemblies");
|
||||
/// <summary>
|
||||
/// Finds any classes with the attribute.
|
||||
/// </summary>
|
||||
/// <param name="attributeType">The attribute type </param>
|
||||
/// <param name="assemblies">The assemblies.</param>
|
||||
/// <param name="onlyConcreteClasses">if set to <c>true</c> only concrete classes.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute(
|
||||
Type attributeType,
|
||||
IEnumerable<Assembly> assemblies,
|
||||
bool onlyConcreteClasses)
|
||||
{
|
||||
if (assemblies == null) throw new ArgumentNullException("assemblies");
|
||||
|
||||
if (TypeHelper.IsTypeAssignableFrom<Attribute>(attributeType) == false)
|
||||
throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type");
|
||||
if (TypeHelper.IsTypeAssignableFrom<Attribute>(attributeType) == false)
|
||||
throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type");
|
||||
|
||||
var foundAssignableTypes = new List<Type>();
|
||||
var foundAssignableTypes = new HashSet<Type>();
|
||||
|
||||
var assemblyList = assemblies.ToArray();
|
||||
|
||||
@@ -418,71 +430,74 @@ namespace Umbraco.Core
|
||||
var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList);
|
||||
|
||||
foreach (var a in referencedAssemblies)
|
||||
{
|
||||
{
|
||||
//get all types in the assembly that are sub types of the current type
|
||||
var allTypes = GetTypesWithFormattedException(a).ToArray();
|
||||
|
||||
var types = allTypes.Where(t => TypeHelper.IsNonStaticClass(t)
|
||||
&& (onlyConcreteClasses == false || t.IsAbstract == false)
|
||||
//the type must have this attribute
|
||||
|
||||
var types = allTypes.Where(t => TypeHelper.IsNonStaticClass(t)
|
||||
&& (onlyConcreteClasses == false || t.IsAbstract == false)
|
||||
//the type must have this attribute
|
||||
&& t.GetCustomAttributes(attributeType, false).Any());
|
||||
|
||||
foundAssignableTypes.AddRange(types);
|
||||
}
|
||||
foreach (var t in types)
|
||||
{
|
||||
foundAssignableTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
return foundAssignableTypes;
|
||||
}
|
||||
return foundAssignableTypes;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds the classes with attribute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies">The assemblies.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute<T>(IEnumerable<Assembly> assemblies)
|
||||
where T : Attribute
|
||||
{
|
||||
return FindClassesWithAttribute<T>(assemblies, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Finds the classes with attribute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies">The assemblies.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute<T>(IEnumerable<Assembly> assemblies)
|
||||
where T : Attribute
|
||||
{
|
||||
return FindClassesWithAttribute<T>(assemblies, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the classes with attribute in filtered local assemblies
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute<T>()
|
||||
where T : Attribute
|
||||
{
|
||||
return FindClassesWithAttribute<T>(GetAssembliesWithKnownExclusions());
|
||||
}
|
||||
/// <summary>
|
||||
/// Finds the classes with attribute in filtered local assemblies
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> FindClassesWithAttribute<T>()
|
||||
where T : Attribute
|
||||
{
|
||||
return FindClassesWithAttribute<T>(GetAssembliesWithKnownExclusions());
|
||||
}
|
||||
|
||||
|
||||
#region Private methods
|
||||
|
||||
/// <summary>
|
||||
/// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly
|
||||
/// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type
|
||||
/// deriving from the base type.
|
||||
/// </summary>
|
||||
/// <param name="assignTypeFrom"></param>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <param name="onlyConcreteClasses"></param>
|
||||
/// <param name="additionalFilter">An additional filter to apply for what types will actually be included in the return value</param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<Type> GetClasses(
|
||||
Type assignTypeFrom,
|
||||
IEnumerable<Assembly> assemblies,
|
||||
#region Private methods
|
||||
|
||||
/// <summary>
|
||||
/// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly
|
||||
/// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type
|
||||
/// deriving from the base type.
|
||||
/// </summary>
|
||||
/// <param name="assignTypeFrom"></param>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <param name="onlyConcreteClasses"></param>
|
||||
/// <param name="additionalFilter">An additional filter to apply for what types will actually be included in the return value</param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<Type> GetClasses(
|
||||
Type assignTypeFrom,
|
||||
IEnumerable<Assembly> assemblies,
|
||||
bool onlyConcreteClasses,
|
||||
Func<Type, bool> additionalFilter = null)
|
||||
{
|
||||
{
|
||||
//the default filter will always return true.
|
||||
if (additionalFilter == null)
|
||||
{
|
||||
additionalFilter = type => true;
|
||||
}
|
||||
|
||||
var foundAssignableTypes = new List<Type>();
|
||||
var foundAssignableTypes = new HashSet<Type>();
|
||||
|
||||
var assemblyList = assemblies.ToArray();
|
||||
|
||||
@@ -510,8 +525,11 @@ namespace Umbraco.Core
|
||||
.ToArray();
|
||||
|
||||
//add the types to our list to return
|
||||
foundAssignableTypes.AddRange(filteredTypes);
|
||||
|
||||
foreach (var t in filteredTypes)
|
||||
{
|
||||
foundAssignableTypes.Add(t);
|
||||
}
|
||||
|
||||
//now we need to include types that may be inheriting from sub classes of the type being searched for
|
||||
//so we will search in assemblies that reference those types too.
|
||||
foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly))
|
||||
@@ -529,54 +547,61 @@ namespace Umbraco.Core
|
||||
|
||||
//if there's a base class amongst the types then we'll only search for that type.
|
||||
//otherwise we'll have to search for all of them.
|
||||
var subTypesToSearch = new List<Type>();
|
||||
var subTypesToSearch = new HashSet<Type>();
|
||||
if (baseClassAttempt.Success)
|
||||
{
|
||||
subTypesToSearch.Add(baseClassAttempt.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
subTypesToSearch.AddRange(subTypeList);
|
||||
foreach (var t in subTypeList)
|
||||
{
|
||||
subTypesToSearch.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var typeToSearch in subTypesToSearch)
|
||||
{
|
||||
//recursively find the types inheriting from this sub type in the other non-scanned assemblies.
|
||||
var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses, additionalFilter);
|
||||
foundAssignableTypes.AddRange(foundTypes);
|
||||
|
||||
foreach (var f in foundTypes)
|
||||
{
|
||||
foundAssignableTypes.Add(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return foundAssignableTypes;
|
||||
}
|
||||
}
|
||||
return foundAssignableTypes;
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetTypesWithFormattedException(Assembly a)
|
||||
{
|
||||
//if the assembly is dynamic, do not try to scan it
|
||||
if (a.IsDynamic)
|
||||
return Enumerable.Empty<Type>();
|
||||
private static IEnumerable<Type> GetTypesWithFormattedException(Assembly a)
|
||||
{
|
||||
//if the assembly is dynamic, do not try to scan it
|
||||
if (a.IsDynamic)
|
||||
return Enumerable.Empty<Type>();
|
||||
|
||||
try
|
||||
{
|
||||
return a.GetExportedTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:");
|
||||
foreach (var loaderException in ex.LoaderExceptions.WhereNotNull())
|
||||
{
|
||||
sb.AppendLine("Exception: " + loaderException);
|
||||
}
|
||||
throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString());
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
return a.GetExportedTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:");
|
||||
foreach (var loaderException in ex.LoaderExceptions.WhereNotNull())
|
||||
{
|
||||
sb.AppendLine("Exception: " + loaderException);
|
||||
}
|
||||
throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace Umbraco.Tests.Models
|
||||
var dataTypeId = Guid.NewGuid();
|
||||
|
||||
var dataTypeData = MockRepository.GenerateMock<IData, IDataValueSetter>();
|
||||
|
||||
dataTypeData
|
||||
.Stub(data => data.ToXMl(Arg<XmlDocument>.Is.Anything))
|
||||
.Return(null) // you have to call Return() even though we're about to override it
|
||||
@@ -98,5 +99,18 @@ namespace Umbraco.Tests.Models
|
||||
((IDataValueSetter)dataTypeData).AssertWasCalled(setter => setter.SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString()));
|
||||
}
|
||||
|
||||
[TestCase(DataTypeDatabaseType.Nvarchar)]
|
||||
[TestCase(DataTypeDatabaseType.Date)]
|
||||
[TestCase(DataTypeDatabaseType.Integer)]
|
||||
[TestCase(DataTypeDatabaseType.Ntext)]
|
||||
public void DefaultData_SetValue_Ensures_Empty_String_When_Null_Value_Any_Data_Type(DataTypeDatabaseType type)
|
||||
{
|
||||
var defaultData = new DefaultData(MockRepository.GenerateMock<BaseDataType>());
|
||||
|
||||
((IDataValueSetter)defaultData).SetValue(null, type.ToString());
|
||||
|
||||
Assert.AreEqual(string.Empty, defaultData.Value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -861,6 +861,22 @@ namespace Umbraco.Tests.Services
|
||||
Assert.That(sut.GetValue<string>("imgCropper"), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Delete_Previous_Versions_Not_Latest()
|
||||
{
|
||||
// Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var content = contentService.GetById(1049);
|
||||
var version = content.Version;
|
||||
|
||||
// Act
|
||||
contentService.DeleteVersion(1049, version, true, 0);
|
||||
var sut = contentService.GetById(1049);
|
||||
|
||||
// Assert
|
||||
Assert.That(sut.Version, Is.EqualTo(version));
|
||||
}
|
||||
|
||||
private IEnumerable<IContent> CreateContentHierarchy()
|
||||
{
|
||||
var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage");
|
||||
|
||||
@@ -2602,9 +2602,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
|
||||
<WebProjectProperties>
|
||||
<UseIIS>True</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>6140</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>6150</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:6140</IISUrl>
|
||||
<IISUrl>http://localhost:6150</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<%@ Page MasterPageFile="../masterpages/umbracoPage.Master" Language="c#" CodeBehind="EditTemplate.aspx.cs"
|
||||
ValidateRequest="false" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.Settings.EditTemplate" %>
|
||||
<%@ Import Namespace="Umbraco.Core" %>
|
||||
<%@ Import Namespace="Umbraco.Core.IO" %>
|
||||
<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
|
||||
<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
|
||||
@@ -19,6 +20,7 @@
|
||||
jQuery(document).ready(function() {
|
||||
//create the editor
|
||||
editor = new Umbraco.Editors.EditTemplate({
|
||||
restServiceLocation: "<%= Url.GetSaveFileServicePath() %>",
|
||||
umbracoPath: '<%= IOHelper.ResolveUrl(SystemDirectories.Umbraco) %>',
|
||||
editorClientId: '<%= editorSource.ClientID %>',
|
||||
useMasterPages: <%=umbraco.UmbracoSettings.UseAspNetMasterPages.ToString().ToLower()%>,
|
||||
|
||||
@@ -62,9 +62,19 @@ $.datepicker._connectDatepicker = function(target, inst) {
|
||||
*/
|
||||
$.datepicker._showDatepickerOverride = $.datepicker._showDatepicker;
|
||||
$.datepicker._showDatepicker = function (input) {
|
||||
// keep the current value
|
||||
var originalval = input.value;
|
||||
|
||||
// Keep the first 10 chars for now yyyy-mm-dd - this removes the time part which was breaking the standardDatePicker parsing code
|
||||
input.value = originalval.length>10 ? originalval.substring(0, 10) : originalval;
|
||||
|
||||
|
||||
// Call the original method which will show the datepicker
|
||||
$.datepicker._showDatepickerOverride(input);
|
||||
|
||||
// Put it back
|
||||
input.value = originalval;
|
||||
|
||||
input = input.target || input;
|
||||
|
||||
// find from button/image trigger
|
||||
|
||||
@@ -101,27 +101,57 @@
|
||||
save: function(templateName, templateAlias, codeVal) {
|
||||
var self = this;
|
||||
|
||||
umbraco.presentation.webservices.codeEditorSave.SaveTemplate(
|
||||
templateName, templateAlias, codeVal, self._opts.templateId, this._opts.masterPageDropDown.val(),
|
||||
function(t) { self.submitSucces(t); },
|
||||
function(t) { self.submitFailure(t); });
|
||||
|
||||
$.post(self._opts.restServiceLocation + "SaveTemplate",
|
||||
JSON.stringify({
|
||||
templateName: templateName,
|
||||
templateAlias: templateAlias,
|
||||
templateContents: codeVal,
|
||||
templateId: self._opts.templateId,
|
||||
masterTemplateId: this._opts.masterPageDropDown.val()
|
||||
}),
|
||||
function (e) {
|
||||
if (e.success) {
|
||||
self.submitSuccess(e);
|
||||
} else {
|
||||
self.submitFailure(e.message, e.header);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
submitSucces: function(t) {
|
||||
if (t != 'true') {
|
||||
top.UmbSpeechBubble.ShowMessage('error', this._opts.text.templateErrorHeader, this._opts.text.templateErrorText);
|
||||
submitSuccess: function (args) {
|
||||
var msg = args.message;
|
||||
var header = args.header;
|
||||
var path = this._opts.treeSyncPath;
|
||||
var pathChanged = false;
|
||||
if (args.path) {
|
||||
if (path != args.path) {
|
||||
pathChanged = true;
|
||||
}
|
||||
path = args.path;
|
||||
}
|
||||
|
||||
top.UmbSpeechBubble.ShowMessage('save', header, msg);
|
||||
UmbClientMgr.mainTree().setActiveTreeType('templates');
|
||||
if (pathChanged) {
|
||||
UmbClientMgr.mainTree().moveNode(this._opts.templateId, path);
|
||||
}
|
||||
else {
|
||||
top.UmbSpeechBubble.ShowMessage('save', this._opts.text.templateSavedHeader, this._opts.text.templateSavedText);
|
||||
UmbClientMgr.mainTree().syncTree(path, true);
|
||||
}
|
||||
|
||||
UmbClientMgr.mainTree().setActiveTreeType('templates');
|
||||
UmbClientMgr.mainTree().syncTree(this._opts.treeSyncPath, true);
|
||||
},
|
||||
|
||||
submitFailure: function(t) {
|
||||
top.UmbSpeechBubble.ShowMessage('error', this._opts.text.templateErrorHeader, this._opts.text.templateErrorText);
|
||||
submitFailure: function (err, header) {
|
||||
top.UmbSpeechBubble.ShowMessage('error', header, err);
|
||||
}
|
||||
});
|
||||
|
||||
//Set defaults for jQuery ajax calls.
|
||||
$.ajaxSetup({
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -80,7 +80,7 @@
|
||||
}),
|
||||
function(e) {
|
||||
if (e.success) {
|
||||
self.submitSuccess(e.message, e.header);
|
||||
self.submitSuccess(e);
|
||||
} else {
|
||||
self.submitFailure(e.message, e.header);
|
||||
}
|
||||
@@ -97,7 +97,7 @@
|
||||
}),
|
||||
function(e) {
|
||||
if (e.success) {
|
||||
self.submitSuccess(e.message, e.header);
|
||||
self.submitSuccess(e);
|
||||
} else {
|
||||
self.submitFailure(e.message, e.header);
|
||||
}
|
||||
@@ -105,8 +105,20 @@
|
||||
}
|
||||
},
|
||||
|
||||
submitSuccess: function (err, header) {
|
||||
top.UmbSpeechBubble.ShowMessage('save', header, err);
|
||||
submitSuccess: function (args) {
|
||||
|
||||
var msg = args.message;
|
||||
var header = args.header;
|
||||
var path = this._opts.treeSyncPath;
|
||||
var pathChanged = false;
|
||||
if (args.path) {
|
||||
if (path != args.path) {
|
||||
pathChanged = true;
|
||||
}
|
||||
path = args.path;
|
||||
}
|
||||
|
||||
top.UmbSpeechBubble.ShowMessage('save', header, msg);
|
||||
|
||||
UmbClientMgr.mainTree().setActiveTreeType(this._opts.currentTreeType);
|
||||
|
||||
@@ -114,11 +126,19 @@
|
||||
|
||||
if (this._opts.editorType == "Template") {
|
||||
//templates are different because they are ID based, whereas view files are file based without a static id
|
||||
UmbClientMgr.mainTree().syncTree(this._opts.treeSyncPath, true);
|
||||
|
||||
if (pathChanged) {
|
||||
UmbClientMgr.mainTree().moveNode(this._opts.templateId, path);
|
||||
}
|
||||
else {
|
||||
UmbClientMgr.mainTree().syncTree(path, true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
//we need to pass in the newId parameter so it knows which node to resync after retreival from the server
|
||||
UmbClientMgr.mainTree().syncTree(this._opts.treeSyncPath, true, null, newFilePath.split("/")[1]);
|
||||
UmbClientMgr.mainTree().syncTree(path, true, null, newFilePath.split("/")[1]);
|
||||
}
|
||||
|
||||
//then we need to update our current tree sync path to represent the new one
|
||||
|
||||
@@ -88,14 +88,25 @@ namespace Umbraco.Web.Routing
|
||||
|
||||
#region Utilities
|
||||
|
||||
private bool FindByUrlAliasEnabled
|
||||
private static bool FindByUrlAliasEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
var hasFinder = ContentFinderResolver.Current.ContainsType<ContentFinderByUrlAlias>();
|
||||
var hasHandler = ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandlers>()
|
||||
&& NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias));
|
||||
return hasFinder || hasHandler;
|
||||
// finder
|
||||
if (ContentFinderResolver.Current.ContainsType<ContentFinderByUrlAlias>())
|
||||
return true;
|
||||
|
||||
// handler wrapped into a finder
|
||||
if (ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandler<global::umbraco.SearchForAlias>>())
|
||||
return true;
|
||||
|
||||
// handler wrapped into special finder
|
||||
if (ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandlers>()
|
||||
&& NotFoundHandlerHelper.IsNotFoundHandlerEnabled<global::umbraco.SearchForAlias>())
|
||||
return true;
|
||||
|
||||
// anything else, we can't detect
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,115 +29,77 @@ namespace Umbraco.Web.Routing
|
||||
|
||||
#region Copied over and adapted from presentation.requestHandler
|
||||
|
||||
void HandlePageNotFound(PublishedContentRequest docRequest)
|
||||
private static void HandlePageNotFound(PublishedContentRequest docRequest)
|
||||
{
|
||||
var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers();
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Running for legacy url='{0}'.", () => url);
|
||||
|
||||
foreach (var handler in GetNotFoundHandlers())
|
||||
foreach (var handler in NotFoundHandlerHelper.GetNotFoundHandlers())
|
||||
{
|
||||
IContentFinder finder = null;
|
||||
var handlerName = handler.GetType().FullName;
|
||||
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}'.", () => handlerName);
|
||||
|
||||
// replace with our own implementation
|
||||
if (handler is global::umbraco.SearchForAlias)
|
||||
finder = new ContentFinderByUrlAlias();
|
||||
else if (handler is global::umbraco.SearchForProfile)
|
||||
finder = new ContentFinderByProfile();
|
||||
else if (handler is global::umbraco.SearchForTemplate)
|
||||
finder = new ContentFinderByNiceUrlAndTemplate();
|
||||
else if (handler is global::umbraco.handle404)
|
||||
finder = new ContentFinderByLegacy404();
|
||||
|
||||
var finder = NotFoundHandlerHelper.SubsituteFinder(handler);
|
||||
if (finder != null)
|
||||
{
|
||||
var finderName = finder.GetType().FullName;
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName);
|
||||
if (finder.TryFindContent(docRequest))
|
||||
{
|
||||
// do NOT set docRequest.PublishedContent again here as
|
||||
// it would clear any template that the finder might have set
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id);
|
||||
if (docRequest.Is404)
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' set status to 404.", () => finderName);
|
||||
|
||||
// if we found a document, break, don't look at more handler -- we're done
|
||||
break;
|
||||
}
|
||||
// can't find a document => continue with other handlers
|
||||
if (finder.TryFindContent(docRequest) == false)
|
||||
continue;
|
||||
|
||||
// if we did not find a document, continue, look at other handlers
|
||||
continue;
|
||||
}
|
||||
// found a document => break, don't run other handlers, we're done
|
||||
|
||||
// else it's a legacy handler, run
|
||||
// in theory an IContentFinder can return true yet set no document
|
||||
// but none of the substitued finders (see SubstituteFinder) do it.
|
||||
|
||||
if (handler.Execute(url) && handler.redirectID > 0)
|
||||
{
|
||||
var redirectId = handler.redirectID;
|
||||
docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId);
|
||||
// do NOT set docRequest.PublishedContent again here
|
||||
// as it would clear any template that the finder might have set
|
||||
|
||||
if (!docRequest.HasPublishedContent)
|
||||
{
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId);
|
||||
break;
|
||||
}
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id);
|
||||
if (docRequest.Is404)
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Finder '{0}' set status to 404.", () => finderName);
|
||||
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId);
|
||||
|
||||
if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404)
|
||||
{
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' set status code to 404.", () => handlerName);
|
||||
docRequest.Is404 = true;
|
||||
}
|
||||
|
||||
//// check for caching
|
||||
//if (handler.CacheUrl)
|
||||
//{
|
||||
// if (url.StartsWith("/"))
|
||||
// url = "/" + url;
|
||||
|
||||
// var cacheKey = (currentDomain == null ? "" : currentDomain.Name) + url;
|
||||
// var culture = currentDomain == null ? null : currentDomain.Language.CultureAlias;
|
||||
// SetCache(cacheKey, new CacheEntry(handler.redirectID.ToString(), culture));
|
||||
|
||||
// HttpContext.Current.Trace.Write("NotFoundHandler",
|
||||
// string.Format("Added to cache '{0}', {1}.", url, handler.redirectID));
|
||||
//}
|
||||
|
||||
// if we found a document, break, don't look at more handler -- we're done
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id);
|
||||
break;
|
||||
}
|
||||
|
||||
// if we did not find a document, continue, look at other handlers
|
||||
// else it's a legacy handler: run
|
||||
|
||||
// can't find a document => continue with other handlers
|
||||
if (handler.Execute(url) == false || handler.redirectID <= 0)
|
||||
continue;
|
||||
|
||||
// found a document ID => ensure it's a valid document
|
||||
var redirectId = handler.redirectID;
|
||||
docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId);
|
||||
|
||||
if (docRequest.HasPublishedContent == false)
|
||||
{
|
||||
// the handler said it could handle the url, and returned a content ID
|
||||
// yet that content ID is invalid... should we run the other handlers?
|
||||
// I don't think so, not here, let the "last chance" finder take care.
|
||||
// so, break.
|
||||
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId);
|
||||
break;
|
||||
}
|
||||
|
||||
// found a valid document => break, don't run other handlers, we're done
|
||||
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId);
|
||||
|
||||
if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404)
|
||||
{
|
||||
LogHelper.Debug<ContentFinderByNotFoundHandlers>("Handler '{0}' set status code to 404.", () => handlerName);
|
||||
docRequest.Is404 = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<INotFoundHandler> GetNotFoundHandlers()
|
||||
{
|
||||
// instanciate new handlers
|
||||
// using definition cache
|
||||
|
||||
var handlers = new List<INotFoundHandler>();
|
||||
|
||||
foreach (var type in NotFoundHandlerHelper.CustomHandlerTypes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handler = Activator.CreateInstance(type) as INotFoundHandler;
|
||||
if (handler != null)
|
||||
handlers.Add(handler);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<ContentFinderByNotFoundHandlers>(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e);
|
||||
}
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using umbraco.interfaces;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an implementation of <see cref="IContentFinder"/> that runs legacy <c>INotFoundHandler</c> in "last chance" situation.
|
||||
/// </summary>
|
||||
public class ContentLastChanceFinderByNotFoundHandlers : IContentFinder
|
||||
{
|
||||
// notes
|
||||
//
|
||||
// at the moment we load the legacy INotFoundHandler
|
||||
// excluding those that have been replaced by proper finders,
|
||||
// and run them.
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find and assign an Umbraco document to a <c>PublishedContentRequest</c>.
|
||||
/// </summary>
|
||||
/// <param name="docRequest">The <c>PublishedContentRequest</c>.</param>
|
||||
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
|
||||
public bool TryFindContent(PublishedContentRequest docRequest)
|
||||
{
|
||||
HandlePageNotFound(docRequest);
|
||||
return docRequest.HasPublishedContent;
|
||||
}
|
||||
|
||||
#region Copied over and adapted from presentation.requestHandler
|
||||
|
||||
private static void HandlePageNotFound(PublishedContentRequest docRequest)
|
||||
{
|
||||
var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers();
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Running for legacy url='{0}'.", () => url);
|
||||
|
||||
var handler = NotFoundHandlerHelper.GetNotFoundLastChanceHandler();
|
||||
var handlerName = handler.GetType().FullName;
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Handler '{0}'.", () => handlerName);
|
||||
|
||||
var finder = NotFoundHandlerHelper.SubsituteFinder(handler);
|
||||
if (finder != null)
|
||||
{
|
||||
var finderName = finder.GetType().FullName;
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName);
|
||||
|
||||
// can't find a document => exit
|
||||
if (finder.TryFindContent(docRequest) == false)
|
||||
return;
|
||||
|
||||
// found a document => we're done
|
||||
|
||||
// in theory an IContentFinder can return true yet set no document
|
||||
// but none of the substitued finders (see SubstituteFinder) do it.
|
||||
|
||||
// do NOT set docRequest.PublishedContent again here
|
||||
// as it would clear any template that the finder might have set
|
||||
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id);
|
||||
if (docRequest.Is404)
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Finder '{0}' set status to 404.", () => finderName);
|
||||
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// else it's a legacy handler, run
|
||||
|
||||
// can't find a document => exit
|
||||
if (handler.Execute(url) == false || handler.redirectID <= 0)
|
||||
return;
|
||||
|
||||
// found a document ID => ensure it's a valid document
|
||||
var redirectId = handler.redirectID;
|
||||
docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId);
|
||||
|
||||
if (docRequest.HasPublishedContent == false)
|
||||
{
|
||||
// the handler said it could handle the url, and returned a content ID
|
||||
// yet that content ID is invalid... exit.
|
||||
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId);
|
||||
return;
|
||||
}
|
||||
|
||||
// found a valid document => return
|
||||
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId);
|
||||
|
||||
if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404)
|
||||
{
|
||||
LogHelper.Debug<ContentLastChanceFinderByNotFoundHandlers>("Handler '{0}' set status code to 404.", () => handlerName);
|
||||
docRequest.Is404 = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using umbraco.interfaces;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
@@ -36,14 +39,14 @@ namespace Umbraco.Web.Routing
|
||||
return url;
|
||||
|
||||
// code from requestModule.UmbracoRewrite
|
||||
string tmp = httpContext.Request.Path.ToLower();
|
||||
var tmp = httpContext.Request.Path.ToLower();
|
||||
|
||||
// note: requestModule.UmbracoRewrite also did some stripping of &umbPage
|
||||
// from the querystring... that was in v3.x to fix some issues with pre-forms
|
||||
// auth. Paul Sterling confirmed in jan. 2013 that we can get rid of it.
|
||||
|
||||
// code from requestHandler.cleanUrl
|
||||
string root = Core.IO.SystemDirectories.Root.ToLower();
|
||||
var root = Core.IO.SystemDirectories.Root.ToLower();
|
||||
if (!string.IsNullOrEmpty(root) && tmp.StartsWith(root))
|
||||
tmp = tmp.Substring(root.Length);
|
||||
tmp = tmp.TrimEnd('/');
|
||||
@@ -55,7 +58,7 @@ namespace Umbraco.Web.Routing
|
||||
// code from UmbracoDefault.Page_PreInit
|
||||
if (tmp != "" && httpContext.Request["umbPageID"] == null)
|
||||
{
|
||||
string tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty);
|
||||
var tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty);
|
||||
int result;
|
||||
if (int.TryParse(tryIntParse, out result))
|
||||
tmp = tmp.Replace(".aspx", string.Empty);
|
||||
@@ -77,7 +80,8 @@ namespace Umbraco.Web.Routing
|
||||
return tmp;
|
||||
}
|
||||
|
||||
static IEnumerable<Type> _customHandlerTypes;
|
||||
private static IEnumerable<Type> _customHandlerTypes;
|
||||
private static Type _customLastChanceHandlerType;
|
||||
|
||||
static void InitializeNotFoundHandlers()
|
||||
{
|
||||
@@ -87,46 +91,128 @@ namespace Umbraco.Web.Routing
|
||||
LogHelper.Debug<NotFoundHandlerHelper>("Registering custom handlers.");
|
||||
|
||||
var customHandlerTypes = new List<Type>();
|
||||
Type customHandlerType = null;
|
||||
|
||||
var customHandlers = new XmlDocument();
|
||||
customHandlers.Load(Core.IO.IOHelper.MapPath(Core.IO.SystemFiles.NotFoundhandlersConfig));
|
||||
|
||||
foreach (XmlNode n in customHandlers.DocumentElement.SelectNodes("notFound"))
|
||||
{
|
||||
if (customHandlerType != null)
|
||||
{
|
||||
LogHelper.Debug<NotFoundHandlerHelper>("Registering '{0}'.", () => customHandlerType.FullName);
|
||||
customHandlerTypes.Add(customHandlerType);
|
||||
}
|
||||
|
||||
var assemblyName = n.Attributes.GetNamedItem("assembly").Value;
|
||||
var typeName = n.Attributes.GetNamedItem("type").Value;
|
||||
|
||||
string ns = assemblyName;
|
||||
var ns = assemblyName;
|
||||
var nsAttr = n.Attributes.GetNamedItem("namespace");
|
||||
if (nsAttr != null && !string.IsNullOrWhiteSpace(nsAttr.Value))
|
||||
if (nsAttr != null && string.IsNullOrWhiteSpace(nsAttr.Value) == false)
|
||||
ns = nsAttr.Value;
|
||||
|
||||
LogHelper.Debug<NotFoundHandlerHelper>("Registering '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName);
|
||||
LogHelper.Debug<NotFoundHandlerHelper>("Configured: '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName);
|
||||
|
||||
Type type = null;
|
||||
customHandlerType = null;
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.Load(new AssemblyName(assemblyName));
|
||||
type = assembly.GetType(ns + "." + typeName);
|
||||
customHandlerType = assembly.GetType(ns + "." + typeName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<NotFoundHandlerHelper>("Error registering handler, ignoring.", e);
|
||||
LogHelper.Error<NotFoundHandlerHelper>("Error: could not load handler, ignoring.", e);
|
||||
}
|
||||
|
||||
if (type != null)
|
||||
customHandlerTypes.Add(type);
|
||||
}
|
||||
|
||||
_customHandlerTypes = customHandlerTypes;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> CustomHandlerTypes
|
||||
{
|
||||
get
|
||||
// what shall we do with the last one, assuming it's not null?
|
||||
// if the last chance finder wants a handler, then use the last one as the last chance handler
|
||||
// else assume that the last one is a normal handler since noone else wants it, and add it to the list
|
||||
if (customHandlerType != null)
|
||||
{
|
||||
return _customHandlerTypes;
|
||||
var lastChanceFinder = ContentLastChanceFinderResolver.Current.Finder; // can be null
|
||||
var finderWantsHandler = lastChanceFinder != null &&
|
||||
lastChanceFinder.GetType() == typeof(ContentLastChanceFinderByNotFoundHandlers);
|
||||
|
||||
if (finderWantsHandler)
|
||||
{
|
||||
LogHelper.Debug<NotFoundHandlerHelper>("Registering '{0}' as \"last chance\" handler.", () => customHandlerType.FullName);
|
||||
_customLastChanceHandlerType = customHandlerType;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Debug<NotFoundHandlerHelper>("Registering '{0}'.", () => customHandlerType.FullName);
|
||||
customHandlerTypes.Add(customHandlerType);
|
||||
_customLastChanceHandlerType = null;
|
||||
}
|
||||
}
|
||||
|
||||
_customHandlerTypes = customHandlerTypes.ToArray();
|
||||
}
|
||||
|
||||
public static IEnumerable<INotFoundHandler> GetNotFoundHandlers()
|
||||
{
|
||||
// instanciate new handlers
|
||||
// using definition cache
|
||||
|
||||
var handlers = new List<INotFoundHandler>();
|
||||
|
||||
foreach (var type in _customHandlerTypes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handler = Activator.CreateInstance(type) as INotFoundHandler;
|
||||
if (handler != null)
|
||||
handlers.Add(handler);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<ContentFinderByNotFoundHandlers>(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e);
|
||||
}
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static bool IsNotFoundHandlerEnabled<T>()
|
||||
{
|
||||
return _customHandlerTypes.Contains(typeof (T));
|
||||
}
|
||||
|
||||
public static INotFoundHandler GetNotFoundLastChanceHandler()
|
||||
{
|
||||
if (_customLastChanceHandlerType == null) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var handler = Activator.CreateInstance(_customLastChanceHandlerType) as INotFoundHandler;
|
||||
if (handler != null)
|
||||
return handler;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<ContentFinderByNotFoundHandlers>(string.Format("Error instanciating handler {0}, ignoring.", _customLastChanceHandlerType.FullName), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IContentFinder SubsituteFinder(INotFoundHandler handler)
|
||||
{
|
||||
IContentFinder finder = null;
|
||||
|
||||
if (handler is global::umbraco.SearchForAlias)
|
||||
finder = new ContentFinderByUrlAlias();
|
||||
else if (handler is global::umbraco.SearchForProfile)
|
||||
finder = new ContentFinderByProfile();
|
||||
else if (handler is global::umbraco.SearchForTemplate)
|
||||
finder = new ContentFinderByNiceUrlAndTemplate();
|
||||
else if (handler is global::umbraco.handle404)
|
||||
finder = new ContentFinderByLegacy404();
|
||||
|
||||
return finder;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,12 +140,35 @@ namespace Umbraco.Web.Routing
|
||||
|
||||
// unless a template has been set already by the finder,
|
||||
// template should be null at that point.
|
||||
var initial = IsInitialPublishedContent;
|
||||
|
||||
// IsInternalRedirect if IsInitial, or already IsInternalRedirect
|
||||
var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent;
|
||||
|
||||
// redirecting to self
|
||||
if (content.Id == PublishedContent.Id) // neither can be null
|
||||
{
|
||||
// no need to set PublishedContent, we're done
|
||||
IsInternalRedirectPublishedContent = isInternalRedirect;
|
||||
return;
|
||||
}
|
||||
|
||||
// else
|
||||
|
||||
// save
|
||||
var template = _template;
|
||||
var renderingEngine = RenderingEngine;
|
||||
|
||||
// set published content - this resets the template, and sets IsInternalRedirect to false
|
||||
PublishedContent = content;
|
||||
IsInternalRedirectPublishedContent = (initial && !IsInitialPublishedContent);
|
||||
if (IsInternalRedirectPublishedContent && UmbracoSettings.For<WebRouting>().InternalRedirectPreservesTemplate)
|
||||
IsInternalRedirectPublishedContent = isInternalRedirect;
|
||||
|
||||
// must restore the template if it's an internal redirect & the config option is set
|
||||
if (isInternalRedirect && UmbracoSettings.For<WebRouting>().InternalRedirectPreservesTemplate)
|
||||
{
|
||||
// restore
|
||||
_template = template;
|
||||
RenderingEngine = renderingEngine;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -179,9 +202,11 @@ namespace Umbraco.Web.Routing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the current published has been obtained from the
|
||||
/// initial published content following internal redirections exclusively.
|
||||
/// Gets or sets a value indicating whether the current published content has been obtained
|
||||
/// from the initial published content following internal redirections exclusively.
|
||||
/// </summary>
|
||||
/// <remarks>Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to
|
||||
/// apply the internal redirect or not, when content is not the initial content.</remarks>
|
||||
public bool IsInternalRedirectPublishedContent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -70,15 +70,15 @@ namespace Umbraco.Web.Security
|
||||
var allowGroupsList = allowGroups as IList<string> ?? allowGroups.ToList();
|
||||
if (allowAction && allowGroupsList.Any(allowGroup => allowGroup != string.Empty))
|
||||
{
|
||||
// Allow only if member's type is in list
|
||||
// Allow only if member is assigned to a group in the list
|
||||
var groups = Roles.GetRolesForUser(member.LoginName);
|
||||
allowAction = groups.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any();
|
||||
allowAction = allowGroupsList.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any();
|
||||
}
|
||||
|
||||
// If specific members defined, check member is of one of those
|
||||
if (allowAction && allowMembers.Any())
|
||||
{
|
||||
// Allow only if member's type is in list
|
||||
// Allow only if member's Id is in the list
|
||||
allowAction = allowMembers.Contains(member.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
<Compile Include="Configuration\WebRouting.cs" />
|
||||
<Compile Include="Editors\MediaController.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentSortOrder.cs" />
|
||||
<Compile Include="Routing\ContentLastChanceFinderByNotFoundHandlers.cs" />
|
||||
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
|
||||
<Compile Include="Security\Providers\UsersMembershipProvider.cs" />
|
||||
<Compile Include="Standalone\ServiceContextManager.cs" />
|
||||
|
||||
@@ -370,8 +370,14 @@ namespace Umbraco.Web
|
||||
_umbracoContext = null;
|
||||
//ensure not to dispose this!
|
||||
Application = null;
|
||||
ContentCache = null;
|
||||
MediaCache = null;
|
||||
|
||||
//Before we set these to null but in fact these are application lifespan singletons so
|
||||
//there's no reason we need to set them to null and this also caused a problem with packages
|
||||
//trying to access the cache properties on RequestEnd.
|
||||
//http://issues.umbraco.org/issue/U4-2734
|
||||
//http://our.umbraco.org/projects/developer-tools/301-url-tracker/version-2/44327-Issues-with-URL-Tracker-in-614
|
||||
//ContentCache = null;
|
||||
//MediaCache = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,23 +302,31 @@ namespace Umbraco.Web
|
||||
typeof(DefaultUrlProvider)
|
||||
);
|
||||
|
||||
// the legacy 404 will run from within ContentFinderByNotFoundHandlers below
|
||||
// so for the time being there is no last chance finder
|
||||
ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver();
|
||||
ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver(
|
||||
// handled by ContentLastChanceFinderByNotFoundHandlers for the time being
|
||||
// soon as we get rid of INotFoundHandler support, we must enable this
|
||||
//new ContentFinderByLegacy404()
|
||||
|
||||
// implement INotFoundHandler support... remove once we get rid of it
|
||||
new ContentLastChanceFinderByNotFoundHandlers());
|
||||
|
||||
ContentFinderResolver.Current = new ContentFinderResolver(
|
||||
// add all known resolvers in the correct order, devs can then modify this list
|
||||
// on application startup either by binding to events or in their own global.asax
|
||||
typeof (ContentFinderByPageIdQuery),
|
||||
typeof (ContentFinderByNiceUrl),
|
||||
typeof (ContentFinderByIdPath),
|
||||
// these will be handled by ContentFinderByNotFoundHandlers
|
||||
// so they can be enabled/disabled even though resolvers are not public yet
|
||||
//typeof (ContentFinderByNiceUrlAndTemplate),
|
||||
//typeof (ContentFinderByProfile),
|
||||
//typeof (ContentFinderByUrlAlias),
|
||||
typeof (ContentFinderByNotFoundHandlers)
|
||||
);
|
||||
// all built-in finders in the correct order, devs can then modify this list
|
||||
// on application startup via an application event handler.
|
||||
typeof (ContentFinderByPageIdQuery),
|
||||
typeof (ContentFinderByNiceUrl),
|
||||
typeof (ContentFinderByIdPath),
|
||||
|
||||
// these will be handled by ContentFinderByNotFoundHandlers so they can be enabled/disabled
|
||||
// via the config file... soon as we get rid of INotFoundHandler support, we must enable
|
||||
// them here.
|
||||
//typeof (ContentFinderByNiceUrlAndTemplate),
|
||||
//typeof (ContentFinderByProfile),
|
||||
//typeof (ContentFinderByUrlAlias),
|
||||
|
||||
// implement INotFoundHandler support... remove once we get rid of it
|
||||
typeof (ContentFinderByNotFoundHandlers)
|
||||
);
|
||||
|
||||
SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ using Umbraco.Web.Macros;
|
||||
using Umbraco.Web.Mvc;
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic.macro;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
|
||||
using Template = umbraco.cms.businesslogic.template.Template;
|
||||
|
||||
namespace Umbraco.Web.WebServices
|
||||
@@ -90,15 +92,22 @@ namespace Umbraco.Web.WebServices
|
||||
public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId)
|
||||
{
|
||||
Template t;
|
||||
bool pathChanged = false;
|
||||
try
|
||||
{
|
||||
t = new Template(templateId)
|
||||
{
|
||||
Text = templateName,
|
||||
Alias = templateAlias,
|
||||
MasterTemplate = masterTemplateId,
|
||||
Alias = templateAlias,
|
||||
Design = templateContents
|
||||
};
|
||||
|
||||
//check if the master page has changed
|
||||
if (t.MasterTemplate != masterTemplateId)
|
||||
{
|
||||
pathChanged = true;
|
||||
t.MasterTemplate = masterTemplateId;
|
||||
}
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
@@ -110,7 +119,17 @@ namespace Umbraco.Web.WebServices
|
||||
{
|
||||
t.Save();
|
||||
|
||||
return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader"));
|
||||
//ensure the correct path is synced as the parent might have been changed
|
||||
// http://issues.umbraco.org/issue/U4-2300
|
||||
if (pathChanged)
|
||||
{
|
||||
//need to re-look it up
|
||||
t = new Template(templateId);
|
||||
}
|
||||
var syncPath = "-1,init," + t.Path.Replace("-1,", "");
|
||||
|
||||
return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader"),
|
||||
new {path = syncPath});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -118,20 +137,21 @@ namespace Umbraco.Web.WebServices
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a successful message
|
||||
/// </summary>
|
||||
/// <param name="message">The message to display in the speach bubble</param>
|
||||
/// <param name="header">The header to display in the speach bubble</param>
|
||||
/// <returns></returns>
|
||||
private JsonResult Success(string message, string header)
|
||||
{
|
||||
return Json(new
|
||||
{
|
||||
success = true,
|
||||
message = message,
|
||||
header = header
|
||||
});
|
||||
/// <summary>
|
||||
/// Returns a successful message
|
||||
/// </summary>
|
||||
/// <param name="message">The message to display in the speach bubble</param>
|
||||
/// <param name="header">The header to display in the speach bubble</param>
|
||||
/// <param name="additionalVals"></param>
|
||||
/// <returns></returns>
|
||||
private JsonResult Success(string message, string header, object additionalVals = null)
|
||||
{
|
||||
var d = additionalVals == null ? new Dictionary<string, object>() : additionalVals.ToDictionary<object>();
|
||||
d["success"] = true;
|
||||
d["message"] = message;
|
||||
d["header"] = header;
|
||||
|
||||
return Json(d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -94,8 +94,7 @@ namespace umbraco
|
||||
{
|
||||
//write out the template header
|
||||
sw.Write("@inherits ");
|
||||
sw.Write(typeof(UmbracoViewPage<>).FullName.TrimEnd("`1"));
|
||||
sw.Write("<dynamic>");
|
||||
sw.Write(typeof(UmbracoTemplatePage).FullName.TrimEnd("`1"));
|
||||
}
|
||||
|
||||
public bool Delete()
|
||||
|
||||
@@ -272,11 +272,15 @@ namespace umbraco.dialogs
|
||||
{
|
||||
if (CurrentApp == Constants.Applications.Content)
|
||||
{
|
||||
Services.ContentService.Move((IContent)currContent, Request.GetItemAs<int>("copyTo"), getUser().Id);
|
||||
//Backwards comp. change, so old events are fired #U4-2731
|
||||
var doc = new Document(currContent as IContent);
|
||||
doc.Move(Request.GetItemAs<int>("copyTo"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Services.MediaService.Move((IMedia)currContent, Request.GetItemAs<int>("copyTo"), getUser().Id);
|
||||
//Backwards comp. change, so old events are fired #U4-2731
|
||||
var media = new umbraco.cms.businesslogic.media.Media(currContent as IMedia);
|
||||
media.Move(Request.GetItemAs<int>("copyTo"));
|
||||
library.ClearLibraryCacheForMedia(currContent.Id);
|
||||
}
|
||||
|
||||
@@ -290,7 +294,9 @@ namespace umbraco.dialogs
|
||||
{
|
||||
//NOTE: We ONLY support Copy on content not media for some reason.
|
||||
|
||||
var newContent = Services.ContentService.Copy((IContent)currContent, Request.GetItemAs<int>("copyTo"), RelateDocuments.Checked, getUser().Id);
|
||||
//Backwards comp. change, so old events are fired #U4-2731
|
||||
var newContent = new Document(currContent as IContent);
|
||||
newContent.Copy(Request.GetItemAs<int>("copyTo"), getUser(), RelateDocuments.Checked);
|
||||
|
||||
feedback.Text = ui.Text("moveOrCopy", "copyDone", nodes, getUser()) + "</p><p><a href='#' onclick='" + ClientTools.Scripts.CloseModalWindow() + "'>" + ui.Text("closeThisWindow") + "</a>";
|
||||
feedback.type = uicontrols.Feedback.feedbacktype.success;
|
||||
|
||||
@@ -65,6 +65,17 @@ namespace umbraco.cms.businesslogic.datatype
|
||||
/// <param name="strDbType"></param>
|
||||
void IDataValueSetter.SetValue(object val, string strDbType)
|
||||
{
|
||||
//We need to ensure that val is not a null value, if it is then we'll convert this to an empty string.
|
||||
//The reason for this is because by default the DefaultData.Value property returns an empty string when
|
||||
// there is no value, this is based on the PropertyDataDto.GetValue return value which defaults to an
|
||||
// empty string (which is called from this class's method LoadValueFromDatabase).
|
||||
//Some legacy implementations of DefaultData are expecting an empty string when there is
|
||||
// no value so we need to keep this consistent.
|
||||
if (val == null)
|
||||
{
|
||||
val = string.Empty;
|
||||
}
|
||||
|
||||
_value = val;
|
||||
//now that we've set our value, we can update our BaseDataType object with the correct values from the db
|
||||
//instead of making it query for itself. This is a peformance optimization enhancement.
|
||||
|
||||
@@ -8,14 +8,14 @@ namespace umbraco.editorControls.imagecropper
|
||||
|
||||
public override XmlNode ToXMl(XmlDocument data)
|
||||
{
|
||||
if (Value.ToString() != "") {
|
||||
XmlDocument xd = new XmlDocument();
|
||||
if (Value != null && Value.ToString() != "")
|
||||
{
|
||||
var xd = new XmlDocument();
|
||||
xd.LoadXml(Value.ToString());
|
||||
return data.ImportNode(xd.DocumentElement, true);
|
||||
} else {
|
||||
return base.ToXMl(data);
|
||||
}
|
||||
|
||||
return base.ToXMl(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user