U4-7048 - refresh IFile instances from disk
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
@@ -9,7 +8,7 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
public class PhysicalFileSystem : IFileSystem
|
||||
{
|
||||
internal string RootPath { get; private set; }
|
||||
private readonly string _rootPath;
|
||||
private readonly string _rootUrl;
|
||||
|
||||
public PhysicalFileSystem(string virtualRoot)
|
||||
@@ -18,8 +17,12 @@ namespace Umbraco.Core.IO
|
||||
if (virtualRoot.StartsWith("~/") == false)
|
||||
throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'");
|
||||
|
||||
RootPath = IOHelper.MapPath(virtualRoot);
|
||||
_rootPath = IOHelper.MapPath(virtualRoot);
|
||||
_rootPath = EnsureDirectorySeparatorChar(_rootPath);
|
||||
_rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
_rootUrl = IOHelper.ResolveUrl(virtualRoot);
|
||||
_rootUrl = _rootUrl.TrimEnd('/');
|
||||
}
|
||||
|
||||
public PhysicalFileSystem(string rootPath, string rootUrl)
|
||||
@@ -43,18 +46,18 @@ namespace Umbraco.Core.IO
|
||||
rootPath = Path.Combine(localRoot, rootPath);
|
||||
}
|
||||
|
||||
RootPath = rootPath;
|
||||
_rootUrl = rootUrl;
|
||||
_rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootUrl = rootUrl.TrimEnd('/');
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
path = EnsureTrailingSeparator(GetFullPath(path));
|
||||
var fullPath = GetFullPath(path);
|
||||
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
return Directory.EnumerateDirectories(path).Select(GetRelativePath);
|
||||
if (Directory.Exists(fullPath))
|
||||
return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
@@ -75,12 +78,13 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
if (DirectoryExists(path) == false)
|
||||
var fullPath = GetFullPath(path);
|
||||
if (Directory.Exists(fullPath) == false)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(GetFullPath(path), recursive);
|
||||
Directory.Delete(fullPath, recursive);
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
@@ -90,7 +94,8 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return Directory.Exists(GetFullPath(path));
|
||||
var fullPath = GetFullPath(path);
|
||||
return Directory.Exists(fullPath);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
@@ -100,17 +105,17 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
{
|
||||
var fsRelativePath = GetRelativePath(path);
|
||||
var fullPath = GetFullPath(path);
|
||||
var exists = File.Exists(fullPath);
|
||||
if (exists && overrideIfExists == false)
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
var exists = FileExists(fsRelativePath);
|
||||
if (exists && overrideIfExists == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
EnsureDirectory(Path.GetDirectoryName(fsRelativePath));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists
|
||||
|
||||
if (stream.CanSeek)
|
||||
stream.Seek(0, 0);
|
||||
|
||||
using (var destination = (Stream)File.Create(GetFullPath(fsRelativePath)))
|
||||
using (var destination = (Stream)File.Create(fullPath))
|
||||
stream.CopyTo(destination);
|
||||
}
|
||||
|
||||
@@ -121,9 +126,7 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
var fsRelativePath = GetRelativePath(path);
|
||||
|
||||
var fullPath = EnsureTrailingSeparator(GetFullPath(fsRelativePath));
|
||||
var fullPath = GetFullPath(path);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -150,12 +153,13 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
if (!FileExists(path))
|
||||
var fullPath = GetFullPath(path);
|
||||
if (File.Exists(fullPath) == false)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(GetFullPath(path));
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
@@ -165,53 +169,101 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return File.Exists(GetFullPath(path));
|
||||
var fullpath = GetFullPath(path);
|
||||
return File.Exists(fullpath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative path.
|
||||
/// </summary>
|
||||
/// <param name="fullPathOrUrl">The full path or url.</param>
|
||||
/// <returns>The path, relative to this filesystem's root.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The relative path is relative to this filesystem's root, not starting with any
|
||||
/// directory separator. All separators are converted to Path.DirectorySeparatorChar.</para>
|
||||
/// </remarks>
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
var relativePath = fullPathOrUrl
|
||||
.TrimStart(_rootUrl)
|
||||
.Replace('/', Path.DirectorySeparatorChar)
|
||||
.TrimStart(RootPath)
|
||||
.TrimStart(Path.DirectorySeparatorChar);
|
||||
// test url
|
||||
var path = EnsureUrlSeparatorChar(fullPathOrUrl);
|
||||
if (PathStartsWith(path, _rootUrl, '/'))
|
||||
return path.Substring(_rootUrl.Length)
|
||||
.Replace('/', Path.DirectorySeparatorChar)
|
||||
.TrimStart(Path.DirectorySeparatorChar);
|
||||
|
||||
return relativePath;
|
||||
// test path
|
||||
path = EnsureDirectorySeparatorChar(fullPathOrUrl);
|
||||
if (PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
|
||||
return path.Substring(_rootPath.Length)
|
||||
.TrimStart(Path.DirectorySeparatorChar);
|
||||
|
||||
return fullPathOrUrl;
|
||||
|
||||
// previous code kept for reference
|
||||
|
||||
//var relativePath = fullPathOrUrl
|
||||
// .TrimStart(_rootUrl)
|
||||
// .Replace('/', Path.DirectorySeparatorChar)
|
||||
// .TrimStart(RootPath)
|
||||
// .TrimStart(Path.DirectorySeparatorChar);
|
||||
//return relativePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path.
|
||||
/// </summary>
|
||||
/// <param name="path">The full or relative path.</param>
|
||||
/// <returns>The full path.</returns>
|
||||
/// <remarks>
|
||||
/// <para>On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this
|
||||
/// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar.</para>
|
||||
/// </remarks>
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
//if the path starts with a '/' then it's most likely not a FS relative path which is required so convert it
|
||||
if (path.StartsWith("/"))
|
||||
{
|
||||
// normalize
|
||||
var opath = path;
|
||||
path = EnsureDirectorySeparatorChar(path);
|
||||
|
||||
// not sure what we are doing here - so if input starts with a (back) slash,
|
||||
// we assume it's not a FS relative path and we try to convert it... but it
|
||||
// really makes little sense?
|
||||
if (path.StartsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
path = GetRelativePath(path);
|
||||
}
|
||||
|
||||
// if already a full path, return
|
||||
if (path.StartsWith(RootPath))
|
||||
if (PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
|
||||
return path;
|
||||
|
||||
// else combine and sanitize, ie GetFullPath will take care of any relative
|
||||
// segments in path, eg '../../foo.tmp' - it may throw a SecurityException
|
||||
// if the combined path reaches illegal parts of the filesystem
|
||||
var fpath = Path.Combine(RootPath, path);
|
||||
var fpath = Path.Combine(_rootPath, path);
|
||||
fpath = Path.GetFullPath(fpath);
|
||||
|
||||
// at that point, path is within legal parts of the filesystem, ie we have
|
||||
// permissions to reach that path, but it may nevertheless be outside of
|
||||
// our root path, due to relative segments, so better check
|
||||
if (fpath.StartsWith(RootPath))
|
||||
if (PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar))
|
||||
return fpath;
|
||||
|
||||
throw new FileSecurityException("File '" + path + "' is outside this filesystem's root.");
|
||||
throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root.");
|
||||
}
|
||||
|
||||
private static bool PathStartsWith(string path, string root, char separator)
|
||||
{
|
||||
// either it is identical to root,
|
||||
// or it is root + separator + anything
|
||||
|
||||
if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false;
|
||||
if (path.Length == root.Length) return true;
|
||||
if (path.Length < root.Length) return false;
|
||||
return path[root.Length] == separator;
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return _rootUrl.TrimEnd("/") + "/" + path
|
||||
.TrimStart(Path.DirectorySeparatorChar)
|
||||
.Replace(Path.DirectorySeparatorChar, '/')
|
||||
.TrimEnd("/");
|
||||
path = EnsureUrlSeparatorChar(path).Trim('/');
|
||||
return _rootUrl + "/" + path;
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
@@ -238,9 +290,19 @@ namespace Umbraco.Core.IO
|
||||
|
||||
protected string EnsureTrailingSeparator(string path)
|
||||
{
|
||||
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal))
|
||||
path = path + Path.DirectorySeparatorChar;
|
||||
return path.EnsureEndsWith(Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
protected string EnsureDirectorySeparatorChar(string path)
|
||||
{
|
||||
path = path.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = path.Replace('\\', Path.DirectorySeparatorChar);
|
||||
return path;
|
||||
}
|
||||
|
||||
protected string EnsureUrlSeparatorChar(string path)
|
||||
{
|
||||
path = path.Replace('\\', '/');
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -15,12 +17,20 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
private string _path;
|
||||
private string _originalPath;
|
||||
private string _content = string.Empty; //initialize to empty string, not null
|
||||
|
||||
protected File(string path)
|
||||
// initialize to string.Empty so that it is possible to save a new file,
|
||||
// should use the lazyContent ctor to set it to null when loading existing.
|
||||
// cannot simply use HasIdentity as some classes (eg Script) override it
|
||||
// in a weird way.
|
||||
private string _content;
|
||||
internal Func<File, string> GetFileContent { get; set; }
|
||||
|
||||
protected File(string path, Func<File, string> getFileContent = null)
|
||||
{
|
||||
_path = path;
|
||||
_originalPath = _path;
|
||||
GetFileContent = getFileContent;
|
||||
_content = getFileContent != null ? null : string.Empty;
|
||||
}
|
||||
|
||||
private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Content);
|
||||
@@ -96,15 +106,26 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets or sets the Content of a File
|
||||
/// </summary>
|
||||
/// <remarks>Marked as DoNotClone, because it should be lazy-reloaded from disk.</remarks>
|
||||
[DataMember]
|
||||
[DoNotClone]
|
||||
public virtual string Content
|
||||
{
|
||||
get { return _content; }
|
||||
get
|
||||
{
|
||||
if (_content != null)
|
||||
return _content;
|
||||
|
||||
// else, must lazy-load, and ensure it's not null
|
||||
if (GetFileContent != null)
|
||||
_content = GetFileContent(this);
|
||||
return _content ?? (_content = string.Empty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_content = value;
|
||||
_content = value ?? string.Empty; // cannot set to null
|
||||
return _content;
|
||||
}, _content, ContentSelector);
|
||||
}
|
||||
@@ -121,17 +142,32 @@ namespace Umbraco.Core.Models
|
||||
return true;
|
||||
}
|
||||
|
||||
// this exists so that class that manage name and alias differently, eg Template,
|
||||
// can implement their own cloning - (though really, not sure it's even needed)
|
||||
protected virtual void DeepCloneNameAndAlias(File clone)
|
||||
{
|
||||
// set fields that have a lazy value, by forcing evaluation of the lazy
|
||||
clone._name = Name;
|
||||
clone._alias = Alias;
|
||||
}
|
||||
|
||||
public override object DeepClone()
|
||||
{
|
||||
var clone = (File)base.DeepClone();
|
||||
//turn off change tracking
|
||||
var clone = (File) base.DeepClone();
|
||||
|
||||
// clear fields that were memberwise-cloned and that we don't want to clone
|
||||
clone._content = null;
|
||||
|
||||
// turn off change tracking
|
||||
clone.DisableChangeTracking();
|
||||
//need to manually assign since they are readonly properties
|
||||
clone._alias = Alias;
|
||||
clone._name = Name;
|
||||
//this shouldn't really be needed since we're not tracking
|
||||
|
||||
// ...
|
||||
DeepCloneNameAndAlias(clone);
|
||||
|
||||
// this shouldn't really be needed since we're not tracking
|
||||
clone.ResetDirtyProperties(false);
|
||||
//re-enable tracking
|
||||
|
||||
// re-enable tracking
|
||||
clone.EnableChangeTracking();
|
||||
|
||||
return clone;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -15,12 +12,14 @@ namespace Umbraco.Core.Models
|
||||
[DataContract(IsReference = true)]
|
||||
public class PartialView : File, IPartialView
|
||||
{
|
||||
|
||||
public PartialView(string path)
|
||||
: base(path)
|
||||
{
|
||||
base.Path = path;
|
||||
}
|
||||
: this(path, null)
|
||||
{ }
|
||||
|
||||
internal PartialView(string path, Func<File, string> getFileContent)
|
||||
: base(path, getFileContent)
|
||||
{ }
|
||||
|
||||
internal PartialViewType ViewType { get; set; }
|
||||
}
|
||||
}
|
||||
9
src/Umbraco.Core/Models/PartialViewType.cs
Normal file
9
src/Umbraco.Core/Models/PartialViewType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal enum PartialViewType : byte
|
||||
{
|
||||
Unknown = 0, // default
|
||||
PartialView = 1,
|
||||
PartialViewMacro = 2
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
@@ -15,16 +13,17 @@ namespace Umbraco.Core.Models
|
||||
public class Script : File
|
||||
{
|
||||
public Script(string path)
|
||||
: base(path)
|
||||
{
|
||||
|
||||
}
|
||||
: this(path, (Func<File, string>) null)
|
||||
{ }
|
||||
|
||||
internal Script(string path, Func<File, string> getFileContent)
|
||||
: base(path, getFileContent)
|
||||
{ }
|
||||
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in future versions")]
|
||||
public Script(string path, IContentSection contentConfig)
|
||||
: this(path)
|
||||
{
|
||||
}
|
||||
: base(path)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current entity has an identity, which in this case is a path/name.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Strings.Css;
|
||||
|
||||
@@ -16,12 +16,16 @@ namespace Umbraco.Core.Models
|
||||
[DataContract(IsReference = true)]
|
||||
public class Stylesheet : File
|
||||
{
|
||||
public Stylesheet(string path)
|
||||
: base(path.EnsureEndsWith(".css"))
|
||||
{
|
||||
public Stylesheet(string path)
|
||||
: this(path, null)
|
||||
{ }
|
||||
|
||||
internal Stylesheet(string path, Func<File, string> getFileContent)
|
||||
: base(path.EnsureEndsWith(".css"), getFileContent)
|
||||
{
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
|
||||
private Lazy<List<StylesheetProperty>> _properties;
|
||||
|
||||
private void InitializeProperties()
|
||||
@@ -81,7 +85,7 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void Property_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
void Property_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var prop = (StylesheetProperty) sender;
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
@@ -29,12 +32,16 @@ namespace Umbraco.Core.Models
|
||||
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<Template, string>(x => x.Name);
|
||||
|
||||
public Template(string name, string alias)
|
||||
: base(string.Empty)
|
||||
: this(name, alias, (Func<File, string>) null)
|
||||
{ }
|
||||
|
||||
internal Template(string name, string alias, Func<File, string> getFileContent)
|
||||
: base(string.Empty, getFileContent)
|
||||
{
|
||||
_name = name;
|
||||
_alias = alias.ToCleanString(CleanStringType.UnderscoreAlias);
|
||||
_masterTemplateId = new Lazy<int>(() => -1);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("This constructor should not be used, file path is determined by alias, setting the path here will have no affect")]
|
||||
public Template(string path, string name, string alias)
|
||||
@@ -123,7 +130,6 @@ namespace Umbraco.Core.Models
|
||||
Key = Guid.NewGuid();
|
||||
}
|
||||
|
||||
|
||||
public void SetMasterTemplate(ITemplate masterTemplate)
|
||||
{
|
||||
if (masterTemplate == null)
|
||||
@@ -139,27 +145,9 @@ namespace Umbraco.Core.Models
|
||||
|
||||
}
|
||||
|
||||
public override object DeepClone()
|
||||
protected override void DeepCloneNameAndAlias(File clone)
|
||||
{
|
||||
|
||||
//We cannot call in to the base classes to clone because the base File class treats Alias, Name.. differently so we need to manually do the clone
|
||||
|
||||
//Memberwise clone on Entity will work since it doesn't have any deep elements
|
||||
// for any sub class this will work for standard properties as well that aren't complex object's themselves.
|
||||
var clone = (Template)MemberwiseClone();
|
||||
//turn off change tracking
|
||||
clone.DisableChangeTracking();
|
||||
//Automatically deep clone ref properties that are IDeepCloneable
|
||||
DeepCloneHelper.DeepCloneRefProperties(this, clone);
|
||||
|
||||
//this shouldn't really be needed since we're not tracking
|
||||
clone.ResetDirtyProperties(false);
|
||||
//re-enable tracking
|
||||
clone.EnableChangeTracking();
|
||||
|
||||
return clone;
|
||||
// do nothing - prevents File from doing its stuff
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
#region Implementation of IEntityFactory<ITemplate,TemplateDto>
|
||||
|
||||
public Template BuildEntity(TemplateDto dto, IEnumerable<IUmbracoEntity> childDefinitions)
|
||||
public Template BuildEntity(TemplateDto dto, IEnumerable<IUmbracoEntity> childDefinitions, Func<File, string> getFileContent)
|
||||
{
|
||||
var template = new Template(dto.NodeDto.Text, dto.Alias)
|
||||
var template = new Template(dto.NodeDto.Text, dto.Alias, getFileContent)
|
||||
{
|
||||
CreateDate = dto.NodeDto.CreateDate,
|
||||
Id = dto.NodeId,
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public object GetCacheItem(string cacheKey, Func<object> getCacheItem)
|
||||
{
|
||||
return InnerProvider.GetCacheItem(cacheKey, () =>
|
||||
var cached = InnerProvider.GetCacheItem(cacheKey, () =>
|
||||
{
|
||||
var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
|
||||
var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
|
||||
@@ -87,18 +87,21 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
return CheckCloneableAndTracksChanges(value);
|
||||
});
|
||||
return CheckCloneableAndTracksChanges(cached);
|
||||
}
|
||||
|
||||
public object GetCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
return InnerProvider.GetCacheItem(cacheKey, () =>
|
||||
var cached = InnerProvider.GetCacheItem(cacheKey, () =>
|
||||
{
|
||||
var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
|
||||
var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
|
||||
if (value == null) return null; // do not store null values (backward compat)
|
||||
|
||||
return CheckCloneableAndTracksChanges(value);
|
||||
}, timeout, isSliding, priority, removedCallback, dependentFiles);
|
||||
}, timeout, isSliding, priority, removedCallback, dependentFiles);
|
||||
|
||||
return CheckCloneableAndTracksChanges(cached);
|
||||
}
|
||||
|
||||
public void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
|
||||
@@ -219,6 +219,15 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return list;
|
||||
}
|
||||
|
||||
protected string GetFileContent(string filename)
|
||||
{
|
||||
using (var stream = FileSystem.OpenFile(filename))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8, true))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose any disposable properties
|
||||
/// </summary>
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal class PartialViewMacroRepository : PartialViewRepository
|
||||
{
|
||||
|
||||
public PartialViewMacroRepository(IUnitOfWork work)
|
||||
: this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/"))
|
||||
{
|
||||
@@ -18,5 +17,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
}
|
||||
|
||||
protected override PartialViewType ViewType { get { return PartialViewType.PartialViewMacro; } }
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual PartialViewType ViewType { get { return PartialViewType.PartialView; } }
|
||||
|
||||
public override IPartialView Get(string id)
|
||||
{
|
||||
if (FileSystem.FileExists(id) == false)
|
||||
@@ -27,29 +29,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return null;
|
||||
}
|
||||
|
||||
string content;
|
||||
using (var stream = FileSystem.OpenFile(id))
|
||||
{
|
||||
var bytes = new byte[stream.Length];
|
||||
stream.Position = 0;
|
||||
stream.Read(bytes, 0, (int)stream.Length);
|
||||
content = Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
var path = FileSystem.GetRelativePath(id);
|
||||
var created = FileSystem.GetCreated(path).UtcDateTime;
|
||||
var updated = FileSystem.GetLastModified(path).UtcDateTime;
|
||||
|
||||
|
||||
var script = new PartialView(path)
|
||||
var script = new PartialView(path, file => GetFileContent(file.Path))
|
||||
{
|
||||
//id can be the hash
|
||||
Id = path.GetHashCode(),
|
||||
Content = content,
|
||||
Key = path.EncodeAsGuid(),
|
||||
CreateDate = created,
|
||||
UpdateDate = updated,
|
||||
VirtualPath = FileSystem.GetUrl(id)
|
||||
VirtualPath = FileSystem.GetUrl(id),
|
||||
ViewType = ViewType
|
||||
};
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
@@ -59,6 +51,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return script;
|
||||
}
|
||||
|
||||
public override void AddOrUpdate(IPartialView entity)
|
||||
{
|
||||
var partialView = entity as PartialView;
|
||||
if (partialView != null)
|
||||
partialView.ViewType = ViewType;
|
||||
|
||||
base.AddOrUpdate(entity);
|
||||
|
||||
// ensure that from now on, content is lazy-loaded
|
||||
if (partialView != null && partialView.GetFileContent == null)
|
||||
partialView.GetFileContent = file => GetFileContent(file.Path);
|
||||
}
|
||||
|
||||
public override IEnumerable<IPartialView> GetAll(params string[] ids)
|
||||
{
|
||||
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -28,33 +26,27 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public override Script Get(string id)
|
||||
{
|
||||
if(FileSystem.FileExists(id) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string content;
|
||||
using (var stream = FileSystem.OpenFile(id))
|
||||
{
|
||||
var bytes = new byte[stream.Length];
|
||||
stream.Position = 0;
|
||||
stream.Read(bytes, 0, (int)stream.Length);
|
||||
content = Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
// get the relative path within the filesystem
|
||||
// (though... id should be relative already)
|
||||
var path = FileSystem.GetRelativePath(id);
|
||||
|
||||
if (FileSystem.FileExists(path) == false)
|
||||
return null;
|
||||
|
||||
// content will be lazy-loaded when required
|
||||
var created = FileSystem.GetCreated(path).UtcDateTime;
|
||||
var updated = FileSystem.GetLastModified(path).UtcDateTime;
|
||||
//var content = GetFileContent(path);
|
||||
|
||||
var script = new Script(path)
|
||||
var script = new Script(path, file => GetFileContent(file.Path))
|
||||
{
|
||||
//id can be the hash
|
||||
Id = path.GetHashCode(),
|
||||
Content = content,
|
||||
Key = path.EncodeAsGuid(),
|
||||
//Content = content,
|
||||
CreateDate = created,
|
||||
UpdateDate = updated,
|
||||
VirtualPath = FileSystem.GetUrl(id)
|
||||
VirtualPath = FileSystem.GetUrl(path)
|
||||
};
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
@@ -64,6 +56,15 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return script;
|
||||
}
|
||||
|
||||
public override void AddOrUpdate(Script entity)
|
||||
{
|
||||
base.AddOrUpdate(entity);
|
||||
|
||||
// ensure that from now on, content is lazy-loaded
|
||||
if (entity.GetFileContent == null)
|
||||
entity.GetFileContent = file => GetFileContent(file.Path);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// </summary>
|
||||
internal class StylesheetRepository : FileRepository<string, Stylesheet>, IStylesheetRepository
|
||||
{
|
||||
|
||||
public StylesheetRepository(IUnitOfWork work, IFileSystem fileSystem)
|
||||
: base(work, fileSystem)
|
||||
{
|
||||
@@ -25,33 +24,28 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public override Stylesheet Get(string id)
|
||||
{
|
||||
id = id.EnsureEndsWith(".css");
|
||||
|
||||
if (FileSystem.FileExists(id) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string content;
|
||||
|
||||
using (var stream = FileSystem.OpenFile(id))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
content = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
// get the relative path within the filesystem
|
||||
// (though... id should be relative already)
|
||||
var path = FileSystem.GetRelativePath(id);
|
||||
|
||||
path = path.EnsureEndsWith(".css");
|
||||
|
||||
if (FileSystem.FileExists(path) == false)
|
||||
return null;
|
||||
|
||||
// content will be lazy-loaded when required
|
||||
var created = FileSystem.GetCreated(path).UtcDateTime;
|
||||
var updated = FileSystem.GetLastModified(path).UtcDateTime;
|
||||
//var content = GetFileContent(path);
|
||||
|
||||
var stylesheet = new Stylesheet(path)
|
||||
var stylesheet = new Stylesheet(path, file => GetFileContent(file.Path))
|
||||
{
|
||||
Content = content,
|
||||
//Content = content,
|
||||
Key = path.EncodeAsGuid(),
|
||||
CreateDate = created,
|
||||
UpdateDate = updated,
|
||||
Id = path.GetHashCode(),
|
||||
VirtualPath = FileSystem.GetUrl(id)
|
||||
VirtualPath = FileSystem.GetUrl(path)
|
||||
};
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
@@ -62,6 +56,15 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
}
|
||||
|
||||
public override void AddOrUpdate(Stylesheet entity)
|
||||
{
|
||||
base.AddOrUpdate(entity);
|
||||
|
||||
// ensure that from now on, content is lazy-loaded
|
||||
if (entity.GetFileContent == null)
|
||||
entity.GetFileContent = file => GetFileContent(file.Path);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -227,6 +227,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
template.ResetDirtyProperties();
|
||||
|
||||
// ensure that from now on, content is lazy-loaded
|
||||
if (template.GetFileContent == null)
|
||||
template.GetFileContent = file => GetFileContent((Template) file, false);
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(ITemplate entity)
|
||||
@@ -295,6 +299,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
// ensure that from now on, content is lazy-loaded
|
||||
if (template.GetFileContent == null)
|
||||
template.GetFileContent = file => GetFileContent((Template) file, false);
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(ITemplate entity)
|
||||
@@ -368,12 +376,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <returns></returns>
|
||||
private ITemplate MapFromDto(TemplateDto dto, IUmbracoEntity[] axisDefinitions)
|
||||
{
|
||||
string csViewName = string.Concat(dto.Alias, ".cshtml");
|
||||
string vbViewName = string.Concat(dto.Alias, ".vbhtml");
|
||||
string masterpageName = string.Concat(dto.Alias, ".master");
|
||||
|
||||
var factory = new TemplateFactory();
|
||||
var template = factory.BuildEntity(dto, axisDefinitions);
|
||||
var template = factory.BuildEntity(dto, axisDefinitions, file => GetFileContent((Template) file, false));
|
||||
|
||||
if (dto.NodeDto.ParentId > 0)
|
||||
{
|
||||
@@ -385,21 +389,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
if (_viewsFileSystem.FileExists(csViewName))
|
||||
{
|
||||
PopulateViewTemplate(template, csViewName);
|
||||
}
|
||||
else if (_viewsFileSystem.FileExists(vbViewName))
|
||||
{
|
||||
PopulateViewTemplate(template, vbViewName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_masterpagesFileSystem.FileExists(masterpageName))
|
||||
{
|
||||
PopulateMasterpageTemplate(template, masterpageName);
|
||||
}
|
||||
}
|
||||
// get the infos (update date and virtual path) that will change only if
|
||||
// path changes - but do not get content, will get loaded only when required
|
||||
GetFileContent(template, true);
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
@@ -407,34 +399,41 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
private void PopulateViewTemplate(ITemplate template, string fileName)
|
||||
{
|
||||
string content;
|
||||
|
||||
using (var stream = _viewsFileSystem.OpenFile(fileName))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8, true))
|
||||
{
|
||||
content = reader.ReadToEnd();
|
||||
}
|
||||
template.UpdateDate = _viewsFileSystem.GetLastModified(fileName).UtcDateTime;
|
||||
template.Content = content;
|
||||
template.VirtualPath = _viewsFileSystem.GetUrl(fileName);
|
||||
private string GetFileContent(ITemplate template, bool init)
|
||||
{
|
||||
var fsname = string.Concat(template.Alias, ".cshtml");
|
||||
if (_viewsFileSystem.FileExists(fsname))
|
||||
return GetFileContent(template, _viewsFileSystem, fsname, init);
|
||||
fsname = string.Concat(template.Alias, ".vbhtml");
|
||||
if (_viewsFileSystem.FileExists(fsname))
|
||||
return GetFileContent(template, _viewsFileSystem, fsname, init);
|
||||
fsname = string.Concat(template.Alias, ".master");
|
||||
if (_masterpagesFileSystem.FileExists(fsname))
|
||||
return GetFileContent(template, _masterpagesFileSystem, fsname, init);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private void PopulateMasterpageTemplate(ITemplate template, string fileName)
|
||||
private string GetFileContent(ITemplate template, IFileSystem fs, string filename, bool init)
|
||||
{
|
||||
string content;
|
||||
|
||||
using (var stream = _masterpagesFileSystem.OpenFile(fileName))
|
||||
// do not update .UpdateDate as that would make it dirty (side-effect)
|
||||
// unless initializing, because we have to do it once
|
||||
if (init)
|
||||
{
|
||||
template.UpdateDate = fs.GetLastModified(filename).UtcDateTime;
|
||||
}
|
||||
template.VirtualPath = fs.GetUrl(filename);
|
||||
|
||||
return init ? null : GetFileContent(fs, filename);
|
||||
}
|
||||
|
||||
private string GetFileContent(IFileSystem fs, string filename)
|
||||
{
|
||||
using (var stream = fs.OpenFile(filename))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8, true))
|
||||
{
|
||||
content = reader.ReadToEnd();
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
template.UpdateDate = _masterpagesFileSystem.GetLastModified(fileName).UtcDateTime;
|
||||
template.Content = content;
|
||||
template.VirtualPath = _masterpagesFileSystem.GetUrl(fileName);
|
||||
}
|
||||
|
||||
#region Implementation of ITemplateRepository
|
||||
|
||||
@@ -140,7 +140,8 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
return repository.ValidateStylesheet(stylesheet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scripts
|
||||
@@ -252,7 +253,6 @@ namespace Umbraco.Core.Services
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Templates
|
||||
|
||||
public ITemplate CreateTemplateWithIdentity(string name, string content, ITemplate masterTemplate = null, int userId = 0)
|
||||
@@ -499,7 +499,8 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
return repository.ValidateTemplate(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Partial Views
|
||||
@@ -571,20 +572,17 @@ namespace Umbraco.Core.Services
|
||||
if (CreatingPartialView.IsRaisedEventCancelled(new NewEventArgs<IPartialView>(partialView, true, partialView.Alias, -1), this))
|
||||
return Attempt<IPartialView>.Fail();
|
||||
|
||||
var uow = _fileUowProvider.GetUnitOfWork();
|
||||
string partialViewHeader = null;
|
||||
IPartialViewRepository repository;
|
||||
string partialViewHeader;
|
||||
switch (partialViewType)
|
||||
{
|
||||
case PartialViewType.PartialView:
|
||||
repository = _repositoryFactory.CreatePartialViewRepository(uow);
|
||||
partialViewHeader = PartialViewHeader;
|
||||
break;
|
||||
case PartialViewType.PartialViewMacro:
|
||||
default:
|
||||
repository = _repositoryFactory.CreatePartialViewMacroRepository(uow);
|
||||
partialViewHeader = PartialViewMacroHeader;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("partialViewType");
|
||||
}
|
||||
|
||||
if (snippetName.IsNullOrWhiteSpace() == false)
|
||||
@@ -610,7 +608,8 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
using (repository)
|
||||
var uow = _fileUowProvider.GetUnitOfWork();
|
||||
using (var repository = GetPartialViewRepository(partialViewType, uow))
|
||||
{
|
||||
repository.AddOrUpdate(partialView);
|
||||
uow.Commit();
|
||||
@@ -636,20 +635,7 @@ namespace Umbraco.Core.Services
|
||||
private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int userId = 0)
|
||||
{
|
||||
var uow = _fileUowProvider.GetUnitOfWork();
|
||||
|
||||
IPartialViewRepository repository;
|
||||
switch (partialViewType)
|
||||
{
|
||||
case PartialViewType.PartialView:
|
||||
repository = _repositoryFactory.CreatePartialViewRepository(uow);
|
||||
break;
|
||||
case PartialViewType.PartialViewMacro:
|
||||
default:
|
||||
repository = _repositoryFactory.CreatePartialViewMacroRepository(uow);
|
||||
break;
|
||||
}
|
||||
|
||||
using (repository)
|
||||
using (var repository = GetPartialViewRepository(partialViewType, uow))
|
||||
{
|
||||
var partialView = repository.Get(path);
|
||||
if (partialView == null)
|
||||
@@ -686,20 +672,7 @@ namespace Umbraco.Core.Services
|
||||
return Attempt<IPartialView>.Fail();
|
||||
|
||||
var uow = _fileUowProvider.GetUnitOfWork();
|
||||
|
||||
IPartialViewRepository repository;
|
||||
switch (partialViewType)
|
||||
{
|
||||
case PartialViewType.PartialView:
|
||||
repository = _repositoryFactory.CreatePartialViewRepository(uow);
|
||||
break;
|
||||
case PartialViewType.PartialViewMacro:
|
||||
default:
|
||||
repository = _repositoryFactory.CreatePartialViewMacroRepository(uow);
|
||||
break;
|
||||
}
|
||||
|
||||
using (repository)
|
||||
using (var repository = GetPartialViewRepository(partialViewType, uow))
|
||||
{
|
||||
repository.AddOrUpdate(partialView);
|
||||
uow.Commit();
|
||||
@@ -739,12 +712,20 @@ namespace Umbraco.Core.Services
|
||||
: Attempt<string>.Fail();
|
||||
}
|
||||
|
||||
private enum PartialViewType
|
||||
private IPartialViewRepository GetPartialViewRepository(PartialViewType partialViewType, IUnitOfWork uow)
|
||||
{
|
||||
PartialView,
|
||||
PartialViewMacro
|
||||
switch (partialViewType)
|
||||
{
|
||||
case PartialViewType.PartialView:
|
||||
return _repositoryFactory.CreatePartialViewRepository(uow);
|
||||
case PartialViewType.PartialViewMacro:
|
||||
return _repositoryFactory.CreatePartialViewMacroRepository(uow);
|
||||
}
|
||||
throw new ArgumentOutOfRangeException("partialViewType");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Audit(AuditType type, string message, int userId, int objectId)
|
||||
{
|
||||
var uow = _dataUowProvider.GetUnitOfWork();
|
||||
@@ -755,8 +736,6 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//TODO Method to change name and/or alias of view/masterpage template
|
||||
|
||||
#region Event Handlers
|
||||
@@ -851,9 +830,5 @@ namespace Umbraco.Core.Services
|
||||
public static event TypedEventHandler<IFileService, DeleteEventArgs<IPartialView>> DeletedPartialView;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -367,6 +367,7 @@
|
||||
<Compile Include="Models\IPublishedContentWithKey.cs" />
|
||||
<Compile Include="Models\IServerRegistration.cs" />
|
||||
<Compile Include="Models\MigrationEntry.cs" />
|
||||
<Compile Include="Models\PartialViewType.cs" />
|
||||
<Compile Include="Models\PublicAccessEntry.cs" />
|
||||
<Compile Include="Models\PublicAccessRule.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentWithKeyExtended.cs" />
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Serialization;
|
||||
@@ -40,9 +42,16 @@ namespace Umbraco.Tests.Models
|
||||
Assert.AreEqual(clone.Name, item.Name);
|
||||
Assert.AreEqual(clone.UpdateDate, item.UpdateDate);
|
||||
|
||||
//This double verifies by reflection
|
||||
// clone.Content should be null but getting it would lazy-load
|
||||
var type = clone.GetType();
|
||||
var contentField = type.BaseType.GetField("_content", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var value = contentField.GetValue(clone);
|
||||
Assert.IsNull(value);
|
||||
|
||||
// this double verifies by reflection
|
||||
// need to exclude content else it would lazy-load
|
||||
var allProps = clone.GetType().GetProperties();
|
||||
foreach (var propertyInfo in allProps)
|
||||
foreach (var propertyInfo in allProps.Where(x => x.Name != "Content"))
|
||||
{
|
||||
Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user