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