Automocking Web Api 2 controller - c#

I am trying to auto mock ApiController class in my test cases. It worked perfectly when I was using WebApi1. I started to use WebApi2 on the new project and I am getting this exception thrown after I try to run my new tests:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Security.Cryptography.CryptographicException: pCertContext is an invalid handle.
at System.Security.Cryptography.CAPI.CertSetCertificateContextProperty(SafeCertContextHandle pCertContext, UInt32 dwPropId, UInt32 dwFlags, SafeLocalAllocHandle safeLocalAllocHandle)
at System.Security.Cryptography.X509Certificates.X509Certificate2.set_Archived(Boolean value)
My test code:
[Theory, AutoMoqData]
public void approparte_status_code_is_returned(
string privateKey,
UsersController sut)
{
var response = sut.GetUser(privateKey);
var result = response;
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
Test case does work if I create sut manually:
[Theory, AutoMoqData]
public void approparte_status_code_is_returned(
string privateKey,
[Frozen]Mock<IUserModel> stubModel)
{
var sut = new UsersController(stubModel.Object);
var response = sut.GetUser(privateKey);
var result = response;
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
It's seems that something goes wrong when trying to mock the ControllerContext.RequestContext.ClientCertificate I've tried to create a fixture without it (using AutoFixture .Without() method) but then even the old tests started to fail.
My AutoMoqDataAttribute:
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute()
: base(new Fixture()
.Customize(new WebApiCustomization()))
{
}
}
WebApi customization:
public class WebApiCustomization : CompositeCustomization
{
public WebApiCustomization()
: base(
new HttpRequestMessageCustomization(),
new AutoMoqCustomization())
{
}
}
HttpRequestMessage customization:
public class HttpRequestMessageCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x =>
{
x.Properties[HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();
})
);
}
}
UsersController:
/// <summary>
/// Handles user's account.
/// </summary>
[RoutePrefix("api/v1/users/{privateKey:length(64)}")]
public class UsersController : ApiController
{
private readonly IUserModel _model;
public UsersController(IUserModel model)
{
_model = model;
}
/// <summary>
/// Returns a user.
/// </summary>
/// <param name="privateKey">The private key of the user.</param>
/// <returns>
/// 200 (OK) with user data is returned when user is found.
/// 404 (Not found) is returned when user is not found.
/// </returns>
[HttpGet]
[Route("")]
public HttpResponseMessage GetUser(string privateKey)
{
UserProjection projection;
try
{
projection = new UserProjection(_model.Get(privateKey));
}
catch (UserNotFoundException)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, projection);
}
}

Note:
The original answer requires the same customization to be copied for each new ApiController.
Generalized approach
An alternative way is to automatically fill the Request property on all ApiControllers (thus saving you from cut, copy, and paste):
internal class ApiControllerCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new FilteringSpecimenBuilder(
new Postprocessor(
new MethodInvoker(
new ModestConstructorQuery()),
new ApiControllerFiller()),
new ApiControllerSpecification()));
}
private class ApiControllerFiller : ISpecimenCommand
{
public void Execute(object specimen, ISpecimenContext context)
{
if (specimen == null)
throw new ArgumentNullException("specimen");
if (context == null)
throw new ArgumentNullException("context");
var target = specimen as ApiController;
if (target == null)
throw new ArgumentException(
"The specimen must be an instance of ApiController.",
"specimen");
target.Request =
(HttpRequestMessage)context.Resolve(
typeof(HttpRequestMessage));
}
}
private class ApiControllerSpecification : IRequestSpecification
{
public bool IsSatisfiedBy(object request)
{
var requestType = request as Type;
if (requestType == null)
return false;
return typeof(ApiController).IsAssignableFrom(requestType);
}
}
}
The value of type HttpRequestMessage, for the Request property, is built using the following customization:
internal class HttpRequestMessageCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
new HttpConfiguration()));
}
}
Packing everything into a composite Customization
Create a Customization composite as below - note that the order of AutoFixture Customizations matter:
internal class ApiControllerConventions : CompositeCustomization
{
internal ApiControllerConventions()
: base(
new HttpRequestMessageCustomization(),
new ApiControllerCustomization(),
new AutoMoqCustomization())
{
}
}
Hope that helps.

Note:
Assuming that the UserController class takes an IUserModel through its constructor.
As it looks like, the default constructor of ApiController performs some work (probably more than simple assignments).
If the UserController class takes an IUserModel through its constructor, you can pick that constructor (the greediest) instead.
Update:
Replace the HttpRequestMessageCustomization customization with:
internal class ApiControllerCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
new HttpConfiguration()));
fixture.Customize<UsersController>(c => c
.OmitAutoProperties()
.With(x => x.Request, fixture.Create<HttpRequestMessage>()));
}
}
And the original test will execute fine.

Based on Nikos' answer:
This is a more generic way of using this customization where the controller type can be supplied and the Customization can be used for any controller
internal class WebApiCustomization<TControllerType> : ICustomization
where TControllerType : ApiController
{
public void Customize(IFixture fixture)
{
fixture.Customize<HttpRequestMessage>(c => c
.Without(x => x.Content)
.Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
new HttpConfiguration()));
fixture.Customize<TControllerType>(c => c
.OmitAutoProperties()
.With(x => x.Request, fixture.Create<HttpRequestMessage>()));
}
}
Then use as follows:
var fixture = new Fixture().Customize(
new WebApiCustomization<UsersController>());
var sut = fixture.Create<UsersController>();

Related

Autofac not resolving interfaces in other project

I'm trying to write a generic command bus (Part of class library) that uses different commands and handlers in each of my services.
The following code produces the following exception:
System.Exception: Command does not have any handler RegisterUserCommand
I was under the impression passing the the ExecutingAssemblies of my UserService would allow the Container to resolve the handler in my UserService but apparently not.
Am I doing something wrong?
CommandBus:
public interface ICommandBus
{
void Send<T>(T Command) where T : ICommand;
}
public class CommandBus : ICommandBus
{
private IContainer Container { get; set; }
public CommandBus(Assembly assembly)
{
Container = new CommandBusContainerConfig().Configure(assembly);
}
public void Send<TCommand>(TCommand command) where TCommand : ICommand
{
var handlers = Container.Resolve<IEnumerable<ICommandHandler<TCommand>>>().ToList();
if (handlers.Count == 1)
{
handlers[0].Handle(command);
}
else if (handlers.Count == 0)
{
throw new System.Exception($"Command does not have any handler {command.GetType().Name}");
}
else
{
throw new System.Exception($"Too many registred handlers - {handlers.Count} for command {command.GetType().Name}");
}
}
}
ContainerBuilder:
public class CommandBusContainerConfig : IContainerConfig
{
public IContainer Configure(Assembly executingAssembly)
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(executingAssembly)
.Where(x => x.IsAssignableTo<ICommandHandler>())
.AsImplementedInterfaces();
builder.Register<Func<Type, ICommandHandler>>(c =>
{
var ctx = c.Resolve<IComponentContext>();
return t =>
{
var handlerType = typeof(ICommandHandler<>).MakeGenericType(t);
return (ICommandHandler)ctx.Resolve(handlerType);
};
});
return builder.Build();
}
}
In my UserService(ASP.Net Core 3), which is a different project that references the above CommandBus:
public class RegisterUserCommand : ICommand
{
public readonly string Name;
public readonly Address Address;
public string MobileNumber;
public string EmailAddress;
public RegisterUserCommand(Guid messageId, string name, string mobileNumber, string emailAddress, Address address)
{
Name = name;
Address = address;
MobileNumber = mobileNumber;
EmailAddress = emailAddress;
}
CommandHandler:
public class RegisterUserComnmandHandler : ICommandHandler<RegisterUserCommand>
{
public void Handle(RegisterUserCommand command)
{
Console.WriteLine($"Create user {command.Name} {command.MobileNumber} - handler");
}
}
Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICommandBus>(new CommandBus(Assembly.GetExecutingAssembly()));
}
Controller:
private readonly ICommandBus _commandBus;
public UsersController(ICommandBus commandBus) {
_commandBus = commandBus;
}
// POST api/values
[HttpPost]
public async Task<IActionResult> Post([FromBody]RegisterUserCommand command)
{
if (ModelState.IsValid)
{
CommandBus commandBus = new CommandBus(Assembly.GetExecutingAssembly());
commandBus.Send(command);
_commandBus.Send(Command); //Same result as above
// return result
return Ok(command);
}
return BadRequest();
}
Thanks,
The main error is here :
builder.RegisterAssemblyTypes(executingAssembly)
.Where(x => x.IsAssignableTo<ICommandHandler>())
.AsImplementedInterfaces();
RegisterUserComnmandHandler is not a ICommandHandler but a ICommandHandler<RegisterUserCommand>. Instead of IsAssignableTo<> method you can use the IsClosedTypeOf which is an Autofac extension which do exactly what you can.
builder.RegisterAssemblyTypes(executingAssembly)
.Where(x => x.IsClosedTypeOf(typeof(ICommandHandler<>)))
.AsImplementedInterfaces();
By the way, in your code sample you are using another Container. Most of the time it is always simple to have a single container for the whole application. To get things organised you can use autofac module. You are also resolving straight from the container and not using scope this means that your instance graph won't be disposed at the end of the operation but will stay for the whole lifetime of the container.
In your controller, I saw that you are building a new CommandBus for each request, which will create a new container. Building a new container is a heavy operation and you should avoid doing it often but only once of the startup of the application.
Also I don't get the point of this registration :
builder.Register<Func<Type, ICommandHandler>>(c =>
{
var ctx = c.Resolve<IComponentContext>();
return t =>
{
var handlerType = typeof(ICommandHandler<>).MakeGenericType(t);
return (ICommandHandler)ctx.Resolve(handlerType);
};
});
It doesn't looks you need it and it seems useless to me
This took me a while to figure out. But my CommandHandler interface was incorrectly defined. It should look like:
public interface ICommandHandler { }
public interface ICommandHandler<T> : ICommandHandler where T : ICommand
{
void Handle(T command);
}
}
When trying to resolve the CommandHandler in the Autofac configuration class, the .Where(x => x.IsAssignableTo<ICommandHandler>()) was failing because the class was assignable to ICommandHandle<T> not ICommandHandler

Custom AuthorizeAttribute throwing error after OnAuthorization

I want to do a custom AuthorizeAttribute. I want to use the IMemoryCache to store the tokens and i'm using a custom provider to inject the IMemoryCache instance. My problem is after OnAuthorization method it is not called my controller's action and it throws an internal server error that i'm not able to catch.
And here is the implementation so far
public class ApiAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public IMemoryCache Cache { get; set; }
/// <summary>
/// Verifica se o token é válido na sessão
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public void OnAuthorization(AuthorizationFilterContext context)
{
//Check we have a valid HttpContext
if (context.HttpContext == null)
throw new ArgumentNullException("httpContext");
string token;
token = context.HttpContext.Request.QueryString.Value;
if (String.IsNullOrEmpty(token))
token = context.HttpContext.Request.Form["token"];
if (String.IsNullOrEmpty(token))
{
context.Result = new UnauthorizedResult();
return;
}
if (Cache == null)
{
context.Result = new UnauthorizedResult();
return;
}
if (token.Contains("="))
{
token = token.Split('=')[1];
}
var tokens = Cache.Get<Dictionary<string, User>>("tokens");
var result = (from t in tokens where t.Key == token select t.Value).ToList();
var controller = (string)context.RouteData.Values["controller"];
var action = (string)context.RouteData.Values["action"];
if (result.Count < 1)
context.Result = new UnauthorizedResult();
}
}
public class CacheProvider : IApplicationModelProvider
{
private IMemoryCache _cache;
public CacheProvider(IMemoryCache cache)
{
_cache = cache;
}
public int Order { get { return -1000 + 10; } }
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
foreach (var controllerModel in context.Result.Controllers)
{
// pass the depencency to controller attibutes
controllerModel.Attributes
.OfType<ApiAuthorizeAttribute>().ToList()
.ForEach(a => a.Cache = _cache);
// pass the dependency to action attributes
controllerModel.Actions.SelectMany(a => a.Attributes)
.OfType<ApiAuthorizeAttribute>().ToList()
.ForEach(a => a.Cache = _cache);
}
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
// intentionally empty
}
}
And here is the controller
[ApiAuthorize]
[HttpPost]
public JsonResult Delete([FromForm] string inputId)
{
//Do stuff
}
Thank in advance
After some digging i found this way to do this, i dont know if it is the best way to achieve that
I Created a policy requirement
public class TokenRequirement : IAuthorizationRequirement
{
}
And an AuthorizationHandler
public class TokenRequirementHandler : AuthorizationHandler<TokenRequirement>
{
public IMemoryCache Cache { get; set; }
public TokenRequirementHandler(IMemoryCache memoryCache)
{
Cache = memoryCache;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TokenRequirement requirement)
{
return Task.Run(() => { //access the cache and then
context.Succeed(requirement); });
}
}
On my Startup i registered the handler and add the Authorization
services.AddAuthorization(options =>
{
options.AddPolicy("Token",
policy => policy.Requirements.Add(new Authorize.TokenRequirement()));
});
services.AddSingleton<IAuthorizationHandler, Authorize.TokenRequirementHandler>();
In the controller i used the Authorize attribute
[Authorize(Policy = "Token")]
[HttpPost]
public JsonResult Delete([FromForm] string inputId)
{
//Do stuff
}
And now it works.
Thank you.

Can't get Moq Setup to not return null

I'm trying to Setup a Mock call to a the IModelFactory interface that I have.
here is the IModelFactory interface
public interface IModelFactory
{
MaterialAcceptedModel Create(MaterialAccepted model);
MaterialAccepted Parse(MaterialAcceptedModel model);
}
This is the ModelFactory class which implements the IModelFactor interface (I've only add here the method I'm trying to test, no need to add the implementation of the Create Method)
public class ModelFactory : IModelFactory
{
private UrlHelper _urlHelper;
private IRTWRepository _repo;
//private IKeysGeneratorService _keysGen;
private IGeocodeService _geocoder;
public ModelFactory(HttpRequestMessage request, IRTWRepository repo,IGeocodeService geocoder)
{
_urlHelper = new UrlHelper(request);
_repo = repo;
_geocoder = geocoder;
}
#region Parses
public MaterialAccepted Parse(MaterialAcceptedModel model)
{
try
{
if (!string.IsNullOrWhiteSpace(model.category))
{
var category = _repo.CategoryRepository.Get(model.category);
if (category == null) return null;
var entry = new MaterialAccepted()
{
business = model.business,
businessService = model.businessService,
residential = model.residential,
residentialService = model.residentialService,
note = model.note,
Category = category
};
return entry;
}
return null;
}
catch
{
return null;
}
}
#endregion
}
I'm using a BaseAPiController that contains the repo and configuration Interfaces
public class BaseApiController : ApiController
{
IRTWRepository _repository;
IModelFactory _modelFactory;
IConfiguration _configuration;
IGeocodeService _geocoder;
public BaseApiController(IRTWRepository repository,IConfiguration configuration)
{
_repository = repository;
_configuration = configuration;
}
protected IRTWRepository TheRepository
{
get
{
return _repository;
}
}
protected IConfiguration TheConfiguration
{
get
{
return _configuration;
}
}
protected IModelFactory TheModelFactory
{
get
{
_geocoder = new GeocodeService(_configuration.GetValue("geocodeGoogleApiKey"));
if (_modelFactory == null)
{
_modelFactory = new ModelFactory(this.Request, _repository,_geocoder);
}
return _modelFactory;
}
}
Here is the action method in the controller I'm trying to test
[HttpPost]
[Route("api/recyclecenters/{rcid}/materials/")]
public IHttpActionResult Post(int rcid, [FromBody]MaterialAcceptedModel model)
{
try
{
if (model != null)
{
var recycleCenter = TheRepository.RecycleCenterRepository.Get(rcid);
if (recycleCenter == null)
return NotFound();
if (!ModelState.IsValid)
return BadRequest(ModelState);
var entity = TheModelFactory.Parse(model);
if (entity == null) return BadRequest("Could not read material accepted in body");
if (TheRepository.MaterialAcceptedRepository.Get(recycleCenter.RecycleCenterId, entity.Category.name) != null)
return Conflict();
recycleCenter.Materials.Add(entity);
if (TheRepository.SaveAll())
{
string locationHeader = Url.Link("Materials", new { rcid = rcid, name = model.category.ToLower() });
return Created<MaterialAcceptedModel>(locationHeader, TheModelFactory.Create(entity));
}
return BadRequest("Could not save to the database");
}
return BadRequest();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
This is the line that returns null even though I mocked it up on my test method
var entity = TheModelFactory.Parse(model);
and this one is my TestClass
namespace API.Tests.Web
{
[TestClass]
public class MaterialsControllerTest
{
private Mock<IRTWRepository> repository;
private Mock<IModelFactory> factory;
private Mock<IConfiguration> configuration;
private Mock<IRTWAPIIdentityService> identityService;
private MaterialsController controller;
RecycleCenter recycleCenter;
private MaterialAccepted CreateMaterial()
{
return new MaterialAccepted()
{
business = true,
businessService = EnumRecycleCenterService.Dropoff,
residential = false,
residentialService = EnumRecycleCenterService.Pickup,
note = "this a note",
Category = new Category()
{
name = "Books"
}
};
}
[TestInitialize]
public void Initialize()
{
repository = new Mock<IRTWRepository>();
factory = new Mock<IModelFactory>();
configuration = new Mock<IConfiguration>();
identityService = new Mock<IRTWAPIIdentityService>();
controller = new MaterialsController(repository.Object,configuration.Object);
controller.Request = new HttpRequestMessage();
recycleCenter = new RecycleCenter(){RecycleCenterId = 1};
}
[TestMethod]
public void Post_ShouldReturnConflictIfTheRecycleCenterAlreadyTakesMaterial()
{
//arrange
repository.Setup(r => r.RecycleCenterRepository.Get(It.IsAny<int>())).Returns(() => recycleCenter);
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
configuration.Setup(c => c.GetValue(It.IsAny<string>())).Returns(() => "APIKEY");
repository.Setup(r => r.MaterialAcceptedRepository.Get(It.IsAny<int>(), It.IsAny<string>())).Returns(() => null);
//act
var actionResult = controller.Post(It.IsAny<int>(),new MaterialAcceptedModel());
//assert
Assert.IsInstanceOfType(actionResult, typeof(ConflictResult));
}
}
}
This is the line that's not working because it always return null instead of a new instance of MaterialAccepted
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
I tried f.Parse(It.IsAny()) but still doesn't work.
To clarify
the above line of code is returning null because is not mocking the f.Parse() call, instead is executing it and returning null because the if condition I have on that method
Anyone could explain why the Setup is not working?
Setting up your Mock using It.IsAny will work:
factory.Setup(f => f.Parse(It.IsAny<MaterialAcceptedModel>()))
.Returns(() => new MaterialAccepted());
However, as has been said by #Macilquham, I can't see where your are passing the Mock to your controller in the supplied code so that it is used by the production code.
If you don't call the method on your Mock object, which you don't, you're currently calling the method on the instance of the real object created by your base class, then it doesn't matter how you set up your mock it's not going to work. If you are able to change your base class, then doing something like this would allow you to work around your problem:
// Add defaulted parameter to base class to allow modelFactory creation
// to be overridden/injected (this will prevent the current default behaviour
// when fetching the factory, if a non-null is passed in)
public BaseApiController(IRTWRepository repository,IConfiguration configuration,
IModelFactory modelFactory = null)
{
_modelFactory = modelFactory;
}
Modify your sut constructor to allow you to supply a modelFactory (again, default it to null), then amend your test as appropriate:
controller = new MaterialsController(repository.Object,configuration.Object,
factory.Object);
You don't seem to be injecting in the IModelFactory into the controller. You need to make sure that your production code is using the Mock you are setting up in the test.
Mock cannot return null directly.
The trick is just to create a null object.
Assuming the object returned is of type class Material:
Material nullMaterial = null;
...
repository.Setup(r => r.MaterialAcceptedRepository
.Get(It.IsAny<int>(), It.IsAny<string>()))
.Returns(nullMaterial);
This should solve your problem

Verify that method is called within Action

I am practicing unit-testing a lot of these days, so bear with me if I fail to understand some basics.
Having these simple abstractions:
public interface ITaskFactory
{
void StartTask(Action action);
}
internal sealed class TaskFactory : ITaskFactory
{
public void StartTask(Action action)
{
Task.Factory.StartNew(action);
}
}
And this class to test (simplified to this case):
internal sealed class TriggerEventDecorator<TEvent> : ITriggerEvent<TEvent> where TEvent : IEvent
{
private readonly ITaskFactory _taskFactory;
private readonly Func<ITriggerEvent<TEvent>> _factory;
public TriggerEventDecorator(ITaskFactory taskFactory, Func<ITriggerEvent<TEvent>> factory)
{
_taskFactory = taskFactory;
_factory = factory;
}
public void Trigger(TEvent evt)
{
_taskFactory.StartTask(() =>
{
_factory().Trigger(evt);
});
}
}
And my test of this class:
public class TriggerEventDecoratorTests
{
[Fact]
public void CanTriggerEventHandler()
{
var evt = new FakeEventWithoutValidation();
Assert.IsAssignableFrom<IEvent>(evt);
var decorated = new Mock<ITriggerEvent<FakeEventWithoutValidation>>(MockBehavior.Strict);
decorated.Setup(x => x.Trigger(evt));
var taskFactory = new Mock<ITaskFactory>(MockBehavior.Strict);
taskFactory.Setup(factory => factory.StartTask(It.IsAny<Action>()));
var decorator = new TriggerEventDecorator<FakeEventWithoutValidation>(taskFactory.Object, () => decorated.Object);
decorator.Trigger(evt);
taskFactory.Verify(x => x.StartTask(It.IsAny<Action>()), Times.Once);
decorated.Verify(x => x.Trigger(evt), Times.Once); // This line is not verified
}
}
The line decorated.Verify(x => x.Trigger(evt), Times.Once); is not verified, it is never invoked.
How do I test that this is trigged in the Action of the _taskFactory?
You didn't invoke the Func method. This is the problem... To do so you'll have to use Callback method.
Change the following sertup:
taskFactory.Setup(factory => factory.StartTask(It.IsAny<Action>()));
To:
taskFactory.Setup(factory => factory.StartTask(It.IsAny<Action>()))
.Callback<Action>((action) => action());

No route specified for message

am following Mark Nihof (Fohjin) architecture for develop a cqrs based ecommerce application. My development framework is Asp.net MVC5. Reporting side work fine but when i try to execute command by browsing localhost:63738/api/Security/Signup it shows me following exception
No route specified for message 'RavenProject.Commands.CreateUserCommand'
My message router class is as follows:
public class MessageRouter : IRouteMessages
{
private readonly IDictionary<Type, ICollection<Action<object>>> _routes;
public MessageRouter()
{
_routes = new Dictionary<Type, ICollection<Action<object>>>();
}
public void Register<TMessage>(Action<TMessage> route) where TMessage : class
{
var routingKey = typeof(TMessage);
ICollection<Action<object>> routes;
if (!_routes.TryGetValue(routingKey, out routes))
_routes[routingKey] = routes = new LinkedList<Action<object>>();
routes.Add(message => route(message as TMessage));
}
public void Route(object message)
{
ICollection<Action<object>> routes;
if (!_routes.TryGetValue(message.GetType(), out routes))
throw new RouteNotRegisteredException(message.GetType());
foreach (var route in routes)
route(message);
}
}
and my route register class is as follows:
public class RegisterCommandHandlersInMessageRouter
{
private static MethodInfo _createPublishActionWrappedInTransactionMethod;
private static MethodInfo _registerMethod;
public static void BootStrap()
{
new RegisterCommandHandlersInMessageRouter().RegisterRoutes(ObjectFactory.GetInstance<IRouteMessages>() as MessageRouter);
}
public void RegisterRoutes(MessageRouter messageRouter)
{
_createPublishActionWrappedInTransactionMethod = GetType().GetMethod("CreatePublishActionWrappedInTransaction");
_registerMethod = messageRouter.GetType().GetMethod("Register");
var commands = CommandHandlerFactory.GetCommands();
var commandHandlers = CommandHandlerFactory.GetCommandHandlers();
foreach (var command in commands)
{
IList<Type> commandHandlerTypes;
if (!commandHandlers.TryGetValue(command, out commandHandlerTypes))
throw new Exception(string.Format("No command handlers found for event '{0}'", command.FullName));
foreach (var commandHandler in commandHandlerTypes)
{
var injectedCommandHandler = GetCorrectlyInjectedCommandHandler(commandHandler);
var action = CreateTheProperAction(command, injectedCommandHandler);
RegisterTheCreatedActionWithTheMessageRouter(messageRouter, command, action);
}
}
}
private static object GetCorrectlyInjectedCommandHandler(Type commandHandler)
{
return ObjectFactory.GetInstance(commandHandler);
}
private static void RegisterTheCreatedActionWithTheMessageRouter(MessageRouter messageRouter, Type commandType, object action)
{
_registerMethod.MakeGenericMethod(commandType).Invoke(messageRouter, new[] { action });
}
private object CreateTheProperAction(Type commandType, object commandHandler)
{
return _createPublishActionWrappedInTransactionMethod.MakeGenericMethod(commandType, commandHandler.GetType()).Invoke(this, new[] { commandHandler });
}
public Action<TCommand> CreatePublishActionWrappedInTransaction<TCommand, TCommandHandler>(TCommandHandler commandHandler)
where TCommand : class
where TCommandHandler : ICommandHandler<TCommand>
{
//return command => ObjectFactory.GetInstance<TransactionHandler<TCommand, TCommandHandler>>().Execute(command, commandHandler);
return command => ObjectFactory.GetInstance<ICommandHandler<TCommand>>().Execute(command);
}
}
Where is my mistake yet i failed to identify.
Note: The method that I call from browser will actually a HTTP POST method but for checking purpose I used HTTP GET.
My Route Register class properly register all routes during bootstrap the application.
One can also check whole my work from following link
https://drive.google.com/file/d/0B1rU7HOTfLweZjFuZlF3M0Z2M28/edit?usp=sharing
You will still have to define the routes. See for example here.
_handler = new FirstTestCommandHandler();
var messageRouter = new MessageRouter();
messageRouter.Register<TestCommand>(x => _handler.Execute(x));
DoNotMock.Add(typeof (IRouteMessages), messageRouter);

Categories