umco / umbraco-ditto

Ditto - the friendly view-model mapper for Umbraco

Home Page:http://our.umbraco.org/projects/developer-tools/ditto

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow UmbracoProperty attribute to choose between own property vs properties collection mapping

mattbrailsford opened this issue · comments

Currently when mapping a property we first check own properties defined on the IPublishedContent instance and if a suitable property isn't found, we check the properties collection instead. The issue is for things like "name", "children", "version" etc, all of which are defined by default on the IPublishedContent, these will hide any properties found in the properties collection with those names so we need to find a way to allow these properties to be mapped in some way.

There is some additional background information in issue #181

I'm struggling to get my head around this one. Why would someone use a property name in the collection that matches a property name on IPublishedContent? To me that's asking for trouble.

My thought would be to check if the type implements IPublished content and if so then skip the first check.

@JimBobSquarePants that was my thought as well really. I do have a local branch where I have implemented some code that, if in debug mode, and when mapping a declared property, if that property is part of IPublishedContent (ie, a property outside the control of the developer) and there is a property existing in the properties collection with that alias that it will write a log message to tell you the fact.

Personally, I'm thinking this is good enough as like you, I think it's a bad idea to be naming properties with the same alias of those reserved property names, but it at least gives the dev something to go off if they do hit a problem.

I've created a branch an commited some code here fc981f8 which performs that check on property being mapped to see if it is an IPublishedContent property and b) if it is hiding an umbraco property and if so logs an warning. There is quite a lot of code in there, but much of it is just to allow me to unit test the fact it works so I need to fake debug mode and also mock the logger so I can confirm the message gets logged.

@mattbrailsford @JimBobSquarePants I have to agree with you both here. I guess this is also to allow for properties Model Builder classes? Those get passed in as IPublishedContent inherited classes to Ditto when that's enabled.

@JimBobSquarePants To answer your question, this happened to both me and a colleague of mine, so it can't be that rare of a situation. Here are some of the properties on IPublishedContent I could see tripping people up:

  • Children
  • CreateDate
  • CreatorName
  • Id
  • ItemType
  • Level
  • Name
  • Parent
  • Path
  • Properties
  • SortOrder
  • UpdateDate
  • Url
  • Version
  • WriterName
  • Item (from the indexer)

In my case, I was using Archetype to create a "picked widget". On this, I allowed the user to pick the "item" (i.e., the widget) with a multinode tree picker. In my colleague's case, he was building a menu with Archetype, and each menu item had "children" menu items (because it is a nested structure). I could also come up with reasonable use cases for each of the above 15 or so property names.

As mentioned before, the only real reason to check for a C# property before an Umbraco property is a potential performance optimization. However, I'm thinking there may be a way to have the best of both worlds.

What about keeping a list of the above property names and use that list to decide which order to map properties? That is, if a property name isn't on the above list, check the C# property followed by the Umbraco property. If a property name is on the above list, check the Umbraco property followed by the C# property.

That way, if you are using Models Builder, you still get the speed of checking C# properties first. And if you happen to name one of your properties with a name in the above list, Ditto won't produce an undesired result.

@Nicholas-Westby checking the declared properties is not just a performance boost, things like Name, Url, etc do not exist in the properties collection so the only way to access them is via the declared properties.

Regarding the "undesired result", the problem is you there are two possible "desired results" depending on what you are doing at the time. What if you have a children property in your collection, but in another mapping you want to access the child nodes instead? Ultimately I think the use of an alias that conflicts with a property on IPublishedProperty should be totally avoided, which is what the warning log I have coded is promoting, and given that the properties in IPublishedContent can't be changed, but the umbraco property aliases can, I think this is why declared properties should take precedence.

@mattbrailsford I was saying that the performance boost came from doing the C# property check before the Umbraco property check, not that doing the C# check at all was the reason for the performance boost.

Regarding fetching the values of the native IPublishedContent C# properties (e.g., Url, Children), I wonder if another processor or a parameter to the existing UmbracoProperty processor might be a solution to help disambiguate which type of property you are after.

@Nicholas-Westby it's an option, but not one I'm particularly keen on as most people will just see them as "Umbraco properties" hence why it's all one attribute at the moment. The difficulty of a parameter or alternative attribute is naming it something meaningful too. I can't think of anything short that would be obvious to others (I've probably changed my terminology several times throughout this issue already :))

[UmbracoProperty(PropertySource = PropertySource.DeclaredProperties)]
[UmbracoProperty(PropertySource = PropertySource.UmbracoProperties)]
[UmbracoProperty(PropertySource = PropertySource.AllProperties)] < Default, own first

?

Does UmbracoProperties suggest properties from the properties collection? Might people think "DeclaredProperties" means the properties they declared in the back office?

I'm leaning more toward "InstanceProperties" rather than "DeclaredProperties". Or maybe "ClassProperties" (while not necessarily perfectly accurate, it is more suggestive of the C# properties).

By the way, I'm not sure what you mean by "own first".

@Nicholas-Westby I'm guess that would be how the current UmbracoProperty works. Looks for a Umbraco property if nothing is there look for an instance property.

@jamiepollock Nope, it currently checks for an instance property and then checks for an Umbraco property.

@Nicholas-Westby sorry, I was basing this off the behaviour of 0.9.0 where the code got an Umbraco Property first before getting an instance property, which has since change in the develop branch.

@mattbrailsford I like the flexibility of the enum PropertySource.

I shall gracefully bow out of the conversation so as not provide any unnecessary noise ;)

Here's one option:

[UmbracoProperty(PropertySource = PropertySource.ClassProperties)]
[UmbracoProperty(PropertySource = PropertySource.UmbracoProperties)]
[UmbracoProperty(PropertySource = PropertySource.ClassThenUmbracoProperties)]
[UmbracoProperty(PropertySource = PropertySource.UmbracoThenClassProperties)]
// The default, which has a list of IPublishedContent property names and intelligently chooses which order to do things in.
[UmbracoProperty(PropertySource = PropertySource.Automatic)]

Probably a bit excessive, IMO. Here's an alternative option:

// Default.
[UmbracoProperty(PropertySource = PropertySource.UmbracoProperties, FallbackPropertySource = PropertySources.ClassProperties)]
// For speed with Models Builder.
[UmbracoProperty(PropertySource = PropertySource.ClassProperties)]

We could also mix ideas between those two options (e.g., the second might also include a PropertySource.Automatic enum).

@Nicholas-Westby I'm currently implementing your option 1 as I though the exact same thing :) (except I don't think the automatic mode is required, otherwise we wouldn't be having this conversation :))

@mattbrailsford Yay! In that case, I would recommend making PropertySource.UmbracoThenClassProperties the default so that it Just Works™ for most people.

ps, the use of "own" properties was kinda borrowed from javacript, where you can ask an object for it's "Own Properties", ie, those declared on the object, not those magic properties that are built into all objects.

@Nicholas-Westby the default will be PropertySource.ClassThenUmbracoProperties, which is as it is now as this will suite most people and I still believe you shouldn't be naming your properties the same. For those that want to flawnt the rules and name their properties after reserved property names, you'll have this option to override.

@mattbrailsford Works for me. Though, might that lead to developers being confused for a bit until they figure out what the problem is (e.g., that they've named their Umbraco property "Children") and that PropertySource is an option that will help them fix that problem?

Maybe a PropertySource.Automatic would help avoid that situation. The only real downside I can see is that it's added complexity (which I recognized is a downside worth factoring in).

@jamiepollock

I was basing this off the behaviour of 0.9.0 where the code got an Umbraco Property first before getting an instance property, which has since change in the develop branch.

Is this true as I can't find anywhere in the repo log that umbraco properties collection was ever checked before an instance property?

Ok, Pull Request is here #185

Please read the body of the PR for what has been implemented.

@Nicholas-Westby I've added an ability to set the DefaultPropertySource which will apply across the board if you are adamant you want the properties collection to be checked first all the time. With regards to the Automatic option though, I just don't think it's worth the development for such an edge case. I've added detailed log messages if you do have any conflicts so I'm going to leave it there.

Closing this issue, any further discussion can happen on the PR #185

@mattbrailsford nope you're right. I misread the code.

Misreading this line at: https://github.com/leekelleher/umbraco-ditto/blob/0.9.0/src/Our.Umbraco.Ditto/ComponentModel/Processors/UmbracoPropertyAttribute.cs#L119

Sorry guys, I was way off the mark yesterday. :/