Closed kjac closed 5 years ago
One workaround could be to create an IPublishedContent
from the IPublishedElement
parameter of PublishedModelFactory.CreateModel(IPublishedElement element)
- something along these lines:
public IPublishedElement CreateModel(IPublishedElement element)
{
// fail fast
if (_modelInfos == null)
return element;
if (!_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo))
return element;
// ReSharper disable once UseMethodIsInstanceOfType
if (modelInfo.ParameterType.IsAssignableFrom(element.GetType()) == false)
{
if (modelInfo.ParameterType == typeof(IPublishedContent))
{
element = new PublishedContentSomethingSomething(element);
}
else
{
throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}.");
}
}
// can cast, because we checked when creating the ctor
return (IPublishedElement) modelInfo.Ctor(element);
}
Where PublishedContentSomethingSomething
is a private implementation of IPublishedContent
:
private class PublishedContentSomethingSomething : IPublishedContent
{
private readonly IPublishedElement _element;
public PublishedContentSomethingSomething(IPublishedElement element)
{
_element = element;
}
public PublishedContentType ContentType => _element.ContentType;
public Guid Key => _element.Key;
public IEnumerable<IPublishedProperty> Properties => _element.Properties;
public IPublishedProperty GetProperty(string alias) => _element.GetProperty(alias);
public int Id { get; }
public string Name { get; }
public string UrlSegment { get; }
public int SortOrder { get; }
public int Level { get; }
public string Path { get; }
public int TemplateId { get; }
public int CreatorId { get; }
public string CreatorName { get; }
public DateTime CreateDate { get; }
public int WriterId { get; }
public string WriterName { get; }
public DateTime UpdateDate { get; }
public string Url { get; }
public string GetUrl(string culture = null) => null;
public PublishedCultureInfo GetCulture(string culture = null) => null;
public IReadOnlyDictionary<string, PublishedCultureInfo> Cultures { get; }
public PublishedItemType ItemType { get; }
public bool IsDraft(string culture = null) => false;
public IPublishedContent Parent { get; }
public IEnumerable<IPublishedContent> Children { get; }
}
It works... but it really feels kinda ugly.
going to look into this today - pretty sure it worked
oh my - I know - hold on
Of course you know. If you didn't we'd be scr***d. š
So... the whole problem comes from... this line in Models Builder source code.
When we generate a model for a content type, how shall we know whether to generate a published content model, or a published element model? Especially since the UI does not support "flagging" elements at the moment? So... Long time ago I implemented a rule that states that
Every content type with an alias ending with "element" is considered an element
And probably made a note "must remember to fix this" - but never fixed.
So,
@zpqrtbnk ah cool. I'll give it a spin soon as I'm able (this afternoon).
@zpqrtbnk it works. But with an unintended side effect: The generated classes lack their "Element" postfix.
Consider this doctype:
The alias is "textElement". But the generated class looks like this:
This of course made ModelsBuilder throw up over the "Text" property, because its original alias was "text" (same as the generated class). Changing the property alias to "bodyText" made it work again, though.
Obviously ;-( Well this is temp, going to deal with it properly.
Have pushed a commit to temp8
which updates ModelsBuilder with a version that supports the "is an element" flag from Core - so in the content type editor you can flag a content type as an element type, and then ModelsBuilder generates it as an element model (and does not strip the trailing "Element" from the alias, if it's there).
Moving the task to review.
How to review: create an element content type, generate models = it should be an element model, and Nested Content should be happy.
@zpqrtbnk oh cool. I will give it a spin.
Indeed it looks good and Nested Content is happy with the element models.
If ModelsBuilder runs in LiveAppData mode, Nested Content breaks with this exception (see full stack trace in the end of the issue description):
This happens when
NestedContentValueConverterBase
passes anIPublishedElement
toPublishedModelFactory.CreateModel(IPublishedElement element)
.PublishedModelFactory
has anIPublishedContent
type registered for the element content type, and thusCreateModel()
fails here.Steps to reproduce
@Model.Value("nestedContentPropertyAlias")
).Umbraco.ModelsBuilder.ModelsMode
toLiveAppData
in<appSettings>
.\src\Umbraco.Web.UI\App_Data\Models
.\src\Umbraco.Web.UI\App_Data\Models
in the Umbraco.Web.UI project.When ModelsBuilder runs in its default mode (PureLive) it works just fine.
Note: In V7 the Nested Content property value converter uses its own logic to convert the items to the concrete implementations of
IPublishedContent
, which is why LiveAppData works in V7.What to do?
I think @zpqrtbnk is the right person to ask that š
In my opinion it makes sense to let Nested Content return
IPublishedElement
items. Question is ifPublishedModelFactory
can be tweaked to handle this case, and how it impacts the generated models (currently they can only be constructed fromIPublishedContent
).Full stack trace
requires #4038