Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to work with Umbraco 9.0.0+ / net5.0+ #30

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,8 @@ Cached/
build/_nuget
src/Our.Umbraco.AuthU.TestHarness
src/Our.Umbraco.AuthU.Web
src/Our.Umbraco.AuthU.Matt.*
src/Our.Umbraco.AuthU.Matt.*

# Ignore umbraco folders restored by nuget
src/Our.Umbraco.HeadRest/wwwroot/umbraco
src/Our.Umbraco.HeadRest/umbraco
33 changes: 13 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,31 @@ Out of the box HeadRest is configured to use UmbracoMapper to perform it's mappi
PM> Install-Package Our.Umbraco.HeadRest

## Configuration
In order to configure HeadRest you will first of all need to create an Umbraco composer + compontent combination, resolving the HeadRest service from the DI container like so:
In order to configure HeadRest you will first of all need to create an Umbraco composer:

````csharp
public class HeadRestConfigComponent : IComponent
public class HeadRestConfigComposer : IComposer
{
private readonly HeadRest _headRest;

public HeadRestConfigComponent(HeadRest headRest)
=> _headRest = headRest;

public void Initialize()
public void Compose(IUmbracoBuilder builder)
{
// Configuration goes here
}

public void Terminate() { }
}

public class HeadRestConfigComposer : ComponentComposer<HeadRestConfigComponent>
{ }
public void Terminate() { }
````

From within the `Initialize` method, you can then configure your endpoint(s) via the `ConfigureEndpoint` method on the resolved HeadRest service instance:
From within the `Compose` method, you can then configure your endpoint(s) via the `ConfigureEndpoint` method on a new instance of the HeadRest service:
````csharp
...
_headRest.ConfigureEndpoint(...);
new HeadRest().ConfigureEndpoint(...);
...
````

### Basic Configuration
For the most basic implementation, the following minimal configuration is all that is needed:
````csharp
_headRest.ConfigureEndpoint(new HeadRestOptions {
new HeadRest().ConfigureEndpoint(new HeadRestOptions {
ViewModelMappings = new HeadRestViewModelMap()
.For(HomePage.ModelTypeAlias).MapTo<HomePageViewModel>()
...
Expand All @@ -57,7 +50,7 @@ This will create an API endpoint at the path `/`, and will be anchored to the fi
### Advanced Configuration
For a more advanced implementation, the following configuration shows all the supported options.
````csharp
_headRest.ConfigureEndpoint("/api/", "/root//nodeTypeAlias[1]", new HeadRestOptions {
new HeadRest().ConfigureEndpoint("/api/", "/root//nodeTypeAlias[1]", new HeadRestOptions {
Mode = HeadRestEndpointMode.Dedicated,
ControllerType = typeof(HeadRestController),
Mapper = ctx => AutoMapper.Map(ctx.Content, ctx.ContentType, ctx.ViewModelType),
Expand Down Expand Up @@ -100,17 +93,17 @@ This will create an endpoint at the url `/api/`, and will be anchored to the nod
````csharp
public class MyHeadRestMapDefinition : IMapDefinition
{
public void DefineMaps(UmbracoMapper mapper)
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<FromType, ToType>(
(frm, ctx) => ..., // Constructor function
(frm, to, ctx) => ... // Map function
}
}

public class MyHeadRestMapDefinisionComposer : IUserComposer
public class MyHeadRestMapDefinisionComposer : IComposer
{
public void Compose(Composition composition)
public void Compose(IUmbracoBuilder builder)
{
composition.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
.Add<MyHeadRestMapDefinition>();
Expand All @@ -130,7 +123,7 @@ This will create an endpoint at the url `/api/`, and will be anchored to the nod
| Supports mixed install | Yes. You can have a headless API + website in one install | No. Headless only install |
| Supports custom backend code | Yes | No |
| Client libraries available | None. Roll your own | .NET Framework, .NET Core, Node.js |
| Hosting | Installs to any Umbraco version (7.12+) | Umbraco Cloud service only |
| Hosting | Installs to any Umbraco version (9.0+) | Umbraco Cloud service only |
| Support | Limited community support | HQ Supported |

## Contributing To This Project
Expand Down
17 changes: 0 additions & 17 deletions src/Our.Umbraco.HeadRest/Composing/HeadRestComponent.cs

This file was deleted.

24 changes: 11 additions & 13 deletions src/Our.Umbraco.HeadRest/Composing/HeadRestComposer.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
using Our.Umbraco.HeadRest.Mapping;
using Microsoft.Extensions.DependencyInjection;
using Our.Umbraco.HeadRest.Mapping;
using Our.Umbraco.HeadRest.Web.Routing;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Mapping;
using Umbraco.Web.Routing;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Routing;

namespace Our.Umbraco.HeadRest.Composing
{
public class HeadRestComposer : IUserComposer
public class HeadRestComposer : IComposer
{
public void Compose(Composition composition)
public void Compose(IUmbracoBuilder builder)
{
composition.WithCollectionBuilder<UrlProviderCollectionBuilder>()
builder.WithCollectionBuilder<UrlProviderCollectionBuilder>()
.InsertBefore<DefaultUrlProvider, HeadRestUrlProvider>();

composition.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
.Add<HeadRestMapDefinition>();

composition.Register<HeadRest>();

composition.Components()
.Append<HeadRestComponent>();
builder.Services.AddTransient<HeadRest>();
}
}
}
110 changes: 82 additions & 28 deletions src/Our.Umbraco.HeadRest/HeadRest.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
using System;
using System.Web.Routing;
using System.Collections.Concurrent;
using Umbraco.Web;
using Umbraco.Core;
using Our.Umbraco.HeadRest.Web.Routing;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Our.Umbraco.HeadRest.Interfaces;
using Our.Umbraco.HeadRest.Web.Controllers;
using Umbraco.Core.Mapping;
using Our.Umbraco.HeadRest.Web.Models;
using Our.Umbraco.HeadRest.Web.Routing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
using Umbraco.Extensions;

namespace Our.Umbraco.HeadRest
{
Expand All @@ -18,48 +25,54 @@ public class HeadRest

internal static ConcurrentDictionary<string, HeadRestConfig> Configs = new ConcurrentDictionary<string, HeadRestConfig>();

private readonly UmbracoMapper _mapper;

public HeadRest(UmbracoMapper mapper)
=> _mapper = mapper;

public void ConfigureEndpoint(HeadRestOptions options)
public void ConfigureEndpoint(IUmbracoBuilder builder, HeadRestOptions options)
{
ConfigureEndpoint("/", "/root/*[@isDoc][1]", options);
ConfigureEndpoint("/", "/root/*[@isDoc][1]", builder, options);
}

public void ConfigureEndpoint(string basePath, HeadRestOptions options)
public void ConfigureEndpoint(string basePath, IUmbracoBuilder builder, HeadRestOptions options)
{
ConfigureEndpoint(basePath, "/root/*[@isDoc][1]", options);
ConfigureEndpoint(basePath, "/root/*[@isDoc][1]", builder, options);
}

public void ConfigureEndpoint(string basePath, string rootNodeXPath, HeadRestOptions options)
public void ConfigureEndpoint(string basePath, string rootNodeXPath, IUmbracoBuilder builder, HeadRestOptions options)
{
var config = _mapper.Map<HeadRestConfig>(options);
var config = new HeadRestConfig(options);
config.BasePath = basePath;
config.RootNodeXPath = rootNodeXPath;
ConfigureEndpoint(config);
ConfigureEndpoint(builder, config);
}

private void ConfigureEndpoint(HeadRestConfig config)
private void ConfigureEndpoint(IUmbracoBuilder builder, HeadRestConfig config)
{
ValidateConfig(config);

if (!Configs.ContainsKey(config.BasePath))
{
if (Configs.TryAdd(config.BasePath, config))
{
RouteTable.Routes.MapUmbracoRoute(
$"HeadRest_{config.BasePath.Trim('/').Replace("/", "_")}",
config.BasePath.EnsureEndsWith("/").TrimStart("/") + "{*"+ RoutePathKey + "}",
new
// from https://docs.umbraco.com/umbraco-cms/reference/routing/custom-routes
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
var controllerName = config.ControllerType.Name;
options.AddFilter(new UmbracoPipelineFilter(controllerName)
{
controller = config.ControllerType.Name.TrimEnd("Controller"),
action = "Index",
headRestConfig = config
},
new HeadRestRouteHandler(config),
new { path = new UmbracoRoutesConstraint() });
Endpoints = app => app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
$"HeadRest_{config.BasePath.Trim('/').Replace("/", "_")}",
config.BasePath.EnsureEndsWith("/").TrimStart("/") + "{*" + RoutePathKey + "}",
new
{
controller = config.ControllerType.Name.TrimEnd("Controller"),
action = "Index",
headRestConfig = config
},
constraints: new { path = new UmbracoRoutesConstraint() }
).ForUmbracoPage(FindContent);
})
});
});
}
}
}
Expand All @@ -76,5 +89,46 @@ private static void ValidateConfig(HeadRestConfig config)
throw new Exception("ViewModelMappings can not be null");
}
}

private static IPublishedContent FindContent(ActionExecutingContext actionExecutingContext)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been moved from the old HeadRestRouteHandler

{
var config = actionExecutingContext.RouteData.Values[HeadRest.ControllerConfigKey] as IHeadRestConfig;

var nodeXPath = config.RootNodeXPath;

if (actionExecutingContext.RouteData?.Values != null)
{
if (actionExecutingContext.RouteData.Values.ContainsKey(HeadRest.RoutePathKey)
&& actionExecutingContext.RouteData.Values[HeadRest.RoutePathKey] != null)
{
var path = actionExecutingContext.RouteData.Values[HeadRest.RoutePathKey].ToString();

// Check for a configured custom route
if (config.CustomRouteMappings != null)
{
var match = config.CustomRouteMappings.GetRouteMapFor(path);
if (match != null)
{
path = match.Target;

actionExecutingContext.RouteData.Values.Add(HeadRest.RouteMapMatchKey, match);
}
}

// Construct xpath from path
var pathParts = path.Trim('/').Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var pathPart in pathParts)
{
nodeXPath += $"/*[@urlName='{pathPart}'][1]";
}
}
}

var ctx = actionExecutingContext.HttpContext.RequestServices.GetRequiredService<IUmbracoContextAccessor>();

var node = ctx.GetRequiredUmbracoContext().Content.GetSingleByXPath(nodeXPath);

return node ?? new NotFoundPublishedContent();
}
}
}
11 changes: 11 additions & 0 deletions src/Our.Umbraco.HeadRest/HeadRestConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ namespace Our.Umbraco.HeadRest
{
internal class HeadRestConfig : HeadRestOptions, IHeadRestConfig
{
public HeadRestConfig() { }

public HeadRestConfig(HeadRestOptions options) : base()
{
Mode = options.Mode;
ControllerType = options.ControllerType;
Mapper = options.Mapper;
ViewModelMappings = options.ViewModelMappings;
CustomRouteMappings = options.CustomRouteMappings;
}

public string BasePath { get; set; }
public string RootNodeXPath { get; set; }
}
Expand Down
17 changes: 10 additions & 7 deletions src/Our.Umbraco.HeadRest/HeadRestOptions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using NPoco;
using Our.Umbraco.HeadRest.Interfaces;
using Our.Umbraco.HeadRest.Web.Controllers;
using Our.Umbraco.HeadRest.Web.Mapping;
using Our.Umbraco.HeadRest.Web.Routing;
using Umbraco.Core.Composing;
using Umbraco.Core.Mapping;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Extensions;

namespace Our.Umbraco.HeadRest
{
Expand All @@ -21,7 +23,9 @@ public HeadRestOptions()
{
Mode = HeadRestEndpointMode.Dedicated;
ControllerType = typeof(HeadRestController);
Mapper = (ctx) => {
Mapper = (ctx) =>
{
var mapper = ctx.HttpContext.RequestServices.GetRequiredService<IUmbracoMapper>();

// Currently have to call the UmbracoMapper.Map function
// via reflection as there are no non-generic versions
Expand All @@ -30,8 +34,8 @@ public HeadRestOptions()
// on https://github.com/umbraco/Umbraco-CMS/issues/6250
// and if this ever changes, we should switch to use the
// map methods instead
var mapFunc = typeof(UmbracoMapper).GetMethods()
.First(m => m.Name == "Map"
var mapFunc = mapper.GetType().GetMethods()
.First(m => m.Name == nameof(mapper.Map)
&& m.GetGenericArguments().Count() == 1
&& m.GetParameters()
.Select(p => p.ParameterType)
Expand All @@ -44,8 +48,7 @@ public HeadRestOptions()

var ctxAction = new Action<MapperContext>(x => x.SetHeadRestMappingContext(ctx));

return mapFunc.Invoke(Current.Mapper, new object[] { ctx.Content, ctxAction });

return mapFunc.Invoke(mapper, new object[] { ctx.Content, ctxAction });
};
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Our.Umbraco.HeadRest/Mapping/HeadRestMapDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Umbraco.Core.Mapping;
using Umbraco.Cms.Core.Mapping;

namespace Our.Umbraco.HeadRest.Mapping
{
public class HeadRestMapDefinition : IMapDefinition
{
public void DefineMaps(UmbracoMapper mapper)
public void DefineMaps(IUmbracoMapper mapper)
{
mapper.Define<HeadRestOptions, HeadRestConfig>(
(src, ctx) => new HeadRestConfig(),
Expand Down
Loading