Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 27, 2014 @ 22:34
    Matt Brailsford
    0

    IPublishedProperty has no context

    Hey Guys,

    So I'm working on vorto and am trying to create some extension methods for IPublishedContent to be able to retrieve the specific value which will be something like this:

    @Model.Content.GetVortoValue<string>("myProp")
    

    The important thing here is that <string> refers to the value type of the end value I want to retrieve.

    So, under the hood of this method, I'm basically calling content.GetProperty(propertyAlias, recursive) to retrieve my property, and then get my dictionary of values (vorto stores multiple values as a dictionary). Once I have my dictionary, I then lookup the relevant value and store it locally.

    Now, the value I currently have is just the raw stored value, so now I need to pass it through the value converters manually to convert it to what the end developer actually wants.

    There is no real easy way to do this yet (this is a question for another thread), but ultimately there is a way if you know the DTDID / PropEditorAlias of the datatype that created the value. So, this I do know as it is stored in my vorto datatypes prevalue.

    So how do I get to the prevalue from where I am? I have a reference to the IPublishedProperty instance so it would seem logical that I should be able to query the IPublishedProperty and ask it what it's datatype / property editor is, however it holds no such properties. It basically holds the alias of the property on the doc type, and then just the values (annoyingly, PublishedPropertyBase holds an instance of PropertyType which has all the info we could need).

    The tricky thing here though is that yes, I think those properties should be exposed, as how can you gain context if you know nothing about where the property has come from (this is especially true when you have gotten the property recursively because you have no idea which node it was retrieved from). But additionally, I guess I only want to know this here because it's the only way I know to run value converters manually (See https://github.com/imulus/Archetype/blob/master/app/Umbraco/Umbraco.Archetype/Models/Property.cs as an example (I've also commented some other bits in that file that could also do with being fixed :))), so fixing IPublishedProperty in this instance would be a legitimate fix, to allow me to do a hack :)

  • Stephen 767 posts 2273 karma points c-trib
    Mar 28, 2014 @ 08:27
    Stephen
    0

    I can feel a great discussion starting. The reason is... Archetype is bound to have the same questions, and I'm facing them in another prototype I'm building. Namely, what-if your property actually is composed of several datatypes (or several instances of the same datatypes)? In your case, for example, a dictionary of (language, image). In Archetype's case, for example, an array of (textstring, textstring, dropdownlist).

    Do you want to write code using the converters by yourself? I think... no. Core should provide solutions to those situations.

    1) PublishedPropertyType

    Yesterday I bumped against the issue with IPublishedProperty not exposing the underlying PublishedPropertyType. I need to think about it but it looks like all that's missing is a getter on IPublishedProperty, because internally every published property knows about its type. And it would make sense considering that IPublishedContent exposes its ContentType. So that could be fixed rather quickly by just adding the following to IPublishedProperty:

    PublishedPropertyType PropertyType { get; }

    So... I'll look into it.

    2) Composed properties

    Now, about properties composed of several values. For Archetype-type of situation I have a prototype that does the following: it introduces a PublishedFragment concept. A PublishedFragment is an IPublishedContent that is "standalone" ie it does not come from the content tree. You instanciate it with a PublishedContentType (which you have to create) and a dictionary of (alias, value) for properties values. The actual property value converter returns an array of IPublishedContent, which internally are PublishedFragments. Then... each property within each fragment works as a standard property so you have everything you want: converters, converted values cache, etc.

    For Vorto, you could return a dictionary of (language, fragment) but that would require creating corresponding actual PublishedContentTypes... not very convenient. Unless we find a way to have "temporary types" ie types that are not defined in the DB. Turns out... we can already do it internally, I think, because we need it for unit tests. So... need to look into it also. But it could drastically simplify both Archetype and Vorto front-end code, I think.

    Ok, that was also kind of a brain dump but... this is an area where we can update Core and make a diff. So... let's discuss?

  • Stephen 767 posts 2273 karma points c-trib
    Mar 28, 2014 @ 09:59
    Stephen
    0

    Just to be clear... what I would like to achieve... with as little code as possible... and leaving aside strongly typed content... would look like:

    Archetype- like:

    @foreach (var item in Model.Content.GetPropertyValue<IEnumerable<IPublishedContent>>("archetype"))
    {
      <div>@item.GetPropertyValue<string>("title") - @item.GetPropertyValue<int>("value")</div>
    }

    Vorto-like:

    <div>Current lang is @Model.Content.GetPropertyValue<VortoValue<string>>("vorto")</div>
    <div>EN lang is @Model.Content.GetPropertyValue<VortoValue<string>>("vorto")["en"]</div>

    This will raise another issue... with property value converters and strongly-typed content. At the moment it is possible to attribute a converter with the PropertyValueType attribute to indicate the CLR type that would be returned by that converter. But... if there's a converter for, say, ArcheType properties, then the strongly-typed returned type would be IEnumerable<MyContentType> ie it would depend on the property. That is, the same converter would return different types depending on the property. So... maybe using an attribute wasn't such a good idea and it should be a method, same as IsConverter().

    Wondering if anybody is actually using the PropertyValueTypeAttribute anyway ?

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 28, 2014 @ 10:12
    Matt Brailsford
    0

    Re PropertyValueTypeAttribute, Archtype does, as does Vorto currently, and so does uTwit (currently unreleased).

    I guess I'm not quite sure how the code example you've proposed for Archtype would work (why is it IEnumerabel<IPublishedContent>?). And equally, for the vorto one, I take it the first example is returning a VortoValue and the internal values should have been passed through the converters? would this be done as part of VortValues value converter logic?

    Ultimately though, I guess if you can "on the fly" convert a nested value in the original type requested, that's what we are really getting at right?

  • Stephen 767 posts 2273 karma points c-trib
    Mar 28, 2014 @ 11:31
    Stephen
    0

    For ArchType it's IEnumerable<IPublishedContent> because IIC ArchType returns an enumeration of a structure containing a given set of properties... Either we create a new "concept" for that structure... or we just map it to an IPublishedContent. Then, converting its properties becomes automatic because IPublishedContent takes care of it.

    As for the Vorto one... I assumed that somehow you'd store some JSON in the DB, ie for property "price" you'd have { "en":1234, "fr":4567 }. We would want to convert this to VortoValue<int>... OK, maybe my first line is not possible. Should be GetPropertyValue<VortoValue<int>>("price").CurrentCulture - which would be an integer, and GetPropertyValue<VortoValue<int>>("price")["en"] - so VortoValue is some sort of dictionary.

    And yes... the point is to convert a nested value in the original type. What I'm trying to imagine is... there's already some logic to do it in Core, and with little work it could be extended to support nested values. Making sense?

  • Comment author was deleted

    Mar 28, 2014 @ 13:33

    I'm not sure I can contribute a whole lot at the moment.  Matt understands the nuances required for Archetype to get the nested property in their proper form better than I do :)

    I think it would be very useful for a published property to be self-aware what property type it is.  I'm assuming that will help select the proper converter to apply.

    I'm not clear on using IEnumerable<IPublishedContent> for Archetype as this seems to muddy up the model IMHO. The data stored in the enumeration would be missing the generic properties (name, parent, doctype, etc).  I'm in favor of just being able to apply a converter to a piece of data.

    This topic is sort of new ground for me so I'm open to all ideas at this point :)

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 28, 2014 @ 14:08
    Matt Brailsford
    0

    I'd be inclined to agree with Kevin regarding the IEnumerable<IPublishedContent> and would probably rather have the flexibility of calling the converters manually. It would defo be cool if Umbraco can take care of some of the heavy lifting, but I wouldn't want it putting restrictions on what we can access back.

  • Stephen 767 posts 2273 karma points c-trib
    Mar 28, 2014 @ 14:26
    Stephen
    0

    Fine. Apart from the IPublishedProperty exposing their property type, what else would you need?

    Kevin mentions "applying a converter to a piece of data"... which basically is what IPublishedProperty does internally. Would it still be of any interest to have some sort of "detached property" that you'd initialize with a data type and a raw value, and then it would manage the converters for you? Ie something very similar to what you have with IPublishedProperty (in fact it could even be the same interface) but which would not belong to a content?

    var prop = new DetachedProperty(dataType, rawValue);
    var convertedValue = prop.Value();

    So you don't have to rewrite the converters logic? You can decide where and how to expose those properties ie you can decide to use the "structure" that you want, but when it comes to the actual property & conversion, it could be built-in?

    Also, just in case... I assume you two plan to support putting an ArchType property into a Vorto thing, and vice-versa?

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 28, 2014 @ 14:29
    Matt Brailsford
    0

    That sounds better to me (and more logical in my head "fake this datatype with this value, and run the converters").

    And yea, I trying to make vorto wrap ANY data type so I would want it to support having an Archetype in.

  • Stephen 767 posts 2273 karma points c-trib
    Mar 28, 2014 @ 17:39
    Stephen
    0

    Following test runs now:

    // I assume in Vorto you would know the ID of the dataType
    // here I just create one for the test
    var dataType = new DataTypeDefinition(-1, Constants.PropertyEditors.IntegerAlias);
    // get a detached property type
    var propertyType = PublishedPropertyType.Detached("alias", dataType);
    // get a detached property
    var property = UmbracoContext.Current.ContentCache.InnerCache.CreateDetachedProperty(propertyType, "5548");
    // ensure type is correct, ensure value is converted into an integer
    Assert.AreEqual(propertyType, property.PropertyType);
    Assert.AreEqual(5548, property.Value);

    And "property" works as a normal property, manages converters, etc. The only difference is that property.PropertyType.ContentType is null.

    What I need to figure out is... at the moment you need to ask the content cache to create a "detached" property (is that a good name?) because how properties are implemented actually depends on the underlying content cache. To some extend. Because of how we want to cache the converted value. Anyway.

    Would that type of enhancement helps?

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 28, 2014 @ 17:49
    Matt Brailsford
    0

    Cool :)

    I guess would could then add an extension to allow you to merge propertyType / property lines to:

    UmbracoContext.Current.ContentCache.InnerCache.CreateDetachedProperty("alias", dataType, "5548");

    or even all of the beginning bit to

    UmbracoContext.Current.ContentCache.InnerCache.CreateDetachedProperty(dtdid, "5548");

    I think the naming is OK, it makes sense when you understand what you are actually doing (faking a property). The only bit that isn't logical to me is that we have to go via the ContentCache.InnerCache (I know you've explained why, it's just not a concept most people know about, but that could just come down to documentation)

    Matt

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 28, 2014 @ 17:55
    Matt Brailsford
    0

    Seems something like

    PublishedProperty.CreateDetachedProperty(...);

    Might be more logical

    Matt

  • Stephen 767 posts 2273 karma points c-trib
    Mar 28, 2014 @ 17:56
    Stephen
    0

    Fine with collapsing lines to CreateDetachedProperty(dataTypeId, "5548") - will just have to set some fixed aliases to property and type... something like "detached-1234" if dataTypeId is 1234. Not an issue.

    About going to the InnerCache: yes, I don't like it either. It does not need to be that way, it's just that it was faster to do it that way. I need to sort out a few things regarding caching properties converted values. Looking at it right now. And then refactor. But we could definitively at least to something along UmbracoContext.Current.ContentCache.CreateDetachedProperty(...) ie skip the InnerCache stuff.

    Caching converted values is... can be tricky.

  • Stephen 767 posts 2273 karma points c-trib
    Mar 28, 2014 @ 17:57
    Stephen
    0

    PublishedProperty.CreateDetachedProperty(...) ah yes. Much better of course. Or PublishedProperty.CreateDetached(...).

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 28, 2014 @ 17:59
    Matt Brailsford
    0

    Yup (I know PublishedProperty class doesn't exist, I'm just thinking contextually, what sounds right, then we can work out what is possible)

  • Stephen 767 posts 2273 karma points c-trib
    Apr 19, 2014 @ 18:13
    Stephen
    2

    Following working now:

    var t1 = new PublishedPropertyType("t1", Constants.PropertyEditors.TextboxAlias);
    var t2 = new PublishedPropertyType("t2", Constants.PropertyEditors.IntegerAlias);
    var p1 = PublishedProperty.GetDetached(t1.Detached(), "hello");
    var p2 = PublishedProperty.GetDetached(t2.Detached(), "1234");
    var c = new DetachedContent(new[] { p1, p2 });

    And then...

    @c.GetPropertyValue<string>("t1")
    @c.GetPropertyValue<int>("t2")

    Where p1 and p2 are normal IPublishedProperty instances. You initialize them with a string value and the IPropertyValueConverter, if any, will do its job. DetachedContent is just a basic container that partially mimics IPublishedContent but of course it is possible to wrap the properties into just anything. It can be used in IPropertyValueEditor but then it should be something along

    object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
    {
      ...
      var t = new PublishedPropertyType(...);
      var p = PublishedProperty.GetDetached(t.Nested(propertyType), v);
      ...
    }

    The important part being t.Nested() vs t.Detached(). This has to do with how we cache the converted values. If the property is fully detached, use t.Detached() and we'll cache the converted values within the property. If the property is returned as part of a content property, use t.Nested(...) to give us the possibility of managing the cache in a clever fashion.

    Please review & comment!

  • Stephen 767 posts 2273 karma points c-trib
    Apr 19, 2014 @ 18:44
    Stephen
    1

    I have pushed to code into 7.1.2 though it remains internal. If you want to experiment, you'll have to change all internal constructors to public on Umbraco.Web.Models.PublishedContent.PublishedPropertyType and to change internal method Umbraco.Web.Models.PublishedProperty.GetDetached to public too. Please test & let me know how it works!

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Apr 19, 2014 @ 22:25
    Matt Brailsford
    0

    Thanks Stephen, this looks pretty cool. Will give it a try and see how it goes.

    Matt

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Apr 20, 2014 @ 10:54
    Matt Brailsford
    0

    Hey Stephan, for the current package I'm working on, I want to generate a complete fake IPublishedContent node, not just the properties. Is there any reason DetachedContent can't be IPublishedContent? (or have an IPublishedContent version?) Is there anything I should be thinking about if I implement my own for the time being?

  • Stephen 767 posts 2273 karma points c-trib
    Apr 20, 2014 @ 15:28
    Stephen
    0

    @Matt: I also have a "PublishedFragment" somewhere here that is similar to a DetachedContent but inherits from IPublishedContent. So it's more or less what you want and I can share it. However, I did not include it in the initial code because it leads to all sorts of issues. It does not belong to the tree, has no Parent, no Children and no Siblings, and so there's a good deal of things that you might think would work - and don't. So I ended up creating the DetachedContent to keep things simple.

    I'll have a look at the PublishedFragment. Out of curiosity, why would you need it?

  • Stephen 767 posts 2273 karma points c-trib
    Apr 20, 2014 @ 21:20
    Stephen
    0

    @Matt: work-in-progress. Not so complex after all. Anything specific you're trying to achieve? We could support creating an in-memory standalone content tree made of detached content, where Parent() and Children() and pretty much everything would work... Do you want them to be of a configured ContentType of do you also want to create your own content types on the fly?

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Apr 20, 2014 @ 21:54
    Matt Brailsford
    0

    Hey Stephen, I'm creating a property editor that can do a few things (linking to / creating content). The linking element between it's features is that of "content", so it would make sense if I could get it to return values in a common way, which IPublishedContent would be perfect. For the linked content, that's fine, because I can just grab the node and return that, but I have 2 different functions too.

    One effectively creates a data node on the fly based upon a doc type, and the other deals with just an RTE. What I want to do then is, for the first one, create a fake IPublishedContent entity, using an existing doc type, and pass in the values it should use (I have a proof of concept of this working by creating my own IPublishedContent entity, though I will take a look at the ContentFragment as I think they are doing the same (I did see the ContentFragment a while ago, but couldn't remember what it was called so wasn't sure I ware making stuff up :))). For the second one though, I don't have a doc type, so I would like to also create that on the fly (a doc type with one property "bodyText" of type RTE), and then create my fake IPublishedContent from that.

    So basically, I want my cake and to eat it too :) I need both.

    For my purposes, I don't currently need to have children / parents (unless I link the parent back to the containing node which holds the property editor?), but I would like to get the GetPropertyValue extension methods to work, aswell as the Umbraco.Field helper.

    Matt

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Apr 20, 2014 @ 21:57
    Matt Brailsford
    0

    PS I wouldn't see the standard IPublishedContent properties that couldn't be populated throwing not implemented exceptions, as long as you can access the properties that are on it, that's all I really need.

  • Stephen 767 posts 2273 karma points c-trib
    Apr 20, 2014 @ 22:04
    Stephen
    0

    OK I think I see what you need. Give me a bit of time then.

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Apr 20, 2014 @ 22:48
    Matt Brailsford
    0

    So here is what I have done, which works for me.

    1. made the various PublishedPropertyType constructors public
    2. made PublishedProperty.GetDetached public
    3. made the PublishedContentType(int id, string alias, IEnumerable
    4. created a "NestedPublishedContent" entity as follows (I'll miss out all the non implemented props / methods):

      public class NestedPublishedContent : PublishedContentBase
      {
          private readonly int _parentContentId;
          private readonly PublishedContentType _contentType;
          private readonly IPublishedProperty[] _properties;
      
      
      public NestedPublishedContent(int parentContentId,
          PublishedContentType contentType, 
          IPublishedProperty[] properties)
      {
          _parentContentId = parentContentId;
          _contentType = contentType;
          _properties = properties;
      }
      
      
      public override PublishedItemType ItemType
      {
          get { return PublishedItemType.Content; }
      }
      
      
      public override bool IsDraft
      {
          get { return false; }
      }
      
      
      public override ICollection<IPublishedProperty> Properties
      {
          get { return _properties; }
      }
      
      
      public override PublishedContentType ContentType
      {
          get { return _contentType; }
      }
      
      
      public override int Id
      {
          get { return _parentContentId; } // Because we don't actually exist, share the same ID as the parent page
      }
      
      
      public override string DocumentTypeAlias
      {
          get { return _contentType.Alias; }
      }
      
      
      public override int DocumentTypeId
      {
          get { return _contentType.Id; }
      }
      
      
      public override IPublishedProperty GetProperty(string alias)
      {
          return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias));
      }
      
      
      public override IPublishedProperty GetProperty(string alias, bool recurse)
      {
          if (recurse)
          {
              throw new NotSupportedException();
          }
          return GetProperty(alias);
      }
      }
      
    5. Implement conversion code (within a value converter) where doc type exists like so:

      var docTypeAlias = "MyDocTypeAlias";
      var contentType = PublishedContentType.Get(PublishedItemType.Content, docTypeAlias);
      var properties = new List<IPublishedProperty>();
      
      
      var propValues = JsonConvert.DeserializeObject<Dictionary<string, object>>(item.RawValue);
      foreach (var jProp in propValues)
      {
         var propType = contentType.GetPropertyType(jProp.Key);
         var prop = PublishedProperty.GetDetached(propType.Nested(propertyType), jProp.Value.ToString(), preview);
         properties.Add(prop);
      }
      
      
      item.Value = new NestedPublishedContent(currentPageId, contentType, properties.ToArray());
      
    6. Implement conversion code (within a value converter) where doc type DOES NOT exist like so:

      var rtePropType = new PublishedPropertyType("bodyText", Constants.PropertyEditors.TinyMCEAlias);
      var rteContentType = new PublishedContentType(-1, "DummyContentAlias", new[] {rtePropType});
      var rteProp = PublishedProperty.GetDetached(rtePropType.Nested(propertyType), item.RawValue, preview);
      item.Value = new NestedPublishedContent(currentPageId, rteContentType, new[] { rteProp });
      

    So this works for me, I can create "on the fly" content where the content type may or may not exist, and which works with GetPropertyValue and Umbraco.Field.

    Worth noting, to get my fake nodes to work with Umbraco.Field, the content needs a value ID (as it gets looked up internally). As we don't have one, I made the decision to reuse the current nodes ID as technically, the value is part of that node, it's just nested far down, so that's why I give the fake node the same ID as it's parent.

    I'm not sure the best way of getting the content nodes id from within a value converter, but I am currently doing the following (maybe you can suggest a better way?)

    var currentPageId = UmbracoContext.Current.PublishedContentRequest != null
        ? UmbracoContext.Current.PublishedContentRequest.PublishedContent.Id
        : 0;
    
  • Stephen 767 posts 2273 karma points c-trib
    Apr 20, 2014 @ 23:16
    Stephen
    0

    Very interesting. Something like that definitively must go into core.

    Within a value converter there's no way really to get the owning content ID... yet I can see why we need it. The item renderer used in Umbraco.Field wants to know about the "current page" just in case it needs to render, for example, macros within an RTE. But what you do will use the ID of the "current request page" and I can think about a few cases where that would lead to strange results.

    Maybe I'd rather leave it to zero and tweak the Umbraco.Field code so that it falls back to UmbracoContext.Current.PublishedContentRequest.PublishedContent.Id when it's zero.The real issue comes from the macro rendering code that wants to run "within the context of a page". Better leave the detached content & property converters code clean.

    Anyway. Getting late, will look at it into more details tomorrow.

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Apr 21, 2014 @ 11:05
    Matt Brailsford
    0

    RE the content ID, I think that would make more sense (leave it zero)

Please Sign in or register to post replies

Write your reply to:

Draft