Skip to content
This repository has been archived by the owner on Dec 13, 2021. It is now read-only.

CurrentContent attribute and value-resolver #79

Merged
merged 14 commits into from
Jun 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/Our.Umbraco.Ditto/Attributes/CurrentContentAsAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Umbraco.Core.Models;

namespace Our.Umbraco.Ditto
{
using System;

/// <summary>
/// The current content attribute.
/// Used for providing Ditto with the current <see cref="IPublishedContent"/> object from Umbraco.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CurrentContentAsAttribute : DittoValueResolverAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="CurrentContentAsAttribute"/> class.
/// </summary>
public CurrentContentAsAttribute()
: base(typeof(CurrentContentAsValueResolver))
{ }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Our.Umbraco.Ditto
{
using System;
using System.ComponentModel;
using System.Globalization;

using global::Umbraco.Core.Models;

/// <summary>
/// The current content value resolver.
/// </summary>
public class CurrentContentAsValueResolver : DittoValueResolver<CurrentContentAsAttribute>
{
/// <summary>
/// Resolves the value.
/// Gets the current <see cref="IPublishedContent"/> from Umbraco.
/// </summary>
/// <param name="context">
/// An <see cref="T:System.ComponentModel.ITypeDescriptorContext" /> that provides a format context.
/// </param>
/// <param name="attribute">
/// The <see cref="CurrentContentAsAttribute"/> containing additional information
/// indicating how to resolve the property.
/// </param>
/// <param name="culture">The <see cref="T:System.Globalization.CultureInfo" /> to use as the current culture.</param>
/// <returns>
/// The <see cref="object"/> representing the raw value.
/// </returns>
public override object ResolveValue(ITypeDescriptorContext context, CurrentContentAsAttribute attribute, CultureInfo culture)
{
// NOTE: [LK] In order to prevent an infinite loop / stack-overflow, we check if the
// property's type matches the containing model's type, then we throw an exception.
if (context.PropertyDescriptor.PropertyType == context.PropertyDescriptor.ComponentType)
{
throw new InvalidOperationException(
string.Format("Unable to process property type '{0}', it is the same as the containing model type.",
context.PropertyDescriptor.PropertyType.Name));
}

return ((IPublishedContent)context.Instance).As(context.PropertyDescriptor.PropertyType);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Our.Umbraco.Ditto
using Umbraco.Core;

namespace Our.Umbraco.Ditto
{
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -107,5 +109,26 @@ public static IEnumerable<Type> GetBaseTypes(this Type type)
type = type.BaseType;
}
}

/// <summary>
/// Gets the type of the enumerable object
/// </summary>
/// <param name="type">The <see cref="Type"/> to check.</param>
/// <returns>The type of the enumerable.</returns>
public static Type GetEnumerableType(this Type type)
{
// if it's not an enumerable why do you call this method all ?
if (!type.IsEnumerable())
return null;

var interfaces = type.GetInterfaces().ToList();
if (type.IsInterface && interfaces.All(i => i != type))
{
interfaces.Add(type);
}

return interfaces.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(i => i.GetGenericArguments()[0]).FirstOrDefault();
}
}
}
14 changes: 14 additions & 0 deletions src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,20 @@ public static class PublishedContentExtensions
// Simple types
result = propertyValue;
}
else if (propertyValue is IPublishedContent && propertyInfo.PropertyType.IsClass)
{
// If the property value is an IPublishedContent, then we can use Ditto to map to the target type.
result = ((IPublishedContent)propertyValue).As(propertyInfo.PropertyType);
}
else if (propertyValue != null
&& propertyValue.GetType().IsEnumerableOfType(typeof(IPublishedContent))
&& propertyInfo.PropertyType.IsEnumerable()
&& propertyInfo.PropertyType.GetEnumerableType() != null
&& propertyInfo.PropertyType.GetEnumerableType().IsClass)
{
// If the property value is IEnumerable<IPublishedContent>, then we can use Ditto to map to the target type.
result = ((IEnumerable<IPublishedContent>)propertyValue).As(propertyInfo.PropertyType.GetEnumerableType());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add additional check for IEnumerable< IPublishedContent> where to result type is IEnumerable< Entity> and do ditto cast there too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this as simple as checking for propertyValue is IEnumerable<IPublishedContent>? or do we have to get funky with all the .IsGenericType checks?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I think might be able to do IsAssignableTo

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propertyValue.IsEnumerableOfType(typeof(IPublishedContent))

Honeymoon is next week 😉

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @JimBobSquarePants!

(See you next week - does it sound weird that we'll see you on your honeymoon?)

else
{
using (DisposableTimer.DebugDuration<object>(string.Format("TypeConverter ({0}, {1})", content.Id, propertyInfo.Name), "Complete"))
Expand Down
2 changes: 2 additions & 0 deletions src/Our.Umbraco.Ditto/Our.Umbraco.Ditto.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Attributes\AppSettingAttribute.cs" />
<Compile Include="Attributes\CurrentContentAsAttribute.cs" />
<Compile Include="Attributes\DittoIgnoreAttribute.cs" />
<Compile Include="Attributes\DittoValueResolverAttribute.cs" />
<Compile Include="Attributes\UmbracoDictionaryAttribute.cs" />
Expand All @@ -98,6 +99,7 @@
<Compile Include="ComponentModel\TypeConverters\DittoContentPickerConverter.cs" />
<Compile Include="ComponentModel\TypeConverters\DittoHtmlStringConverter.cs" />
<Compile Include="ComponentModel\ValueResolvers\AppSettingValueResolver.cs" />
<Compile Include="ComponentModel\ValueResolvers\CurrentContentAsValueResolver.cs" />
<Compile Include="ComponentModel\ValueResolvers\DittoValueResolver.cs" />
<Compile Include="ComponentModel\ValueResolvers\UmbracoDictionaryValueResolver.cs" />
<Compile Include="ComponentModel\ValueResolvers\UmbracoPropertyValueResolver.cs" />
Expand Down
75 changes: 75 additions & 0 deletions tests/Our.Umbraco.Ditto.Tests/CurrentContentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
namespace Our.Umbraco.Ditto.Tests
{
using System;
using NUnit.Framework;
using Our.Umbraco.Ditto.Tests.Mocks;

[TestFixture]
public class CurrentContentTests
{
public class MyModel
{
[CurrentContentAs]
public MyMetaDataModel MetaData1 { get; set; }

public MyMetaDataModel MetaData2 { get; set; }
}

public class MyMetaDataModel
{
public string MetaTitle { get; set; }

public string MetaDescription { get; set; }

public string MetaKeywords { get; set; }
}

public class MyCircularReferenceModel
{
[CurrentContentAs]
public MyCircularReferenceModel MyCircularReferenceProperty { get; set; }
}

[Test]
public void CurrentContent_Property_Mapped()
{
var metaTitle = new PublishedContentPropertyMock
{
Alias = "metaTitle",
Value = "This is the meta title"
};
var metaDescription = new PublishedContentPropertyMock
{
Alias = "metaDescription",
Value = "This is the meta description"
};
var metaKeywords = new PublishedContentPropertyMock
{
Alias = "metaKeywords",
Value = "these,are,meta,keywords"
};

var content = new PublishedContentMock
{
Properties = new[] { metaTitle, metaDescription, metaKeywords }
};

var model = content.As<MyModel>();

Assert.That(model, Is.Not.Null);
Assert.That(model.MetaData1, Is.Not.Null, "We expect the property to be populated.");
Assert.That(model.MetaData1, Is.TypeOf<MyMetaDataModel>());
Assert.That(model.MetaData2, Is.Null, "We expect the property not to be populated.");
}

[Test]
public void CurrentContent_InfineLoop_Check()
{
var content = new PublishedContentMock();

TestDelegate code = () => { content.As<MyCircularReferenceModel>(); };

Assert.Throws<InvalidOperationException>(code);
}
}
}
3 changes: 3 additions & 0 deletions tests/Our.Umbraco.Ditto.Tests/Models/ComplexModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public class ComplexModel
[UmbracoProperty("Id")]
[TypeConverter(typeof(MockPublishedContentConverter))]
public IPublishedContent MyPublishedContent { get; set; }

[AppSetting("MyAppSettingKey")]
public string MyAppSettingProperty { get; set; }
}

public class NameValueResovler : DittoValueResolver
Expand Down
25 changes: 25 additions & 0 deletions tests/Our.Umbraco.Ditto.Tests/Models/InnerContentModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Globalization;

namespace Our.Umbraco.Ditto.Tests.Models
{
public class InnerContentModel
{
public int Id { get; set; }

public string Name { get; set; }

public InnerContentModel2 ContentProp { get; set; }

public IEnumerable<InnerContentModel2> ContentListProp { get; set; }
}

public class InnerContentModel2
{
public int Id { get; set; }

public string Name { get; set; }

public string InnerProp { get; set; }
}
}
2 changes: 2 additions & 0 deletions tests/Our.Umbraco.Ditto.Tests/Our.Umbraco.Ditto.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,12 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="CurrentContentTests.cs" />
<Compile Include="AppSettingsTests.cs" />
<Compile Include="Mocks\PublishedContentMock.cs" />
<Compile Include="Mocks\PublishedContentPropertyMock.cs" />
<Compile Include="MockValueResolverTests.cs" />
<Compile Include="Models\InnerContentModel.cs" />
<Compile Include="Models\ComplexModel.cs" />
<Compile Include="Models\PrefixedModel.cs" />
<Compile Include="Models\SimpleModel.cs" />
Expand Down