How to use Dependency Injection in XAF (ASP.NET Custom Actions) Part 4


Now we get to the interesting part. Dependency Injection for an ApiAction.

The interface of the first blog post IRenamer looks like this:

public interface IRenamer
{
    void RenameMe(MyBo1 myBo1);
}

Our implementation from WebApi looks like this

public class WebApiRenamer : IRenamer
{
    public void RenameMe(MyBo1 myBo1)
    {
        myBo1.MyName = string.Format("I was renamed by '{0}'. Original Name '{1}'", typeof (WebApiRenamer).FullName, myBo1.MyName);
    }
}

Now we should register our Renamer in the Bootstrapper:

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var unityContainer = new UnityContainer();

        unityContainer.RegisterType<IDataLayerHelper, DataLayerHelper>(new ContainerControlledLifetimeManager());
        unityContainer.RegisterType<IXpoHelper, XpoHelper>();
        unityContainer.RegisterType<IBusinessObjectRepository, MyBo1Repository>();
        unityContainer.RegisterType<IRenamer, WebApiRenamer>();

        return unityContainer;
    }
}

The WebApiRepository

We need to extend the IBusinessObjectRepository with the method Rename that takes the id of an BusinessObject and returns it's id:

public interface IBusinessObjectRepository
{
    Task<IEnumerable<MyBo1>> GetBusinessObjects();

    Task<MyBo1> GetBusinessObjectById(int id);

    Task<MyBo1> Save(MyBo1 bo);

    Task<MyBo1> Delete(int id);

    Task<MyBo1> Save(int id, MyBo1 bo);

    Task<int> Rename(int id);
}

The implementation can look like this:

public class MyBo1Repository : IBusinessObjectRepository
{
    //...

    public Task<MyBo1> Rename(int id)
    {
        return Task.Run(() =>
        {
            var bo = this.BusinessObjectsXPO.FirstOrDefault(m => m.Oid == id);

            if (bo != null)
            {
                bo.RenameMe();

                bo.Session.CommitTransaction();

                return CreateBusinessObject(bo);
            }
            return null;
        });
    }
}

Let's test this:

[Test]
public async void Rename_Will_Be_Called_Correctly()
{
    var unityContainer = new UnityContainer();

    var mockRenamer = new Mock<IRenamer>();
    mockRenamer.Setup(m => m.RenameMe(It.IsAny<Module.BusinessObjects.MyBo1>()));

    unityContainer.RegisterInstance<IRenamer>(mockRenamer.Object);

    //Arrange
    var xpoHelper = new XpoHelper(unityContainer, CreateDataLayer());

    var session = xpoHelper.GetNewSession();

    var bo = new Module.BusinessObjects.MyBo1(session);

    bo.MyName = "TestName";

    session.CommitTransaction();

    var oid = session.FindObject<Module.BusinessObjects.MyBo1>(null).Oid;

    var repo = new MyBo1Repository(xpoHelper);

    //Act

    var result = await repo.Rename(oid);

    //Assert
    Assert.That(result, Is.Not.Null);
    Assert.That(result.Oid, Is.EqualTo(1));
    Assert.That(result.MyName, Is.EqualTo("TestName"));

    mockRenamer.Verify(m => m.RenameMe(It.IsAny<Module.BusinessObjects.MyBo1>()), Times.Exactly(1));
}

Also test the Real implementation:

[TestFixture]
[Category("CI")]
public class WebApiRenamerTest
{
    [Test]
    public void Ctor_Does_Not_Throw_An_Exception()
    {
        //Arrange / Act / Assert
        Assert.DoesNotThrow(() =>
        {
            var renamer = new WebApiRenamer();

            Assert.That(renamer, Is.InstanceOf<WebApiRenamer>());
        });
    }

    [Test]
    public void Rename_Does_Rename_Object()
    {
        //Arrange
        var renamer = new WebApiRenamer();

        var session = new Session(new SimpleDataLayer(new InMemoryDataStore()));
        var bo = new MyBo1(session);
        bo.MyName = "Test";

        //Act
        renamer.RenameMe(bo);

        //Assert
        Assert.That(bo.MyName, Is.EqualTo("I was renamed by 'XAFDISolution.WebApi.Domain.WebApiRenamer'. Original Name 'Test'"));
    }
}

The WebApiController

Extend the MyBusinessObjectController with the Rename method:

// PUT api/MyBusinessObject/5
[HttpPut]
public async Task<MyBo1> Rename(int id)
{
   return await _repository.Rename(id);
}

The HttpPutAttribute tells the framework that this will be a Put-Http-Request.

Test it:

[Test]
public async void Rename_Will_Call_Rename_And_Return_TheObject()
{
    //Arrange
    var mockRepo = new Mock<IBusinessObjectRepository>();
    mockRepo.Setup(m => m.Rename(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1(){MyName = "Renamed"}));

    var controller = new MyBusinessObjectController(mockRepo.Object);

    //Act
    var actual = await controller.Rename(1);

    //Assert
    Assert.That(actual, Is.Not.Null);
    Assert.That(actual.MyName, Is.EqualTo("Renamed"));
}

Fiddler!

One thing we need to tell WebApi how to handle routes with a custom pattern:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapHttpRoute(
            name: "DefaultApi2",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

The DefaultApi2 will handle our custom actions that will accept a id in the url.

Then we can call fiddler:

Call Fiddler

Result:

Result from fiddler

WebMvc

Repository

We need to extend the WebApiBoRepository:

public async Task<MyBo1> Rename(int id)
{
    var client = CreateHttpClient();

    var response = await client.PutAsync("api/MyBusinessObject/Rename/" + id, new MyBo1(), new JsonMediaTypeFormatter());

    response.EnsureSuccessStatusCode();

    return await response.Content.ReadAsAsync<MyBo1>();
}

Controller

The BOController must be extended:

// POST: /BO/Rename/5
[HttpPut]
public async Task<ActionResult> Rename(int id)
{
    if (ModelState.IsValid)
    {
        try
        {
            var bo = await _repository.Rename(id);

            return RedirectToAction("Details", bo);
        }
        catch
        {
            return View("Index");
        }
    }
    return View("Index");
}

Test it:

[Test]
public async void Rename_Will_Call_Repo_Rename_Correct()
{
    //Arrange
    var mockRepository = new Mock<IBusinessObjectRepository>();
    mockRepository.Setup(m => m.Rename(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1() { Oid = 1}));

    var controller = new BOController(mockRepository.Object);

    
    //Act
    var result = (RedirectToRouteResult)await controller.Rename(1);

    //Assert
    mockRepository.Verify(m => m.Rename(1), Times.Exactly(1));
}

Extent the Index.cshtml:

@model IEnumerable<XAFDISolution.DTO.MyBo1>

@{
    ViewBag.Title = "View1";
}

<h2>View1</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Oid)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MyName)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Oid)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.MyName)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Oid }) |
            @Html.ActionLink("Details", "Details", new { id=item.Oid }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Oid })
            @Html.ActionLink("Rename", "Rename", new { id=item.Oid })
        </td>
    </tr>
}

</table>

Action!

Call it

See the action!

Source is updated:

See here.