Adventures on the edge

Learning new development technologies (2015 - 2018)
    "When living on the bleeding edge - you sometimes have to bleed" -BillKrat

ASP.NET MVC / REST Web API / Unity IOC

Source code available on GitHub => https://github.com/BillKrat/Framework

“Dependency Injection has become a first class citizen”, I heard this in a video I was watching on ASP.NET MVC 5 and judging by the latest release it is very impressive indeed;  Microsoft nailed it with ASP.NET MVC

I’ve been using the Microsoft.Practices.Unity container (not to be confused with the Unity gaming framework) since its conception and would like to continue doing so  (versus using the ASP.NET MVC internal IOC container).  So I created a wrapper for Unity’s IUnityContainer instance and expose it from my IGwnContainer Container property (code shown below).   I use lazy instantiation to load my Unity container in the getter which comes registered with a logger, event aggregator, AppSettings, and other features provided by my GwnClassBase.

using Microsoft.Practices.Unity;

namespace Gwn.Library.Common.Entities
{
public class GwnContainer : IGwnContainer
{
private IUnityContainer _container;

public IUnityContainer Container
{
get
{ // Lazy instantiation so we'll always return
// the same container
if (_container == null)
{
_container = new GwnClassBase().GetContainer();
_container.RegisterInstance<IGwnContainer>(this);
}
return _container;
}
set { _container = value; }
}
}
}
 
With that done I simply have to register it with the ASP.NET MVC IOC container within the Startup.ConfigureService method as a singleton (line 49 below), I’ll piggy back on the IOC container to ride through the system; I can have the best of both worlds.
 

StartupCode
Figure 1 coding in Startup.cs required to implement Unity Container

On line 72 in figure 1, I added the IGwnContainer interface to the Startup.Configure method parameters which uses ASP.NET MVC parameter injection to provide an instance.  on lines 76-78, I register IFactory and IConcreteProcessor with my Unity Container which are used by my DataController Web API REST service. 

That is all the “coding” my ASP.NET MVC application required to activate the REST Service – I can now do the following from my Web application!  How is this possible?

URL
Figure 2 using a REST Web API service from a ASP.NET MVC application

Well, there is one more step to perform, but it doesn’t require coding, by adding a reference to my Gwn.Library.AspNet project it automagically pulled in the DataController (code shown below) simply because it exists in the library and has a Route(“svc/[controller]”) attribute.  Now any URL for this web application that starts with “svc/data” automatically ends up in this DataController – very cool….

Note: referencing the code below, my Unity container comes into play in the DataController constructor.  I use constructor injection so that the ASP.NET MVC framework will serve up my IGwnContainer instance where I use the Container property to resolve the IFactory instance.  From that point on the _factory field will be used by the data controller to handle request.

using Microsoft.AspNet.Mvc;
using Gwn.Library.Data.Results;
using Gwn.Library.Data.Events;
using Microsoft.Practices.Unity;
using Gwn.Library.Data.Factories;
using Gwn.Library.Common.Entities;

// ----[ !!! THIS FILE IS LINKED !!! ]----------------------------------------
// FROM: Gwn.Library.AspNet\Controllers\DataController.cs
// TO: Gwn.Test.Library.Data (Gwn.Library.AspNet folder)\DataController.cs
// ---------------------------------------------------------------------------
// For more information on enabling Web API for empty projects,
// visit http://go.microsoft.com/fwlink/?LinkID=397860

namespace Gwn.Library.AspNet.Controllers
{
[Route("svc/[controller]")]
public class DataController : Controller
{
public IUnityContainer Container;
private IFactory _factory;

public DataController(IGwnContainer gwnContainer)
{
Container = gwnContainer.Container;
_factory = Container.Resolve<IFactory>();
}
[HttpGet("{factoryName}")]
public ServiceResult Get(string factoryName)
{
var args = new ServiceEventArgs(ServiceEventType.Get);

var factoryArgs = new FactoryEventArgs(factoryName);
var factory = _factory.GetFactory(this, factoryArgs);
var processor = factory.GetConcrete();

var serviceResult = processor.Execute<ServiceResult>(this, args);
return serviceResult;
}
[HttpGet("{factoryName}/{id}")]
public ServiceResult Get(string factoryName, string id)
{
var args = new ServiceEventArgs(ServiceEventType.Get, id);

var factoryArgs = new FactoryEventArgs(factoryName);
var factory = _factory.GetFactory(this, factoryArgs);
var processor = factory.GetConcrete();

var serviceResult = processor.Execute<ServiceResult>(this, args);
return serviceResult;
}
[HttpPost]
public ServiceResult Post([FromBody]ServiceParameter para)
{
var args = new ServiceEventArgs(ServiceEventType.Post, para.Id);

var factoryArgs = new FactoryEventArgs(para.Factory);
var factory = _factory.GetFactory(this, factoryArgs);
var processor = factory.GetConcrete();

var serviceResult = processor.Execute<ServiceResult>(this, args);
return serviceResult;
}

[HttpPut("{id}")]
public ServiceResult Put(string id, [FromBody]ServiceParameter para)
{
var args = new ServiceEventArgs(ServiceEventType.Put, id);

var factoryArgs = new FactoryEventArgs(para.Factory);
var factory = _factory.GetFactory(this, factoryArgs);
var processor = factory.GetConcrete();

var serviceResult = processor.Execute<ServiceResult>(this, args);
return serviceResult;

}

[HttpDelete("{factoryName}/{id}")]
public ServiceResult Delete(string factoryName, string id)
{
var args = new ServiceEventArgs(ServiceEventType.Delete, id);

var factoryArgs = new FactoryEventArgs(factoryName);
var factory = _factory.GetFactory(this, factoryArgs);
var processor = factory.GetConcrete();

var serviceResult = processor.Execute<ServiceResult>(this, args);
return serviceResult;
}
}
}

Obviously I don’t want this Gwn.Library.AspNet project to become bloated with every applications data requirements, so I created a generic DataController that handles the REST operations GET, POST, PUT, and DELETE.  This nicely covers my data CRUDL requirements (create, read, update, delete, and list) .  

It is the IFactory implementation, which will be configured by the application, that will handle what implementation to use for the application’s data requirements.   I’ll show code below that demonstrates how this generic DataController can allow the application to use its own implementation.

The default Factory implementation will handle most requirements; it will permit most Factory classes to look as simple as the following:

namespace Gwn.Library.Data.Factories
{
/// <summary>
/// Test factory uses the default configuration which
/// depends on the following interfaces to be registered:
/// <see cref="IFactory"/>,
/// <see cref="IConcreteProcessor"/>
/// </summary>
/// <example><code><![CDATA[
///
/// _container = new GwnClassBase().GetContainer();
/// _container
/// .RegisterType<IFactory, TestFactory>()
/// .RegisterType<IConcreteProcessor, TestProcessor>()
/// ;
///
/// ]]></code></example>
public class TestFactory : FactoryBase
{
}
}
 
The default FactoryBase operations used by the DataController will simply serve up the IConcreteProcessor that is registered within the Unity container (line 27 below).  The power of the IFactory interface comes when you want to process more than one data access layer for handling data.
 

DataController

 

public class TestFactory : FactoryBase
{
public override IFactory GetFactory(object sender, EventArgs e)
{
// Use Unity named registrations for factory pattern.
// These registrations could be made here - but are
// better done in the application
Container
// http://MyApp/svc/data/UserData/MyID
.RegisterType<IFactory, UserFactory>("UserData")

// http://MyApp/svc/Data/TestData/MyID
.RegisterType<IFactory, TestFactory>("TestData")
;

var args = e as FactoryEventArgs;
return Container.Resolve<IFactory>(args.FactoryName);
}
}
 
Above I can now select the factory to be used by the URL, you’ll see I have URL options for UserData and TestData.  Just as I used named registrations for IFactory so that I can select UserData or TestData, I could just as easily do the same with the IConcreteProcessor which provides an unlimited number of usages – all URL command line driven.
 
I should note that if I want more features than the default REST operations provide, I can easily add a new FooController and use http://MyApp/svc/foo/para1/para2/  versus http://MyApp/svc/Data/factory/id 
As for the Processor, the following is the code that I am using for TDD, it is what provided the result you saw in the bottom pane of figure 2.
 
Processor
 
The following code is the unit test used for TDD of the DataController class:
 
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Practices.Unity;
using Gwn.Library.Common.Entities;
using Gwn.Library.Data.Factories;
using Gwn.Library.Data.Processors;
using Gwn.Library.AspNet.Controllers;
using Gwn.Library.Data.Events;
using System;
using Gwn.Library.Data.Results;

namespace Gwn.Test.Library.Data.Fixtures
{
[TestClass]
public class DataControllerFixture
{
private const string clientFactoryParameter = "Test";

private IUnityContainer _container;
private DataController _controller;

[TestInitialize]
public void InitializeTest()
{
_container = new GwnContainer().Container;

// Register the TestFactory and TestProcessor for use
// by the DataController (in Gwn.Library.AspNet)
_container
.RegisterType<IFactory, TestFactory>()
// ▼▼▼▼▼▼▼▼▼▼▼▼▼
.RegisterType<IConcreteProcessor, TestProcessor>();
// ▲▲▲▲▲▲▲▲▲▲▲▲▲
_controller = _container.Resolve<DataController>();

Assert.IsNotNull(_controller, "Controller null, all test will fail");
}


// ▼▼▼▼▼▼▼▼▼▼▼▼▼
public class TestProcessor : ProcessorBase
// ▲▲▲▲▲▲▲▲▲▲▲▲▲
{
public override T Execute<T>(object sender, EventArgs e)
{
var args = this.EventArgs as FactoryEventArgs;
var returnResult = new ServiceResult();

Func<string> parameter = () =>
args.ServiceParameter == null ? "NA" : (args.ServiceParameter.Id ?? "NA");

var data = $"ID: [{args.Id ?? "NA"} / {parameter()}] TYPE:{args.ServiceEventType}";

returnResult.Data = data;
returnResult.FriendlyMessage = args.ServiceParameter?.Message ?? "Friendly Message";
returnResult.DeveloperMessage =
$"Factory=[{args.FactoryName}] [{args.ServiceParameter?.Tag ?? "NA"}]";

return (T)(object)returnResult;
}
}


[TestMethod] // template: svc/data/Test
public void Can_GET_FromDataController()
{
var result = _controller.Get(clientFactoryParameter);

var expected = "ID: [NA / NA] TYPE:Get";

Assert.AreEqual(expected, result.Data);
Assert.AreEqual("Friendly Message", result.FriendlyMessage);
Assert.AreEqual("Factory=[Test] [NA]", result.DeveloperMessage);
}

[TestMethod] // template: svc/data/Test/100
public void Can_GET_WithID_FromDataController()
{
var para = "100";

var result = _controller.Get(clientFactoryParameter, para);

var expected = "ID: [100 / NA] TYPE:Get";

Assert.AreEqual(expected, result.Data);
Assert.AreEqual("Friendly Message", result.FriendlyMessage);
Assert.AreEqual("Factory=[Test] [NA]", result.DeveloperMessage);
}

[TestMethod] // template: svc/data [FromBody]ServiceParameter para
public void Can_POST_ToDataController()
{
var serviceParameter = new ServiceParameter
{
Id = "500",
Factory = "Test-Factory"
};

var result = _controller.Post(serviceParameter);

var expected = "ID: [NA / 500] TYPE:Post";

Assert.AreEqual(expected, result.Data);
Assert.AreEqual("Friendly Message", result.FriendlyMessage);
Assert.AreEqual("Factory=[Test-Factory] [NA]", result.DeveloperMessage);
}

[TestMethod] // template: svc/data/250 [FromBody]ServiceParameter para
public void Can_PUT_ToDataController()
{
var serviceParameter = new ServiceParameter
{
Id = "500",
Factory = "Test-Factory",
Message = "Replace Friendly Message",
Tag = "Can be used for anything"
};

var result = _controller.Put("250", serviceParameter);

var expected = "ID: [250 / 500] TYPE:Put";

Assert.AreEqual(expected, result.Data);
Assert.AreEqual("Replace Friendly Message", result.FriendlyMessage);
Assert.AreEqual("Factory=[Test-Factory] [Can be used for anything]", result.DeveloperMessage);
}
}
}

Note that the POST and PUT operations provide a ServerParameter object which can contain as many parameters as required for the application.
 
public class ServiceParameter : ParameterBase
{
#region The Id and Factory Properties are used by framework

public string Id { get; set; }

public string Factory { get; set; }

#endregion


// The following are used by the implementation as applicable

public string Tag { get; set; }

public string Message { get; set; }

}
Comments are closed