Skip to content

Instantly share code, notes, and snippets.

@icavalheiro
Last active August 8, 2021 04:01
Show Gist options
  • Save icavalheiro/582f009cc958f95478a2efda6c718ee0 to your computer and use it in GitHub Desktop.
Save icavalheiro/582f009cc958f95478a2efda6c718ee0 to your computer and use it in GitHub Desktop.
Umbraco Json controller based on the ContentType renderer. Use this to return the content as a json instead of a view (great for headless mode)
using Newtonsoft.Json;
using System;
using System.Net;
using System.Web;
using System.Web.Mvc;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
namespace JsonView
{
public abstract class JsonController : RenderMvcController
{
/// <summary>
/// Encapsulate themethod into a simpler signature for easy passing around
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
private bool AllowAccessTo(IPublishedContent content)
{
var memberId = Umbraco.MembershipHelper.GetCurrentMember()?.Key;
var member = (memberId != null) ? Services.MemberService.GetByKey((Guid)memberId) : null;
return AllowAccessTo(content, Request, member);
}
/// <summary>
/// Used to determine if the current user has access or not the given content.
/// Otherwise the content will just be ignored.
/// </summary>
/// <param name="content">Content to check for access</param>
/// <param name="request">The current request pipeline</param>
/// <returns></returns>
protected virtual bool AllowAccessTo(IPublishedContent content, HttpRequestBase request, IMember currentLoggedMember)
{
return true;
}
/// <summary>
/// Extract pagination information from the current Query parameters from Request
/// </summary>
/// <returns>The tuple (of3) with the extracted information</returns>
protected virtual (bool shouldPaginate, int pageQuery, int pageSizeQuery) ExtractPaginationInfo()
{
var shouldPaginate = false;
if (int.TryParse(Request["page"], out var pageQuery))
{
shouldPaginate = true;
}
else
{
pageQuery = -1;
}
if (int.TryParse(Request["pageSize"], out var pageSizeQuery))
{
shouldPaginate = true;
if (pageQuery == -1)
{
pageQuery = 0;
}
}
else
{
pageSizeQuery = 20;
}
return (shouldPaginate, pageQuery, pageSizeQuery);
}
/// <summary>
/// Entrypoint.
/// This methods will be called everytime the ContentType route is used
/// independetly of the View being used.
/// </summary>
/// <param name="model">This is the model that Umbraco will send us</param>
/// <returns>A valid MVC Action Result</returns>
[HttpGet]
public override ActionResult Index(ContentModel model)
{
if (!ShouldReturnJson())
{
return View();
}
return GetJsonResponse(model);
}
/// <summary>
/// Wheter this controller should return a Json or not.
/// Usefull if you want to make this controller also returns a HTML view
/// </summary>
/// <returns>If it should return a Json</returns>
protected virtual bool ShouldReturnJson()
{
if (RequireJsonQuery())
{
if (Request["json"] != "true")
{
return false;
}
}
if (Request["json"] == "false")
{
return false;
}
return true;
}
/// <summary>
/// Get the ActionResult response with the Json conversion of the model (or not depending on the "ShouldReturnJson" method)
/// </summary>
/// <param name="model">Model to be used</param>
/// <returns></returns>
protected virtual ActionResult GetJsonResponse(ContentModel model)
{
return Content(GetJson(model), "application/json");
}
/// <summary>
/// Converts a model into a Json string
/// </summary>
/// <param name="model">Model to be used</param>
/// <returns>A Json object in string format</returns>
protected virtual string GetJson(ContentModel model)
{
return JsonConvert.SerializeObject(GetElementToSerialize(model));
}
/// <summary>
/// Basicly converts a ContentModel into a ApiElement
/// </summary>
/// <param name="model">Model to be used</param>
/// <returns>ApiElement ready for serialization</returns>
protected virtual Node GetElementToSerialize(ContentModel model)
{
var element = GetBaseElement(model);
if(!AllowAccessTo(element))
{
throw new System.Web.Http.HttpResponseException(HttpStatusCode.Unauthorized);
}
var (shouldPaginate, pageQuery, pageSizeQuery) = ExtractPaginationInfo();
if (shouldPaginate)
{
return new Node(element, AllowAccessTo, ShouldIncludeChildren(), true, pageQuery, pageSizeQuery);
}
else
{
return new Node(element, AllowAccessTo, ShouldIncludeChildren());
}
}
/// <summary>
/// Get the base element to be used in the json creation
/// </summary>
/// <param name="model">The controller model</param>
/// <returns>IPublishedContent to be used as the base</returns>
protected virtual IPublishedContent GetBaseElement(ContentModel model)
{
return model.Content;
}
/// <summary>
/// Defines wheter or not this controller should require the ?json=true query to be set
/// </summary>
/// <returns></returns>
protected virtual bool RequireJsonQuery()
{
return false;
}
/// <summary>
/// Defines wheter or not this controller should include the children of the base element
/// </summary>
/// <returns>True if it should include</returns>
protected virtual bool ShouldIncludeChildren()
{
return true;
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core;
using System;
namespace JsonView
{
public static class Jsonify
{
private static (object, bool) TryImageCropper(object rawVal)
{
if (typeof(ImageCropperValue).IsInstanceOfType(rawVal))
{
var val = rawVal as ImageCropperValue;
var dic = new Dictionary<string, string>();
foreach (var crop in val.Crops)
{
dic.Add(crop.Alias, val.Src + val.GetCropUrl(crop.Alias));
}
return (dic, true);
}
return (null, false);
}
private static (object, bool) TryPublishedElement(object rawVal, Func<IPublishedContent, bool> accessChecker)
{
if (typeof(IPublishedElement).IsInstanceOfType(rawVal))
{
var content = rawVal as IPublishedElement;
//special case for media items
if (content.ContentType.ItemType.ToString() == "Media")
{
var file = content.GetProperty(Constants.Conventions.Media.File);
var val = file.GetValue() as ImageCropperValue;
return (val.Src, true);
}
return (new Node(content, accessChecker), true);
}
return (null, false);
}
private static (object, bool) TryPublishedContent(object rawVal, Func<IPublishedContent, bool> accessChecker, bool includeChildren, bool paginateChildren, int page, int pageSize)
{
if (typeof(IPublishedContent).IsInstanceOfType(rawVal))
{
var content = rawVal as IPublishedContent;
if (!accessChecker(content))
return (null, true);
return (new Node(content, accessChecker, includeChildren, paginateChildren, page, pageSize), true);
}
return (null, false);
}
private static (object, bool) TryIenumarablePublishedThing(object rawVal, Func<IPublishedContent, bool> accessChecker, bool includeChildren, bool paginateChildren, int page, int pageSize)
{
if (typeof(IEnumerable<IPublishedElement>).IsInstanceOfType(rawVal) ||
typeof(IEnumerable<IPublishedContent>).IsInstanceOfType(rawVal))
{
var content = rawVal as IEnumerable<dynamic>;
return (content
.Select(x => TryJsonify(x, accessChecker, includeChildren, paginateChildren, page, pageSize))
.Where(x => x != null)
.ToArray(), true);
}
return (null, false);
}
private static (object, bool) TryHtmlString(object rawVal)
{
if (typeof(HtmlString).IsInstanceOfType(rawVal))
{
return (rawVal.ToString(), true);
}
return (null, false);
}
public static object TryJsonify(object rawVal, Func<IPublishedContent, bool> accessChecker, bool includeChildren, bool paginateChildren, int page, int pageSize)
{
var (val, succeeded) = TryImageCropper(rawVal);
if (succeeded) return val;
(val, succeeded) = TryPublishedElement(rawVal, accessChecker);
if (succeeded) return val;
(val, succeeded) = TryPublishedContent(rawVal, accessChecker, includeChildren, paginateChildren, page, pageSize);
if (succeeded) return val;
(val, succeeded) = TryIenumarablePublishedThing(rawVal, accessChecker, includeChildren, paginateChildren, page, pageSize);
if (succeeded) return val;
(val, succeeded) = TryHtmlString(rawVal);
if (succeeded) return val;
//nothing worked
return rawVal;
}
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Umbraco.Core.Models.PublishedContent;
namespace JsonView
{
/// <summary>
/// Class designed to facilitate the serialization of Umbraco Elements
/// </summary>
public sealed partial class Node
{
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public string Url;
[JsonProperty("id")]
public string Id;
[JsonProperty("name")]
public string Name;
[JsonProperty("attributes")]
public Dictionary<string, object> Attributes;
[JsonProperty("children")]
public Node[] Children;
[JsonProperty("pagination")]
public object PaginationInfo;
public static bool ShouldInclude(IPublishedContent content, HttpRequestBase request, Func<IPublishedContent, HttpRequestBase, bool> allowAccessTo)
{
return allowAccessTo(content, request);
}
private static Dictionary<string, object> ToAttributes(IEnumerable<IPublishedProperty> properties, Func<IPublishedContent, bool> accessChecker, bool includeChildren, bool paginateChildren, int page, int pageSize)
{
var dic = new Dictionary<string, object>();
foreach (var prop in properties)
{
dic.Add(prop.Alias, Jsonify.TryJsonify(prop.GetValue(), accessChecker, includeChildren, paginateChildren, page, pageSize));
}
return dic;
}
public Node(IPublishedElement element, Func<IPublishedContent, bool> accessChecker)
{
Id = element.Key.ToString();
Name = element.ContentType.Alias;
Attributes = ToAttributes(element.Properties, accessChecker, false, false, 0, int.MaxValue);
}
public Node(IPublishedContent content, Func<IPublishedContent, bool> accessChecker, bool includeChildren = true, bool paginateChildren = false, int page = 0, int pageSize = 20)
{
Id = content.Key.ToString();
Url = content.Url;
Name = content.Name;
Attributes = ToAttributes(content.Properties, accessChecker, includeChildren, paginateChildren, page, pageSize);
if (includeChildren)
{
var children = content
.Children
.Where(x => accessChecker(x));
if (paginateChildren)
{
PaginationInfo = new
{
Page = page,
PageSize = pageSize,
Total = content.Children.Count()
};
children = children
.Skip(page * pageSize)
.Take(pageSize);
}
Children = children
.Select(x => new Node(x, accessChecker, true, paginateChildren, 0, pageSize))//children will always display the first page
.ToArray();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment