Copied to clipboard

Flag this post as spam?

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


  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Apr 25, 2015 @ 15:06
    Jan Skovgaard
    0

    Exposing MNTP items to WebApi

    Hi guys

    For a fun side-project I'm doing in my spare time I need to expose some MNTP items to WebApi so I can consume some of the content using JavaScript.

    I'm almost there - Since I'm not a backend developer and no c# guru I need some advice on how to extend the functionality so I can get the picked items exposed in my JSON too.

    I have been using the code from René Pjengaards Umbraco DK festival demo here https://github.com/rpjengaard/AngularApiTalk/tree/master/code

    I need to expose some properties from my Homepage node and then some of the "related" properties from the selected elements from the MNTP.

    My current model code looks like this

    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using Umbraco.Core.Models;
    using Umbraco.Web;
    
    namespace IsItCgYetApp.ApiModels
    {
        public class HomePageDateItemsModel
        {
            [JsonProperty("umbracoNodeId")]
            public int UmbracoNodeId { get; set; }
            [JsonProperty("header")]
            public string Header { get; set; }
            [JsonProperty("bodyText")]
            public string BodyText { get; set; }
            [JsonProperty("startDate")]
            public DateTime StartDate { get; set; }
            [JsonProperty("endDate")]
            public DateTime EndDate { get; set; }
            [JsonProperty("pickDatetimeElements")]
            public string PickDatetimeElements { get; set; }
    
            public static HomePageDateItemsModel GetFromContent(IPublishedContent a)
            {
                return new HomePageDateItemsModel
                {
    
                    UmbracoNodeId = a.Id,
                    Header = a.GetPropertyValue<string>("header"),
                    BodyText = a.GetPropertyValue<string>("bodyText"),
                    StartDate = a.GetPropertyValue<DateTime>("startDate"),
                    EndDate = a.GetPropertyValue<DateTime>("endDate"),
                    PickDatetimeElements = a.GetPropertyValue<string>("pickDatetimeElements")
                };
            }
        }
    }
    

    And my controller looks like this

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Text;
    using System.Threading.Tasks;
    using Skybrud.WebApi.Json;
    using Skybrud.WebApi.Json.Meta;
    using Umbraco.Core;
    using Umbraco.Web;
    using Umbraco.Web.WebApi;
    using Umbraco.Core.Models;
    using IsItCgYetApp.ApiModels;
    
    namespace IsItCgYetApp.ApiControllers
    {
        [JsonOnlyConfiguration]
        public class HomePageDateItemsController : UmbracoApiController
        {
    
            private UmbracoHelper _helper = new UmbracoHelper(UmbracoContext.Current);
    
            public object GetAllItems(int umbracoNodeId) {
    
                var c = _helper.TypedContent(umbracoNodeId);
    
                return Request.CreateResponse(JsonMetaResponse.GetSuccessFromObject(c, HomePageDateItemsModel.GetFromContent));
    
            }
        }
    }
    

    And the output I'm currently getting looks like this

    {
        "meta": {
            "code": 200
        },
        "data": {
            "umbracoNodeId": 1061,
            "header": "Nope!",
            "bodyText": "<p>...Still some time to go untill <a href=\"http://codegarden15.com\" target=\"_blank\" title=\"The official Codegarden 15 website.\">#CG15</a>. Stay tuned!</p>",
            "startDate": "2015-06-10T09:00:00",
            "endDate": "2015-06-12T16:00:00",
            "pickDatetimeElements": "1105,1106,1117,1107,1108,1109,1110,1111,1112,1113,1114,1115"
        }
    }
    

    As you can see in the "pickDatetimeElements" property the value is a comma separated string of node id's. Instead of the nodeid's I would like to have the node items returned so I can consume the data from these instead.

    The desired output should look something like this

    {
        "meta": {
            "code": 200
        },
        "data": {
            "umbracoNodeId": 1061,
            "header": "Nope!",
            "bodyText": "<p>...Still some time to go untill <a href=\"http://codegarden15.com\" target=\"_blank\" title=\"The official Codegarden 15 website.\">#CG15</a>. Stay tuned!</p>",
            "startDate": "2015-06-10T09:00:00",
            "endDate": "2015-06-12T16:00:00",
            "pickDatetimeElements": {
                "dateTimeElement": {
                    "header": "Header 1",
                    "bodyText": "Some body text 1"
                },
                "dateTimeElement": {
                    "header": "Header 2",
                    "bodyText": "Some body text 2"
                }
            }
        }
    }
    

    Now I just wonder how I can achieve that?

    I'm thinking that I should perhaps split the id's from the "PickDatetimeElements" property in the "GetFromContent" method and then loop them over and assign the values to an array/list and then assign the list to the "PickDateimeElements?

    Can anyone help me out showing an example of this?

    Thanks in advance.

    Cheers, Jan

  • Anders Bjerner 487 posts 2990 karma points MVP 8x admin c-trib
    Apr 25, 2015 @ 16:51
    Anders Bjerner
    100

    Hi Jan,

    The last JSON is actually invalid since you have an object with the same key twice. Rather than "pickerDateElements" being an object, I think what you really want is an array instead. If that is the case, have a look at the code below.

    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using Umbraco.Core.Models;
    using Umbraco.Web;
    
    namespace IsItCgYetApp.ApiModels {
        public class HomePageDateItemsModel {
            [JsonProperty("umbracoNodeId")]
            public int UmbracoNodeId { get; set; }
            [JsonProperty("header")]
            public string Header { get; set; }
            [JsonProperty("bodyText")]
            public string BodyText { get; set; }
            [JsonProperty("startDate")]
            public DateTime StartDate { get; set; }
            [JsonProperty("endDate")]
            public DateTime EndDate { get; set; }
            [JsonProperty("pickDatetimeElements")]
            public IEnumerable<PickerDateElement> PickDatetimeElements { get; set; }
    
            public static HomePageDateItemsModel GetFromContent(IPublishedContent a) {
    
                // Get the string value (?? makes sure we at least have an empty string rather than NULL)
                string elementsValue = a.GetPropertyValue<string>("pickDatetimeElements") ?? "";
    
                // Split the string and parse the IDs into integers
                IEnumerable<int> elementIds = (
                    from str in elementsValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                    select Int32.Parse(str)
                );
    
                // Grab the selected nodes from the cache
                IEnumerable<PickerDateElement> elements = (
                    from id in elementIds
                    let content = UmbracoContext.Current.ContentCache.GetById(id)
                    where content != null // check whether the content is NULL - eg. if the node has been deleted
                    select PickerDateElement.GetFromContent(content)
                );
    
                return new HomePageDateItemsModel {
    
                    UmbracoNodeId = a.Id,
                    Header = a.GetPropertyValue<string>("header"),
                    BodyText = a.GetPropertyValue<string>("bodyText"),
                    StartDate = a.GetPropertyValue<DateTime>("startDate"),
                    EndDate = a.GetPropertyValue<DateTime>("endDate"),
                    PickDatetimeElements = elements
                };
            }
        }
    
        public class PickerDateElement {
    
            [JsonProperty("header")]
            public string Header { get; set; }
    
            [JsonProperty("bodyText")]
            public string BodyText { get; set; }
    
            public static PickerDateElement GetFromContent(IPublishedContent a) {
                return new PickerDateElement {
                    Header = a.GetPropertyValue<string>("header"),
                    BodyText = a.GetPropertyValue<string>("bodyText")
                };
            }
    
        }
    
    }
    

    That should do the trick ;)

    (I have only tested that the code above compiles, not that it actually works)

  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Apr 25, 2015 @ 16:51
    Sebastiaan Janssen
    1

    I would add to your HomePageDateItemsModel:

    public List<DateTimeElement> DateTimeElemenets { get; set; }
    

    And add a class:

    public class DateTimeElement
    {
        public string Header { get; set; }
        public string BodyText { get; set; }
    }
    

    Then in return new HomePageDateItemsModel add:

    private pUmbracoHelper _helper = new UmbracoHelper(UmbracoContext.Current);
    
    
    foreach (string nodeId in PickDatetimeElements.Split(','))
    {
        var node = _helper.TypedContent(nodeId);
        var header = node.GetPropertyValue<string>("header");
        var bodyText = node.GetPropertyValue<string>("bodyText");
        DateTimeElemenets.Add(new DateTimeElement { Header = header, BodyText = bodyText });
    }
    
  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Apr 25, 2015 @ 16:52
    Sebastiaan Janssen
    0

    Damn, beat me to it. Yeah same approach but without the nasty SQL looking LINQ syntax.. ;-)

  • Anders Bjerner 487 posts 2990 karma points MVP 8x admin c-trib
    Apr 25, 2015 @ 17:07
    Anders Bjerner
    0

    My example will compile ;)

    But yes, LINQ can be a challenge if not a regular user. At Skybrud.dk we have isolated this into a couple of extension method, so we can simply call content.TypedCsvContent("propertyAlias") or even content.TypedCsvContent("propertyAlias", PickerDateElement.GetFromContent).

    public static class PublishedContentExtensions {
    
        /// <summary>
        /// Converts a comma separated string into an array of integers.
        /// </summary>
        /// <param name="source">The comma separated string to be converted.</param>
        public static int[] CsvToInt(this string source) {
            return (
                from piece in (source ?? "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                where Regex.IsMatch(piece, "^[0-9]+$")
                select Int32.Parse(piece)
            ).ToArray();
        }
    
        /// <summary>
        /// Returns whether a given node should be hidden in navigation (if the <em>umbracoNaviHide</em> property has
        /// been checked in Umbraco).
        /// </summary>
        /// <param name="content">IPublishedContent</param>
        public static bool Hidden(this IPublishedContent content) {
            return content.GetPropertyValue<bool>("umbracoNaviHide");
        }
    
        /// <summary>
        /// Converts a property with comma seperated IDs into a list of <code>IPublishedContent</code> by using the
        /// content cache. Instances are converted to the type of <code>T</code> using the specified
        /// <code>func</code>.
        /// </summary>
        /// <param name="content">IPublishedContent</param>
        /// <param name="propertyAlias">The alias of the property containing the IDs.</param>
        /// <param name="func">The delegate function to be used for the conversion.</param>
        /// <returns>Collection of <code>T</code>.</returns>
        public static IEnumerable<T> TypedCsvContent<T>(this IPublishedContent content, string propertyAlias, Func<IPublishedContent, T> func) {
            if (func == null) throw new ArgumentNullException("func");
            return (
                from id in content.GetPropertyValue<string>(propertyAlias).CsvToInt()
                let item = UmbracoContext.Current.ContentCache.GetById(id)
                where item != null
                select func(item)
            );
        }
    
    }
    

    Given this class with extension methods, Jan's code could instead look something like:

    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using Umbraco.Core.Models;
    using Umbraco.Web;
    
    namespace IsItCgYetApp.ApiModels {
        public class HomePageDateItemsModel {
            [JsonProperty("umbracoNodeId")]
            public int UmbracoNodeId { get; set; }
            [JsonProperty("header")]
            public string Header { get; set; }
            [JsonProperty("bodyText")]
            public string BodyText { get; set; }
            [JsonProperty("startDate")]
            public DateTime StartDate { get; set; }
            [JsonProperty("endDate")]
            public DateTime EndDate { get; set; }
            [JsonProperty("pickDatetimeElements")]
            public IEnumerable<PickerDateElement> PickDatetimeElements { get; set; }
    
            public static HomePageDateItemsModel GetFromContent(IPublishedContent a) {
                return new HomePageDateItemsModel {
                    UmbracoNodeId = a.Id,
                    Header = a.GetPropertyValue<string>("header"),
                    BodyText = a.GetPropertyValue<string>("bodyText"),
                    StartDate = a.GetPropertyValue<DateTime>("startDate"),
                    EndDate = a.GetPropertyValue<DateTime>("endDate"),
                    PickDatetimeElements = a.TypedCsvContent("pickDatetimeElements", PickerDateElement.GetFromContent)
                };
            }
        }
    
        public class PickerDateElement {
    
            [JsonProperty("header")]
            public string Header { get; set; }
    
            [JsonProperty("bodyText")]
            public string BodyText { get; set; }
    
            public static PickerDateElement GetFromContent(IPublishedContent a) {
                return new PickerDateElement {
                    Header = a.GetPropertyValue<string>("header"),
                    BodyText = a.GetPropertyValue<string>("bodyText")
                };
            }
    
        }
    
    }
    

    It gives some cleaner code :D

  • Anders Bjerner 487 posts 2990 karma points MVP 8x admin c-trib
    Apr 25, 2015 @ 17:13
    Anders Bjerner
    0

    Hmm. Regarding my comment that your example wouldn't compile, it appears that generics are stripped in the notification emails. So node.GetPropertyValue<string>("header") becomes node.GetPropertyValue("header") in the email.

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Apr 25, 2015 @ 17:39
    Jan Skovgaard
    0

    Hi guys

    Thanks for the input - I'll try it out and see if I can get it to work :)

    Would I be able to create the PickerDateElement class as it's own .cs file to keep things clean?

    /Jan

  • Anders Bjerner 487 posts 2990 karma points MVP 8x admin c-trib
    Apr 25, 2015 @ 17:43
    Anders Bjerner
    0

    Yes, it is in fact considered best procedure to keep classes in their own files, so go ahead ;)

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Apr 25, 2015 @ 17:59
    Jan Skovgaard
    0

    I Had a notion about that - Not only does your sample compile...it bloody works too! :)

    So...I guess it's more like keg of beer that I owe you now - Thank you very much sir.

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Apr 25, 2015 @ 18:08
    Jan Skovgaard
    0

    Oh! I just have one more final question...Say that I don't want to pass a node id to my controller in order to get the data but just want to call it and have the data by going to /Umbraco/Api/HomePage/GetAllItems ? Currently I'm using /Umbraco/Api/HomePage/GetAllItems?umbracoNodeId=1061

    I bet it's something really simple that will make me feel embarrased once I figure it out. But right now I can't wrap my head around it :-/

    /Jan

  • Anders Bjerner 487 posts 2990 karma points MVP 8x admin c-trib
    Apr 25, 2015 @ 18:34
    Anders Bjerner
    1

    By declaring two method with different signatures (parameters), your code could look something like this:

    public object GetAllItems() {
        return GetAllItems(1061);
    }
    
    public object GetAllItems(int umbracoNodeId) {
    
        var c = _helper.TypedContent(umbracoNodeId);
    
        return Request.CreateResponse(JsonMetaResponse.GetSuccessFromObject(c, HomePageDateItemsModel.GetFromContent));
    
    }
    

    For method parameters in C#, you can also define a default value of simple types, so rather than declaring two method, you just declare one like:

    public object GetAllItems(int umbracoNodeId = 1061) {
    
        var c = _helper.TypedContent(umbracoNodeId);
    
        return Request.CreateResponse(JsonMetaResponse.GetSuccessFromObject(c, HomePageDateItemsModel.GetFromContent));
    
    }
    

    It gives somewhat the same result, so it really comes down to your personal code style.

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Apr 25, 2015 @ 22:36
    Jan Skovgaard
    0

    Hi Anders

    Yeah, I know that I can just hardcode the id in...but was thinking if I could just tell the controller to get the homepage properties without hard-coding the id...but of course I can't since it somehow needs to know exactly what node to get the data from...so brainfart by me :)

    /Jan

  • Anders Bjerner 487 posts 2990 karma points MVP 8x admin c-trib
    Apr 25, 2015 @ 23:18
    Anders Bjerner
    1

    Hi Jan,

    There are still a few options left - these two are probably the two that makes the most sense:

    App Settings
    Rather than hardcoding the ID somewhere in a DLL, you can add the ID to app settings in your Web.config. You can read from app settings like:

    string value = WebConfigurationManager.AppSettings["NaNaNaNaNaNaNaNaBatJan"];
    

    Or:

    int value = Int32.Parse(WebConfigurationManager.AppSettings["NaNaNaNaNaNaNaNaBatJan"]);
    

    You can find the WebConfigurationManager class in the System.Web.Configuration namespace.

    XPath
    Another way is using an XPath query to match a specific node - eg:

    UmbracoContext.Current.ContentCache.GetSingleByXPath("//BatCave/BatMobile")
    
  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Apr 26, 2015 @ 09:54
    Sebastiaan Janssen
    1

    Have you tried to play with TypedContentAtRoot? If you know where your content lives under the root then it should be easy to use a combination of .Childern.First() and .Descendants.

    Am on mobile so this code won't compile.. :p

Please Sign in or register to post replies

Write your reply to:

Draft