ContentData

2 posts

JOS.ContentJson gets replaced by JOS.ContentSerializer

I've spent some time rebuilding the JOS.ContentJson library from scratch and when doing that I realised that it was kind of dumb to limit the library to only JSON. So I've added the possibility to replace the default serializer with a custom one, so you can return the data in any format you like(you have to build the serializer yourself though, I only provide a JSON serializer out of the box). By doing that, the name ContentJson looks kind of lame, so I've released a new package named JOS.ContentSerializer instead.

You can install it by doing Install-Package JOS.ContentSerializer
If you are using the JOS.ContentJson package, you will see that I've released a new major version, 3.0. I've marked many of the methods obsolete and I've also added a readme.txt that tells you to uninstall the package and install the new one instead.

One thing that's great(and bad) with this rewrite is that I could remove/refactor whatever I wanted so it's possible that I've removed something that you guys where using. I can't be sure since everything was based on extension methods in the old library which kind of makes it impossible for me to know which breaking changes I might have introduced, hence the new major version.

Another great thing is that the library now uses dependency injection everywhere, so it's extremely easy to customize the behavior. You can find the code and a more detailed README here.

If I've removed anything you were using, please create a new issue on GitHub and I will fix it asap.

Quick example

Consider the following pagetype:

public class DemoPage : PageData  
{
    [CultureSpecific]
    [Display(
        Name = "String",
        GroupName = SystemTabNames.Content,
        Order = 100)]
    public virtual string String { get; set; }

    [CultureSpecific]
    [Display(
        Name = "ContentArea",
        GroupName = SystemTabNames.Content,
        Order = 200)]
    public virtual ContentArea MainContentArea { get; set; }

    [CultureSpecific]
    [Display(
        Name = "Degrees",
        GroupName = SystemTabNames.Content,
        Order = 300)]
    public virtual double Degrees { get; set; }

    [CultureSpecific]
    [Display(
        Name = "Int",
        GroupName = SystemTabNames.Content,
        Order = 400)]
    public virtual int Int { get; set; }

    [CultureSpecific]
    [Display(
        Name = "Date",
        GroupName = SystemTabNames.Content,
        Order = 500)]
    public virtual DateTime DateTime { get; set; }

    [CultureSpecific]
    [Display(
        Name = "Bool",
        GroupName = SystemTabNames.Content,
        Order = 600)]
    public virtual bool Bool { get; set; }

    [CultureSpecific]
    [Display(
        Name = "PageType",
        GroupName = SystemTabNames.Content,
        Order = 700)]
    public virtual PageType PageType { get; set; }

    [CultureSpecific]
    [Display(
        Name = "ContentReference",
        GroupName = SystemTabNames.Content,
        Order = 800)]
    public virtual ContentReference ContentReference { get; set; }

    [CultureSpecific]
    [Display(
        Name = "PageReference",
        GroupName = SystemTabNames.Content,
        Order = 900)]
    public virtual PageReference PageReference { get; set; }

    [CultureSpecific]
    [Display(
        Name = "Url",
        GroupName = SystemTabNames.Content,
        Order = 1000)]
    public virtual Url Url { get; set; }

    [Display(
        Name = "InternalBlock",
        GroupName = SystemTabNames.Content,
        Order = 1100)]
    public virtual VimeoVideoBlock InternalBlock { get; set; }

    [Display(
        Name = "ContentReferenceList",
        GroupName = SystemTabNames.Content,
        Order = 1200)]
    public virtual IList<ContentReference> ContentReferenceList { get; set; }

    [Display(
        Name = "XhtmlString",
        GroupName = SystemTabNames.Content,
        Order = 1300)]
    public virtual XhtmlString XhtmlString { get; set; }

    [Display(
        Name = "LinkItemCollection",
        GroupName = SystemTabNames.Content,
        Order = 1400)]
    public virtual LinkItemCollection LinkItemCollection { get; set; }

    [Display(
        Name = "SelectOne",
        GroupName = SystemTabNames.Content,
        Order = 1500)]
    [SelectOne(SelectionFactoryType = typeof(ContactPageSelectionFactory))]
    public virtual string SelectOne { get; set; }

    [Display(
        Name = "SelectMany",
        GroupName = SystemTabNames.Content,
        Order = 1600)]
    [SelectMany(SelectionFactoryType = typeof(ContactPageSelectionFactory))]
    public virtual string SelectMany { get; set; }
}

By calling .ToJson on it like this

public class DemoPageController : PageController<DemoPage>  
{
    public string Index(DemoPage currentPage)
    {
        return currentPage.ToJson();
    }
}

You would get the following result

{
    "string": "This is a string",
    "mainContentArea": {
        "vimeoVideoBlock": [{
            "name": "My Vimeo Block"
        }]
    },
    "degrees": 133.7,
    "int": 1337,
    "dateTime": "2017-05-18T00:00:00+02:00",
    "bool": true,
    "pageType": "DemoPage",
    "contentReference": "http://localhost:52467/about-us/management/",
    "pageReference": "http://localhost:52467/alloy-plan/download-alloy-plan/",
    "url": "http://localhost:52467/globalassets/alloy-meet/alloymeet.png",
    "internalBlock": {
        "name": "Im a Vimeo block",
        "mainContentArea": {
            "youtubeVideoBlock": [{
                "name": "I am a youtube block in a contentarea on a Vimeoblock"
            }]
        }
    },
    "contentReferenceList": ["http://localhost:52467/search/", "http://localhost:52467/alloy-meet/"],
    "xhtmlString": "<p>I am a xhtmlstring, do you like it?</p>\n<p>Im <strong>bold not <em>bald</em></strong></p>",
    "linkItemCollection": [{
        "href": "http://localhost:52467/alloy-plan/?query=any",
        "title": "Any title",
        "target": "_blank",
        "text": "Any text"
    }, {
        "href": "https://josef.guru",
        "title": "External link",
        "target": "_blank",
        "text": "External"
    }, {
        "href": "mailto:[email protected]",
        "title": "Email link",
        "target": null,
        "text": "Email"
    }],
    "selectOne": [{
        "selected": false,
        "text": "Amar Gupta",
        "value": "34"
    }, {
        "selected": false,
        "text": "Fiona Miller",
        "value": "33"
    }, {
        "selected": true,
        "text": "Michelle Hernandez",
        "value": "30"
    }, {
        "selected": false,
        "text": "Robert Carlsson",
        "value": "32"
    }, {
        "selected": false,
        "text": "Todd Slayton",
        "value": "31"
    }],
    "selectMany": [{
        "selected": false,
        "text": "Amar Gupta",
        "value": "34"
    }, {
        "selected": true,
        "text": "Fiona Miller",
        "value": "33"
    }, {
        "selected": false,
        "text": "Michelle Hernandez",
        "value": "30"
    }, {
        "selected": false,
        "text": "Robert Carlsson",
        "value": "32"
    }, {
        "selected": false,
        "text": "Todd Slayton",
        "value": "31"
    }]
}

EPiServer ContentData to JSON

pug with puppies

Note, the project has seen a major update(even more awesomeness), you can read more at GitHub or here

When working with EPiServer you would normally use standard Razor views to render your frontend code...you will get syntax support in Visual Studio and can use many nice little HtmlHelpers, I love it. But...im a backend developer. The frontend people at my work hates Visual Studio. They are also using some cool sounding tools like Backbone and Handlebars so they don't want to use ordinary Razor views...

In another blog post I showed how we transfered the EPiServer properties to our frontend templates so Im not going to show that again, but basically we are dumping a javascript object in the DOM and then our frontend framework picks it up and generates the markup.

So...how are we generating the JSON?

First I built a thing that took all items in a ContentArea, filtered out all items that had a specific attribute on their Class and inherited from a specific Class, then I selected all properties in the class that had a specific attribute. It worked but it was very annoying to set it up to say the least. One more huge annoyance was that it didn't supported ContentAreas and nested Internalblocks.

So this weekend I rebuilt everything. My goal was to get rid of the inheritance/attribute-mayhem, support nested blocks/contentAreas(recursive) and make it easy for developers to use it.

I think I've succeeded :)

Currently the following property types are supported:

  • String
  • bool
  • XhtmlString
  • ContentArea (different ContentTypes as well!)
  • InternalBlock (using Blocks as a property)
  • Double
  • Int
  • DateTime
  • SelectOne
  • SelectMany
  • PageReference
  • ContentReference
  • LinkItemCollection
  • Url

Let me show you guys how it can be used:

public class StartpageController : PageController<Startpage>  
{
    public string Index(Startpage currentPage)
    {
        Response.ContentType = "application/json";
        var json = currentPage.ToJson();
        return json;
    }
}

That's it! I've created an extension method to the ContentData-type so it works both on pages and blocks.

This is how my Startpage looks:

[ContentType(DisplayName = "Startpage", GUID = "a6762bfb-973b-41c1-acf8-7d26567cd71d", Description = "")]
    public class Startpage : PageData
    {
        [Display(Name = "String", Order = 100)]
        [JsonProperty("string")]
        public virtual string Heading { get; set; }

        [Display(Name = "XhtmlString", Order = 110)]
        [JsonProperty("xhtmlString")]
        public virtual XhtmlString HtmlText { get; set; }

        [Display(Name = "InternalBlock", Order = 160)]
        [JsonProperty("internalBlock")]
        public virtual InternalBlock InternalBlock { get; set; }

        [Display(Name = "Contentarea", Order = 170)]
        [JsonProperty("contentArea")]
        public virtual ContentArea ContentArea { get; set; }
    }

The only thing that needs to be added to make the property appear in the JSON output is the JsonProperty-attribute(Newtonsoft).

Im thinking about removing that and instead use the Display-attribute and make the JsonProperty-attribute optional if you want to specify a custom JSON key.

Note, the above is now implemented, you dont need the JsonProperty attribute. Read more at GitHub. EPiServer - ContentJson

The JSON genereated would look like this:

{
        "heading": "This is the heading",
        "body": "<p>This is cool because <strong>JOSEF WROTE IT</strong></p>",
        "internalBlock": {
            "heading" : "Internalblock Heading here",
            "subHeading" : "This is my subheading"
        },
        "contentArea": {
            "sharedBlockName" : [
                {
                    "heading" : "Hello",
                    "subHeading" : "Hey you"
                },
                {
                    "heading" : "Goodbye",
                    "subHeading" : "Cheers"
                }
            ],
            "rappersBlock" : [
                {
                    "name" : "Eminem",
                    "isTheGreatestRapperEver": true,
                    "sucks" : false
                },
                {
                    "name" : "Big Sean",
                    "isTheGreatestRapperEver" : false,
                    "sucks" : false
                },
                {
                    "name" : "Flo Rida",
                    "isTheGreatestRapperEver" : false,
                    "sucks" : true
                }
            ]
        }
    }

That's just a simple example, head over to the GitHub repo and look at some other examples and try it out!

Don't forget to look at how I solved the SelectOne/SelectMany property type, I think it turned out quite nicely

XForms support

Yeah...im thinking about it, but I will not implement it right now, it will come, someday. Right now I just don't feel like parsing XML... :)