Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Christian Klattrup 8 posts 90 karma points
    Apr 15, 2019 @ 08:39
    Christian Klattrup
    0

    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:

    GlobalConfiguration.Configure(WebApiConfig.Register);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    

    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:

    • Followed some of the answers given here
    • Various versions of a routeprefix both incl.- excluding "umbraco"
    • Tried moving above methods to other classes, but none seem to be fired on startup.

    Any ideas?

  • Christian Klattrup 8 posts 90 karma points
    Apr 15, 2019 @ 09:48
    Christian Klattrup
    0

    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?

  • Marc Goodson 2155 posts 14408 karma points MVP 9x c-trib
    Apr 15, 2019 @ 10:34
    Marc Goodson
    1

    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

    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();
            }
        }
    }
    

    regards

    Marc

  • Christian Klattrup 8 posts 90 karma points
    Apr 15, 2019 @ 10:39
    Christian Klattrup
    0

    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."

  • Marc Goodson 2155 posts 14408 karma points MVP 9x c-trib
    Apr 15, 2019 @ 11:53
    Marc Goodson
    0

    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?

  • Christian Klattrup 8 posts 90 karma points
    Apr 15, 2019 @ 11:58
    Christian Klattrup
    0

    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.

  • Marc Goodson 2155 posts 14408 karma points MVP 9x c-trib
    Apr 15, 2019 @ 13:21
    Marc Goodson
    0

    Hi Christian

    If I inherit from UmbracoApiController

    eg

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using Umbraco.Web.WebApi;
    
    namespace Umbraco8.Controllers
    {
        //[RoutePrefix("umbraco/api/members")]
        public class MembershipApiController : UmbracoApiController
        {
            [HttpGet]
            [Route("fish/api/members")]
            public IEnumerable<string> GetAllMembers()
            {
                return new[] { "Table", "Chair", "Desk", "Computer", "Beer fridge" };
            }
            [HttpGet]
            public IEnumerable<string> GetAllMembers2()
            { 
    
                return new[] { "Table", "Chair", "Desk", "Computer", "Beer fridge" };
            }
        }
    }
    

    then I have success with a request to /fish/api/members

    enter image description here

    but also I have called

     GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
    

    in the IComponent, (without that the attributes do not work)

    switching to inheriting from a plain ApiController also works!

    enter image description here

    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?

  • Christian Klattrup 8 posts 90 karma points
    Apr 15, 2019 @ 13:30
    Christian Klattrup
    0

    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.

  • Dave Woestenborghs 3504 posts 12135 karma points MVP 9x admin c-trib
    Apr 15, 2019 @ 14:11
    Dave Woestenborghs
    0

    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

  • Marc Goodson 2155 posts 14408 karma points MVP 9x c-trib
    Apr 15, 2019 @ 17:31
    Marc Goodson
    1

    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:

    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)

  • Christian Klattrup 8 posts 90 karma points
    Apr 16, 2019 @ 06:18
    Christian Klattrup
    0

    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. :-/

  • Dave Woestenborghs 3504 posts 12135 karma points MVP 9x admin c-trib
    Apr 16, 2019 @ 06:44
    Dave Woestenborghs
    1

    Hi Christian,

    Maybe you can post your code ? That may give us some more details we are missing now.

    Dave

  • Christian Klattrup 8 posts 90 karma points
    Apr 16, 2019 @ 06:52
    Christian Klattrup
    1

    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:

    1. Create custom API routes composer as pr. Marcs posts.
    2. Call GlobalConfiguration.Configuration.MapHttpAttributeRoutes(); in the Initialize method.
    3. Call GlobalConfiguration.Configuration.Initializer(GlobalConfiguration.Configuration); right after.
    4. You're now able to use route attributes in Umbraco 8 :-)

    Thank you for your time guys!

  • Marc Goodson 2155 posts 14408 karma points MVP 9x c-trib
    Apr 16, 2019 @ 13:21
    Marc Goodson
    1

    Glad you got there in the end!

    Now we need to update the docs accordingly!

  • Dave de Moel 122 posts 574 karma points c-trib
    Apr 17, 2019 @ 14:17
    Dave de Moel
    102

    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()
            {
            }
        }
    }
    
  • Bjarne Fyrstenborg 1284 posts 4038 karma points MVP 8x c-trib
    Aug 05, 2019 @ 07:55
    Bjarne Fyrstenborg
    1

    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 default JsonMediaTypeFormatter and was fixed here https://github.com/umbraco/Umbraco-CMS/commit/bc2b9ab29850e6473f7c7b6724a5f0e9e1e7a563

    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);
    }
    

    /Bjarne

  • Christian Klattrup 8 posts 90 karma points
    Apr 23, 2019 @ 07:31
    Christian Klattrup
    1

    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) :-)

  • Marc Goodson 2155 posts 14408 karma points MVP 9x c-trib
    Apr 23, 2019 @ 20:12
    Marc Goodson
    0

    cool, explains why mine worked, eg component just happened to be first!

  • Jose Marcenaro 12 posts 113 karma points c-trib
    Dec 18, 2020 @ 00:19
    Jose Marcenaro
    0

    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:

    jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
    

    Why? I have no clue - but I read it here and it worked for me.

  • Thomsen 113 posts 336 karma points
    Mar 01, 2021 @ 08:48
    Thomsen
    1

    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.

    enter image description here

    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:

    enter image description here

    Output:

    enter image description here

  • Michael Cleverly 8 posts 120 karma points
    Nov 16, 2021 @ 10:04
    Michael Cleverly
    0

    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()
            {
    
            }
        }
    }
    
Please Sign in or register to post replies

Write your reply to:

Draft