3

In my Stateful service, I can write to the ServiceEventSource by calling this:

ServiceEventSource.Current.ServiceMessage(this.Context, "this is my log message");

Does anyone know how I can make that same call in my Stateless WebAPI controller? It seems like I'm unable to get the context into the controller. I noticed it's only available in my OwinCommunicationListener.

Basically, I want to be able to log my controllers like this:

public async Task<IHttpActionResult> Get(string id)
{
   ServiceEventSource.Current.ServiceMessage(this.Context, "this is my log message");
   //Do something
   return Ok(100);
}
LoekD
  • 11,402
  • 17
  • 27
TheSugoiBoi
  • 160
  • 1
  • 13

1 Answers1

3

One way of solving this is to work with Dependency Injection and an IoC, much like you would with a regular WebAPI solution.

If you are using the out-of-the-box supplied OwinCommuncationController and the Startup class you could initialize and add a container to the Startup.ConfigureApp(...) method:

public static class Startup
{
    // This code configures Web API. The Startup class is specified as a type
    // parameter in the WebApp.Start method.
    public static void ConfigureApp(IAppBuilder appBuilder)
    {
        // Configure Web API for self-host. 
        HttpConfiguration config = new HttpConfiguration();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        // Add a DependencyResolver here

        appBuilder.UseWebApi(config);
    }
}

You could use any IoC that you like, here I'll show it for TinyIoC but similar approach for any (Windsor, Unity, Ninject, AutoFac...).

For TinyIoC add the NuGet TinyIoC and TinyIoC.AspNetExtensions and add a class that implements IDependencyResolver:

public class TinyIoCResolver : IDependencyResolver
{
    private readonly TinyIoCContainer _container;

    public TinyIoCResolver(TinyIoCContainer container)
    {
        _container = container;
    }

    public object GetService(Type serviceType)
    {
        return _container.Resolve(serviceType);
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return _container.Resolve(serviceType);
        }
        catch (TinyIoCResolutionException)
        {
            return null;
        }
    }

    public IDependencyScope BeginScope()
    {
        return new TinyIoCResolver(_container.GetChildContainer());
    }

    public void Dispose()
    {
        // Handle dispose
    }
}

Note, this is just the simples implementation, for a better look at this article http://blog.i-m-code.com/2014/04/15/tinyioc-mvc-and-webapi-configuration/

Not update your Startup to allow WebApi to use the DependencyResolver:

public static class Startup
{
    public static void ConfigureApp(IAppBuilder appBuilder, TinyIoCContainer container)
    {
       ...

        config.DependencyResolver = new TinyIoCResolver(container);

       ...
    }
}

And finally register your dependency (StatelessServiceContext) in your service:

internal sealed class WebApiService : StatelessService
{
    public TinyIoCContainer Container { get; private set; }

    public WebApiService(StatelessServiceContext context)
        : base(context)
    {
        Container = new TinyIoCContainer();
        Container.Register<StatelessServiceContext>(context);
    }

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new ServiceInstanceListener[]
        {
            new ServiceInstanceListener(serviceContext => new OwinCommunicationListener(appBuilder => Startup.ConfigureApp(appBuilder, Container), serviceContext, ServiceEventSource.Current, "ServiceEndpoint"))
        };
    }
}

Notice that you have to make a slight change to how you call the Startup.ConfigureApp method in order to supply your container as well.

Now all you have to do is to add StatelessServiceContext as a dependency in the constructor of your ApiController and store it as a member in your controller:

public ValuesController(StatelessServiceContext serviceContext) 
{
    _serviceContext = serviceContext;
}

And from within your controller actions use it:

ServiceEventSource.Current.ServiceMessage(_serviceContext, "this is my log message");

There are many variations to how you can do this, when and where you can create the container, how to resolve the controllers and so on. There should be plenty of guides on how to setup ASP.NET WebApi and IoC dependency injection.

yoape
  • 3,285
  • 1
  • 14
  • 27
  • Glad I could help :) – yoape Feb 07 '17 at 21:11
  • Hey @yoape - Thanks for the detailed code and instructions. I'm following it but I'm encountering this error on the WebAPI controller: "An error occurred when trying to create a controller of type 'CacheController'. Make sure that the controller has a parameterless public constructor. Type 'Endpoint.Controllers.CacheController' does not have a default constructor". – TheSugoiBoi Feb 08 '17 at 21:15
  • Did you set the DependencyResolver to one that uses your IoC container? – yoape Feb 08 '17 at 21:16
  • i.e. this part in ``Startup.ConfigureApp(...)``: ``var container = TinyIoCContainer.Current; config.DependencyResolver = new TinyIoCResolver(container);`` – yoape Feb 08 '17 at 21:17
  • The error you are seeing sounds like the error ASP.NET would throw at you when the default resolver can't construct the controller, which it won't be able to when there is no parameterless constructor – yoape Feb 08 '17 at 21:19
  • hey @yoape - I've got that added in my Startup.cs > ConfigureApp. Why are you setting a "var container" inside ConfigureApp if the input parameters already have a "container" variable? I removed the "var" so that it's just: container = TinyIoCContainer.Current; config.DependencyResolver = new TinyIoCResolver(container); Is that wrong? – TheSugoiBoi Feb 08 '17 at 21:30
  • you are completely right about that, didn't see that when I wrote up the answer, good catch. the line ``var container = TinyIoCContainer.Current;`` should go. I'll update the answer so it doesn't trick anyone else. Sorry about that. – yoape Feb 08 '17 at 21:32
  • No problem! Unfortunately, I'm still having issues with the controller constructor. If I add another constructor without any parameters in there, the error goes away but the code never reaches the constructor with the StatelessServiceContext serviceContext :/ – TheSugoiBoi Feb 08 '17 at 21:36
  • Ah, I re-read your comment. Looks like you were telling me to remove the "var container = TinyIoCContainer.Current;" line altogether. I removed that line, added a parameterless constructor, and it worked. Thanks so much, @yoape !! – TheSugoiBoi Feb 08 '17 at 21:55
  • Was just thinking, if you add a parameterless constructor to your controller then you don't get a hold of the ServiceContext in the controller any more. Don't add a parameterless constructor, the fact that it blows up without it only shows that we haven't setup our dependency resolver correctly. – yoape Feb 08 '17 at 22:01
  • Found it! There is a problem with the ``TinyIoCDependencyResolver``. For services it cannot find it throws an ``TinyIoCResolutionException`` instead of returning null which allows the underlying resolution service in ASP.NET to resolve other services not registered with the container. Just change the ``GetService(Type serviceType)`` method to catch this exception and return null then. After that all should work just fine. – yoape Feb 08 '17 at 22:23
  • Updated the code to reflect this. Better yet, copy the implementation from the linked article (http://blog.i-m-code.com/2014/04/15/tinyioc-mvc-and-webapi-configuration/), it is a more complete implementation of the resolver really. – yoape Feb 08 '17 at 22:32
  • Glad I could help you :) – yoape Feb 08 '17 at 23:00