Last active
August 8, 2021 04:01
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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