Casting in GetPropertyValue with Nested Content only works with IEnumerable<IPublishedContent>
It seems when using GetPropertyValue with Nested Content and using the overload to cast to a specific type T it only work when casting to IEnumerable<IPublishedContent>.
E.g. if I have some different transport methods in Nested Content, which use a document type Transport then I can get the transport methods with the following code:
node.GetPropertyValue<>
when using ModelsBuilder if would be great to be able to cast this as IEnumerable<Transport> but this will return null.
node.GetPropertyValue<IEnumerable<IPublishedContent>("transportMethods").Select(x => new Transport(x));
Not sure if this also is an issue with latest version of Umbraco and Nested Content in core?
Furthermore if I know node is a specific document type (in my case the property might exists on different node types), I can also cast to this type with ModelsBuilder model and use the property, which return IEnumerable<IPublishedContent>, but it has the same issues, because the property use GetPropertyValue as the example above.
node.GetPropertyValue<IEnumerable<IPublishedContent>("transportMethods").Select(x => new Transport(x));
Is how I do it
if you have multiple "documenttypes" in your nested content type, then you could return them as IEnumerable of IPublishedContent , and then do a switch to then cast the node to the desired model, based upon its doctypealias
Takes a bit of tapping on the keyboard to get working, but, well worth it.
foreach (var module in Model.MyNestedContentModules.OrderBy(x => x.SortOrder))
{
switch (module.DocumentTypeAlias)
{
case "moduleText":
@Html.Partial("Modules/Text", new ModuleText(module))
break;
case "moduleTextAndImage":
@Html.Partial("Modules/TextAndImage", new ModuleTextAndImage(module))
break;
case "moduleGallery":
@Html.Partial("Modules/Gallery", new ModuleGallery(module))
break;
case "moduleLogos":
@Html.Partial("Modules/Logos", new ModuleLogos(module))
break;
case "moduleVideo":
@Html.Partial("Modules/Video", new ModuleVideo(module))
break;
default:
break;
}
}
That might work well to seperate modules like this, however if the transport methods were different document types, e.g. Car, Bus, Train, Flight you probably just want it as one collection as IEnumerable<IPublishedContent> and loop these and maybe to something specific based on each type. Of course if much of the layout is different depending on each type, it is great to move this stuff to seperate partials.
My example is purely just trying to demonstrate, how you can roll through the NC, and then assign to a different Model. What you actual do with the "model" is then up to you, and specific to your own needs
As always with Umbraco - there's 1000000000001 ways to skin a cat.
I know I can create a new model like in the examples, but it doesn't solve my initial question on why this only works when using GetPropertyValue and casting to IEnumerable<IPublishedContent> and OfType<T> only works when T is IPublishedContent.
I'd questioned more why you are using "GetPropertyValue" still, and not using the ModelsBuilder, which is the way you are shown in the "Umbraco Training".
and as I mentioned in my initial post, when using a specific property in a ModelsBuilder generated property it actually does use GetPropertyValue under the hood.
E.g.
var transportMethods = node.OfType<Destination>().TransportMethods;
in this case the property TransportMethods returns an IEnumerable<IPublishedContent>.
This is because ModelsBuilder generates this part of the models in DLL mode (which is included in compiled Umbraco.Web.PublishedContentModels.dll) in e.g. /App_Data/Models/Destination.generated.cs:
[ImplementPropertyType("transportMethods")]
public IEnumerable<IPublishedContent> TransportMethods
{
get { return this.GetPropertyValue<IEnumerable<IPublishedContent>>("transportMethods"); }
}
So it is the same as using GetPropertyValue<T>.
Using ModelsBuilder doesn't mean you can't/shouldn't use GetPropertyValue or GetPropertyValue<T> - these methods are still available to use.
There are quite a few things going on here... it can be confusing which bit is doing what. I'll try to explain in due course, but ultimately the specific issue is that .NET can't cast a IEnumerable<IPublishedContent> object to IEnumerable<Transport> object ... it just doesn't know how to do it.
OK, to walk through what is happening...
NestedContent's ValueConverter defines what the expected output object-type will be... e.g. typeof(IEnumerable<IPublishedContent>)
ModelsBuilder explicitly uses this value. Of course, due to the nature of NestedContent, we can't use explicit document-type models, due to polymorphism, etc. The common interface has to be IPublishedContent. Keep in mind this is only for ModelsBuilder, (or anything else that uses the GetPropertyValueType call).
With that said, in NestedContent, it does call the ModelBuilder's factory to get the correct document-type object/model...
Meaning that if you call node.GetPropertyValue<IEnumerable<IPublishedContent>>("transportMethods"), you will actually get a enumerable of Transport objects.
The issue is now with how to cast them to the appropriate object-type/model - for intellisense, etc.
I'm not sure why the .OfType<T> extension method doesn't work here. Looking at the source-code, it should be casting accordingly. I guess there must be an upcasting issue?
var items = node.GetPropertyValue<IEnumerable<IPublishedContent>>("transportMethods");
var t1 = items.Cast<Transport>();
var t2 = items.OfType<Transport>();
When outputting .Count() values of t1 and t2, then @t2.Count() returns 0 and @t1.Count() returns an ysod which says Our.Umbraco.NestedContent.Models.DetachedPublishedContent cannot be converted to the type Umbraco.Web.PublishedContentModels.Transport.
Bjarne, can I check which version of NestedContent are you using?
I'm assuming it's the latest standalone package version, v0.5.0? (which is the version that we ported into Umbraco core).
If it's an earlier version, than that didn't support ModelsBuilder, it was only introduced in v0.5.0.
Where I think it is a bit inconsistent with Nested Content is for example with OfType extension method.
E.g. on a destination node I have a MNTP (the legacy one with alias Umbraco.MultiNodeTreePicker) to pick multiple popular hotels.
I can then use one of the following:
var destination = node.OfType<Destination>();
var popularHotels = destination.GetPropertyValue<IEnumerable<IPublishedContent>>("popularHotels").OfType<Hotel>();
or
var destination = node.OfType<Destination>();
var popularHotels = destination.PopularHotels.OfType<Hotel>();
with the same .OfType<T> on Nested Content IEnumerable<IPublishedContent> it returns null. (maybe this has been fixed is a newer version of Nested Content or in the core Nested Content).
However the following also doesn't work the MNTP (I haven't tested this with the newer Umbraco.MultiNodeTreePicker2):
Yes, the project is an older project, which has been upgraded several times and later migrated to Umbraco Cloud.
I am not sure why the existing Nested Content package hasn't been upgraded to v0.5.0, but it is now running on Umbraco v7.10.4, so Nested Content is included in core and we use this for new properties based on Nested Content and I think we have replaced some of the properties to use the core Nested Content property editor. However some doc types is using the Nested Content package and I think there wasn't an easy way to switch to these to use the core Nested Content without breaking stuff and since many nodes are based on this doc type.
So I guess that why it still run with the legacy Nested Content package, but I will check if we can upgrade this to v0.5.0 or maybe replace with the core Nested Content package at some point.
Note, if you do change the property-editor via the SQL UPDATE, then keep in mind that using Cloud, the Deploy JSON schema files need to be updated too ... I wouldn't suggest doing it manually. I'd probably go via the back-office and re-save the data-types.
Thanks for the link and elaboration on how to switch to core Nested Content. We might have a closer look at this at some point, when we do some other updates on the project.
Casting in GetPropertyValue with Nested Content only works with IEnumerable<IPublishedContent>
It seems when using
GetPropertyValue
with Nested Content and using the overload to cast to a specific typeT
it only work when casting toIEnumerable<IPublishedContent>
.E.g. if I have some different transport methods in Nested Content, which use a document type
Transport
then I can get the transport methods with the following code: node.GetPropertyValue<>when using ModelsBuilder if would be great to be able to cast this as
IEnumerable<Transport
> but this will return null.and furthermore this also returns null when using
OfType
:and workaround is to use the following:
Not sure if this also is an issue with latest version of Umbraco and Nested Content in core?
Furthermore if I know
node
is a specific document type (in my case the property might exists on different node types), I can also cast to this type with ModelsBuilder model and use the property, which returnIEnumerable<IPublishedContent>
, but it has the same issues, because the property useGetPropertyValue
as the example above.If it still is an issue in core, would it be possible to cast this to
IEnumerable<T>
or/and useOfType<T>
on this?/Bjarne
Is how I do it
if you have multiple "documenttypes" in your nested content type, then you could return them as IEnumerable of IPublishedContent , and then do a switch to then cast the node to the desired model, based upon its doctypealias
Takes a bit of tapping on the keyboard to get working, but, well worth it.
That might work well to seperate modules like this, however if the transport methods were different document types, e.g.
Car
,Bus
,Train
,Flight
you probably just want it as one collection asIEnumerable<IPublishedContent>
and loop these and maybe to something specific based on each type. Of course if much of the layout is different depending on each type, it is great to move this stuff to seperate partials.My example is purely just trying to demonstrate, how you can roll through the NC, and then assign to a different Model. What you actual do with the "model" is then up to you, and specific to your own needs
As always with Umbraco - there's 1000000000001 ways to skin a cat.
Good luck!
I know I can create a new model like in the examples, but it doesn't solve my initial question on why this only works when using
GetPropertyValue
and casting toIEnumerable<IPublishedContent>
andOfType<T>
only works when T isIPublishedContent
.This is Umbraco :-)
You have to expect quirks like this :-)
I'd questioned more why you are using "GetPropertyValue" still, and not using the ModelsBuilder, which is the way you are shown in the "Umbraco Training".
Maybe you need to go on some Training? :-)
@Paul
I have been on the training courses.
and as I mentioned in my initial post, when using a specific property in a ModelsBuilder generated property it actually does use
GetPropertyValue
under the hood.E.g.
in this case the property
TransportMethods
returns anIEnumerable<IPublishedContent>
.This is because ModelsBuilder generates this part of the models in DLL mode (which is included in compiled Umbraco.Web.PublishedContentModels.dll) in e.g.
/App_Data/Models/Destination.generated.cs
:So it is the same as using
GetPropertyValue<T>
.Using ModelsBuilder doesn't mean you can't/shouldn't use
GetPropertyValue
orGetPropertyValue<T>
- these methods are still available to use.Hi Bjarne,
To expand on my "it's more a .NET casting issue" tweet reply...
There are quite a few things going on here... it can be confusing which bit is doing what. I'll try to explain in due course, but ultimately the specific issue is that .NET can't cast a
IEnumerable<IPublishedContent>
object toIEnumerable<Transport>
object ... it just doesn't know how to do it.OK, to walk through what is happening...
NestedContent's ValueConverter defines what the expected output object-type will be... e.g.
typeof(IEnumerable<IPublishedContent>)
See here: https://github.com/umbraco/Umbraco-CMS/blob/release-7.11.1/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs#L33
ModelsBuilder explicitly uses this value. Of course, due to the nature of NestedContent, we can't use explicit document-type models, due to polymorphism, etc. The common interface has to be
IPublishedContent
.Keep in mind this is only for ModelsBuilder, (or anything else that uses the
GetPropertyValueType
call).With that said, in NestedContent, it does call the ModelBuilder's factory to get the correct document-type object/model...
See here: https://github.com/umbraco/Umbraco-CMS/blob/release-7.11.1/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentPublishedPropertyTypeExtensions.cs#L114
Meaning that if you call
node.GetPropertyValue<IEnumerable<IPublishedContent>>("transportMethods")
, you will actually get a enumerable ofTransport
objects.The issue is now with how to cast them to the appropriate object-type/model - for intellisense, etc.
I'm not sure why the
.OfType<T>
extension method doesn't work here. Looking at the source-code, it should be casting accordingly. I guess there must be an upcasting issue?https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L1027
Personally I'd try the
.Cast<T>
extension method... see if that works?Cheers,
- Lee
Hi Lee
Thank you for your elaboration of this.
I have tried with
.Cast<T>
on this:When outputting
.Count()
values oft1
andt2
, then@t2.Count()
returns 0 and@t1.Count()
returns an ysod which saysOur.Umbraco.NestedContent.Models.DetachedPublishedContent
cannot be converted to the typeUmbraco.Web.PublishedContentModels.Transport
.Bjarne, can I check which version of NestedContent are you using? I'm assuming it's the latest standalone package version, v0.5.0? (which is the version that we ported into Umbraco core).
If it's an earlier version, than that didn't support ModelsBuilder, it was only introduced in v0.5.0.
In the
packages.config
it has listed v0.3.0which also match the version number of the assembly
Our.Umbraco.NestedContent.dll
I am not sure if there are some changes in the later versions, which might break some of the existing functionality where it is using Nested Content.
Where I think it is a bit inconsistent with Nested Content is for example with
OfType
extension method.E.g. on a destination node I have a MNTP (the legacy one with alias Umbraco.MultiNodeTreePicker) to pick multiple popular hotels.
I can then use one of the following:
or
with the same
.OfType<T>
on Nested ContentIEnumerable<IPublishedContent>
it returns null. (maybe this has been fixed is a newer version of Nested Content or in the core Nested Content).However the following also doesn't work the MNTP (I haven't tested this with the newer Umbraco.MultiNodeTreePicker2):
OK, ModelBuilder support was added to Nested Content in v0.5.0.
None of this is going to work out-of-the-box with v0.3.0.
There aren't any breaking-changes between v0.3.0 up to v0.5.0. Here are the release notes for v0.4.0 and v0.5.0.
If you don't want to upgrade it, then your only option is to do as you already are...
I am curious why you're using v0.3.0 - since it was released over 2 years ago. Are you working on old build?
Cheers,
- Lee
Thanks Lee
Yes, the project is an older project, which has been upgraded several times and later migrated to Umbraco Cloud.
I am not sure why the existing Nested Content package hasn't been upgraded to v0.5.0, but it is now running on Umbraco v7.10.4, so Nested Content is included in core and we use this for new properties based on Nested Content and I think we have replaced some of the properties to use the core Nested Content property editor. However some doc types is using the Nested Content package and I think there wasn't an easy way to switch to these to use the core Nested Content without breaking stuff and since many nodes are based on this doc type.
So I guess that why it still run with the legacy Nested Content package, but I will check if we can upgrade this to v0.5.0 or maybe replace with the core Nested Content package at some point.
/Bjarne
It is possible to hot-swap the package version to the core version, see this thread for details: https://our.umbraco.com/forum/using-umbraco-and-getting-started/90229-migrating-nested-content-to-77#comment-284837
Note, if you do change the property-editor via the SQL UPDATE, then keep in mind that using Cloud, the Deploy JSON schema files need to be updated too ... I wouldn't suggest doing it manually. I'd probably go via the back-office and re-save the data-types.
Thanks for the link and elaboration on how to switch to core Nested Content. We might have a closer look at this at some point, when we do some other updates on the project.
is working on a reply...