2013-05-31 17:20:56 -10:00
using System ;
2017-05-31 12:25:05 +02:00
using System.Collections.Concurrent ;
2013-05-31 17:20:56 -10:00
using System.Linq ;
using System.Management.Instrumentation ;
using System.Net.Http ;
using System.Net.Http.Formatting ;
using System.Threading.Tasks ;
2013-11-20 14:18:03 +11:00
using System.Web.Http ;
2013-05-31 17:20:56 -10:00
using System.Web.Http.Controllers ;
2013-11-20 14:18:03 +11:00
using System.Web.Http.Routing ;
2013-05-31 17:20:56 -10:00
using System.Web.Mvc ;
using Umbraco.Core ;
2013-10-08 12:38:27 +11:00
using Umbraco.Web.Models.Trees ;
2013-11-20 14:18:03 +11:00
using Umbraco.Web.Mvc ;
2013-05-31 17:20:56 -10:00
using Umbraco.Web.WebApi ;
using umbraco.cms.presentation.Trees ;
2017-05-31 12:25:05 +02:00
using Umbraco.Core.Services ;
2013-07-02 17:47:20 +10:00
using ApplicationTree = Umbraco . Core . Models . ApplicationTree ;
2013-05-31 17:20:56 -10:00
using UrlHelper = System . Web . Http . Routing . UrlHelper ;
namespace Umbraco.Web.Trees
{
internal static class ApplicationTreeExtensions
{
2017-08-17 11:22:05 +02:00
2017-05-31 12:25:05 +02:00
private static readonly ConcurrentDictionary < Type , TreeAttribute > TreeAttributeCache = new ConcurrentDictionary < Type , TreeAttribute > ( ) ;
internal static TreeAttribute GetTreeAttribute ( this Type treeControllerType )
{
return TreeAttributeCache . GetOrAdd ( treeControllerType , type = >
{
//Locate the tree attribute
var treeAttributes = type
. GetCustomAttributes < TreeAttribute > ( false )
. ToArray ( ) ;
if ( treeAttributes . Length = = 0 )
{
throw new InvalidOperationException ( "The Tree controller is missing the " + typeof ( TreeAttribute ) . FullName + " attribute" ) ;
}
//assign the properties of this object to those of the metadata attribute
return treeAttributes [ 0 ] ;
} ) ;
2017-08-17 11:22:05 +02:00
}
2017-05-31 12:25:05 +02:00
internal static TreeAttribute GetTreeAttribute ( this ApplicationTree tree )
{
return tree . GetRuntimeType ( ) . GetTreeAttribute ( ) ;
2017-08-17 11:22:05 +02:00
}
2017-05-31 12:25:05 +02:00
internal static string GetRootNodeDisplayName ( this TreeAttribute attribute , ILocalizedTextService textService )
2017-08-17 11:22:05 +02:00
{
2017-05-31 12:25:05 +02:00
//if title is defined, return that
if ( string . IsNullOrEmpty ( attribute . Title ) = = false )
return attribute . Title ;
//try to look up a tree header matching the tree alias
var localizedLabel = textService . Localize ( "treeHeaders/" + attribute . Alias ) ;
if ( string . IsNullOrEmpty ( localizedLabel ) = = false )
return localizedLabel ;
//is returned to signal that a label was not found
return "[" + attribute . Alias + "]" ;
2017-08-17 11:22:05 +02:00
}
2013-05-31 17:20:56 -10:00
2013-09-04 12:01:41 +10:00
internal static Attempt < Type > TryGetControllerTree ( this ApplicationTree appTree )
2013-05-31 17:20:56 -10:00
{
//get reference to all TreeApiControllers
var controllerTrees = UmbracoApiControllerResolver . Current . RegisteredUmbracoApiControllers
2013-09-26 15:55:38 +10:00
. Where ( TypeHelper . IsTypeAssignableFrom < TreeController > )
2013-05-31 17:20:56 -10:00
. ToArray ( ) ;
//find the one we're looking for
2013-09-10 18:28:45 +10:00
var foundControllerTree = controllerTrees . FirstOrDefault ( x = > x = = appTree . GetRuntimeType ( ) ) ;
2013-05-31 17:20:56 -10:00
if ( foundControllerTree = = null )
{
2013-09-12 12:54:34 +02:00
return Attempt < Type > . Fail ( new InstanceNotFoundException ( "Could not find tree of type " + appTree . Type + " in any loaded DLLs" ) ) ;
2013-06-20 17:47:14 +10:00
}
2013-09-12 12:54:34 +02:00
return Attempt . Succeed ( foundControllerTree ) ;
2013-06-20 17:47:14 +10:00
}
2013-11-20 14:18:03 +11:00
/// <summary>
/// This will go and get the root node from a controller tree by executing the tree's GetRootNode method
/// </summary>
/// <param name="appTree"></param>
/// <param name="formCollection"></param>
/// <param name="controllerContext"></param>
/// <returns></returns>
/// <remarks>
/// This ensures that authorization filters are applied to the sub request
/// </remarks>
internal static async Task < Attempt < TreeNode > > TryGetRootNodeFromControllerTree ( this ApplicationTree appTree , FormDataCollection formCollection , HttpControllerContext controllerContext )
2013-06-20 17:47:14 +10:00
{
var foundControllerTreeAttempt = appTree . TryGetControllerTree ( ) ;
if ( foundControllerTreeAttempt . Success = = false )
{
2013-09-12 12:54:34 +02:00
return Attempt < TreeNode > . Fail ( foundControllerTreeAttempt . Exception ) ;
2013-06-20 17:47:14 +10:00
}
2013-11-20 14:18:03 +11:00
2013-06-20 17:47:14 +10:00
var foundControllerTree = foundControllerTreeAttempt . Result ;
//instantiate it, since we are proxying, we need to setup the instance with our current context
2013-09-26 15:55:38 +10:00
var instance = ( TreeController ) DependencyResolver . Current . GetService ( foundControllerTree ) ;
2013-11-20 14:18:03 +11:00
//NOTE: This is all required in order to execute the auth-filters for the sub request, we
// need to "trick" web-api into thinking that it is actually executing the proxied controller.
var urlHelper = controllerContext . Request . GetUrlHelper ( ) ;
//create the proxied URL for the controller action
var proxiedUrl = controllerContext . Request . RequestUri . GetLeftPart ( UriPartial . Authority ) +
urlHelper . GetUmbracoApiService ( "GetRootNode" , instance . GetType ( ) ) ;
//add the query strings to it
proxiedUrl + = "?" + formCollection . ToQueryString ( ) ;
//create proxy route data specifying the action / controller to execute
var proxiedRouteData = new HttpRouteData (
controllerContext . RouteData . Route ,
new HttpRouteValueDictionary ( new { action = "GetRootNode" , controller = ControllerExtensions . GetControllerName ( instance . GetType ( ) ) } ) ) ;
2015-01-06 17:39:07 +11:00
2013-11-20 14:18:03 +11:00
//create a proxied controller context
var proxiedControllerContext = new HttpControllerContext (
controllerContext . Configuration ,
proxiedRouteData ,
new HttpRequestMessage ( HttpMethod . Get , proxiedUrl ) )
{
ControllerDescriptor = new HttpControllerDescriptor ( controllerContext . ControllerDescriptor . Configuration , ControllerExtensions . GetControllerName ( instance . GetType ( ) ) , instance . GetType ( ) )
} ;
2015-01-06 17:39:07 +11:00
if ( WebApiVersionCheck . WebApiVersion > = Version . Parse ( "5.0.0" ) )
{
//In WebApi2, this is required to be set:
// proxiedControllerContext.RequestContext = controllerContext.RequestContext
// but we need to do this with reflection because of codebase changes between version 4/5
2015-01-06 18:23:47 +11:00
//NOTE: Use TypeHelper here since the reflection is cached
var controllerContextRequestContext = TypeHelper . GetProperty ( controllerContext . GetType ( ) , "RequestContext" ) . GetValue ( controllerContext ) ;
TypeHelper . GetProperty ( proxiedControllerContext . GetType ( ) , "RequestContext" ) . SetValue ( proxiedControllerContext , controllerContextRequestContext ) ;
2015-01-06 17:39:07 +11:00
}
2013-11-20 14:18:03 +11:00
instance . ControllerContext = proxiedControllerContext ;
2013-09-04 12:01:41 +10:00
instance . Request = controllerContext . Request ;
2015-01-06 17:39:07 +11:00
if ( WebApiVersionCheck . WebApiVersion > = Version . Parse ( "5.0.0" ) )
{
//now we can change the request context's route data to be the proxied route data - NOTE: we cannot do this directly above
// because it will detect that the request context is different throw an exception. This is a change in webapi2 and we need to set
// this with reflection due to codebase changes between version 4/5
// instance.RequestContext.RouteData = proxiedRouteData;
2015-01-06 18:23:47 +11:00
//NOTE: Use TypeHelper here since the reflection is cached
var instanceRequestContext = TypeHelper . GetProperty ( typeof ( ApiController ) , "RequestContext" ) . GetValue ( instance ) ;
TypeHelper . GetProperty ( instanceRequestContext . GetType ( ) , "RouteData" ) . SetValue ( instanceRequestContext , proxiedRouteData ) ;
2015-01-06 17:39:07 +11:00
}
2013-11-20 14:18:03 +11:00
//invoke auth filters for this sub request
var result = await instance . ControllerContext . InvokeAuthorizationFiltersForRequest ( ) ;
//if a result is returned it means they are unauthorized, just throw the response.
if ( result ! = null )
{
throw new HttpResponseException ( result ) ;
}
2013-06-20 17:47:14 +10:00
//return the root
2013-07-30 13:29:05 +10:00
var node = instance . GetRootNode ( formCollection ) ;
return node = = null
2013-09-12 12:54:34 +02:00
? Attempt < TreeNode > . Fail ( new InvalidOperationException ( "Could not return a root node for tree " + appTree . Type ) )
: Attempt < TreeNode > . Succeed ( node ) ;
2013-06-20 17:47:14 +10:00
}
2013-09-04 12:01:41 +10:00
internal static Attempt < TreeNodeCollection > TryLoadFromControllerTree ( this ApplicationTree appTree , string id , FormDataCollection formCollection , HttpControllerContext controllerContext )
2013-06-20 17:47:14 +10:00
{
var foundControllerTreeAttempt = appTree . TryGetControllerTree ( ) ;
if ( foundControllerTreeAttempt . Success = = false )
{
2013-09-12 12:54:34 +02:00
return Attempt < TreeNodeCollection > . Fail ( foundControllerTreeAttempt . Exception ) ;
2013-05-31 17:20:56 -10:00
}
2013-06-20 17:47:14 +10:00
var foundControllerTree = foundControllerTreeAttempt . Result ;
2013-05-31 17:20:56 -10:00
//instantiate it, since we are proxying, we need to setup the instance with our current context
2013-09-26 15:55:38 +10:00
var instance = ( TreeController ) DependencyResolver . Current . GetService ( foundControllerTree ) ;
2013-05-31 17:20:56 -10:00
instance . ControllerContext = controllerContext ;
2013-09-04 12:01:41 +10:00
instance . Request = controllerContext . Request ;
2013-05-31 17:20:56 -10:00
//return it's data
2013-09-12 12:54:34 +02:00
return Attempt . Succeed ( instance . GetNodes ( id , formCollection ) ) ;
2013-05-31 17:20:56 -10:00
}
2013-07-11 13:26:54 +10:00
internal static Attempt < TreeNode > TryGetRootNodeFromLegacyTree ( this ApplicationTree appTree , FormDataCollection formCollection , UrlHelper urlHelper , string currentSection )
2013-07-09 18:51:45 +10:00
{
var xmlTreeNodeAttempt = TryGetRootXmlNodeFromLegacyTree ( appTree , formCollection , urlHelper ) ;
if ( xmlTreeNodeAttempt . Success = = false )
{
2013-09-12 12:54:34 +02:00
return Attempt < TreeNode > . Fail ( xmlTreeNodeAttempt . Exception ) ;
2013-07-09 18:51:45 +10:00
}
2013-10-28 13:45:38 +11:00
2013-11-15 18:26:40 +11:00
//the root can potentially be null, in that case we'll just return a null success which means it won't be included
if ( xmlTreeNodeAttempt . Result = = null )
{
return Attempt < TreeNode > . Succeed ( null ) ;
}
2013-10-28 13:45:38 +11:00
var legacyController = new LegacyTreeController ( xmlTreeNodeAttempt . Result , appTree . Alias , currentSection , urlHelper ) ;
var newRoot = legacyController . GetRootNode ( formCollection ) ;
return Attempt . Succeed ( newRoot ) ;
2013-07-09 18:51:45 +10:00
}
internal static Attempt < XmlTreeNode > TryGetRootXmlNodeFromLegacyTree ( this ApplicationTree appTree , FormDataCollection formCollection , UrlHelper urlHelper )
2013-05-31 17:20:56 -10:00
{
2013-06-20 17:47:14 +10:00
var treeDefAttempt = appTree . TryGetLegacyTreeDef ( ) ;
if ( treeDefAttempt . Success = = false )
2013-05-31 17:20:56 -10:00
{
2013-09-12 12:54:34 +02:00
return Attempt < XmlTreeNode > . Fail ( treeDefAttempt . Exception ) ;
2013-05-31 17:20:56 -10:00
}
2013-06-20 17:47:14 +10:00
var treeDef = treeDefAttempt . Result ;
var bTree = treeDef . CreateInstance ( ) ;
var treeParams = new LegacyTreeParams ( formCollection ) ;
bTree . SetTreeParameters ( treeParams ) ;
2013-10-28 13:45:38 +11:00
var xmlRoot = bTree . RootNode ;
return Attempt . Succeed ( xmlRoot ) ;
2013-06-20 17:47:14 +10:00
}
2013-05-31 17:20:56 -10:00
2013-09-04 12:01:41 +10:00
internal static Attempt < TreeDefinition > TryGetLegacyTreeDef ( this ApplicationTree appTree )
2013-06-20 17:47:14 +10:00
{
//This is how the legacy trees worked....
var treeDef = TreeDefinitionCollection . Instance . FindTree ( appTree . Alias ) ;
return treeDef = = null
2013-09-12 12:54:34 +02:00
? Attempt < TreeDefinition > . Fail ( new InstanceNotFoundException ( "Could not find tree of type " + appTree . Alias ) )
: Attempt < TreeDefinition > . Succeed ( treeDef ) ;
2013-06-20 17:47:14 +10:00
}
2013-07-11 13:26:54 +10:00
internal static Attempt < TreeNodeCollection > TryLoadFromLegacyTree ( this ApplicationTree appTree , string id , FormDataCollection formCollection , UrlHelper urlHelper , string currentSection )
2013-06-20 17:47:14 +10:00
{
2013-07-09 18:51:45 +10:00
var xTreeAttempt = appTree . TryGetXmlTree ( id , formCollection ) ;
if ( xTreeAttempt . Success = = false )
{
2013-09-12 12:54:34 +02:00
return Attempt < TreeNodeCollection > . Fail ( xTreeAttempt . Exception ) ;
2013-07-09 18:51:45 +10:00
}
2013-10-14 15:36:17 +11:00
return Attempt . Succeed ( LegacyTreeDataConverter . ConvertFromLegacy ( id , xTreeAttempt . Result , urlHelper , currentSection , formCollection ) ) ;
2013-07-09 18:51:45 +10:00
}
internal static Attempt < MenuItemCollection > TryGetMenuFromLegacyTreeRootNode ( this ApplicationTree appTree , FormDataCollection formCollection , UrlHelper urlHelper )
{
var rootAttempt = appTree . TryGetRootXmlNodeFromLegacyTree ( formCollection , urlHelper ) ;
if ( rootAttempt . Success = = false )
{
2013-09-12 12:54:34 +02:00
return Attempt < MenuItemCollection > . Fail ( rootAttempt . Exception ) ;
2013-07-09 18:51:45 +10:00
}
2013-07-11 13:26:54 +10:00
var currentSection = formCollection . GetRequiredString ( "section" ) ;
var result = LegacyTreeDataConverter . ConvertFromLegacyMenu ( rootAttempt . Result , currentSection ) ;
2013-09-12 12:54:34 +02:00
return Attempt . Succeed ( result ) ;
2013-07-09 18:51:45 +10:00
}
internal static Attempt < MenuItemCollection > TryGetMenuFromLegacyTreeNode ( this ApplicationTree appTree , string parentId , string nodeId , FormDataCollection formCollection , UrlHelper urlHelper )
{
var xTreeAttempt = appTree . TryGetXmlTree ( parentId , formCollection ) ;
if ( xTreeAttempt . Success = = false )
{
2013-09-12 12:54:34 +02:00
return Attempt < MenuItemCollection > . Fail ( xTreeAttempt . Exception ) ;
2013-07-09 18:51:45 +10:00
}
2013-07-11 13:26:54 +10:00
var currentSection = formCollection . GetRequiredString ( "section" ) ;
var result = LegacyTreeDataConverter . ConvertFromLegacyMenu ( nodeId , xTreeAttempt . Result , currentSection ) ;
2013-07-09 18:51:45 +10:00
if ( result = = null )
{
2013-09-12 12:54:34 +02:00
return Attempt < MenuItemCollection > . Fail ( new ApplicationException ( "Could not find the node with id " + nodeId + " in the collection of nodes contained with parent id " + parentId ) ) ;
2013-07-09 18:51:45 +10:00
}
2013-09-12 12:54:34 +02:00
return Attempt . Succeed ( result ) ;
2013-07-09 18:51:45 +10:00
}
private static Attempt < XmlTree > TryGetXmlTree ( this ApplicationTree appTree , string id , FormDataCollection formCollection )
{
var treeDefAttempt = appTree . TryGetLegacyTreeDef ( ) ;
2013-06-20 17:47:14 +10:00
if ( treeDefAttempt . Success = = false )
{
2013-09-12 12:54:34 +02:00
return Attempt < XmlTree > . Fail ( treeDefAttempt . Exception ) ;
2013-07-09 18:51:45 +10:00
}
2013-06-20 17:47:14 +10:00
var treeDef = treeDefAttempt . Result ;
//This is how the legacy trees worked....
2013-05-31 17:20:56 -10:00
var bTree = treeDef . CreateInstance ( ) ;
2013-06-20 17:47:14 +10:00
var treeParams = new LegacyTreeParams ( formCollection ) ;
2013-05-31 17:20:56 -10:00
//we currently only support an integer id or a string id, we'll refactor how this works
//later but we'll get this working first
int startId ;
if ( int . TryParse ( id , out startId ) )
{
treeParams . StartNodeID = startId ;
}
else
{
treeParams . NodeKey = id ;
}
var xTree = new XmlTree ( ) ;
bTree . SetTreeParameters ( treeParams ) ;
bTree . Render ( ref xTree ) ;
2013-09-12 12:54:34 +02:00
return Attempt . Succeed ( xTree ) ;
2013-05-31 17:20:56 -10:00
}
}
2013-11-20 14:18:03 +11:00
2013-05-31 17:20:56 -10:00
}