diff --git a/src/Orchard.Tests/ContentManagement/ContentHelpers.cs b/src/Orchard.Tests/ContentManagement/ContentHelpers.cs new file mode 100644 index 000000000..81f1371cd --- /dev/null +++ b/src/Orchard.Tests/ContentManagement/ContentHelpers.cs @@ -0,0 +1,30 @@ +using Orchard.ContentManagement; +using Orchard.ContentManagement.FieldStorage.InfosetStorage; +using Orchard.ContentManagement.Records; + +namespace Orchard.Tests.ContentManagement { + public class ContentHelpers { + public static ContentItem PreparePart(TPart part, string contentType, int id = -1) + where TPart : ContentPart + where TRecord : ContentPartRecord, new() { + + part.Record = new TRecord(); + return PreparePart(part, contentType, id); + } + + public static ContentItem PreparePart(TPart part, string contentType, int id = -1) + where TPart : ContentPart { + + var contentItem = part.ContentItem = new ContentItem { + VersionRecord = new ContentItemVersionRecord { + ContentItemRecord = new ContentItemRecord() + }, + ContentType = contentType + }; + contentItem.Record.Id = id; + contentItem.Weld(part); + contentItem.Weld(new InfosetPart()); + return contentItem; + } + } +} diff --git a/src/Orchard.Tests/ContentManagement/InfosetHelperTests.cs b/src/Orchard.Tests/ContentManagement/InfosetHelperTests.cs new file mode 100644 index 000000000..debca4aae --- /dev/null +++ b/src/Orchard.Tests/ContentManagement/InfosetHelperTests.cs @@ -0,0 +1,58 @@ +using NUnit.Framework; +using Orchard.ContentManagement; +using Orchard.ContentManagement.FieldStorage.InfosetStorage; +using Orchard.ContentManagement.Records; + +namespace Orchard.Tests.ContentManagement { + public class InfosetHelperTests { + [Test] + public void StoreByNameSavesIntoInfoset() { + var part = new TestPart(); + ContentHelpers.PreparePart(part, "Test"); + part.Foo = 42; + var infosetXml = part.As().Infoset.Element; + var testPartElement = infosetXml.Element(typeof (TestPart).Name); + Assert.That(testPartElement, Is.Not.Null); + var fooAttribute = testPartElement.Attr("Foo"); + + Assert.That(part.Foo, Is.EqualTo(42)); + Assert.That(fooAttribute, Is.EqualTo(42)); + } + + [Test] + public void RetrieveSavesIntoInfoset() { + var part = new TestPartWithRecord(); + ContentHelpers.PreparePart(part, "Test"); + part.Record.Foo = 42; + var infosetXml = part.As().Infoset.Element; + var testPartElement = infosetXml.Element(typeof (TestPartWithRecord).Name); + Assert.That(testPartElement, Is.Null); + + var foo = part.Foo; + testPartElement = infosetXml.Element(typeof(TestPartWithRecord).Name); + Assert.That(testPartElement, Is.Not.Null); + var fooAttribute = testPartElement.Attr("Foo"); + + Assert.That(foo, Is.EqualTo(42)); + Assert.That(fooAttribute, Is.EqualTo(42)); + } + + public class TestPart : ContentPart { + public int Foo { + get { return this.Retrieve("Foo"); } + set { this.Store("Foo", value); } + } + } + + public class TestPartWithRecordRecord : ContentPartRecord { + public virtual int Foo { get; set; } + } + + public class TestPartWithRecord : ContentPart { + public int Foo { + get { return Retrieve(r => r.Foo); } + set { Store(r => r.Foo, value); } + } + } + } +} diff --git a/src/Orchard.Tests/ContentManagement/XmlHelperTests.cs b/src/Orchard.Tests/ContentManagement/XmlHelperTests.cs new file mode 100644 index 000000000..bf1081ced --- /dev/null +++ b/src/Orchard.Tests/ContentManagement/XmlHelperTests.cs @@ -0,0 +1,395 @@ +using System; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; +using Orchard.ContentManagement; + +namespace Orchard.Tests.ContentManagement { + [TestFixture] + public class XmlHelperTests { + [Test] + public void AddEl() { + var el = new XElement("data"); + el + .AddEl(new XElement("node1"), new XElement("node2")) + .AddEl(new XElement("node3")); + + Assert.That(el.Descendants().Count(), Is.EqualTo(3)); + Assert.That(el.Descendants().First().Name.ToString(), Is.EqualTo("node1")); + Assert.That(el.Descendants().ElementAt(1).Name.ToString(), Is.EqualTo("node2")); + Assert.That(el.Descendants().ElementAt(2).Name.ToString(), Is.EqualTo("node3")); + } + + [Test] + public void Val() { + var el = new XElement("data"); + el = el.Val(123); + var val = el.Val(); + + Assert.That(val, Is.EqualTo(123)); + Assert.That(el.ToString(SaveOptions.DisableFormatting), + Is.EqualTo("123")); + } + + [Test] + public void Infinities() { + var el = new XElement("data") + .Attr("doubleplus", double.PositiveInfinity) + .Attr("doubleminus", double.NegativeInfinity) + .Attr("floatplus", float.PositiveInfinity) + .Attr("floatminus", float.NegativeInfinity); + + Assert.That(el.Attr("doubleplus"), Is.EqualTo("infinity")); + Assert.That(el.Attr("doubleminus"), Is.EqualTo("-infinity")); + Assert.That(el.Attr("floatplus"), Is.EqualTo("infinity")); + Assert.That(el.Attr("floatminus"), Is.EqualTo("-infinity")); + + Assert.That(double.IsPositiveInfinity(el.Attr("doubleplus")), Is.True); + Assert.That(double.IsNegativeInfinity(el.Attr("doubleminus")), Is.True); + Assert.That(double.IsPositiveInfinity(el.Attr("floatplus")), Is.True); + Assert.That(double.IsNegativeInfinity(el.Attr("floatminus")), Is.True); + } + + [Test] + public void StringToAttribute() { + var el = new XElement("data"); + el.Attr("foo", "bar"); + + Assert.That(el.Attribute("foo").Value, Is.EqualTo("bar")); + } + + [Test] + public void IntToAttribute() { + var el = new XElement("data"); + el.Attr("foo", 42); + + Assert.That(el.Attribute("foo").Value, Is.EqualTo("42")); + } + + [Test] + public void BoolToAttribute() { + var el = new XElement("data"); + el.Attr("foo", true); + el.Attr("bar", false); + + Assert.That(el.Attribute("foo").Value, Is.EqualTo("true")); + Assert.That(el.Attribute("bar").Value, Is.EqualTo("false")); + } + + [Test] + public void DateTimeToAttribute() { + var el = new XElement("data"); + el.Attr("foo", new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)); + + Assert.That(el.Attribute("foo").Value, Is.EqualTo("1970-05-21T13:55:21.934Z")); + } + + [Test] + public void DoubleFloatDecimalToAttribute() { + var el = new XElement("data"); + el.Attr("double", 12.456D); + el.Attr("float", 12.457F); + el.Attr("decimal", 12.458M); + + Assert.That(el.Attribute("double").Value, Is.EqualTo("12.456")); + Assert.That(el.Attribute("float").Value, Is.EqualTo("12.457")); + Assert.That(el.Attribute("decimal").Value, Is.EqualTo("12.458")); + } + + [Test] + public void ReadAttribute() { + var el = XElement.Parse(""); + + Assert.That(el.Attr("foo"), Is.EqualTo("bar")); + Assert.That(el.Attr("bar"), Is.Null); + } + + [Test] + public void StringToElement() { + var el = new XElement("data"); + el.El("foo", "bar"); + + Assert.That(el.Element("foo").Value, Is.EqualTo("bar")); + } + + [Test] + public void IntToElement() { + var el = new XElement("data"); + el.El("foo", 42); + + Assert.That(el.Element("foo").Value, Is.EqualTo("42")); + } + + [Test] + public void BoolToElement() { + var el = new XElement("data"); + el.El("foo", true); + el.El("bar", false); + + Assert.That(el.Element("foo").Value, Is.EqualTo("true")); + Assert.That(el.Element("bar").Value, Is.EqualTo("false")); + } + + [Test] + public void DateTimeToElement() { + var el = new XElement("data"); + el.El("foo", new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)); + + Assert.That(el.Element("foo").Value, Is.EqualTo("1970-05-21T13:55:21.934Z")); + } + + [Test] + public void DoubleFloatDecimalToElement() { + var el = new XElement("data"); + el.El("double", 12.456D); + el.El("float", 12.457F); + el.El("decimal", 12.458M); + + Assert.That(el.Element("double").Value, Is.EqualTo("12.456")); + Assert.That(el.Element("float").Value, Is.EqualTo("12.457")); + Assert.That(el.Element("decimal").Value, Is.EqualTo("12.458")); + } + + [Test] + public void ReadElement() { + var el = XElement.Parse("bar"); + + Assert.That(el.El("foo"), Is.EqualTo("bar")); + Assert.That(el.El("bar"), Is.Null); + } + + [Test] + public void SerializeObject() { + var target = new Target { + AString = "foo", + AnInt = 42, + ABoolean = true, + ADate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc), + ADouble = 12.345D, + AFloat = 23.456F, + ADecimal = 34.567M, + ANullableInt = 42, + ANullableBoolean = true, + ANullableDate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc), + ANullableDouble = 12.345D, + ANullableFloat = 23.456F, + ANullableDecimal = 34.567M + }; + var el = new XElement("data"); + el.With(target) + .ToAttr(t => t.AString) + .ToAttr(t => t.AnInt) + .ToAttr(t => t.ABoolean) + .ToAttr(t => t.ADate) + .ToAttr(t => t.ADouble) + .ToAttr(t => t.AFloat) + .ToAttr(t => t.ADecimal) + .ToAttr(t => t.ANullableInt) + .ToAttr(t => t.ANullableBoolean) + .ToAttr(t => t.ANullableDate) + .ToAttr(t => t.ANullableDouble) + .ToAttr(t => t.ANullableFloat) + .ToAttr(t => t.ANullableDecimal); + + + Assert.That(el.Attr("AString"), Is.EqualTo("foo")); + Assert.That(el.Attr("AnInt"), Is.EqualTo("42")); + Assert.That(el.Attr("ABoolean"), Is.EqualTo("true")); + Assert.That(el.Attr("ADate"), Is.EqualTo("1970-05-21T13:55:21.934Z")); + Assert.That(el.Attr("ADouble"), Is.EqualTo("12.345")); + Assert.That(el.Attr("AFloat"), Is.EqualTo("23.456")); + Assert.That(el.Attr("ADecimal"), Is.EqualTo("34.567")); + Assert.That(el.Attr("ANullableInt"), Is.EqualTo("42")); + Assert.That(el.Attr("ANullableBoolean"), Is.EqualTo("true")); + Assert.That(el.Attr("ANullableDate"), Is.EqualTo("1970-05-21T13:55:21.934Z")); + Assert.That(el.Attr("ANullableDouble"), Is.EqualTo("12.345")); + Assert.That(el.Attr("ANullableFloat"), Is.EqualTo("23.456")); + Assert.That(el.Attr("ANullableDecimal"), Is.EqualTo("34.567")); + } + + [Test] + public void DeSerializeObject() { + var target = new Target(); + var el = + XElement.Parse( + ""); + el.With(target) + .FromAttr(t => t.AString) + .FromAttr(t => t.AnInt) + .FromAttr(t => t.ABoolean) + .FromAttr(t => t.ADate) + .FromAttr(t => t.ADouble) + .FromAttr(t => t.AFloat) + .FromAttr(t => t.ADecimal) + .FromAttr(t => t.ANullableInt) + .FromAttr(t => t.ANullableBoolean) + .FromAttr(t => t.ANullableDate) + .FromAttr(t => t.ANullableDouble) + .FromAttr(t => t.ANullableFloat) + .FromAttr(t => t.ANullableDecimal); + + Assert.That(target.AString, Is.EqualTo("foo")); + Assert.That(target.AnInt, Is.EqualTo(42)); + Assert.That(target.ABoolean, Is.True); + Assert.That(target.ADate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc))); + Assert.That(target.ADouble, Is.EqualTo(12.345D)); + Assert.That(target.AFloat, Is.EqualTo(23.456F)); + Assert.That(target.ADecimal, Is.EqualTo(34.567M)); + Assert.That(target.ANullableInt, Is.EqualTo(42)); + Assert.That(target.ANullableBoolean, Is.True); + Assert.That(target.ANullableDate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc))); + Assert.That(target.ANullableDouble, Is.EqualTo(12.345D)); + Assert.That(target.ANullableFloat, Is.EqualTo(23.456F)); + Assert.That(target.ANullableDecimal, Is.EqualTo(34.567M)); + } + + [Test] + public void DeSerializeFromMissingAttributeLeavesValueIntact() { + var target = new Target { + AString = "foo", + AnInt = 42, + ABoolean = true, + ADate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc), + ADouble = 12.345D, + AFloat = 23.456F, + ADecimal = 34.567M, + ANullableInt = 42, + ANullableBoolean = true, + ANullableDate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc), + ANullableDouble = 12.345D, + ANullableFloat = 23.456F, + ANullableDecimal = 34.567M + }; + var el = new XElement("data"); + el.With(target) + .FromAttr(t => t.AString) + .FromAttr(t => t.AnInt) + .FromAttr(t => t.ABoolean) + .FromAttr(t => t.ADate) + .FromAttr(t => t.ADouble) + .FromAttr(t => t.AFloat) + .FromAttr(t => t.ADecimal) + .FromAttr(t => t.ANullableInt) + .FromAttr(t => t.ANullableBoolean) + .FromAttr(t => t.ANullableDate) + .FromAttr(t => t.ANullableDouble) + .FromAttr(t => t.ANullableFloat) + .FromAttr(t => t.ANullableDecimal); + + Assert.That(target.AString, Is.EqualTo("foo")); + Assert.That(target.AnInt, Is.EqualTo(42)); + Assert.That(target.ABoolean, Is.True); + Assert.That(target.ADate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc))); + Assert.That(target.ADouble, Is.EqualTo(12.345D)); + Assert.That(target.AFloat, Is.EqualTo(23.456F)); + Assert.That(target.ADecimal, Is.EqualTo(34.567M)); + Assert.That(target.ANullableInt, Is.EqualTo(42)); + Assert.That(target.ANullableBoolean, Is.True); + Assert.That(target.ANullableDate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc))); + Assert.That(target.ANullableDouble, Is.EqualTo(12.345D)); + Assert.That(target.ANullableFloat, Is.EqualTo(23.456F)); + Assert.That(target.ANullableDecimal, Is.EqualTo(34.567M)); + } + + [Test] + public void AttrWithContext() { + var el = new XElement("data") + .With(new {foo = 123}) + .ToAttr(o => o.foo); + var val = el.Attr(o => o.foo); + + Assert.That(val, Is.EqualTo(123)); + } + + [Test] + public void ContextSwitch() { + var el = new XElement("data"); + el.With(new {foo = "bar"}) + .ToAttr(o => o.foo) + .With(new {bar = "baz"}) + .ToAttr(o => o.bar); + + Assert.That(el.Attr("foo"), Is.EqualTo("bar")); + Assert.That(el.Attr("bar"), Is.EqualTo("baz")); + } + + [Test] + public void ImplicitConversion() { + var el = new XElement("data") + .With(new {foo = "bar"}) + .ToAttr(o => o.foo); + Func func = e => e.Attr("foo"); + + Assert.That(func(el), Is.EqualTo("bar")); + } + + [Test] + public void NullSerializes() { + var target = new Target(); + var el = new XElement("data"); + el.With(target) + .ToAttr(t => t.AString) + .ToAttr(t => t.ANullableInt) + .ToAttr(t => t.ANullableBoolean) + .ToAttr(t => t.ANullableDate) + .ToAttr(t => t.ANullableDouble) + .ToAttr(t => t.ANullableFloat) + .ToAttr(t => t.ANullableDecimal); + + Assert.That(el.Attr("AString"), Is.EqualTo("")); + Assert.That(el.Attr("ANullableInt"), Is.EqualTo("null")); + Assert.That(el.Attr("ANullableBoolean"), Is.EqualTo("null")); + Assert.That(el.Attr("ANullableDate"), Is.EqualTo("null")); + Assert.That(el.Attr("ANullableDouble"), Is.EqualTo("null")); + Assert.That(el.Attr("ANullableFloat"), Is.EqualTo("null")); + Assert.That(el.Attr("ANullableDecimal"), Is.EqualTo("null")); + } + + [Test] + public void DeSerializeNull() { + var target = new Target(); + var el = + XElement.Parse( + ""); + el.With(target) + .FromAttr(t => t.AString) + .FromAttr(t => t.ANullableInt) + .FromAttr(t => t.ANullableBoolean) + .FromAttr(t => t.ANullableDate) + .FromAttr(t => t.ANullableDouble) + .FromAttr(t => t.ANullableFloat) + .FromAttr(t => t.ANullableDecimal); + + Assert.That(target.AString, Is.EqualTo("null")); + Assert.That(target.ANullableInt, Is.Null); + Assert.That(target.ANullableBoolean, Is.Null); + Assert.That(target.ANullableDate, Is.Null); + Assert.That(target.ANullableDouble, Is.Null); + Assert.That(target.ANullableFloat, Is.Null); + Assert.That(target.ANullableDecimal, Is.Null); + } + + private class Target { + public string AString { get; set; } + public int AnInt { get; set; } + public bool ABoolean { get; set; } + public DateTime ADate { get; set; } + public double ADouble { get; set; } + public float AFloat { get; set; } + public decimal ADecimal { get; set; } + public int? ANullableInt { get; set; } + public bool? ANullableBoolean { get; set; } + public DateTime? ANullableDate { get; set; } + public double? ANullableDouble { get; set; } + public float? ANullableFloat { get; set; } + public decimal? ANullableDecimal { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index b3131d35b..84ef8958a 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -161,6 +161,7 @@ + Code @@ -180,6 +181,7 @@ + @@ -210,6 +212,7 @@ Code + diff --git a/src/Orchard.Web/Core/Common/Models/BodyPart.cs b/src/Orchard.Web/Core/Common/Models/BodyPart.cs index 0e9901cda..7934f2002 100644 --- a/src/Orchard.Web/Core/Common/Models/BodyPart.cs +++ b/src/Orchard.Web/Core/Common/Models/BodyPart.cs @@ -3,11 +3,8 @@ using Orchard.ContentManagement; namespace Orchard.Core.Common.Models { public class BodyPart : ContentPart { public string Text { - get { return Get("Text"); } - set { - Set("Text", value); - Record.Text = value; - } + get { return Retrieve(x => x.Text); } + set { Store(x => x.Text, value); } } } } diff --git a/src/Orchard.Web/Core/Common/Models/CommonPart.cs b/src/Orchard.Web/Core/Common/Models/CommonPart.cs index dc61bab6a..23fc22f4e 100644 --- a/src/Orchard.Web/Core/Common/Models/CommonPart.cs +++ b/src/Orchard.Web/Core/Common/Models/CommonPart.cs @@ -1,5 +1,4 @@ using System; -using System.Xml; using Orchard.Core.Common.Utilities; using Orchard.ContentManagement; using Orchard.ContentManagement.Aspects; @@ -23,73 +22,56 @@ namespace Orchard.Core.Common.Models { get { return _container.Value; } set { _container.Value = value; } } - + public DateTime? CreatedUtc { - get { - var dateTime = Get("CreatedUtc"); - return String.IsNullOrEmpty(dateTime) ? (DateTime?)null : XmlConvert.ToDateTime(dateTime, XmlDateTimeSerializationMode.Utc); - } - set { - string dateTime = value.HasValue ? XmlConvert.ToString(value.Value, XmlDateTimeSerializationMode.Utc) : ""; - Set("CreatedUtc", dateTime); - Record.CreatedUtc = value; - } + get { return Retrieve(x => x.CreatedUtc); } + set { Store(x => x.CreatedUtc, value); } } public DateTime? PublishedUtc { - get { - var dateTime = Get("PublishedUtc"); - return String.IsNullOrEmpty(dateTime) ? (DateTime?)null : XmlConvert.ToDateTime(dateTime, XmlDateTimeSerializationMode.Utc); - } - set { - string dateTime = value.HasValue ? XmlConvert.ToString(value.Value, XmlDateTimeSerializationMode.Utc) : ""; - Set("PublishedUtc", dateTime); - Record.PublishedUtc = value; - } + get { return Retrieve(x => x.PublishedUtc); } + set { Store(x => x.PublishedUtc, value); } } public DateTime? ModifiedUtc { + get { return Retrieve(x => x.ModifiedUtc); } + set { Store(x => x.ModifiedUtc, value); } + } + + CommonPartVersionRecord PartVersionRecord { get { - var dateTime = Get("ModifiedUtc"); - return String.IsNullOrEmpty(dateTime) ? (DateTime?)null : XmlConvert.ToDateTime(dateTime, XmlDateTimeSerializationMode.Utc); - } - set { - string dateTime = value.HasValue ? XmlConvert.ToString(value.Value, XmlDateTimeSerializationMode.Utc) : ""; - Set("ModifiedUtc", dateTime); - Record.ModifiedUtc = value; + var versionPart = this.As>(); + return versionPart == null ? null : versionPart.Record; } } public DateTime? VersionCreatedUtc { get { - var dateTime = this.As>().Get("CreatedUtc"); - return String.IsNullOrEmpty(dateTime) ? (DateTime?)null : XmlConvert.ToDateTime(dateTime, XmlDateTimeSerializationMode.Utc); + return PartVersionRecord == null ? CreatedUtc : PartVersionRecord.CreatedUtc; } set { - string dateTime = value.HasValue ? XmlConvert.ToString(value.Value, XmlDateTimeSerializationMode.Utc) : ""; - this.As>().Set("CreatedUtc", dateTime); + if (PartVersionRecord != null) + PartVersionRecord.CreatedUtc = value; } } public DateTime? VersionPublishedUtc { get { - var dateTime = this.As>().Get("PublishedUtc"); - return String.IsNullOrEmpty(dateTime) ? (DateTime?)null : XmlConvert.ToDateTime(dateTime, XmlDateTimeSerializationMode.Utc); + return PartVersionRecord == null ? PublishedUtc : PartVersionRecord.PublishedUtc; } set { - string dateTime = value.HasValue ? XmlConvert.ToString(value.Value, XmlDateTimeSerializationMode.Utc) : ""; - this.As>().Set("PublishedUtc", dateTime); + if (PartVersionRecord != null) + PartVersionRecord.PublishedUtc = value; } } public DateTime? VersionModifiedUtc { get { - var dateTime = this.As>().Get("ModifiedUtc"); - return String.IsNullOrEmpty(dateTime) ? (DateTime?)null : XmlConvert.ToDateTime(dateTime, XmlDateTimeSerializationMode.Utc); + return PartVersionRecord == null ? ModifiedUtc : PartVersionRecord.ModifiedUtc; } set { - string dateTime = value.HasValue ? XmlConvert.ToString(value.Value, XmlDateTimeSerializationMode.Utc) : ""; - this.As>().Set("ModifiedUtc", dateTime); + if (PartVersionRecord != null) + PartVersionRecord.ModifiedUtc = value; } } } diff --git a/src/Orchard.Web/Core/Common/Models/IdentityPart.cs b/src/Orchard.Web/Core/Common/Models/IdentityPart.cs index ba96dd101..0642acedc 100644 --- a/src/Orchard.Web/Core/Common/Models/IdentityPart.cs +++ b/src/Orchard.Web/Core/Common/Models/IdentityPart.cs @@ -3,11 +3,8 @@ using Orchard.ContentManagement; namespace Orchard.Core.Common.Models { public class IdentityPart : ContentPart { public string Identifier { - get { return Get("Identifier"); } - set { - Set("Identifier", value); - Record.Identifier = value; - } + get { return Retrieve(x => x.Identifier); } + set { Store(x => x.Identifier, value); } } } } diff --git a/src/Orchard.Web/Core/Navigation/Models/MenuWidgetPart.cs b/src/Orchard.Web/Core/Navigation/Models/MenuWidgetPart.cs index abb0f5eae..340f1c181 100644 --- a/src/Orchard.Web/Core/Navigation/Models/MenuWidgetPart.cs +++ b/src/Orchard.Web/Core/Navigation/Models/MenuWidgetPart.cs @@ -1,48 +1,32 @@ -using System.Globalization; -using Orchard.ContentManagement; +using Orchard.ContentManagement; using Orchard.ContentManagement.Records; namespace Orchard.Core.Navigation.Models { public class MenuWidgetPart : ContentPart { public int StartLevel { - get { return int.Parse(Get("StartLevel") ?? "0", CultureInfo.InvariantCulture); } - set { - Set("StartLevel", value.ToString(CultureInfo.InvariantCulture)); - Record.StartLevel = value; - } + get { return Retrieve(x => x.StartLevel); } + set { Store(x => x.StartLevel, value); } } public int Levels { - get { return int.Parse(Get("Levels") ?? "0", CultureInfo.InvariantCulture); } - set { - Set("Levels", value.ToString(CultureInfo.InvariantCulture)); - Record.Levels = value; - } + get { return Retrieve(x => x.Levels); } + set { Store(x => x.Levels, value); } } public bool Breadcrumb { - get { return bool.Parse(Get("Breadcrumb") ?? "false"); } - set { - Set("Breadcrumb", value.ToString()); - Record.Breadcrumb = value; - } + get { return Retrieve(x => x.Breadcrumb); } + set { Store(x => x.Breadcrumb, value); } } public bool AddHomePage { - get { return bool.Parse(Get("AddHomePage") ?? "false"); } - set { - Set("AddHomePage", value.ToString()); - Record.AddHomePage = value; - } + get { return Retrieve(x => x.AddHomePage); } + set { Store(x => x.AddHomePage, value); } } public bool AddCurrentPage { - get { return bool.Parse(Get("AddCurrentPage") ?? "false"); } - set { - Set("AddCurrentPage", value.ToString()); - Record.AddCurrentPage = value; - } + get { return Retrieve(x => x.AddCurrentPage); } + set { Store(x => x.AddCurrentPage, value); } } public ContentItemRecord Menu { diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs index 500a5be16..7bb0a3dba 100644 --- a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs +++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs @@ -1,6 +1,4 @@ -using System; -using System.Globalization; -using Orchard.ContentManagement; +using Orchard.ContentManagement; using Orchard.Settings; namespace Orchard.Core.Settings.Models { @@ -8,55 +6,52 @@ namespace Orchard.Core.Settings.Models { public const int DefaultPageSize = 10; public string PageTitleSeparator { - get { return Get("PageTitleSeparator"); } - set { Set("PageTitleSeparator", value); } + get { return this.Retrieve(x => x.PageTitleSeparator); } + set { this.Store(x => x.PageTitleSeparator, value); } } public string SiteName { - get { return Get("SiteName"); } - set { Set("SiteName", value); } + get { return this.Retrieve(x => x.SiteName); } + set { this.Store(x => x.SiteName, value); } } public string SiteSalt { - get { return Get("SiteSalt"); } - set { Set("SiteSalt", value); } + get { return this.Retrieve(x => x.SiteSalt); } + set { this.Store(x => x.SiteSalt, value); } } public string SuperUser { - get { return Get("SuperUser"); } - set { Set("SuperUser", value); } + get { return this.Retrieve(x => x.SuperUser); } + set { this.Store(x => x.SuperUser, value); } } public string HomePage { - get { return Get("HomePage"); } - set { Set("HomePage", value); } + get { return this.Retrieve(x => x.HomePage); } + set { this.Store(x => x.HomePage, value); } } public string SiteCulture { - get { return Get("SiteCulture"); } - set { Set("SiteCulture", value); } + get { return this.Retrieve(x => x.SiteCulture); } + set { this.Store(x => x.SiteCulture, value); } } public ResourceDebugMode ResourceDebugMode { - get { - var value = Get("ResourceDebugMode"); - return String.IsNullOrEmpty(value) ? ResourceDebugMode.Disabled : (ResourceDebugMode)Enum.Parse(typeof(ResourceDebugMode), value); - } - set { Set("ResourceDebugMode", value.ToString()); } + get { return this.Retrieve(x => x.ResourceDebugMode); } + set { this.Store(x => x.ResourceDebugMode, value); } } public int PageSize { - get { return int.Parse(Get("PageSize") ?? "0", CultureInfo.InvariantCulture); } - set { Set("PageSize", value.ToString(CultureInfo.InvariantCulture)); } + get { return this.Retrieve(x => x.PageSize); } + set { this.Store(x => x.PageSize, value); } } public string SiteTimeZone { - get { return Get("SiteTimeZone"); } - set { Set("SiteTimeZone", value); } + get { return this.Retrieve(x => x.SiteTimeZone); } + set { this.Store(x => x.SiteTimeZone, value); } } public string BaseUrl { - get { return Get("BaseUrl"); } - set { Set("BaseUrl", value); } + get { return this.Retrieve(x => x.BaseUrl); } + set { this.Store(x => x.BaseUrl, value); } } } } diff --git a/src/Orchard.Web/Core/Title/Models/TitlePart.cs b/src/Orchard.Web/Core/Title/Models/TitlePart.cs index 7d44a8c29..7cd87e9af 100644 --- a/src/Orchard.Web/Core/Title/Models/TitlePart.cs +++ b/src/Orchard.Web/Core/Title/Models/TitlePart.cs @@ -6,13 +6,8 @@ namespace Orchard.Core.Title.Models { public class TitlePart : ContentPart, ITitleAspect { [Required] public string Title { - get { - return Get("Title"); - } - set { - Set("Title", value); - Record.Title = value; - } + get { return Retrieve(x => x.Title); } + set { Store(x => x.Title, value); } } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Migrations.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Migrations.cs index 7bbf899b2..03781ff40 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Migrations.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Migrations.cs @@ -83,13 +83,6 @@ namespace Orchard.AntiSpam { public class AkismetMigrations : DataMigrationImpl { public int Create() { - - SchemaBuilder.CreateTable("AkismetSettingsPartRecord", - table => table.ContentPartVersionRecord() - .Column("TrustAuthenticatedUsers") - .Column("ApiKey") - ); - return 1; } } diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Models/AkismetSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Models/AkismetSettingsPart.cs index 6e5d042ce..c4394946e 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Models/AkismetSettingsPart.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Models/AkismetSettingsPart.cs @@ -3,13 +3,13 @@ namespace Orchard.AntiSpam.Models { public class AkismetSettingsPart : ContentPart { public bool TrustAuthenticatedUsers { - get { return bool.Parse(Get("TrustAuthenticatedUsers") ?? "false"); } - set { Set("TrustAuthenticatedUsers", value.ToString()); } + get { return this.Retrieve(x => x.TrustAuthenticatedUsers); } + set { this.Store(x => x.TrustAuthenticatedUsers, value); } } public string ApiKey { - get { return Get("ApiKey"); } - set { Set("ApiKey", value); } + get { return this.Retrieve(x => x.ApiKey); } + set { this.Store(x => x.ApiKey, value); } } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Autoroute/Models/AutoroutePart.cs b/src/Orchard.Web/Modules/Orchard.Autoroute/Models/AutoroutePart.cs index ac9c8ab9f..46416af3c 100644 --- a/src/Orchard.Web/Modules/Orchard.Autoroute/Models/AutoroutePart.cs +++ b/src/Orchard.Web/Modules/Orchard.Autoroute/Models/AutoroutePart.cs @@ -5,27 +5,16 @@ namespace Orchard.Autoroute.Models { public class AutoroutePart : ContentPart, IAliasAspect { public string CustomPattern { - get { return Get("CustomPattern"); } - set { - Set("CustomPattern", value); - Record.CustomPattern = value; - } + get { return Retrieve(x => x.CustomPattern); } + set { Store(x => x.CustomPattern, value); } } - public bool UseCustomPattern { - get { return bool.Parse(Get("UseCustomPattern")); } - set { - Set("UseCustomPattern", value.ToString()); - Record.UseCustomPattern = value; - } + get { return Retrieve(x => x.UseCustomPattern); } + set { Store(x => x.UseCustomPattern, value); } } - public string DisplayAlias { - get { return Get("DisplayAlias"); } - set { - Set("DisplayAlias", value); - Record.DisplayAlias = value; - } + get { return Retrieve(x => x.DisplayAlias); } + set { Store(x => x.DisplayAlias, value); } } public string Path { diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeSiteSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeSiteSettingsPart.cs index 57567487d..ed276675b 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeSiteSettingsPart.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/Models/ThemeSiteSettingsPart.cs @@ -3,8 +3,8 @@ namespace Orchard.Themes.Models { public class ThemeSiteSettingsPart : ContentPart { public string CurrentThemeName { - get { return Get("CurrentThemeName"); } - set { Set("CurrentThemeName", value); } + get { return this.Retrieve(x => x.CurrentThemeName); } + set { this.Store(x => x.CurrentThemeName, value); } } } } \ No newline at end of file diff --git a/src/Orchard/ContentManagement/ContentPart.cs b/src/Orchard/ContentManagement/ContentPart.cs index 335f1b4ca..05ac134a7 100644 --- a/src/Orchard/ContentManagement/ContentPart.cs +++ b/src/Orchard/ContentManagement/ContentPart.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; +using System.Linq.Expressions; using System.Web.Mvc; using Autofac; -using Orchard.ContentManagement.FieldStorage.InfosetStorage; using Orchard.ContentManagement.MetaData.Models; using Orchard.ContentManagement.Records; using Orchard.ContentManagement.Utilities; @@ -79,33 +79,49 @@ namespace Orchard.ContentManagement { return true; } - public virtual string Get(string fieldName) { - return this.As().Get(GetType().Name, fieldName, null, false); + + public T Retrieve(string fieldName) { + return InfosetHelper.Retrieve(this, fieldName); } - public string GetVersioned(string fieldName) { - return this.As().Get(GetType().Name, fieldName, null, true); + public T RetrieveVersioned(string fieldName) { + return this.Retrieve(fieldName, true); } - public virtual void Set(string fieldName, string value) { - this.As().Set(GetType().Name, fieldName, null, value, false); + + public virtual void Store(string fieldName, T value) { + InfosetHelper.Store(this, fieldName, value); } - public void SetVersionned(string fieldName, string value) { - this.As().Set(GetType().Name, fieldName, null, value, true); + + public virtual void StoreVersioned(string fieldName, T value) { + this.Store(fieldName, value, true); } + } public class ContentPart : ContentPart { static protected bool IsVersionableRecord { get; private set;} + static ContentPart() { IsVersionableRecord = typeof (TRecord).IsAssignableTo(); } - public override string Get(string fieldName) { - return this.As().Get(GetType().Name, fieldName, null, IsVersionableRecord); + protected TProperty Retrieve(Expression> targetExpression) { + return InfosetHelper.Retrieve(this, targetExpression); } - public override void Set(string fieldName, string value) { - this.As().Set(GetType().Name, fieldName, null, value, IsVersionableRecord); + + protected TProperty Retrieve( + Expression> targetExpression, + Func defaultExpression) { + + return InfosetHelper.Retrieve(this, targetExpression, defaultExpression); + } + protected ContentPart Store( + Expression> targetExpression, + TProperty value) { + + InfosetHelper.Store(this, targetExpression, value); + return this; } public readonly LazyField _record = new LazyField(); diff --git a/src/Orchard/ContentManagement/InfosetHelper.cs b/src/Orchard/ContentManagement/InfosetHelper.cs new file mode 100644 index 000000000..6c7310814 --- /dev/null +++ b/src/Orchard/ContentManagement/InfosetHelper.cs @@ -0,0 +1,112 @@ +using System; +using System.Linq.Expressions; +using System.Xml.Linq; +using Orchard.ContentManagement.FieldStorage.InfosetStorage; +using Orchard.Utility; + +namespace Orchard.ContentManagement { + public static class InfosetHelper { + + public static TProperty Retrieve(this TPart contentPart, + Expression> targetExpression, + bool versioned = false) where TPart : ContentPart { + + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + + var infosetPart = contentPart.As(); + var el = infosetPart == null + ? null + : (versioned ? infosetPart.VersionInfoset.Element : infosetPart.Infoset.Element) + .Element(contentPart.GetType().Name); + return el == null ? default(TProperty) : el.Attr(name); + } + + public static TProperty Retrieve(this ContentPart contentPart, string name, + bool versioned = false) { + var infosetPart = contentPart.As(); + var el = infosetPart == null + ? null + : (versioned ? infosetPart.VersionInfoset.Element : infosetPart.Infoset.Element) + .Element(contentPart.GetType().Name); + return el == null ? default(TProperty) : el.Attr(name); + } + + public static void Store(this TPart contentPart, + Expression> targetExpression, + TProperty value, bool versioned = false) where TPart : ContentPart { + + var partName = contentPart.GetType().Name; + + var infosetPart = contentPart.As(); + var infoset = (versioned ? infosetPart.VersionInfoset : infosetPart.Infoset); + var partElement = infoset.Element.Element(partName); + if (partElement == null) { + partElement = new XElement(partName); + infoset.Element.Add(partElement); + } + + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + + partElement.Attr(name, value); + } + + public static void Store(this ContentPart contentPart, string name, + TProperty value, bool versioned = false) { + + var partName = contentPart.GetType().Name; + + var infosetPart = contentPart.As(); + var infoset = (versioned ? infosetPart.VersionInfoset : infosetPart.Infoset); + var partElement = infoset.Element.Element(partName); + if (partElement == null) { + partElement = new XElement(partName); + infoset.Element.Add(partElement); + } + partElement.Attr(name, value); + } + + public static TProperty Retrieve(this TPart contentPart, + Expression> targetExpression) + where TPart : ContentPart { + + var getter = ReflectionHelper.GetGetter(targetExpression); + return contentPart.Retrieve(targetExpression, getter); + } + + public static TProperty Retrieve(this TPart contentPart, + Expression> targetExpression, + Delegate defaultExpression) + where TPart : ContentPart { + + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + + var infosetPart = contentPart.As(); + var el = infosetPart == null + ? null + : infosetPart.Infoset.Element.Element(contentPart.GetType().Name); + if (el == null || el.Attribute(name) == null) { + // Property has never been stored. Get it from the default expression and store that. + var defaultValue = defaultExpression == null + ? default(TProperty) + : (TProperty)defaultExpression.DynamicInvoke(contentPart.Record); + contentPart.Store(name, defaultValue); + return defaultValue; + } + return el.Attr(name); + } + + public static void Store(this TPart contentPart, + Expression> targetExpression, + TProperty value) + where TPart : ContentPart { + + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + propertyInfo.SetValue(contentPart.Record, value, null); + contentPart.Store(name, value); + } + } +} diff --git a/src/Orchard/ContentManagement/XmlHelper.cs b/src/Orchard/ContentManagement/XmlHelper.cs new file mode 100644 index 000000000..f92bd8cb0 --- /dev/null +++ b/src/Orchard/ContentManagement/XmlHelper.cs @@ -0,0 +1,345 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Xml; +using System.Xml.Linq; +using Orchard.Utility; + +namespace Orchard.ContentManagement { + public static class XmlHelper { + /// + /// Like Add, but chainable. + /// + /// The parent element. + /// The elements to add. + /// Itself + public static XElement AddEl(this XElement el, params XElement[] children) { + el.Add(children.Cast()); + return el; + } + + /// + /// Gets the string value of an attribute, and null if the attribute doesn't exist. + /// + /// The element. + /// The name of the attribute. + /// The string value of the attribute if it exists, null otherwise. + public static string Attr(this XElement el, string name) { + var attr = el.Attribute(name); + return attr == null ? null : attr.Value; + } + + /// + /// Gets a typed value from an attribute. + /// + /// The type of the value + /// The element. + /// The name of the attribute. + /// The attribute value + public static T Attr(this XElement el, string name) { + + var attr = el.Attribute(name); + return attr == null ? default(T) : Parse(attr.Value); + } + + /// + /// Sets an attribute value. This is chainable. + /// + /// The type of the value. + /// The element. + /// The attribute name. + /// The value to set. + /// Itself + public static XElement Attr(this XElement el, string name, T value) { + el.SetAttributeValue(name, ToString(value)); + return el; + } + + /// + /// Returns the text contents of a child element. + /// + /// The parent element. + /// The name of the child element. + /// The text for the child element, and null if it doesn't exist. + public static string El(this XElement el, string name) { + var childElement = el.Element(name); + return childElement == null ? null : childElement.Value; + } + + /// + /// Creates and sets the value of a child element. This is chainable. + /// + /// The type of the value. + /// The parent element. + /// The name of the child element. + /// The value to set. + /// Itself + public static XElement El(this XElement el, string name, T value) { + el.SetElementValue(name, value); + return el; + } + + /// + /// Sets a property value from an attribute of the same name. + /// + /// The type of the target object. + /// The type of the target property + /// The element. + /// The target object. + /// The property expression. + /// Itself + public static XElement FromAttr(this XElement el, TTarget target, + Expression> targetExpression) { + + if (target == null) return el; + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + var attr = el.Attribute(name); + + if (attr == null) return el; + propertyInfo.SetValue(target, el.Attr(name), null); + return el; + } + + /// + /// Sets an attribute with the value of a property of the same name. + /// + /// The type of the object. + /// The type of the property. + /// The element. + /// The object. + /// The property expression. + /// Itself + public static XElement ToAttr(this XElement el, TTarget target, + Expression> targetExpression) { + + if (target == null) return el; + var propertyInfo = ReflectionHelper.GetPropertyInfo(targetExpression); + var name = propertyInfo.Name; + var val = (TProperty)propertyInfo.GetValue(target, null); + + el.Attr(name, ToString(val)); + return el; + } + + /// + /// Gets the text value of an element as the specified type. + /// + /// The type to parse the element as. + /// The element. + /// The value of the element as type TValue. + public static TValue Val(this XElement el) { + return Parse(el.Value); + } + + /// + /// Sets the value of an element. + /// + /// The type of the value to set. + /// The element. + /// The value. + /// The element. + public static XElement Val(this XElement el, TValue value) { + el.SetValue(ToString(value)); + return el; + } + + /// + /// Serializes the provided value as a string. + /// + /// The type of the value. + /// The value. + /// The string representation of the value. + public static string ToString(T value) { + var type = typeof(T); + if (type == typeof(string)) { + return Convert.ToString(value); + } + if ((!type.IsValueType || Nullable.GetUnderlyingType(type) != null) && + value == null && + type != typeof(string)) { + + return "null"; + } + + if (type == typeof(DateTime) || type == typeof(DateTime?)) { + return XmlConvert.ToString(Convert.ToDateTime(value), + XmlDateTimeSerializationMode.Utc); + } + + if (type == typeof(bool) || + type == typeof(bool?)) { + return Convert.ToBoolean(value) ? "true" : "false"; + } + + if (type == typeof(int) || + type == typeof(int?)) { + + return Convert.ToInt64(value).ToString(CultureInfo.InvariantCulture); + } + + if (type == typeof(double) || + type == typeof(double?)) { + + var doubleValue = (double)(object)value; + if (double.IsPositiveInfinity(doubleValue)) { + return "infinity"; + } + if (double.IsNegativeInfinity(doubleValue)) { + return "-infinity"; + } + return doubleValue.ToString(CultureInfo.InvariantCulture); + } + + if (type == typeof(float) || + type == typeof(float?)) { + + var floatValue = (float)(object)value; + if (float.IsPositiveInfinity(floatValue)) { + return "infinity"; + } + if (float.IsNegativeInfinity(floatValue)) { + return "-infinity"; + } + return floatValue.ToString(CultureInfo.InvariantCulture); + } + + if (type == typeof(decimal) || + type == typeof(decimal?)) { + + var decimalValue = Convert.ToDecimal(value); + return decimalValue.ToString(CultureInfo.InvariantCulture); + } + + throw new NotSupportedException(String.Format("Could not handle type {0}", type.Name)); + } + + /// + /// Parses a string value as the provided type. + /// + /// The destination type + /// The string representation of the value to parse. + /// The parsed value with type T. + public static T Parse(string value) { + var type = typeof(T); + + if (type == typeof(string)) { + return (T)(object)value; + } + if (value == null || + "null".Equals(value, StringComparison.Ordinal) && + ((!type.IsValueType || Nullable.GetUnderlyingType(type) != null))) { + + return default(T); + } + + if ("infinity".Equals(value, StringComparison.Ordinal)) { + if (type == typeof(float) || type == typeof(float?)) return (T)(object)float.PositiveInfinity; + if (type == typeof(double) || type == typeof(double?)) return (T)(object)double.PositiveInfinity; + throw new NotSupportedException(String.Format("Infinity not supported for type {0}", type.Name)); + } + if ("-infinity".Equals(value, StringComparison.Ordinal)) { + if (type == typeof(float)) return (T)(object)float.NegativeInfinity; + if (type == typeof(double)) return (T)(object)double.NegativeInfinity; + throw new NotSupportedException(String.Format("Infinity not supported for type {0}", type.Name)); + } + if (type == typeof(int) || type == typeof(int?)) { + return (T)(object)int.Parse(value, CultureInfo.InvariantCulture); + } + if (type == typeof(bool) || type == typeof(bool?)) { + return (T)(object)value.Equals("true", StringComparison.Ordinal); + } + if (type == typeof(DateTime) || type == typeof(DateTime?)) { + return (T)(object)XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.Utc); + } + if (type == typeof(double) || type == typeof(double?)) { + return (T)(object)double.Parse(value, CultureInfo.InvariantCulture); + } + if (type == typeof(float) || type == typeof(float?)) { + return (T)(object)float.Parse(value, CultureInfo.InvariantCulture); + } + if (type == typeof(decimal) || type == typeof(decimal?)) { + return (T)(object)decimal.Parse(value, CultureInfo.InvariantCulture); + } + throw new NotSupportedException(String.Format("Could not handle type {0}", type.Name)); + } + + /// + /// Gives context to an XElement, enabling chained property operations. + /// + /// The type of the context. + /// The element. + /// The context. + /// The element with context. + public static XElementWithContext With(this XElement el, TContext context) { + return new XElementWithContext(el, context); + } + + /// + /// A wrapper for XElement, with context, for strongly-typed manipulation + /// of an XElement. + /// + /// The type of the context. + public class XElementWithContext { + public XElementWithContext(XElement element, TContext context) { + Element = element; + Context = context; + } + + public XElement Element { get; private set; } + public TContext Context { get; private set; } + + public static implicit operator XElement(XElementWithContext elementWithContext) { + return elementWithContext.Element; + } + + /// + /// Replaces the current context with a new one, enabling chained action on different objects. + /// + /// The type of the new context. + /// The new context. + /// A new XElementWithContext, that has the new context. + public XElementWithContext With(TNewContext context) { + return new XElementWithContext(Element, context); + } + + /// + /// Sets the value of a context property as an attribute of the same name on the element. + /// + /// The type of the property. + /// The property expression. + /// Itself + public XElementWithContext ToAttr( + Expression> targetExpression) { + Element.ToAttr(Context, targetExpression); + return this; + } + + /// + /// Gets an attribute on the element and sets the property of the same name on the context with its value. + /// + /// The type of the property. + /// The property expression. + /// Itself + public XElementWithContext FromAttr( + Expression> targetExpression) { + Element.FromAttr(Context, targetExpression); + return this; + } + + /// + /// Evaluates an attribute from an expression. + /// It's a nice strongly-typed way to read attributes. + /// + /// The type of the property. + /// The property expression. + /// The attribute, ready to be cast. + public TProperty Attr(Expression> expression) { + var propertyInfo = ReflectionHelper.GetPropertyInfo(expression); + var name = propertyInfo.Name; + return Element.Attr(name); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 508c83a2d..63dd8b74b 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -159,6 +159,8 @@ + + @@ -577,6 +579,7 @@ + diff --git a/src/Orchard/Utility/ReflectionHelper.cs b/src/Orchard/Utility/ReflectionHelper.cs new file mode 100644 index 000000000..f1c52c1e5 --- /dev/null +++ b/src/Orchard/Utility/ReflectionHelper.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace Orchard.Utility { + public class ReflectionHelper { + private static readonly ConcurrentDictionary _getterCache = + new ConcurrentDictionary(); + + public delegate TProperty PropertyGetterDelegate(T target); + + /// + /// Gets property info out of a Lambda. + /// + /// The return type of the Lambda. + /// The Lambda expression. + /// The property info. + public static PropertyInfo GetPropertyInfo(Expression> expression) { + var memberExpression = expression.Body as MemberExpression; + if (memberExpression == null) { + throw new InvalidOperationException("Expression is not a member expression."); + } + var propertyInfo = memberExpression.Member as PropertyInfo; + if (propertyInfo == null) { + throw new InvalidOperationException("Expression is not for a property."); + } + return propertyInfo; + } + + /// + /// Gets a delegate from a property expression. + /// + /// The type of the property. + /// The property expression. + /// The delegate. + public static PropertyGetterDelegate GetGetter( + Expression> targetExpression) { + + var propertyInfo = GetPropertyInfo(targetExpression); + return (PropertyGetterDelegate)_getterCache + .GetOrAdd(propertyInfo.Name, + s => Delegate.CreateDelegate(typeof(PropertyGetterDelegate), propertyInfo.GetGetMethod())); + } + } +} \ No newline at end of file