Cross-platform Image Preview and Capture with Xamarin Forms

Introduction

Recently, we had a need to be able to capture images efficiently and to process those images in Xamarin Forms. We were doing this image processing as part of a label reading view for our app onboarding process.

This image capture had to be:

  1. Efficient
  2. Low-memory
  3. Simple

As it turned out, this isn’t too hard to do in Xamarin Forms, but there was a lot to learn. This article will walk you through that process so you don’t have to find all of this information yourself.

If you just want the code and you’d rather skip all the explanations, you can find the complete solution on my GitHub.

Because we also process this image for text, I’ll be doing a follow-up article on how to do that. The code for the text processing is on my GitHub as well.

Custom Renderer (Android)

Because camera preview works very differently on iOS and Android, a custom renderer is needed to implement the different functionality.

For our purposes, we called this renderer CameraPageRenderer. It renders a full screen preview of the image, with optional informational text.

The constructor will look like this – we need quite a few handler classes to accomplish our task:

public CameraPageRenderer(Context context) : base(context) {
  cameraManager = (CameraManager)Context.GetSystemService(Context.CameraService);
  windowManager = Context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
  StateCallback = new CameraStateCallback(this);
  SessionCallback = new CameraCaptureSessionCallback(this);
  CaptureListener = new CameraCaptureListener(this);
  CameraImageReaderListener = new CameraImageListener(this);
  OrientationEventListener = new CameraPageOrientationEventListener(this, Context, global::Android.Hardware.SensorDelay.Normal);
}

Definitions for these classes can be found throughout this article or on the GitHub repository.

First, you need to get camera permissions. In order to do this, you will need an event handler in your main activity called OnCameraAccepted.

public event EventHandler OnCameraAccepted;

Then you need to override OnRequestPermissionsResult in your main activity and trigger the event:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults){
  foreach(permission in permissions){
    if(permission == Manifest.Permission.Camera) OnCameraAccepted(this, null);
  }
}

In your custom renderer for Android, make sure to subscribe to this event before checking permissions. If you have permissions, simply call your StartCamera code immediately. In our case, we’ll be doing this in the OnSurfaceTextureAvailable method:

public void OnSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
  Surface = surface; // store our surface in our renderer

  // if the camera permission has to be accepted, then
  // the camera will be started when that happens.
  (CurrentContext as MainActivity).OnCameraAccepted += StartCamera;

  if (ContextCompat.CheckSelfPermission(CurrentContext, Manifest.Permission.Camera) != Permission.Granted) {
    ActivityCompat.RequestPermissions(CurrentContext, new string[] { Manifest.Permission.Camera }, 1);
  } else {
    StartCamera();
  }
}

Our StartCamera method should look something like this. Error handling and particulars of our application have been left out, and the constant LabelReaderConstants.MinimumUsefulImageWidthPixels should be replaced with your own:

public void StartCamera(object sender = null, EventArgs args = null) {
  string cameraId =
    GetCameraIdForOrientation(LensFacing.Back) ??
    GetCameraIdForOrientation(LensFacing.Front) ??
    GetCameraIdForOrientation(LensFacing.External);

  CameraCharacteristics characteristics = cameraManager.GetCameraCharacteristics(cameraId);
  sensorOrientation = (int)characteristics.Get(CameraCharacteristics.SensorOrientation); // store the orientation for later use

  SetupPreviewMatrix();

  // get the best size based on some minimum width for processing
  var map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
  global::Android.Util.Size[] outputSizes = map.GetOutputSizes((int)ImageFormatType.Jpeg);
  IEnumerable<global::Android.Util.Size> bigSizes = outputSizes.Where(size => size.Width >= LabelReaderConstants.MinimumUsefulImageWidthPixels);
  if (!bigSizes.Any()) {
    bestWidth = outputSizes.Max(size => size.Width);
  } else { // use the biggest if none fit our goal width
    bestWidth = bigSizes.Min(size => size.Width);
  }

  global::Android.Util.Size bestSize = outputSizes.First(size => size.Width == bestWidth);

  // set our reader, add a listener for new images
  Reader = ImageReader.NewInstance(bestSize.Width, bestSize.Height, ImageFormatType.Jpeg, 2);
  Reader.SetOnImageAvailableListener(CameraImageReaderListener, null);
  // finally, open the camera
  cameraManager.OpenCamera(cameraId, StateCallback, null);
}

Here are the two methods referenced in the code above:

private string GetCameraIdForOrientation(LensFacing facingToMatch) {
  CameraCharacteristics characteristics = null;
  return cameraManager.GetCameraIdList().FirstOrDefault(id => {
    characteristics = cameraManager.GetCameraCharacteristics(id);
    int lensFacing = (int)characteristics.Get(CameraCharacteristics.LensFacing);
    return lensFacing == (int)facingToMatch;
  });
}

public void SetupPreviewMatrix() {
  float landscapeScreenRotation = 0.0f;
  if(windowManager.DefaultDisplay.Rotation == SurfaceOrientation.Rotation270) {
    landscapeScreenRotation = 180.0f;
  }

  float width = mainLayout.Width;
  float height = mainLayout.Height;

  Matrix matrix = new Matrix();
  matrix.PostRotate(360.0f - landscapeScreenRotation - sensorOrientation, width / 2.0f, height / 2.0f);
  if (sensorOrientation != 180) {
    matrix.PostScale(width / height, height / width, width / 2.0f, height / 2.0f);
  }
  LiveView.SetTransform(matrix);
}

SetupPreviewMatrix applies transforms to the image preview layer that will ensure it is oriented correctly for the user that is viewing it. This code only handles landscape as that’s all that was required for our project.

Opening the camera will trigger the state callback, which is where we’ll start the session and set our capture options for the preview:

public class CameraStateCallback : CameraDevice.StateCallback { 
  private readonly CameraPageRenderer _renderer;
  public CameraStateCallback(CameraPageRenderer renderer) {
    _renderer = renderer;
  }
  public override void OnOpened(CameraDevice camera) {
    // request a preview capture of the camera, and notify the session
    // that we will be rendering to the image reader, as well as the preview surface.
    _renderer.Camera = camera; // set our camera
    var surface = new Surface(_renderer.Surface); // use our stored surface (texture) to render preview
    _renderer.Builder = camera.CreateCaptureRequest(CameraTemplate.Preview);
    // auto focus the camera
    _renderer.Builder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture);
    _renderer.Builder.Set(CaptureRequest.ControlAfTrigger, (int)ControlAFTrigger.Start);
    _renderer.Builder.AddTarget(surface);
    // start session targeting our image reader and the texture surface
    camera.CreateCaptureSession(new List<Surface> { surface, _renderer.Reader.Surface }, _renderer.SessionCallback, null);
  }
}

Once the camera is opened it will call our session callback object, which looks like this. The constant LabelReaderConstants.ImageCaptureBeginDelayMilliseconds should be replaced with your own:

public class CameraCaptureSessionCallback : CameraCaptureSession.StateCallback {
  private readonly CameraPageRenderer _renderer;
  public CameraCaptureSessionCallback(CameraPageRenderer renderer) {
    _renderer = renderer;
  }
  public override void OnConfigured(CameraCaptureSession session) {
    // set a repeating request for a live preview of the camera
    _renderer.Session = session;
    CaptureRequest request = _renderer.Builder.Build();
    _renderer.Request = request;
    session.SetRepeatingRequest(request, _renderer.CaptureListener, null);
    _renderer.CaptureImage(); // capture single image for processing
  }
}

You’ll notice you need to set a capture listener for the preview images, which you can create as an empty class like this:

public class CameraCaptureListener : CameraCaptureSession.CaptureCallback {
}

The CaptureImage method called after the session is created should look like this:

public void CaptureImage() {
  CaptureRequest.Builder builder = Camera.CreateCaptureRequest(CameraTemplate.StillCapture);
  builder.AddTarget(Reader.Surface);
  Session.Capture(builder.Build(), CaptureListener, null);
}

It simply builds a new request for a single still capture image, targeting the image reader surface. The image reader image ready listener will be called because we set it up in the StartCamera method.

Here is our image available listener for the image reader. Replace LabelReaderConstants.ImageCaptureDelayMilliseconds with a constant of your own:

public class CameraImageListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener {
  private readonly CameraPageRenderer _renderer;
  public CameraImageListener(CameraPageRenderer renderer) {
    _renderer = renderer;
  }
  public void OnImageAvailable(ImageReader reader) {
    if (_renderer.CancellationToken.IsCancellationRequested) { return; }
    // get the byte array data from the first plane
    // of the image. This is sufficient for a JPEG
    // image
    Image image = reader.AcquireLatestImage();
    if (image != null) {
      Image.Plane[] planes = image.GetPlanes();
      ByteBuffer buffer = planes[0].Buffer;
      byte[] bytes = new byte[buffer.Capacity()];
      buffer.Get(bytes);
      // close the image so we can handle another image later
      image.Close();
      (_renderer.Element as LabelReader)?.ProcessPhoto(bytes);
      _renderer.CurrentContext.RunOnUiThread(async () => {
        try {
          await Task.Delay(LabelReaderConstants.ImageCaptureDelayMilliseconds, _renderer.CancellationToken);
        } catch (TaskCanceledException) {
          return;
        }
        _renderer.CaptureImage();
      });
    }
  }
}

It processes the image through our view model, capturing another image after a specified delay. Keep in mind that the CaptureImage call has to be on the main thread for an image capture event to be received.

ViewModel/View

You’ll notice we reference a LabelReader class in the code above, that handles the actual image processing. This is our view class, which we’ll explain below.

The LabelReader view is very simple. It’s just an ContentPage view that will be rendered into by our custom renderer. As such I haven’t included it here.

The view model that we bind to it is a little more interesting. We will have two views/viewmodels. The outer “parent” view is called LabelReaderPage and the inner custom renderer view is the empty once mentioned above.

The LabelReader view code-behind binds a take photo command, and exposes a public method to call it:

public partial class LabelReader : ContentPage
{
  public LabelReader ()
  {
    InitializeComponent ();
  }

  public static readonly BindableProperty TakePhotoCommandProperty =
    BindableProperty.Create(propertyName: nameof(TakePhotoCommand),
      returnType: typeof(ICommand),
      declaringType: typeof(LabelReaderPage));

  public void ProcessPhoto(object image) {
    TakePhotoCommand.Execute(image);
  }

  public void Cancel() {

  }

  /// <summary>
  /// The command for processing photo data.
  /// </summary>
  public ICommand TakePhotoCommand {
    get => (ICommand)GetValue(TakePhotoCommandProperty);
    set => SetValue(TakePhotoCommandProperty, value);
  }
}

The label reader page view simple binds this property:

<?xml version="1.0" encoding="UTF-8"?>
<views:LabelReader
  xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:views="clr-namespace:xxx.Views;assembly=xxx"
  x:Class="xxx.Views.LabelReaderPage"
  TakePhotoCommand="{ Binding TakePhoto }">
</views:LabelReader>

This is the viewmodel that we wire to it:

public class LabelReaderPageViewModel  {
  private BufferBlock<object> ImageQueue;
  private CancellationTokenSource CancellationTokenSource;
  private CancellationToken CancellationToken;

  private Task BackgroundOperation;

  public LabelReaderPageViewModel(INavigationService navigationService) : base(navigationService) {
    CancellationTokenSource = new CancellationTokenSource();
    CancellationToken = CancellationTokenSource.Token;
    ImageQueue = new BufferBlock<object>(new DataflowBlockOptions {
      BoundedCapacity = 1
    });
    BackgroundOperation = Task.Run(() => ProcessImageAsync(CancellationToken));
  }

  private void StopBackgroundOperations() {
    CancellationTokenSource.Cancel();
  }

  private async void ProcessImageAsync(CancellationToken cancellationToken) {
    while (!cancellationToken.IsCancellationRequested) {
      object image = await ImageQueue.ReceiveAsync(cancellationToken);
      // do your image processing here
    }
  }

  /// <summary>
  /// A command that is executed when a photo is taken.
  /// </summary>
  public ICommand TakePhoto => new Command(async (object image) => {
    if (CancellationToken.IsCancellationRequested) { return; }
    // receive any pending image(s), so that our background task will get the latest image
    // when it completes processing on the previous image
    IList<object> queuedData;
    ImageQueue.TryReceiveAll(out queuedData);
    queuedData = null;
    // force GC collect our unused byte arrays
    // so we don't overflow adding another
    GC.Collect();
    GC.WaitForPendingFinalizers();
    await ImageQueue.SendAsync(image, CancellationToken);
  });
}

We use a BufferBlock with a max capacity of 1, so that if we receive more images than we can process, it won’t take up too much memory. forcing garbage collection when we receive an image is not always necessary, but it helps ensure that we don’t get memory overflow issues. We TryReceiveAll on the buffer block to clear it of any previous images we haven’t finished processing, and then we send the new image for processing in our background task, where we await the new value.

Custom Renderer (iOS)

This code in iOS is much simpler. Not in the least because the “surface” for the image preview handles rotation almost natively in their API.

public class CameraPageRenderer : PageRenderer, IAVCaptureVideoDataOutputSampleBufferDelegate {
  /// <summary>
  /// The session we have opened with the camera.
  /// </summary>
  AVCaptureSession captureSession;
  /// <summary>
  /// The camera input in our session.
  /// </summary>
  AVCaptureDeviceInput captureDeviceInput;
  /// <summary>
  /// The output class for frames from our camera session.
  /// </summary>
  AVCaptureVideoDataOutput videoDataOutput;
  /// <summary>
  /// The layer containing the video preview for still image capture
  /// </summary>
  AVCaptureVideoPreviewLayer videoPreviewLayer;
  /// <summary>
  /// The cancellation token source for canceling tasks run in the background
  /// </summary>
  CancellationTokenSource cancellationTokenSource;
  /// <summary>
  /// The cancellation token for canceling tasks run in the background
  /// </summary>
  CancellationToken cancellationToken;

  public CameraPageRenderer() : base() {

  }

  public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations() {
    return UIInterfaceOrientationMask.Landscape;
  }

  protected override void OnElementChanged(VisualElementChangedEventArgs e) {
    base.OnElementChanged(e);
    SetupUserInterface();
    SetupEventHandlers();
  }

  public override void WillAnimateRotation(UIInterfaceOrientation toInterfaceOrientation, double duration) {
    base.WillAnimateRotation(toInterfaceOrientation, duration);
    videoPreviewLayer.Connection.VideoOrientation = GetCaptureOrientation(toInterfaceOrientation);
  }

  public override async void ViewDidLoad() {
    cancellationTokenSource = new CancellationTokenSource();
    cancellationToken = cancellationTokenSource.Token;
    base.ViewDidLoad();
    await AuthorizeCamera();
    SetupLiveCameraStream();
  }

  /// <summary>
  /// Gets authorization to access the camera.
  /// </summary>
  /// <returns></returns>
  async Task AuthorizeCamera() {
    var authStatus = AVCaptureDevice.GetAuthorizationStatus(AVMediaType.Video);
    if (authStatus != AVAuthorizationStatus.Authorized) {
      await AVCaptureDevice.RequestAccessForMediaTypeAsync(AVMediaType.Video);
    }
  }

  /// <summary>
  /// Gets a useable camera for the orientation we require.
  /// </summary>
  /// <param name="orientation"></param>
  /// <returns></returns>
  public AVCaptureDevice GetCameraForOrientation(AVCaptureDevicePosition orientation) {
    var devices = AVCaptureDevice.DevicesWithMediaType(AVMediaType.Video);
    foreach (var device in devices) {
      if (device.Position == orientation) {
        return device;
      }
    }
    return null;
  }

  /// <summary>
  /// Gets the orientation to capture the live preview image at
  /// based on the screen orientation. Always the nearest
  /// landscape mode.
  /// </summary>
  /// <returns></returns>
  private AVCaptureVideoOrientation GetCaptureOrientation(UIInterfaceOrientation orientation) {
  switch (orientation) {
    case UIInterfaceOrientation.LandscapeLeft:
      return AVCaptureVideoOrientation.LandscapeLeft;
    case UIInterfaceOrientation.LandscapeRight:
      return AVCaptureVideoOrientation.LandscapeRight;
    case UIInterfaceOrientation.Portrait:
      return AVCaptureVideoOrientation.LandscapeLeft;
    case UIInterfaceOrientation.PortraitUpsideDown:
      return AVCaptureVideoOrientation.LandscapeRight;
    default:
      return AVCaptureVideoOrientation.LandscapeLeft;
    }
  }

  /// <summary>
  /// Starts a session with the camera, and creates the classes
  /// needed to view a video preview, and capture a still image.
  /// </summary>
  public void SetupLiveCameraStream() {
    captureSession = new AVCaptureSession() {
      SessionPreset = new NSString(AVCaptureSession.PresetHigh)
    };
    videoPreviewLayer = new AVCaptureVideoPreviewLayer(captureSession) {
      Frame = View.Frame,
      Orientation = GetCaptureOrientation(UIApplication.SharedApplication.StatusBarOrientation)
    };
    View.Layer.AddSublayer(videoPreviewLayer);

    AVCaptureDevice captureDevice =
      GetCameraForOrientation(AVCaptureDevicePosition.Back) ??
      GetCameraForOrientation(AVCaptureDevicePosition.Front) ??
      GetCameraForOrientation(AVCaptureDevicePosition.Unspecified);

    captureDeviceInput = AVCaptureDeviceInput.FromDevice(captureDevice);
    captureSession.AddInput(captureDeviceInput);

    videoDataOutput = new AVCaptureVideoDataOutput();

    videoDataOutput.SetSampleBufferDelegateQueue(this, new CoreFoundation.DispatchQueue("frameQueue"));

    captureSession.AddOutput(videoDataOutput);
    captureSession.StartRunning();

    // set last processed time to now so the handler for video frames will wait an appropriate length of time
    // before processing images.
    lastImageProcessedTime = DateTime.Now;
  }

  /// <summary>
  /// Create the UI elements for the user interface.
  /// </summary>
  void SetupUserInterface() {
    // ui label with instructions is centered at the top.
    // to get it to appear at the top, the height must be adjusted to fit.
    // to accomplish this, I call SizeToFit, then set the frame to have
    // the same width as the screen, while preserving the height.
    UILabel takePhotoLabel = new UILabel();
    takePhotoLabel.Text = LabelReaderConstants.PhotoCaptureInstructions;
    int labelMargin = LabelReaderConstants.PhotoCaptureInstructionsMargin;
    takePhotoLabel.Frame = new CoreGraphics.CGRect(labelMargin, labelMargin, View.Frame.Width - labelMargin, View.Frame.Height - labelMargin);
    takePhotoLabel.BackgroundColor = ColorExtensions.ToUIColor(Color.Transparent);
    takePhotoLabel.TextColor = ColorExtensions.ToUIColor(Color.White);
    takePhotoLabel.TextAlignment = UITextAlignment.Center;
    takePhotoLabel.Lines = 0;
    takePhotoLabel.SizeToFit();
    takePhotoLabel.Frame = new CoreGraphics.CGRect(labelMargin, labelMargin, View.Frame.Width - labelMargin, takePhotoLabel.Frame.Height);

    View.AddSubview(takePhotoLabel);
  }

  /// <summary>
  /// Sets up event handlers for UI elements.
  /// </summary>
  void SetupEventHandlers() {

  }

  private bool imageProcessingStarted = false;
  private DateTime lastImageProcessedTime = DateTime.Now;

  [Export("captureOutput:didOutputSampleBuffer:fromConnection:")]
  public void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection) {
    if (!imageProcessingStarted) {
      if ((DateTime.Now - lastImageProcessedTime).TotalMilliseconds < LabelReaderConstants.ImageCaptureBeginDelayMilliseconds) { return; }
      imageProcessingStarted = true;
    }
    if((DateTime.Now - lastImageProcessedTime).TotalMilliseconds < LabelReaderConstants.ImageCaptureDelayMilliseconds) { return; }
    lastImageProcessedTime = DateTime.Now;
    (Element as LabelReader).ProcessPhoto(sampleBuffer);
  }

  public override void ViewDidUnload() {
    base.ViewDidUnload();
    cancellationTokenSource.TryCancelAndDispose();
    captureDeviceInput.TryDispose();
    videoDataOutput.TryDispose();
    captureSession.StopRunning();
    captureSession.TryDispose();
  }
}

Much of this code works very similarly to the Android code for image preview and capture. With iOS, though, a few things are simpler.

For one, we can set the orientation easily as part of their API for video capture. And due to the high quality of the video capture, we can simply process the frames that the video preview is outputting. We also do not have to delay an image capture task, but rather can simply check the last processed photo time when we receive a new frame.

If you’re wondering why we didn’t use higher quality still image captures for iOS, that’s because iOS produces a shutter sound that can be really annoying when you’re taking pictures at a constant rate, and there’s no easy way to turn this sound off.

Conclusions

Image capture with image preview cross-platform is relatively easy to accomplish with Xamarin Forms. It wasn’t simple, although the Android code is simpler if you use the older camera API.

There seems to be a need in Xamarin Forms for an image preview control that is cross-platform, and a cross-platform mechanism for capturing high-quality images from this preview.

The full code is posted on my GitHub for anyone to view and use, and is significantly simpler than the examples that it was created from. Hopefully this serves as a useful resource for those looking for this functionality in their Xamarin apps.

If there’s anything you’d like explained in more detail, please send me an email to let me know (I’ve disabled comments due to spam).

Dependency Injection in .NET MVC 5 and WebApi

Intro

Dependency injection is a very popular concept today, usually as part of an MVC web application. Many new frameworks use DI by default, such as AngularJS and .NET Core, but what do those of us working on previous version of .NET MVC do? Well, there’s a solution.

MVC 5 provides many areas where we can inject our own behavior. This is part of the inversion of control philosophy that underlies many libraries. This means that to dependency inject our own services, all we need to do is create our own MVC controller factory and API controller factory, and come up with a method of registering our services.

This article will lead you through that process, along with full code examples.

Getting Started

Solution Structure

The structure should be as follows. Each item should be a project of the same name. The YourApp.Web project should be a .NET MVC web project, and all projects should output a DLL with the same name as the project. The remaining projects should be class libraries. Service should reference Interface, Web should reference Service and Windsor.

  • YourApp.Interface
  • YourApp.Service
  • YourApp.Web
  • YourApp.Windsor

Packages

The first thing you will need is to download a few packages. We will be using Castle.Windsor as our DI container library. Find this package on NuGet and add it to your project.

Castle Windsor NuGet
Castle Windsor NuGet

Structure

In order for DI to be effective, you will need a services “layer” that contains your business logic. This layer should be made of classes which handle your business logic. For the purpose of this example, we will use the interface and implementation below for a single injected service.

This file should go in YourApp.Interface.

namespace YourApp.Interface {
    public interface IDemoService {
        string SayHello();
    }
}

This file should go in YourApp.Service.

namespace YourApp.Service {
    public class DemoService : IDemoService {
        public string SayHello(){
            return "Hello";
        }
    }
}

Web Config

You will need to add this section to your web config.

<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />

You will need to add this information under the castle section. Add one component tag for each service you will be using. You can set the lifestyle as transient for now.

<castle>
    <components>
      <component id="Demo" service="YourApp.Service.IDemoService, YourApp.Interface" type="YourApp.Service.DemoService, YourApp.Service" lifestyle="transient"></component>
    </components>
</castle>

Defining Dependencies

In order for DI to work, we must first define what our dependencies are and what they resolve to. The installer below has been created for this purpose.

Windsor “installers” register the interfaces and their implementations. In this case, we are registering all controllers, all API controllers, and anything in our app configuration file (services above).

The configuration for the IActionInvoker is a little more special. What we’re doing here is specifying our own custom implementation, as well as passing a parameter to the constructor (the parameter is the DI container). You’ll see why we have to do this later.

There are two methods. One registers for the current assembly, which we won’t be using. The other registers using a specified assembly, which we will pass from our web project. The second method is the one you should be concerned with. The first is just a default which implements the necessary interface.

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Mvc;

namespace YourApp.Windsor {
    public class ServiceInstaller : IWindsorInstaller {
        public void Install(IWindsorContainer container, IConfigurationStore store) {
            container.Register(Classes.FromThisAssembly()
                   .BasedOn<IController>().LifestyleTransient());
            container.Register(Classes.FromThisAssembly()
                   .BasedOn<IHttpController>().LifestyleTransient());
            container.Register(Classes.FromThisAssembly()
                   .BasedOn<FilterAttribute>().LifestyleTransient());
            container.Register(Component.For<IActionInvoker>()
                   .ImplementedBy<WindsorActionInvoker>()
                   .DependsOn(
                      Dependency.OnValue("container", container)
                   ).LifestyleTransient());
            container.Install(Castle.Windsor.Installer.Configuration.FromAppConfig());
        }

        public void InstallFromAssembly(
           IWindsorContainer container, 
           IConfigurationStore store, 
           Assembly assembly
        ) {
            container.Register(Classes.FromAssembly(assembly)
                   .BasedOn<IController>().LifestyleTransient());
            container.Register(Classes.FromAssembly(assembly)
                   .BasedOn<IHttpController>().LifestyleTransient());
            container.Register(Classes.FromAssembly(assembly)
                   .BasedOn<FilterAttribute>().LifestyleTransient());
            container.Register(Component.For<IActionInvoker>()
                   .ImplementedBy<WindsorActionInvoker>()
                   .DependsOn(
                      Dependency.OnValue("container", container)
                   ).LifestyleTransient());
            container.Install(Castle.Windsor.Installer.Configuration.FromAppConfig());
        }
    }
}

Of course, this is just the base class. We’ll inherit from this to pass in our web assembly during installation.

The class below is the installer which will actually be called when the Windsor container is initialized.

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using YourApp.Interface;
using YourApp.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Mvc;

namespace YourApp.Web.Windsor {
    public class WebServiceInstaller : ServiceInstaller, IWindsorInstaller {
        new public void Install(IWindsorContainer container, IConfigurationStore store) {
            InstallFromAssembly(container, store, Assembly.GetExecutingAssembly());
        }
    }
}

Now we just need to call our installer from the application start method in global.asax.cs.

protected void Application_Start() {
    // install windsor (find our class which implements IWindsorInstaller, and calls the Install method)
    Container = new WindsorContainer();
    Container.Install(FromAssembly.This());
}

Replacing the Controller Factory

ASP.NET MVC uses a controller factory to create instances of each controller when the application receives a request. This is what we will be replacing. Our new factory will resolve the controller type using our DI container, which will inject the services into the constructor.

Creating the New Factory

The new factory is pretty simple. When a controller is done being used, we release it from our container. When a controller is created, we resolve the dependencies through our container, and continue on as usual, calling the default factory methods.

using Castle.MicroKernel;
using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace YourApp.Windsor {
    public class WindsorControllerFactory : DefaultControllerFactory {
        private readonly IWindsorContainer _Container;

        public WindsorControllerFactory(IWindsorContainer container) {
            _Container = container;
        }

        public override void ReleaseController(IController controller) {
            _Container.Release(controller);  // The important part: release the component
        }

        protected override IController GetControllerInstance(
            RequestContext requestContext, 
            Type controllerType
        ) {
            if (controllerType == null) {
                throw new HttpException(404, 
                  string.Format(
                      "The controller for path '{0}' could not be found.", 
                      requestContext.HttpContext.Request.Path
                ));
            }

            Controller controller = (Controller)_Container.Resolve(controllerType);

            // new code
            if (controller != null) {
                // Don't worry about this yet. This will help us inject
                //dependencies into our action filters later
                controller.ActionInvoker = _Container.Resolve<IActionInvoker>();
            }

            return controller;
        }
    }
}

The above code follows the same process as the default controller factory that .NET provides, except that it uses the DI container to resolve the controller. The Resolve method is what injects our dependencies into the constructor.

Modify the Application Start

Now that we have this new controller factory, how will we use it? .NET MVC provides a point of extensibility for this, so we will add it there. Here is the code for this.

protected void Application_Start() {
    // install windsor
    Container = new WindsorContainer();
    Container.Install(FromAssembly.This());
    // use new controller factory
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container));
}

Replacing the Action Invoker

The action invoker is the class which determines how actions are called on a controller. This is the point of extensibility where we will dependency inject properties into the filters.

There is one issue however: by this point in the request lifecycle the action filter objects have already been created, so we can’t use the DI container Resolve method to inject the properties into the constructor.

To solve this problem, we will use the extension method below, which injects the services into an action filter for every matching public property.

using Castle.MicroKernel;
using Castle.MicroKernel.ComponentActivator;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;

namespace YourApp.Windsor {
    public static class WindsorExtension {
        public static void InjectProperties(this IKernel kernel, object target) {
            var type = target.GetType();
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var property in props) {
                if (property.CanWrite && kernel.HasComponent(property.PropertyType)) {
                    var value = kernel.Resolve(property.PropertyType);
                    try {
                        property.SetValue(target, value, null);
                    } catch (Exception ex) {
                        var message = string.Format(
                            @"Error setting property {0} on type {1}.
                            See inner exception for more information.", 
                            property.Name, type.FullName
                        );
                        throw new ComponentActivatorException(message, ex, null);
                    }
                }
            }
        }
    }
}

Creating the New Action Invoker

Now we can use this new extension method to loop through the already created action filters and inject their properties, continuing with the default invoke call after we’ve finished injecting our services.

using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace YourApp.Windsor {
    public class WindsorActionInvoker : ControllerActionInvoker {
        readonly IWindsorContainer container;

        public WindsorActionInvoker(IWindsorContainer container) {
            this.container = container;
        }

        protected override ActionExecutedContext InvokeActionMethodWithFilters(
                ControllerContext controllerContext,
                IList filters,
                ActionDescriptor actionDescriptor,
                IDictionary<string, object> parameters) {
            foreach (IActionFilter actionFilter in filters) {
                container.Kernel.InjectProperties(actionFilter);
            }
            return base.InvokeActionMethodWithFilters(
                controllerContext, 
                filters, 
                actionDescriptor, 
                parameters
            );
        }

        protected override AuthorizationContext InvokeAuthorizationFilters(
           ControllerContext controllerContext, 
           IList filters, 
           ActionDescriptor actionDescriptor
        ) {
            foreach (IAuthorizationFilter authFilter in filters) {
                container.Kernel.InjectProperties(authFilter);
            }
            return base.InvokeAuthorizationFilters(
                controllerContext, 
                filters, 
                actionDescriptor
            );
        }
    }
}

Using the New Action Invoker

The controller factory has already been set up to use the new action invoker. That’s what the mysterious line was near the end of our controller factory. To recap, that was this:
controller.ActionInvoker = _Container.Resolve<IActionInvoker>();.
The _Container.Resolve call correctly calls our custom action invoker because we registered it in our installer at the beginning of the tutorial.

Replacing the WebApi Dependency Resolver

The .NET WebApi does not use the same concept of a controller factory (or at least all attempts to use it for DI have been unsuccessful). Instead, we will replace the default dependency resolver with our own.

Creating the New Dependency Resolver

Given that the dependency resolver for .NET WebApi is very similar to what any other DI container does, it is very easy to replace this with our own, using our Windsor DI container instead.

using Castle.MicroKernel;
using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Dependencies;

namespace YourApp.Windsor {
    public class WindsorDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver {
        private readonly IWindsorContainer _container;

        public WindsorDependencyResolver(IWindsorContainer container) {
            if (container == null) {
                throw new ArgumentNullException("container");
            }

            _container = container;
        }
        public object GetService(Type t) {
            return _container.Kernel.HasComponent(t) ? _container.Resolve(t) : null;
        }

        public IEnumerable<object> GetServices(Type t) {
            return _container.ResolveAll(t).Cast<object>().ToArray();
        }

        public IDependencyScope BeginScope() {
            return new WindsorDependencyScope(_container);
        }

        public void Dispose() {

        }
    }
}

Updating the Application Start

To add our new resolver to the WebApi, we again find ourselves in the application start. here we can set our new resolver.

protected void Application_Start() {
    // install windsor
    Container = new WindsorContainer();
    Container.Install(FromAssembly.This());
    // resolve references for API controllers
    // adding a collection sub-resolver resolves things like List when you've only mapped Type.
    // thismay not be needed, but you should test your code with and without it to be sure
    Container.Kernel.Resolver.AddSubResolver(new CollectionResolver(Container.Kernel, true));
    // replace actual dependency resolver with our own
    var dependencyResolver = new WindsorDependencyResolver(Container);
    GlobalConfiguration.Configuration.DependencyResolver = dependencyResolver;
    // use new controller factory
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container));
}

Update the Application Code

Our solution is now ready for dependency injection, but our controllers and actions do not yet have properties to dependency inject into. We will show the updated structure of our controller and action filter classes.

Updating Controllers

Adding DI to our controllers is pretty easy. Our new controller factory will look for a constructor to inject the services into, matching any types with the services we specified. In this case, that’s our demo service. Our new controller constructor will look like this.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

using YourApp.Interface;

namespace YourApp.Web.Controllers {
    public class HomeController : BaseController {
        private readonly IDemoService _DemoService;

        public HomeController() {

        }

        public HomeController(IDemoService demoService) {
            _DemoService = demoService;
        }

        //... actions here
    }
}

You can now reference _DemoService in any of your action methods. You can also easily replace the implementation, or add new services to be injected into the constructor.

Updating Action Filters

If you remember, our action filters have already been instantiated by the time we can inject the properties, so we had to create an extension method to do this. This problem also rules out using constructor injection, so we will take a different approach: property injection. Here is how that looks for a simple filter.

using YourApp.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;

namespace YourApp.Web
{
    public class LayoutFilterAttribute : ActionFilterAttribute {
        // this property is filled in with our service by our special IActionInvoker
        public IDemoService _DemoService { get; set; }

        public LayoutFilterAttribute()
        {
            
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // inject data into layout
            filterContext.Controller.ViewBag.message = _DemoService.SayHello();
        }
    }
}

Conclusion

So that’s all there is to it! It can be quite a lengthy process, but it’s well worth the results, especially if upgrading to .NET Core is not an option, but you’d like to have fine control over your DI process.

The code given here is highly re-usable. If done correctly, you should have a YourApp.Windsor project that you can use in all .NET MVC 5 or WebApi projects for DI, which you can build and use as a dependency for any number of projects without modification.

DI will definitely help you more loosely couple the components of your application, as well as making changes in your application code much easier. You can also dependency inject services into your services, data access objects into your data access layer, and so on, making the pattern highly beneficial, especially when an application may use several implementations of the same service, or may be moved to another data store in the future. DI is good programming practice, and moreover it makes code much more pleasant to work in, and much easier to understand.

Another good takeaway is .NET’s approach to frameworks. They use inversion of control heavily, so there is always a point of extensibility that you can override to provide your own custom functionality. If you ever find yourself wishing a .NET library worked differently, remember: Microsoft has probably left the library heavily open to extension, so there’s no need to force it to do your bidding.

That’s all. I hope this becomes very useful in many people’s projects, it definitely has been for me.