I'm trying to use a Generic Controller in my Web API. My goal, which I am currently failing at, is to pass in an object from my front end that will have say a typeId. Based on this typeId I was going to use a factory to inject the correct class implementation of a generic interface. I believe my Factory, Interface and Service is correct, but for some reason when I add a Generic to the API I am getting a 404. It works without a generic and just a test method. I am using autofac for my IoC registration.
API Controller:
public class ListItemsController<T> : ApiControllerBase
{
private readonly IListItemsService<T> _service;
public ListItemsController(int listItemTypeId)
{
_service = ListItemsFactory<T>.InitializeService(listItemTypeId);
}
[HttpGet]
[Route("{listItemTypeId: int}")]
public IEnumerable<T> GetAll()
{
return _service.GetAll();
}
[HttpGet]
[Route("test")]
public IHttpActionResult Test()
{
return Ok();
}
}
Factory:
public class ListItemsFactory<T>
{
public ListItemsFactory(IPrimaryContext context) : base()
{
}
public static IListItemsService<T> InitializeService(int listItemType)
{
switch (listItemType)
{
case 1: return (IListItemsService<T>)
new FloorTypeService(new PrimaryContext());
default: return null;
}
}
}
Interface:
public interface IListItemsService<T>
{
IEnumerable<T> GetAll();
void Save(T obj);
T GetById(int id);
void Delete(int id);
}
Error:
No HTTP resource was found that matches the request URI 'http://localhost:9000/api/v1/listitems/test'. No type was found that matches the controller named 'listitems'.
I'm not sure what piece I'm missing here. I'm using routing attributes but here is my API config:
private static void SetupRoutes(HttpConfiguration config)
{
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
config.Routes.MapHttpRoute("DefaultApi", "api/v{version}/{controller}/{id}",
new { id = RouteParameter.Optional });
}
Instead of resolving the type and trying to map to the right Controller, you also can create a Controller for each Type, which inherits from your GenericController. Then you don't have to copy the Code, but have a Controller for each Type, where you can route to by RouteAttribute.:
public class ListItemsController<T> : ApiControllerBase
{
//Properties/Fields should be protected to can be accessed from InstanceController.
protected readonly IListItemsService<T> _service;
// I think listItemTypeId is not necessary, if generic-type T is used?
public ListItemsController()
{
_service = ListItemsFactory<T>.InitializeService();
}
[HttpGet] // No need for RouteAttribute, because it will be in InstanceController.
public IEnumerable<T> GetAll()
{
return _service.GetAll();
}
[HttpGet]
[Route("test")] // This can rest here, because you want to use it.
public IHttpActionResult Test()
{
return Ok();
}
}
The implemented InstanceController can look like this:
[RoutePrefix("api/{controller}")]
public class FloorItemsController ListItemsController<Floor>
{
// delegate the Constructor-call to base()
public ListItemsController()
:base()
{
}
// No need to reimplement Methods.
}
The RouteConfiguration should be set back to default, because RouteAttributes are set for this.
Basically, what you'll need to do is to replace the controller activator, with a custom implementation.
First, createa class that implements the IHttpControllerSelector interface. Take a look at this link for some of the thing you should be aware before creating a custom activator. At the bottom there's a link to some code example of a custom implmentation.
Now, this depends on what your rules will actually be, but for perfomance reasons,you should try to build a solution that always map the same controller name to the same closed type of your generic controller type. A simple implementation for your case would look something like this:
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//get the generyc type of your controller
var genericControllerType = typeof(ListItemsController<>);
// Get the route value from which you'll get the type argument from your controller.
string typeParameterArgument = GetRouteVariable<string>(routeData, 'SomeKeyUsedToDecideTheClosedType');
Type typeArgument = //Somehow infer the generic type argument, form your route value based on your needs
Type[] typeArgs = { typeof(typeArgument) };
//obtain the closed generyc type
var t = genericControllerType.MakeGenericType(typeArgs);
//configuration must be an instance of HttpConfiguration, most likeley you would inject this on the activator constructor on the config phase
new HttpControllerDescriptor(_configuration, t.Name, t);
}
Finally, on your ApiConfig class you'll need to add this line:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),
new MyOwnActivatior());
I can't test this code right now, so it might need some tweaking, but hopefully this will guide you on the right direction. Do take notice of the link i pasted above, since there are important considerations you'll need to take into account before implementing a custom activator. Also, check the code example linked on that post to see how to implement the GetControllerMapping method
Related
I have controllers which, for the sake of backwards compatibility, only have one action. The JSON request comes with an attribute "type" which determines what the action should do with it.
My idea for a clean solution was to build a set of action handlers. They all inherit from an abstract class called ActionHandler which has two methods
public abstract bool CanHandle(ClientRequest request);
and
public abstract object Handle(dynamic request)
And it has a property
public abstract string ActionForController { get; }
in which the specific actionhandlers just return the name of the controller they want to handle for. This is not very important, but may help clarify something later.
The controller is inserted with an ActionHandlerRegister which has an IEnumerable and a method "GetActionHandler". It returns the first specific ActionHandler that can handle the request.
public ActionHandler GetActionHandler(ClientRequest request)
{
foreach(var actionHandler in ActionHandlers)
{
if (actionHandler.CanHandle(request))
{
return actionHandler;
}
}
throw new BadRequestException(string.Format(CultureInfo.InvariantCulture, BadRequestExceptionTemplate, request.Type));
}
The controllers look like this:
public class LogController : ControllerBase
{
private readonly IActionHandlerRegister<LogController> logHandlers;
public LogController(IActionHandlerRegister<LogController> logHandlers)
{
this.logHandlers = logHandlers ?? throw new ArgumentNullException(nameof(logHandlers));
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] dynamic rawJson)
{
var jsonBody = ((JsonElement)rawJson).ToString();
if (string.IsNullOrEmpty(jsonBody))
{
return BadRequest(ActionHandler.BadRequestRequestNullOrTypeMissingError);
}
var clientRequest = JsonSerializer.Deserialize<ClientRequest>(jsonBody);
if (clientRequest == null || string.IsNullOrEmpty(clientRequest.Type))
{
return BadRequest(ActionHandler.BadRequestRequestNullOrTypeMissingError);
}
try
{
var handler = logHandlers.GetActionHandler(clientRequest);
var result = handler.Handle(rawJson);
return Ok(result);
}
catch (BadRequestException ex)
{
return BadRequest(ex.Message);
}
}
}
For people paying attention: yes, I'm passing the rawjson to handler.Handle. This is because "ClientRequest" is something generic (from which I can read the type) but the handler needs the specific request, so it's deserializing again to something more specific. Maybe there are better solutions for that. Feel free to tell me.
In startup.cs, the insertion of the ActionHandlerRegister into the controller looks like this:
public void RegisterActionHandlersAsSingleton(IServiceCollection services)
{
IEnumerable<ActionHandler> listOfActionHandlers =
from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
from actionHandlerType in domainAssembly.GetTypes()
where actionHandlerType.IsAssignableFrom(typeof(ActionHandler))
select (ActionHandler)Activator.CreateInstance(actionHandlerType);
services.AddSingleton<IActionHandlerRegister<LogController>>(new ActionHandlerRegister<LogController>(listOfActionHandlers.Where(a => a.ActionForController == nameof(LogController))));
// other controllers ...
}
You might be able to guess, this last piece of code crashes at runtime telling me it's unable to cast to ActionHandler.
System.InvalidCastException: Unable to cast object of type
'System.Object' to type
'TcServerModules.ActionHandlers.ActionHandler'.
I have been playing around with different solutions, but none of them scratch that itch. What would be a nice, true-to OO-design principle
BusinessAction is used to represent an action that can be performed by a user. Each action is related to the specific entity, so if for example, that entity is Order, business actions could be CancelOrder, IssueRefund, etc.
public abstract class BusinessAction<T>
{
public Guid Id { get; init; }
public Func<T, bool> IsEnabledFor { get; init; }
}
public class CancelOrderAction : BusinessAction<Order>
{
public CancelOrderAction ()
{
Id = Guid.Parse("0e07d05c-6298-4c56-87d7-d2ca339fee1e");
IsEnabledFor = o => o.Status == OrderStatus.Active;
}
}
Then I need to group all actions related to the specific type.
public interface IActionRegistry
{
Task<IEnumerable<Guid>> GetEnabledActionIdsForAsync(Guid entityId);
}
public class ActionRegistry<T> : IActionRegistry
where T : BaseEntity
{
private readonly IEnumerable<BusinessAction<T>> _actions;
private readonly IRepository<T> _repository;
public ActionRegistry(IEnumerable<BusinessAction<T>> actions, IRepository<T> repository)
{
_actions = actions;
_repository = repository;
}
public async Task<IEnumerable<Guid>> GetEnabledActionIdsForAsync(Guid entityId)
{
var entity = await _repository.FindByIdAsync(entityId);
return entity == null
? Enumerable.Empty<Guid>()
: _actions.Where(a => a.IsEnabledFor(entity)).Select(a => a.Id);
}
}
Finally, there is an API endpoint that receives entity type (some enumeration that is later on mapped to real .NET type) and ID of an entity. The API endpoint is responsible to return action IDs that are enabled for the current state of the entity.
public class RequestHandler : IRequestHandler<Request, IEnumerable<Guid>>>
{
private readonly Func<Type, IActionRegistry> _registryFactory;
public RequestHandler(Func<Type, IActionRegistry> registryFactory)
{
_registryFactory = registryFactory;
}
public async Task<IEnumerable<Guid>> Handle(Request request, CancellationToken cancellationToken)
{
var type = request.EntityType.GetDotnetType();
var actionRegistry = _registryFactory(type);
var enabledActions = await actionRegistry.GetEnabledActionIdsForAsync(request.EntityId);
return enabledActions;
}
}
The question is: How can I configure the dependency injection container in ASP.NET (using default option or Autofac) so that Func<Type, IActionRegistry> can be resolved?
For parameters in ActionRegistry<T> I guess I can do:
builder.RegisterAssemblyTypes().AsClosedTypesOf(typeof(BusinessAction<>));
builder.RegisterGeneric(typeof(Repository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
But, how can I configure Func<Type, IActionRegistry> so that I am able to automatically connect a request for Order with ActionRegistry<Order>? Is there a way to do that or I will need to manually configure the factory by writing some switch statement based on type (and how will that look)?
Is there a better way to achieve what I need here? The end goal is that once I have runtime type, I can get a list of business actions related to that type as well as a repository (so that I can fetch entity from DB).
What you're trying to do is possible, but it's not a common thing and isn't something magic you'll get out of the box. You'll have to write code to implement it.
Before I get to that... from a future perspective, you might get help faster and more eyes on your question if your repro is far more minimal. The whole BusinessAction<T> isn't really needed; the RequestHandler isn't needed... honestly, all you need to repro what you're doing is:
public interface IActionRegistry
{
}
public class ActionRegistry<T> : IActionRegistry
{
}
If the other stuff is relevant to the question, definitely include it... but in this case, it's not, so adding it in here just makes the question harder to read through and answer. I know I, personally, will sometimes just skip questions where there's a lot of extra stuff because there are only so many hours in the day, you know?
Anyway, here's how you'd do it, in working example form:
var builder = new ContainerBuilder();
// Register the action registry generic but not AS the interface.
// You can't register an open generic as a non-generic interface.
builder.RegisterGeneric(typeof(ActionRegistry<>));
// Manually build the factory method. Going from reflection
// System.Type to a generic ActionRegistry<Type> is not common and
// not directly supported.
builder.Register((context, parameters) => {
// Capture the lifetime scope or you'll get an exception about
// the resolve operation already being over.
var scope = context.Resolve<ILifetimeScope>();
// Here's the factory method. You can add whatever additional
// enhancements you need, like better error handling.
return (Type type) => {
var closedGeneric = typeof(ActionRegistry<>).MakeGenericType(type);
return scope.Resolve(closedGeneric) as IActionRegistry;
};
});
var container = builder.Build();
// Now you can resolve it and use it.
var factory = container.Resolve<Func<Type, IActionRegistry>>();
var instance = factory(typeof(DivideByZeroException));
Assert.Equal("ActionRegistry`1", instance.GetType().Name);
Assert.Equal("DivideByZeroException", instance.GetType().GenericTypeArguments[0].Name);
In the code below I want to get data using Repository or from multiple repositories in a controller.
I have two question:
How not to use an empty constructor?
and when used the uncommented code returns a null exception?
public class StudentController : Controller
{
private IStudentRepository _studentRepository;
public StudentController() { }
public StudentController(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
public ActionResult Index()
{
var x = _studentRepository.GetAll().ToList();
return View();
//using (var unitOfWork = new UnitOfWork(new PollingSystemDbContext()))
//{
// var x = unitOfWork.IStudentRepository.GetAll();
// var list = x.ToList();
// return View();
//}
}
}
• By default Controllers require a parameter-less constructor to function. If you don't want to keep the unused parameter-less constructor in the Controller, you can either:
Override the DefaultControllerFactory and wire it so it returns a new instance of a Controller with your dependencies.
public class CustomControllerFactory : DefaultControllerFactory {
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) {
if (controllerType == typeof(StudentController)) {
return new StudentController(new StudentRepository());
}
return base.GetControllerInstance(requestContext, controllerType);
}
}
Then register the new factory in global.asax
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
Keep in mind, that for each controller, you'll need to wire the dependencies yourself.
A better approach is to look into using an IoC Container such as StructureMap to wire up the dependencies for you.
Simply install the following package StructureMap.MVC5.Update.
If your dependencies follow the default convention of IDependency for interfaces, and Dependency for implementations, they will be automatically picked up and wired by StructureMap.
If you need to wire a specific dependency, navigate to DefaultRegistry.cs and add your dependencies.
• The commented code returns null reference exception because you're not passing the model back to the view
var list = x.ToList();
return View(list); //pass the list back
In a simple mvc 4 app I installed Ninject.MVC3 nuget package.
This is my controller, very basic, ISomeClass is being injected in the constructor by ninject.
public class HomeController : Controller
{
private readonly ISomeClass _someClass;
public HomeController(ISomeClass someclass)
{
_someClass = someclass;
}
public ActionResult Index()
{
return View();
}
[ChildActionOnly]
public PartialViewResult MiniView()
{
return PartialView("miniview", _someClass.GetName());
}
}
This is SomeClass
public class SomeClass : ISomeClass
{
private readonly string _someName;
public SomeClass(string someName)
{
_someName = someName;
}
public string GetName()
{
return _someName;
}
}
In Index.cshtml view I have
#{ Html.RenderAction("MiniView","Home"); }
Now in NinjectWebCommon when I go to register the service I need to know if the request was a child action request or not. Like when I call Html.RenderAction. This is what I am trying but it is not working.
kernel.Bind<ISomeClass>().To<SomeClass>()
.WithConstructorArgument("someName", c => IsChildAction(c) ? "Child" : "Nope");
IsChildAction method - Always returns false.
private static bool IsChildAction(Ninject.Activation.IContext c)
{
var handler = HttpContext.Current.Handler;
/*Cant do this, ChildActionMvcHandler is internal*/
return handler is System.Web.Mvc.Html.ChildActionExtensions.ChildActionMvcHandler;
//OR
//This is how ControllerContext.IsChildAction gets its value in System.Web.Mvc but
//RouteData.DataTokens is empty for me
return ((MvcHandler)handler).RequestContext.RouteData.DataTokens
.ContainsKey("ParentActionViewContext");
}
Any ideas if this can be done?
ps: this is not actual code, just trying something. Is this something I should definately not do? Why?
I ended up checking if the current request has a previous handler. Seems like it is being set only on child actions.
HttpContext.Current.PreviousHandler != null &&
HttpContext.Current.PreviousHandler is MvcHandler;
If the IsChildAction is something only known at runtime and within the controller, I suggest you don't pass an instance of SomeClass. Pass a factory instead and build that instance when you need it using the factory. I think that approach would work best in your case.
For using the factory you can use Ninject.Extensions.Factory or implement one yourself:
public class SomeClassFactory
{
private readonly IKernel _kernel;
public SomeClassFactory(IKernel kernel)
{
_kernel = kernel;
}
public SomeClass Create(string name, bool isChild)
{
var childString = (isChild) ? "Child" : "Nope";
return _kernel.Get<SomeClass>(new ConstructorArgument("someName", childString));
}
}
UPDATE:
After you said a Factory wouldn't work, the only way I could figure out for you to have access to the RequestContext during controller creation would be with a custom controller factory. You won't be able to get it statically in your bindings, I'm afraid.
The code below performs resolution on your SomeClass at runtime depending on whether the RequestContext contains the information about child action. It uses a custom IControllerFactory which certainly could have a better implementation, but it is enough to show how it can be done.
internal class CustomControllerFactory : DefaultControllerFactory
{
internal const string ParentActionViewContextToken = "ParentActionViewContext";
private readonly IResolutionRoot _resolutionRoot;
public CustomControllerFactory(IResolutionRoot resolutionRoot)
{
_resolutionRoot = resolutionRoot;
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
//You can improve this later if you want -> you'll need to figure out if your controller will fit into this case
//You can use marker interfaces, common supertype, etc... that's up to you
if (controllerName.Equals("home", StringComparison.InvariantCultureIgnoreCase))
{
var controllerType = typeof (HomeController);
var isChild = requestContext.RouteData.DataTokens.ContainsKey(ParentActionViewContextToken);
var constructorArgument = new ConstructorArgument("someName", (isChild) ? "Child" : "Nope");
var requestForDependency = _resolutionRoot.CreateRequest(typeof(IServiceClient), null, new Parameter[] { constructorArgument }, true, true);
var dependency = _resolutionRoot.Resolve(requestForDependency).SingleOrDefault();
return (IController)_resolutionRoot.Get(controllerType, new ConstructorArgument("service", dependency));
}
//Will go through the default pipeline (IDependencyResolver will be called, not affecting DI of other controllers)
return base.CreateController(requestContext, controllerName);
}
}
Make sure you bind it:
kernel.Bind<IControllerFactory>().To<CustomControllerFactory>();
I'm writing a project in c# asp.net mvc3, and I have a helper-class that looks like this:
using System.Collections.Generic;
using System.Web;
namespace CoPrice.Helpers
{
public static class Messages
{
public static IList<Message> All
{
get
{
var list = ((IList<Message>)HttpContext.Current.Session["_messages"]) ?? new List<Message>();
HttpContext.Current.Session["_messages"] = new List<Message>();
return list;
}
}
public static bool Exists
{
get
{
return (((IList<Message>)HttpContext.Current.Session["_messages"]) ?? new List<Message>()).Count > 0;
}
}
public static void Add(MessageType type, string message)
{
Message m = new Message
{
Type = type,
Text = message
};
HttpContext.Current.Session["_messages"] = HttpContext.Current.Session["_messages"] as List<Message> ?? new List<Message>();
((IList<Message>)HttpContext.Current.Session["_messages"]).Add(m);
}
public enum MessageType
{
Info,
Success,
Error
}
public struct Message
{
public MessageType Type;
public string Text;
}
}
}
However, when I try to use these in a test, it crashes (cause of HttpContext.Current beeing null). How can I make this work both in tests and in the app itself? I don't mind having to change this class to use something else than HttpContext.Current to access the session, but I want it to have properties as it does, so it can't take the session-object as a parameter.
Any ideas on how to solve this problem?
You need to define an IMessagesProvider and use an DI container to inject the implementation of the IMessagesProvider. In real use you'll have an implementation that uses the ASP.Net session. In test use you'll mostly mock it. BTW, you probably shouldn't have a static Messages class. In fact, IMessagesProvider will probably replace your static Messages class.
Ex:
public class FooController : Controller
{
IMessagesProvider _messages;
public FooController(IMessagesProvider messages)
{
// Your DI container will inject the messages provider automatically.
_messages = messages;
}
public ActionResult Index()
{
ViewData["messages"] = _messages.GetMessages(Session.SessionId);
return View();
}
}
Mind you, this is a very simple example. In fact, you probably want one more class which is the model that drives the view. A quick intro:
public ActionResult Index()
{
var model = PrepareModel(_messages);
return View(model);
}
"PrepareModel" is your very own method that instantiates a new model class, fills it with the necessary data, and then you send it off to your view. It's typical to have one model class per view defined. E.g. you'd have model classes like "SignupFormModel", "DashboardModel", "ChatModel", etc., Doing so allows you to have strongly-typed views as well (a great thing).
You can also implement a mock session object that inherits from HttpSessionStateBase class
#xanadont is right that you need to turn Messages into a normal class. But (and I know this is heresy in some circles) there's no need for a one-off interface and a full-blown DI framework. Just make the methods on your Messages class virtual so you can mock them in your unit-tests, and use constructor injection:
public class FooController : Controller
{
Messages _messages;
// MVC will call this ctor
public FooController() : this(new Messages())
{
}
// call this ctor in your unit-tests with a mock object, testing subclass, etc.
public FooController(Messages messages)
{
_messages = messages;
}
}