Custom ApiController in Umbraco 8 cannot be reached.
Hello!
I'm currently trying to migrate an Umbraco 7 project to Umbraco 8 but are having difficulties reaching my custom .net ApiControllers. It just throws the typical "No umbraco document matches the url" 404 error. Google haven't been to much help either, so I hope someone in here has a solution or at least a pointer to where to go :-)
In V7 we have a class which inherits from IApplicationEventHandler in which we register routes via the "OnApplicationStarting" method:
This has to my understanding been deppricated as to why the routes is no longer registered and therefore cannot be reached or atleast thats my best guess as to why I cannot reach them.
My controller is inheriting from the built in "ApiController" class.
Have you called GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
in the Initialize() method of an IComponent.
This is the magic that maps any API routes using route attributes.
eg
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using Umbraco.Core.Composing;
using Umbraco.Web;
using Umbraco.Web.Mvc;
namespace Umbraco8.Components
{
public class RegisterCustomApiRoutesComposer : ComponentComposer<RegisterCustomApiRoutesComponent>
{
}
public class RegisterCustomApiRoutesComponent : IComponent
{
public void Initialize()
{
GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
}
public void Terminate()
{
throw new NotImplementedException();
}
}
}
Doing the above, however, results in the following error:
"The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code."
When I'm referring to ApiController i mean the one that is built in with .net Web Api, which is the one I want to inherit from in my controllers. Doing that gives me no connection to the controllers.
Everything do, however, seems to work if my Controller inherits from the UmbracoApiController and I call the full route from Postman like:
"~/umbraco/api/umbracocontent/~", which means that it toally ignores the RoutePrefix and Route attributes.
in the IComponent, (without that the attributes do not work)
switching to inheriting from a plain ApiController also works!
so key difference seems to be that call to MapHttpAttributeRoutes() what I'm not sure is why that 'just works' for me, and not for yourself, I did see an issue on the issue tracker regarding order of Components, and in my V8 project I have lots of components and composers investigating different aspects, I'm not sure if that means by luck mine is being executed when GlobalConfiguration exists....
It do seems weird that it's working on your end, as I have tried what you propose. But I will look deeper into it tomorrow and then come back with the results.
Ps. calling GlobalConfiguration.Configuration.EnsureInitialized(); before MapHttpAttributeRoutes() didn't help either.
and therefore whether adding the Component like so:
public class RegisterCustomRouteComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Components().Append<RegisterCustomRouteComponent>();
}
}
would ensure that at least one IUserComposer exists, and fixes the order... (bit of a guess)
I have now tried adding the RuntimeLevel attribute but that didn't solve the problem, also I have tried adding the component as to your suggestion Marc, still to no avail.
I get the error:
"The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code."
I have tried calling GlobalConfiguration.Configuration.EnsureInitialized(); before the routemapping which didn't solve it either. :-/
After grave digging through lots of old github posts on other products i finally seem to have found a solution on the initializer problem which in the end solves the first problem.
If one calls
configuration.Initializer(configuration);
After
MapHttpAttributeRoutes();
It ensures that the configuration is initialized before handling any requests.
Everything now works and I am able to call my controllers thorugh my route prefixes!
TL;DR for further references:
Create custom API routes composer as pr. Marcs posts.
Call GlobalConfiguration.Configuration.MapHttpAttributeRoutes(); in the Initialize method.
Call GlobalConfiguration.Configuration.Initializer(GlobalConfiguration.Configuration); right after.
You're now able to use route attributes in Umbraco 8 :-)
Another option is to ensure your Configuration component is always first in the Components collection as such:
internal class ApiConfigurationComposer : IComposer
{
public void Compose(Composition composition)
{
composition.Components().Insert<ApiConfigurationComponent>();
}
}
Insert inserts the type at the top of the collection, ensuring it is always called first. I prefer this over using the Initializer function as the latter might cause exceptions in other composers (for example from 3rd party packages) if they use the configuration class for their own configuration.
Full class:
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
internal class ApiConfigurationComposer : IComposer
{
public void Compose(Composition composition)
{
composition.Components().Insert<ApiConfigurationComponent>();
}
public class ApiConfigurationComponent : IComponent
{
public void Initialize()
{
RouteTable.Routes.MapMvcAttributeRoutes();
GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
GlobalConfiguration.Configuration.Formatters.Clear();
GlobalConfiguration.Configuration.Formatters.Add(new JsonMediaTypeFormatter
{
SerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Formatting = Formatting.Indented
}
});
}
public void Terminate()
{
}
}
}
Note that if using Doc Type Grid Editor package in Umbraco v8, this was breaking the preview functionality of DTGE in backoffice and maybe part of the functionality in other packages as well.
Using this instead ensure the DTGE preview still works.
public void Initialize()
{
RouteTable.Routes.MapMvcAttributeRoutes();
GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
// Remove all json formatters then add our custom one
var toRemove = GlobalConfiguration.Configuration.Formatters.Where(t => (t is JsonMediaTypeFormatter)).ToList();
foreach (var r in toRemove)
{
GlobalConfiguration.Configuration.Formatters.Remove(r);
}
var jsonFormatter = new JsonMediaTypeFormatter
{
SerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Formatting = Newtonsoft.Json.Formatting.Indented
}
};
GlobalConfiguration.Configuration.Formatters.Add(jsonFormatter);
}
I have changed my implementation to follow the tips given by Dave dM and can confirm that it removes the necessity for calling GlobalConfiguration.Configuration.Initializer(GlobalConfiguration.Configuration) :-)
if after applying Bjarn procedure for the JSON formatters you end up receiving XML responses - in some edge cases where the Accept header is not received - you might restore the default JSON serialization with this:
I found out that all my struggle with this lied in the fact that the Route decoration per default refers to System.Web.Mvc.RouteAttribute in the Umbraco API controller.
Changing that to use System.Web.Http.RouteAttribute instead made it work "out of the box" as per Marc's first post, without configuring and tweaking further:
Thank you all for contributing with your solutions. Here is the full working Example, based on the inital question:
public class UmbracoComponents
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class MapHttpRoutesComposer : ComponentComposer<MapHttpRoutesComponent>
{
}
public void Compose(Composition composition)
{
composition.Components().Insert<MapHttpRoutesComponent>();
}
public class MapHttpRoutesComponent : IComponent
{
// initialize: runs once when Umbraco starts
public void Initialize()
{
GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
GlobalConfiguration.Configuration.Initializer(GlobalConfiguration.Configuration);
GlobalConfiguration.Configuration.EnsureInitialized();
}
public void Terminate()
{
}
}
}
Custom ApiController in Umbraco 8 cannot be reached.
Hello!
I'm currently trying to migrate an Umbraco 7 project to Umbraco 8 but are having difficulties reaching my custom .net ApiControllers. It just throws the typical "No umbraco document matches the url" 404 error. Google haven't been to much help either, so I hope someone in here has a solution or at least a pointer to where to go :-)
In V7 we have a class which inherits from IApplicationEventHandler in which we register routes via the "OnApplicationStarting" method:
This has to my understanding been deppricated as to why the routes is no longer registered and therefore cannot be reached or atleast thats my best guess as to why I cannot reach them.
My controller is inheriting from the built in "ApiController" class.
A specific route prefix: "api/media";
What I have tried so far:
Any ideas?
Aha if I navigate to the original URL and not the routePrefix'ed it is reached.
Is this expected behavior? That umbraco now ignores RoutePrefix / Route .net attributes?
Hi Christian
Have you called GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
in the Initialize() method of an IComponent.
This is the magic that maps any API routes using route attributes.
eg
regards
Marc
Hello Marc,
Thank you for your response.
Doing the above, however, results in the following error:
"The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code."
Hmm, that's what I added to make my UmbracoAuthorizedApiController route attributes to work...
what does your ApiController look like? and what are the routes you are trying to map?
When I'm referring to ApiController i mean the one that is built in with .net Web Api, which is the one I want to inherit from in my controllers. Doing that gives me no connection to the controllers.
Everything do, however, seems to work if my Controller inherits from the UmbracoApiController and I call the full route from Postman like: "~/umbraco/api/umbracocontent/~", which means that it toally ignores the RoutePrefix and Route attributes.
Hi Christian
If I inherit from UmbracoApiController
eg
then I have success with a request to /fish/api/members
but also I have called
in the IComponent, (without that the attributes do not work)
switching to inheriting from a plain ApiController also works!
so key difference seems to be that call to MapHttpAttributeRoutes() what I'm not sure is why that 'just works' for me, and not for yourself, I did see an issue on the issue tracker regarding order of Components, and in my V8 project I have lots of components and composers investigating different aspects, I'm not sure if that means by luck mine is being executed when GlobalConfiguration exists....
be interesting to see if you called
GlobalConfiguration.Configuration.EnsureInitialized();
before, whether that would make a difference?
Hey Marc,
Thank you for putting so much effort into this!
It do seems weird that it's working on your end, as I have tried what you propose. But I will look deeper into it tomorrow and then come back with the results.
Ps. calling GlobalConfiguration.Configuration.EnsureInitialized(); before MapHttpAttributeRoutes() didn't help either.
To be continued.
Hi Christian, Marc,
Following this thread with a interest.
@Christian
Maybe setting the runtime level on your composer can solve your issue ?
https://our.umbraco.com/documentation/Implementation/Composing/#runtimelevel
Dave
Good shout Dave.. I was also wondering if this might be related: https://github.com/umbraco/Umbraco-CMS/issues/5174
and therefore whether adding the Component like so:
would ensure that at least one IUserComposer exists, and fixes the order... (bit of a guess)
Hey Guys,
Dave thank you for joining in.
I have now tried adding the RuntimeLevel attribute but that didn't solve the problem, also I have tried adding the component as to your suggestion Marc, still to no avail.
I get the error: "The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code."
I have tried calling GlobalConfiguration.Configuration.EnsureInitialized(); before the routemapping which didn't solve it either. :-/
Hi Christian,
Maybe you can post your code ? That may give us some more details we are missing now.
Dave
After grave digging through lots of old github posts on other products i finally seem to have found a solution on the initializer problem which in the end solves the first problem.
If one calls configuration.Initializer(configuration); After MapHttpAttributeRoutes();
It ensures that the configuration is initialized before handling any requests. Everything now works and I am able to call my controllers thorugh my route prefixes!
TL;DR for further references:
Thank you for your time guys!
Glad you got there in the end!
Now we need to update the docs accordingly!
Another option is to ensure your Configuration component is always first in the Components collection as such:
Insert inserts the type at the top of the collection, ensuring it is always called first. I prefer this over using the Initializer function as the latter might cause exceptions in other composers (for example from 3rd party packages) if they use the configuration class for their own configuration.
Full class:
Note that if using Doc Type Grid Editor package in Umbraco v8, this was breaking the preview functionality of DTGE in backoffice and maybe part of the functionality in other packages as well.
I found this old issue for Umbraco https://issues.umbraco.org/issue/U4-8614 where
JsonCamelCaseFormatter
breaks the global configuration for the defaultJsonMediaTypeFormatter
and was fixed here https://github.com/umbraco/Umbraco-CMS/commit/bc2b9ab29850e6473f7c7b6724a5f0e9e1e7a563Using this instead ensure the DTGE preview still works.
/Bjarne
I have changed my implementation to follow the tips given by Dave dM and can confirm that it removes the necessity for calling GlobalConfiguration.Configuration.Initializer(GlobalConfiguration.Configuration) :-)
cool, explains why mine worked, eg component just happened to be first!
Thanks everyone for figuring this out for us!
Small note:
if after applying Bjarn procedure for the JSON formatters you end up receiving XML responses - in some edge cases where the Accept header is not received - you might restore the default JSON serialization with this:
Why? I have no clue - but I read it here and it worked for me.
I found out that all my struggle with this lied in the fact that the Route decoration per default refers to System.Web.Mvc.RouteAttribute in the Umbraco API controller.
Changing that to use System.Web.Http.RouteAttribute instead made it work "out of the box" as per Marc's first post, without configuring and tweaking further:
Output:
Thank you all for contributing with your solutions. Here is the full working Example, based on the inital question:
is working on a reply...