diff --git a/.gitignore b/.gitignore index 67bcafd9ca..9496a86e70 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,5 @@ src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/ src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/ build/_BuildOutput/ tools/NDepend/ +src/*.vspx +src/*.psess diff --git a/src/Umbraco.Core/Models/PropertyExtensions.cs b/src/Umbraco.Core/Models/PropertyExtensions.cs index b03f078a9f..ae01532c87 100644 --- a/src/Umbraco.Core/Models/PropertyExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyExtensions.cs @@ -22,10 +22,15 @@ namespace Umbraco.Core.Models /// Xml of the property and its value public static XElement ToXml(this Property property) { - string nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias(); + return property.ToXml(ApplicationContext.Current.Services.DataTypeService); + } + + internal static XElement ToXml(this Property property, IDataTypeService dataTypeService) + { + var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias(); var xd = new XmlDocument(); - XmlNode xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, ""); + var xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, ""); //Add the property alias to the legacy schema if (UmbracoSettings.UseLegacyXmlSchema) @@ -37,9 +42,17 @@ namespace Umbraco.Core.Models //This seems to fail during testing //SD: With the new null checks below, this shouldn't fail anymore. - var dt = property.PropertyType.DataType(property.Id); + var dt = property.PropertyType.DataType(property.Id, dataTypeService); if (dt != null && dt.Data != null) - { + { + //We've already got the value for the property so we're going to give it to the + // data type's data property so it doesn't go re-look up the value from the db again. + var defaultData = dt.Data as IDataValueSetter; + if (defaultData != null) + { + defaultData.SetValue(property.Value, property.PropertyType.DataTypeDatabaseType.ToString()); + } + xmlNode.AppendChild(dt.Data.ToXMl(xd)); } diff --git a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs index b8ee04df33..304d7e7b9d 100644 --- a/src/Umbraco.Core/Models/PropertyTypeExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTypeExtensions.cs @@ -1,4 +1,5 @@ -using umbraco.interfaces; +using Umbraco.Core.Services; +using umbraco.interfaces; namespace Umbraco.Core.Models { @@ -9,6 +10,7 @@ namespace Umbraco.Core.Models /// /// PropertyType that references a DataType /// Id of the Property which references this DataType through its PropertyType + /// /// /// /// This extension method is left internal because we don't want to take @@ -16,10 +18,10 @@ namespace Umbraco.Core.Models /// be replaced by PropertyEditors. It is however needed to generate xml /// for a property/propertytype when publishing. /// - internal static IDataType DataType(this PropertyType propertyType, int propertyId) + internal static IDataType DataType(this PropertyType propertyType, int propertyId, IDataTypeService dataTypeService) { Mandate.ParameterNotNull(propertyType, "propertyType"); - var dataType = ApplicationContext.Current.Services.DataTypeService.GetDataTypeById(propertyType.DataTypeId); + var dataType = dataTypeService.GetDataTypeById(propertyType.DataTypeId); if (dataType == null) { return null; diff --git a/src/Umbraco.Tests/Models/DataValueSetterTests.cs b/src/Umbraco.Tests/Models/DataValueSetterTests.cs new file mode 100644 index 0000000000..db59217166 --- /dev/null +++ b/src/Umbraco.Tests/Models/DataValueSetterTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Diagnostics; +using System.Xml; +using NUnit.Framework; +using Rhino.Mocks; +using Rhino.Mocks.Interfaces; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Tests.TestHelpers; +using umbraco.cms.businesslogic.datatype; +using umbraco.interfaces; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DataValueSetterTests : BaseUmbracoApplicationTest + { + protected override void FreezeResolution() + { + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper()); + base.FreezeResolution(); + } + + [Test] + public void LoadValueFromDatabase_Is_Not_Called_When_SetValue_Is_Used() + { + // Arrange + var baseDataType = MockRepository.GenerateStub(); + var dataTypeData = MockRepository.GenerateMock(baseDataType); + dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation); + + // Act + + ((IDataValueSetter)dataTypeData).SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString()); + var val = dataTypeData.Value; + + // Assert + + dataTypeData.AssertWasNotCalled(data => data.LoadValueFromDatabase()); + } + + [Test] + public void LoadValueFromDatabase_Is_Called_When_SetValue_Is_Not_Used() + { + // Arrange + var baseDataType = MockRepository.GenerateStub(); + var dataTypeData = MockRepository.GenerateMock(baseDataType); + dataTypeData + .Stub(data => data.LoadValueFromDatabase()).WhenCalled(invocation => Debug.WriteLine("asdf")); + dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation); + + // Act + + var val = dataTypeData.Value; + + // Assert + + dataTypeData.AssertWasCalled(data => data.LoadValueFromDatabase()); + } + + [Test] + public void SetValue_Is_Called_When_Executing_ToXml_On_A_Property_With_DataType_That_Implements_IDataValueSetter() + { + // Arrange + var dataTypeId = Guid.NewGuid(); + + var dataTypeData = MockRepository.GenerateMock(); + dataTypeData + .Stub(data => data.ToXMl(Arg.Is.Anything)) + .Return(null) // you have to call Return() even though we're about to override it + .WhenCalled(invocation => + { + var xmlDoc = (XmlDocument) invocation.Arguments[0]; + invocation.ReturnValue = xmlDoc.CreateElement("test"); + }); + + var dataType = MockRepository.GenerateStub(); + dataType.Stub(type => type.Data).Return(dataTypeData); + + var dataTypeSvc = MockRepository.GenerateStub(); + dataTypeSvc.Stub(service => service.GetDataTypeById(dataTypeId)).Return(dataType); + + var property = new Property( + 1234, + Guid.NewGuid(), + new PropertyType(dataTypeId, DataTypeDatabaseType.Nvarchar) + { + Alias = "test" + }, "Hello world"); + + // Act + + var xml = property.ToXml(dataTypeSvc); + + // Assert + + ((IDataValueSetter)dataTypeData).AssertWasCalled(setter => setter.SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString())); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/PackagingServiceTests.cs b/src/Umbraco.Tests/Services/PackagingServiceTests.cs new file mode 100644 index 0000000000..abdfb2f9f3 --- /dev/null +++ b/src/Umbraco.Tests/Services/PackagingServiceTests.cs @@ -0,0 +1,41 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + //[TestFixture] + //public class PackagingServiceTests : BaseServiceTest + //{ + // [Test] + // public void Export_Content() + // { + // var yesNo = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.TrueFalse)); + // var txtField = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.Textbox)); + + // var contentWithDataType = MockedContentTypes.CreateSimpleContentType( + // "test", + // "Test", + // new PropertyTypeCollection( + // new PropertyType[] + // { + // new PropertyType(new DataTypeDefinition(-1, txtField.Id) + // { + // Name = "Testing Textfield", DatabaseType = DataTypeDatabaseType.Ntext + // }), + // new PropertyType(new DataTypeDefinition(-1, yesNo.Id) + // { + // Name = "Testing intfield", DatabaseType = DataTypeDatabaseType.Integer + // }) + // })); + + // var content = MockedContent.CreateSimpleContent(contentWithDataType); + // content.Name = "Test"; + + // var exported = ServiceContext.PackagingService.Export(content); + + // } + //} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 5a98e303a4..b1a5b8b925 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -46,6 +46,8 @@ namespace Umbraco.Tests.TestHelpers //Used to flag if its the first test in the current fixture private bool _isFirstTestInFixture = false; + private ApplicationContext _appContext; + [SetUp] public override void Initialize() { @@ -59,7 +61,7 @@ namespace Umbraco.Tests.TestHelpers var dbFactory = new DefaultDatabaseFactory( GetDbConnectionString(), GetDbProviderName()); - ApplicationContext.Current = new ApplicationContext( + _appContext = new ApplicationContext( //assign the db context new DatabaseContext(dbFactory), //assign the service context @@ -79,7 +81,12 @@ namespace Umbraco.Tests.TestHelpers //ensure the configuration matches the current version for tests SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); } - + + protected override void SetupApplicationContext() + { + ApplicationContext.Current = _appContext; + } + /// /// The database behavior to use for the test/fixture /// diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index dbf87b4aee..d898ef6c5f 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -25,6 +25,8 @@ namespace Umbraco.Tests.TestHelpers SetupPluginManager(); + SetupApplicationContext(); + FreezeResolution(); } @@ -40,7 +42,7 @@ namespace Umbraco.Tests.TestHelpers ApplicationContext.Current = null; ResetPluginManager(); } - + /// /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 66764feae5..747cb52cee 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -185,9 +185,11 @@ + + diff --git a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs index d72212aa5f..a974fda7c2 100644 --- a/src/umbraco.cms/businesslogic/datatype/DefaultData.cs +++ b/src/umbraco.cms/businesslogic/datatype/DefaultData.cs @@ -14,7 +14,7 @@ namespace umbraco.cms.businesslogic.datatype /// /// Default implementation of the IData interface that stores data inside the Umbraco database. /// - public class DefaultData : IData, IDataWithPreview + public class DefaultData : IData, IDataWithPreview, IDataValueSetter { private int _propertyId; private object _value; @@ -57,10 +57,29 @@ namespace umbraco.cms.businesslogic.datatype _value = InitValue; } + /// + /// This is here for performance reasons since in some cases we will have already resolved the value from the db + /// and want to just give this object the value so it doesn't go re-look it up from the database. + /// + /// + /// + void IDataValueSetter.SetValue(object val, string strDbType) + { + _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. + var dbType = BaseDataType.GetDBType(strDbType); + var fieldName = BaseDataType.GetDataFieldName(dbType); + _dataType.SetDataTypeProperties(fieldName, dbType); + + //ensures that it doesn't go back to the db + _valueLoaded = true; + } + /// /// Loads the data value from the database. /// - protected virtual void LoadValueFromDatabase() + protected internal virtual void LoadValueFromDatabase() { var sql = new Sql(); sql.Select("*") @@ -243,5 +262,7 @@ namespace umbraco.cms.businesslogic.datatype } #endregion + + } } diff --git a/src/umbraco.interfaces/IData.cs b/src/umbraco.interfaces/IData.cs index c0580f0fee..17a374ff05 100644 --- a/src/umbraco.interfaces/IData.cs +++ b/src/umbraco.interfaces/IData.cs @@ -3,6 +3,15 @@ using System.Xml; namespace umbraco.interfaces { + /// + /// Internal interface used to decorate any IData that can be optimized when exporting + /// XML like in the packaging service. Instead of relying on the IData to go get the value + /// from the db, any IData that implements this can have it's value set from the packaging service. + /// + internal interface IDataValueSetter + { + void SetValue(object val, string strDbType); + } /// /// The IData is part of the IDataType interface for creating new data types in the umbraco backoffice. diff --git a/src/umbraco.interfaces/Properties/AssemblyInfo.cs b/src/umbraco.interfaces/Properties/AssemblyInfo.cs index 06f4dbdf64..709f231e00 100644 --- a/src/umbraco.interfaces/Properties/AssemblyInfo.cs +++ b/src/umbraco.interfaces/Properties/AssemblyInfo.cs @@ -9,4 +9,11 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("umbraco.interfaces")] [assembly: AssemblyDescription("Core assembly containing legacy interfaces")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Umbraco CMS")] \ No newline at end of file +[assembly: AssemblyProduct("Umbraco CMS")] + +[assembly: InternalsVisibleTo("cms")] +[assembly: InternalsVisibleTo("Umbraco.Core")] +[assembly: InternalsVisibleTo("Umbraco.Tests")] + +//allow this to be mocked in our unit tests +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/umbraco.sln b/src/umbraco.sln index fee2486039..eba2ad7b9b 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -67,6 +67,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C ..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99794542-89EC-43BA-88BE-E31A9D61423B}" + ProjectSection(SolutionItems) = preProject + Performance1.psess = Performance1.psess + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -157,4 +162,7 @@ Global {73529637-28F5-419C-A6BB-D094E39DE614} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} {B555AAE6-0F56-442F-AC9F-EF497DB38DE7} = {DD32977B-EF54-475B-9A1B-B97A502C6E58} EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal