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 7 posts 78 karma points
    1 week ago
    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 7 posts 78 karma points
    1 week ago
    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 811 posts 5325 karma points MVP 3x c-trib
    7 days ago
    Marc Goodson
    0

    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 7 posts 78 karma points
    7 days ago
    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 811 posts 5325 karma points MVP 3x c-trib
    7 days ago
    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 7 posts 78 karma points
    7 days ago
    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 811 posts 5325 karma points MVP 3x c-trib
    7 days ago
    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 7 posts 78 karma points
    7 days ago
    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 3159 posts 10354 karma points MVP 3x admin c-trib
    7 days ago
    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 811 posts 5325 karma points MVP 3x c-trib
    7 days ago
    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 7 posts 78 karma points
    6 days ago
    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 3159 posts 10354 karma points MVP 3x admin c-trib
    6 days ago
    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 7 posts 78 karma points
    6 days ago
    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 811 posts 5325 karma points MVP 3x c-trib
    6 days ago
    Marc Goodson
    1

    Glad you got there in the end!

    Now we need to update the docs accordingly!

  • Dave de Moel 100 posts 453 karma points c-trib
    5 days ago
    Dave de Moel
    1

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

Write your reply to:

Draft