I want to create controllers action from database (ASP.NET MVC 6 vNext). I have table controller and also actions
action table has properties { ViewPath, ActionName } Where actionName is {Controller}/{ActionName}
I want build pages like this. How can i make it?
I have class for MVC 4 but I need to rewrite it to MVC 6
public class ITSDefaultController : DefaultControllerFactory
{
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
try
{
return base.CreateController(requestContext, controllerName) as Controller;
}
catch (Exception)
{
Controller controller = new ITSControllerBase();
using (var db = new ITS.Database.DatabaseDataContext())
{
string action = requestContext.RouteData.Values["action"] as string;
DynamicAction dynamicAction = null;
if (!db.Controllers.Any(x => x.ControllerName == controllerName && x.Views.Any(v => v.ViewName == action)))
{
dynamicAction = Actions["NotFound"].First();
requestContext.RouteData.Values["controller"] = "NotFound";
requestContext.RouteData.Values["action"] = "Index";
}
else
{
dynamicAction = new DynamicAction()
{
ActionName = db.Views.First(d => d.ViewName == action && d.Controller.ControllerName == controllerName).ViewName,
Result = () => new ViewResult()
};
}
if (dynamicAction != null)
{
controller.ActionInvoker = new DynamicActionInvoker() { DynamicAction = dynamicAction };
}
return controller;
}
}
}
public override void ReleaseController(IController controller)
{
base.ReleaseController(controller);
}
public static ConcurrentDictionary> Actions = new ConcurrentDictionary>();
}
actually I have the same need to replace Mvc pipeline components by some custom ones, and I found that the IControllerFactory and IControllerActivator and their default implementations still the same, then the experience was to replace the Mvc 6 DefaultControllerFactory by the CustomControllerFactory, I've done some tests on the startup class on ConfigureServices :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var serviceDescriptor = services.FirstOrDefault(s => s.ServiceType.FullName.Contains("IControllerFactory"));
var serviceIndex = services.IndexOf(serviceDescriptor);
services.Insert(serviceIndex, new ServiceDescriptor(typeof(IControllerFactory), typeof(CustomControllerFactory), ServiceLifeTime.Singleton));
services.RemoveAt(serviceIndex + 1);
}
and it s done, also you can add an extension method to the IServiceCollection interface:
public static class ServiceCollectionExtensions
{
public static void(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton)
{
var serviceDescriptor = services.FirstOrDefault(s => s.ServiceType == serviceType);
var serviceIndex = services.IndexOf(serviceDescriptor);
services.Insert(serviceIndex, new ServiceDescriptor(serviceType, implementationType, serviceLifetime));
services.RemoveAt(serviceIndex + 1);
}
}
after this modification you can use it as simple as this :
......
services.AddMvc();
services.ReplaceService(typeof(IControllerActivator), typeof(CustomControllerActivator));
services.ReplaceService(typeof(IControllerFactory), typeof(CustomControllerFactory));
......
then you can replace any component on the Mvc 6 Pipeline;
public class HomeController : Controller
{
public string _MyName { get; set; }
// GET: Home
public ActionResult Index()
{
return Content(_MyName);
}
public HomeController(string Name)
{
_MyName = Name;
}
}
public class MyCustomController : IControllerFactory
{
public IController CreateController(RequestContext requestContext, string controllerName)
{
HomeController objHomeController = new HomeController("Any thing Which you want to pass/inject.");
return objHomeController;
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
public void ReleaseController(IController controller)
{
IDisposable disposable = controller as IDisposable;
if(disposable!=null)
{
disposable.Dispose();
}
}
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new MyCustomController());
}
Related
What i'm looking is for a way to use an action filters that can log info about an method/action that is called from another action. Is this possible?
I'm going to give an example i'm trying to make it work:
I have an action with the name Create from the CampaignsController that gets posted and inside that action i call another "action/method" that i want to get info logged.
Wha't i've found so far:
Action filter only work on classes that extend Controller.
I can't use filters on action/method that get called inside another action.
I can put information inside TempData and after that access it inside the action filter, but i don't know if this is the best solution.
Thanks.
The code i'm using is below:
CampaingsController
namespace GBD_01.Controllers
{
[SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
public class CampaignsController : Controller
{
private GBD.FrontOffice.Controllers.FieldsController controller = new GBD.FrontOffice.Controllers.FieldsController();
[HttpGet]
public ActionResult Create(string returnUrl = "")
{
ViewBag.returnUrl = returnUrl;
return View();
}
[HttpPost]
public ActionResult Create(Campaign model, string returnUrl)
{
if (model.Type == "EMAIL")
{
ModelState.Remove("message");
}
else if(model.Type == "SMS")
{
ModelState.Remove("URL");
}
if (ModelState.IsValid)
{
EgoiClass egoiCls = new EgoiClass();
if (model.Type == "EMAIL")
{
egoiCls.createCampaignEmail(model.CampaignName, model.Subject, model.URL);
}
else if (model.Type == "SMS")
egoiCls.createCampaignSMS(model.CampaignName, model.Subject, model.message, model.URL);
}
return View(model);
}
method of the object that is called:
namespace GBD_01.Controllers
{
public class EgoiController
{
public Dictionary<string, object> createCampaignEmail(string subject, string internal_Name, string url)
{
try
{
string apikey = vars.getKeyValue("EgoiApiKey");
if (!String.IsNullOrEmpty(apikey))
{
GBD.FrontOffice.Controllers.FieldsController fieldCtrl = new GBD.FrontOffice.Controllers.FieldsController();
EgoiApi api = EgoiApiFactory.getApi(Protocol.XmlRpc);
EgoiMap map = new EgoiMap();
EgoiMap mapResult = new EgoiMap();
string senderCode = vars.getKeyValue("EgoiSender");/*ProjectSession.EgoiSenderCode*/;
map.Add("apikey", apikey);
map.Add("from", senderCode);
if (!String.IsNullOrEmpty(subject))
map.Add("subject", subject);
/* OPTIONS FOR LINKS IN NEWSLETTERS*/
if (!string.IsNullOrEmpty(vars.getKeyValue("EgoiLinks")))
{
string[] links = vars.getKeyValue("EgoiLinks").Split(';');
foreach (string s in links)
{
map.Add(s, "1");
}
}
if (!String.IsNullOrEmpty(internal_Name))
map.Add("internal_name", internal_Name);
if (!String.IsNullOrEmpty(url))
map.Add("url", url);
map.Add("listID", vars.getKeyValue("EgoiContactListId"));
mapResult = api.createCampaignEmail(map);
Dictionary<string, object> item = (Dictionary<string, object>)mapResult;
if (!item.ContainsKey("ERROR"))
return true;
else
return false;
}
}
catch (Exception ex) { }
return false;
}
}
}
What i mean about action filter is something like:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class LogActionResult : ActionFilterAttribute //TESTE - os action filter apenas funcion em controladores e acções
{
private static readonly ILog log = LogManager.GetLogger(typeof(LogActionResult).Name);
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ActionExecutedContext context = filterContext;
StringBuilder logMessage = new StringBuilder();
Log("OnActionExecuting", filterContext.RouteData);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
Log("OnResultExecuted", filterContext.RouteData);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
Log("OnResultExecuting ", filterContext.RouteData);
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0}- controller:{1} action:{2}", methodName,
controllerName,
actionName);
Debug.WriteLine(message);
}
}
I want to use there is a log package to save information about what happened and information about the api call:
log4net.ILog logger = log4net.LogManager.GetLogger("LoginLog");
logger.Info("Acção: action name | result: '"+mapResult+"' | logged at " + DateTime.Now);
I am attempting to write a filter that wraps data to follow the JSON API spec and so far I've got it working on all cases where I directly return an ActionResult, such as ComplexTypeJSON. I am trying to get it to work in situations like ComplexType where I do not have to run the Json function constantly.
[JSONAPIFilter]
public IEnumerable<string> ComplexType()
{
return new List<string>() { "hello", "world" };
}
[JSONAPIFilter]
public JsonResult ComplexTypeJSON()
{
return Json(new List<string>() { "hello", "world" });
}
However, by the time public override void OnActionExecuted(ActionExecutedContext filterContext) runs when I navigate to ComplexType, the filterContext.Result is a Content Result, that is just a string where filterContext.Result.Content is simply:
"System.Collections.Generic.List`1[System.String]"
Is there a way I can set something up to make ComplexType become JsonResult rather than ContentResult?
For context, here are the exact files:
TestController.cs
namespace MyProject.Controllers
{
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using MyProject.Filters;
public class TestController : Controller
{
[JSONAPIFilter]
public IEnumerable<string> ComplexType()
{
return new List<string>() { "hello", "world" };
}
[JSONAPIFilter]
public JsonResult ComplexTypeJSON()
{
return Json(new List<string>() { "hello", "world" });
}
// GET: Test
[JSONAPIFilter]
public ActionResult Index()
{
return Json(new { foo = "bar", bizz = "buzz" });
}
[JSONAPIFilter]
public string SimpleType()
{
return "foo";
}
[JSONAPIFilter]
public ActionResult Throw()
{
throw new InvalidOperationException("Some issue");
}
}
}
JSONApiFilter.cs
namespace MyProject.Filters
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MyProject.Exceptions;
using MyProject.Models.JSONAPI;
public class JSONAPIFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
private static readonly ISet<Type> IgnoredTypes = new HashSet<Type>()
{
typeof(FileResult),
typeof(JavaScriptResult),
typeof(HttpStatusCodeResult),
typeof(EmptyResult),
typeof(RedirectResult),
typeof(ViewResultBase),
typeof(RedirectToRouteResult)
};
private static readonly Type JsonErrorType = typeof(ErrorModel);
private static readonly Type JsonModelType = typeof(ResultModel);
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (IgnoredTypes.Any(x => x.IsInstanceOfType(filterContext.Result)))
{
base.OnActionExecuted(filterContext);
return;
}
var resultModel = ComposeResultModel(filterContext.Result);
var newJsonResult = new JsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = resultModel
};
filterContext.Result = newJsonResult;
base.OnActionExecuted(filterContext);
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
if (modelState == null || modelState.IsValid)
{
base.OnActionExecuting(filterContext);
}
else
{
throw new ModelStateException("Errors in ModelState");
}
}
public virtual void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.Exception == null) return;
// Todo: if modelstate error, do not provide that message
// set status code to 404
var errors = new List<string>();
if (!(filterContext.Exception is ModelStateException))
{
errors.Add(filterContext.Exception.Message);
}
var modelState = filterContext.Controller.ViewData.ModelState;
var modelStateErrors = modelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage).ToList();
if (modelStateErrors.Any()) errors.AddRange(modelStateErrors);
var errorCode = (int)System.Net.HttpStatusCode.InternalServerError;
var errorModel = new ErrorModel()
{
status = errorCode.ToString(),
detail = filterContext.Exception.StackTrace,
errors = errors,
id = Guid.NewGuid(),
title = filterContext.Exception.GetType().ToString()
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = errorCode;
var newResult = new JsonResult() { Data = errorModel, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
filterContext.Result = newResult;
}
private ResultModel ComposeResultModel(ActionResult actionResult)
{
var newModelData = new ResultModel() { };
var asContentResult = actionResult as ContentResult;
if (asContentResult != null)
{
newModelData.data = asContentResult.Content;
return newModelData;
}
var asJsonResult = actionResult as JsonResult;
if (asJsonResult == null) return newModelData;
var dataType = asJsonResult.Data.GetType();
if (dataType != JsonModelType)
{
newModelData.data = asJsonResult.Data;
}
else
{
newModelData = asJsonResult.Data as ResultModel;
}
return newModelData;
}
}
}
There are two options:
1.use ApiController instead of Controller
The apicontroller will return json result,and the default serializer is Newtonsoft.json(here),so you can use like this below:
//the response type
public class SimpleRes
{
[JsonProperty(PropertyName = "result")]
public string Result;
}
//the controller
public class TestController : ApiController
{
[HttpGet]
[HttpPost]
[JSONAPIFilter]
public SimpleRes TestAction()
{
return new SimpleRes(){Result = "hello world!"};
}
}
2.wrap your response with your own ActionResult if you insist using Controller:
//json container
public class AjaxMessageContainer<T>
{
[JsonProperty(PropertyName = "result")]
public T Result { set; get; }
}
//your own actionresult
public class AjaxResult<T> : ActionResult
{
private readonly T _result;
public AjaxResult(T result)
{
_result = result;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = "application/json";
var result = JsonConvert.SerializeObject(new AjaxMessageContainer<T>
{
Result = _result,
});
var bytes =
new UTF8Encoding().GetBytes(result);
context.HttpContext.Response.OutputStream.Write(bytes, 0, bytes.Length);
}
}
//your controller
[JSONAPIFilter]
public AjaxResult<List<String>> TestSimple()
{
return AjaxResult<List<String>>(new List<string>() { "hello", "world" });
}
and if you wanna get response string from filter for log or something:
var result = filterContext.Response.Content.ReadAsStringAsync();
I think this is what you are looking for :
public class JSONAPIFilterAttribute : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuted(ActionExecutedContext context)
{
context.Result = new JsonResult
{
Data = ((ViewResult)context.Result).ViewData.Model
};
}
}
From #roosteronacid : return jsonresult in actionfilter
I have just encountered the same issue and found a slightly different approach.
The basic idea was from NOtherDev.
I would introduce an IActionInvoker.
public class ControllerActionInvokerWithDefaultJsonResult : ControllerActionInvoker
{
public const string JsonContentType = "application/json";
protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
{
if (controllerContext.HttpContext.Request.Path.StartsWith("/api/"))
{
return (actionReturnValue as ActionResult)
?? new JsonResult
{
Data = actionReturnValue,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
return base.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue);
}
}
In this case every request starting with "/api/" will have transformed result to json, however only when the actionReturnValue is not a type inherited from ActionResult already.
IActionInvoker is resolved by DependencyResolver, so you need to define the registration in your favorite ioc container which you have set as DependencyResolver.
myFavoriteContainer.Register<IActionInvoker, ControllerActionInvokerWithDefaultJsonResult>(Lifestyle.Transient);
For JsonResult you could use the built-in or this.
In a case you are using async action methods you should inherit from AsyncControllerActionInvoker instead of ControllerActionInvoker and I assume you will need to add an another registration for IAsyncActionInvoker as well. I am not sure about changes in the async part of the invoker itself.
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 8 years ago.
I've created custom action filter attribute for logging error and save logs into database:
public class LogAttribute : ActionFilterAttribute
{
public string Description { get; set; }
public IUnitOfWork Uow { get; set; }
public ILogActionService LogActionService { get; set; }
public IApplicationUserManager ApplicationUserManager { get; set; }
public LogAttribute(string desciption)
{
Description = desciption;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//var userId = filterContext.HttpContext.User.Identity.GetUserId();
//var user = await ApplicationUserManager.FindByIdAsync(2);
var model = new LogAction(filterContext.ActionDescriptor.ActionName,
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, Description);
LogActionService.AddLog(model);
Uow.SaveAllChanges();
base.OnActionExecuted(filterContext);
}
}
So, I also created custom filter provider for injecting my dependency using StructureMap:
public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
private Func<IContainer> _container;
public StructureMapFilterProvider(Func<IContainer> container)
{
_container = container;
}
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
var container = _container();
foreach (var filter in filters)
{
container.BuildUp(filter.Instance);
yield return filter;
}
}
}
Then I've registered that in Global.asax:
cfg.For<IFilterProvider>().Use(new StructureMapFilterProvider(() => SmObjectFactory.Container));
cfg.Policies.SetAllProperties(x => x.Matching(p => p.DeclaringType.CanBeCastTo(typeof(ActionFilterAttribute)) &&
p.DeclaringType.Namespace.StartsWith("MyNamespace") &&
!p.PropertyType.IsPrimitive &&
p.PropertyType != typeof(string)));
Everything should be ok, But when I decorate my action method with Log attribute, I get Object reference not set to an instance of an object. I set break point in OnActionExecuted it tells that LogActionService is null:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//var userId = filterContext.HttpContext.User.Identity.GetUserId();
//var user = await ApplicationUserManager.FindByIdAsync(2);
var model = new LogAction(filterContext.ActionDescriptor.ActionName,
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, Description);
LogActionService.AddLog(model);
Uow.SaveAllChanges();
base.OnActionExecuted(filterContext);
}
Defining StructureMapFilterProvider to setup property injection:
using StructureMap;
using System.Collections.Generic;
using System.Web.Mvc;
namespace DI06.CustomFilters
{
public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
private readonly IContainer _container;
public StructureMapFilterProvider(IContainer container)
{
_container = container;
}
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
foreach (var filter in filters)
{
_container.BuildUp(filter.Instance);
yield return filter;
}
}
}
}
Creating our custom StructureMap Container:
using System;
using System.Threading;
using DI06.Services;
using DI06.Services.Contracts;
using StructureMap;
namespace DI06.IocConfig
{
public static class SmObjectFactory
{
private static readonly Lazy<Container> _containerBuilder =
new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
public static IContainer Container
{
get { return _containerBuilder.Value; }
}
private static Container defaultContainer()
{
return new Container(x =>
{
x.For<ILogActionService>().Use<LogActionService>();
x.Policies.SetAllProperties(y =>
{
y.OfType<ILogActionService>();
});
});
}
}
}
And then it's necessary to remove the old FilterAttributeFilterProvider and replace it with the new StructureMapFilterProvider (in Application_Start method):
var filterProvider = FilterProviders.Providers.Single(provider => provider is FilterAttributeFilterProvider);
FilterProviders.Providers.Remove(filterProvider);
FilterProviders.Providers.Add(SmObjectFactory.Container.GetInstance<StructureMapFilterProvider>());
I'm using ASP.NET Web API 2 with attribute routing but i can't seem to get the versioning using media types application/vnd.company[.version].param[+json] to work.
I get the following error:
The given key was not present in the dictionary.
which originates from testing the key _actionParameterNames[descriptor] in FindActionMatchRequiredRouteAndQueryParameters() method.
foreach (var candidate in candidatesFound)
{
HttpActionDescriptor descriptor = candidate.ActionDescriptor;
if (IsSubset(_actionParameterNames[descriptor], candidate.CombinedParameterNames))
{
matches.Add(candidate);
}
}
Source: ApiControllerActionSelector.cs
After further debugging I've realized that if you have two controllers
[RoutePrefix("api/people")]
public class PeopleController : BaseApiController
{
[Route("")]
public HttpResponseMessage GetPeople()
{
}
[Route("identifier/{id}")]
public HttpResponseMessage GetPersonById()
{
}
}
[RoutePrefix("api/people")]
public class PeopleV2Controller : BaseApiController
{
[Route("")]
public HttpResponseMessage GetPeople()
{
}
[Route("identifier/{id}")]
public HttpResponseMessage GetPersonById()
{
}
}
you can't use your custom ApiVersioningSelector : DefaultHttpControllerSelector because it will test the keys,as stated above, from all controllers having the same [RoutePrefix("api/people")] and obviously an exception will be thrown.
Just to be sure the right controller was selected
I don't know if this is a bug, but using route [RoutePrefix("api/v1/people")] to version API makes me sad.
NOTE: This works great without attribute routing.
UPDATE
public class ApiVersioningSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _HttpConfiguration;
public ApiVersioningSelector(HttpConfiguration httpConfiguration)
: base(httpConfiguration)
{
_HttpConfiguration = httpConfiguration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
var attributedRoutesData = request.GetRouteData().GetSubRoutes();
var subRouteData = attributedRoutesData.LastOrDefault(); //LastOrDefault() will get PeopleController, FirstOrDefault will get People{version}Controller which we don't want
var actions = (ReflectedHttpActionDescriptor[])subRouteData.Route.DataTokens["actions"];
var controllerName = actions[0].ControllerDescriptor.ControllerName;
//For controller name without attribute routing
//var controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor oldControllerDescriptor;
if (controllers.TryGetValue(controllerName, out oldControllerDescriptor))
{
var apiVersion = GetVersionFromMediaType(request);
var newControllerName = String.Concat(controllerName, "V", apiVersion);
HttpControllerDescriptor newControllerDescriptor;
if (controllers.TryGetValue(newControllerName, out newControllerDescriptor))
{
return newControllerDescriptor;
}
return oldControllerDescriptor;
}
return null;
}
private string GetVersionFromMediaType(HttpRequestMessage request)
{
var acceptHeader = request.Headers.Accept;
var regularExpression = new Regex(#"application\/vnd\.mycompany\.([a-z]+)\.v([0-9]+)\+json",
RegexOptions.IgnoreCase);
foreach (var mime in acceptHeader)
{
var match = regularExpression.Match(mime.MediaType);
if (match != null)
{
return match.Groups[2].Value;
}
}
return "1";
}
}
Thanks for the sharing your code. I have modified your version controller selector like below and tried some scenarios and it seems to work well. Can you try updating your controller selector like below and see if it works?
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor controllerDescriptor = null;
// get list of all controllers provided by the default selector
IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//check if this route is actually an attribute route
IEnumerable<IHttpRouteData> attributeSubRoutes = routeData.GetSubRoutes();
var apiVersion = GetVersionFromMediaType(request);
if (attributeSubRoutes == null)
{
string controllerName = GetRouteVariable<string>(routeData, "controller");
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
string newControllerName = String.Concat(controllerName, "V", apiVersion);
if (controllers.TryGetValue(newControllerName, out controllerDescriptor))
{
return controllerDescriptor;
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
else
{
// we want to find all controller descriptors whose controller type names end with
// the following suffix(ex: CustomersV1)
string newControllerNameSuffix = String.Concat("V", apiVersion);
IEnumerable<IHttpRouteData> filteredSubRoutes = attributeSubRoutes.Where(attrRouteData =>
{
HttpControllerDescriptor currentDescriptor = GetControllerDescriptor(attrRouteData);
bool match = currentDescriptor.ControllerName.EndsWith(newControllerNameSuffix);
if (match && (controllerDescriptor == null))
{
controllerDescriptor = currentDescriptor;
}
return match;
});
routeData.Values["MS_SubRoutes"] = filteredSubRoutes.ToArray();
}
return controllerDescriptor;
}
private HttpControllerDescriptor GetControllerDescriptor(IHttpRouteData routeData)
{
return ((HttpActionDescriptor[])routeData.Route.DataTokens["actions"]).First().ControllerDescriptor;
}
// Get a value from the route data, if present.
private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
{
object result = null;
if (routeData.Values.TryGetValue(name, out result))
{
return (T)result;
}
return default(T);
}
This is my controller
public class SuggestionController : ApiController
{
public ISuggestionRepository Repository { get; private set; }
public SuggestionController(ISuggestionRepository repository)
{
this.Repository = repository;
}
// to post suggestion
[HttpPost]
[ActionName("PostSuggestion")]
public HttpResponseMessage PostSuggestion(Suggestion suggestion)
{
var answerCorrect = this.Repository.CreateSuggestion(suggestion);
if (answerCorrect == true)
return Request.CreateResponse(HttpStatusCode.OK);
else
return Request.CreateResponse(HttpStatusCode.Conflict);
}
}
and this is my RegisterServices method in NinjectWebCommon.cs
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ICompetitionRepository>().To(typeof(CompetitionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Competition>());
kernel.Bind<ISubmissionRepository>().To(typeof(SubmissionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Submission>());
kernel.Bind<IUserRepository>().To(typeof(UserRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<User>());
kernel.Bind<ISuggestionRepository>().To(typeof(SuggestionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Suggestion>());
}
But am getting an exception that my suggestion controller does not have a default constructor and its showing a 500 internal server when am hitting the controller from a client app
I know that we get the exception of controller not having default constructor if the ninject dependency is not working properly but the below is another controller i have implemeneted similar to suggestion controller and its working absolutely fine.
public IUserRepository Repository { get; private set; }
public SSOController(IUserRepository repository)
{
this.Repository = repository;
}
[HttpPost]
[ActionName("PostUser")]
public HttpResponseMessage PostUser([FromBody]string id)
{
var accessToken = id;
var client = new FacebookClient(accessToken);
dynamic result = client.Get("me", new { fields = "name,email" });
string name = result.name;
string email = result.email;
var existingUser = this.Repository.FindByUserIdentity(name);
if (existingUser == null)
{
var newUser = new User
{
Username = name,
Email = email,
};
var success = this.Repository.CreateAccount(newUser);
if (!success)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
//return created status code as we created the user
return Request.CreateResponse<User>(HttpStatusCode.Created, newUser);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
}
I have no idea where am going wrong. Please let me know if u have any suggestions.
EDIT:
my Global.asax
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
AuthConfig.RegisterAuth();
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy =
IncludeErrorDetailPolicy.Always;
}
Dependency resolver am using
// Provides a Ninject implementation of IDependencyScope
// which resolves services using the Ninject container.
public class NinjectDependencyScope : IDependencyScope
{
IResolutionRoot resolver;
public NinjectDependencyScope(IResolutionRoot resolver)
{
this.resolver = resolver;
}
public object GetService(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.TryGet(serviceType);
}
public System.Collections.Generic.IEnumerable<object> GetServices(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.GetAll(serviceType);
}
public void Dispose()
{
IDisposable disposable = resolver as IDisposable;
if (disposable != null)
disposable.Dispose();
resolver = null;
}
}
// This class is the resolver, but it is also the global scope
// so we derive from NinjectScope.
public class NinjectDependencyResolver : NinjectDependencyScope, IDependencyResolver
{
IKernel kernel;
public NinjectDependencyResolver(IKernel kernel)
: base(kernel)
{
this.kernel = kernel;
}
public IDependencyScope BeginScope()
{
return new NinjectDependencyScope(kernel.BeginBlock());
}
}
and calling it in CreateKernel() method in NinjectWebCommon
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
// Install our Ninject-based IDependencyResolver into the Web API config
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
Suggestion Repository
public class SuggestionRepository : Repository<Suggestion>, ISuggestionRepository
{
public SuggestionRepository(IServiceContext<Suggestion> servicecontext)
: base(servicecontext)
{ }
public bool CreateSuggestion(Suggestion suggestion)
{
this.ServiceContext.Create(suggestion);
this.ServiceContext.Save();
return true;
}
}
ISuggestionRepository
public interface ISuggestionRepository
{
bool CreateSuggestion(Suggestion suggestion);
}
Repository
public abstract class Repository<T>
{
public IServiceContext<T> ServiceContext { get; private set; }
public Repository(IServiceContext<T> serviceContext)
{
this.ServiceContext = serviceContext;
}
}
IserviceContext
public interface IServiceContext<T>
{
IQueryable<T> QueryableEntities { get; }
void Create(T entity);
void Update(T entity);
void Delete(T entity);
void Save();
}
Since you're using WebApi, you will need to use the WebApi extension for Ninject. Unfortunately, the current Ninject.WebApi nuget package is out of date, and doesn't work with the released version of WebApi.
Temporarily, until Remo gets around to updating Ninject.WebApi to the release version, you can use Ninject.WebApi-RC http://nuget.org/packages/Ninject.Web.WebApi-RC
http://www.eyecatch.no/blog/2012/06/using-ninject-with-webapi-rc/
EDIT:
To recap the information discussed in comments, Here are the recommendations:
1) Use Ninject.MVC3 and Ninject.Web.WebApi (but use Ninject.Web.WebApi-RC until the official is updated) as discussed above. Do not use a custom DependencyResolver, and let Ninject.Web.Mvc and .WebApi do their job.
2) Change your bindings to this:
kernel.Bind<ICompetitionRepository>().To<CompetitionRepository>();
... similar bindings
3) Add a generic binding for your ServiceContext
kernel.Bind(typeof(IServiceContext<>)).To(typeof(InMemoryDataContext<>));
I think the problem is you're using the ApiController.
Controllers and apiControllers are using a different dependancy injection container.
Both of them however expose the same methods.
If the working controller is inheriting the Controller class then that's your cause.
For a work around take a look at
this topic
I have faced the same issue.
This is how I rectified:
I created a WebContainerManager which is just a static wrapper around container.
Static container wrappers useful when you don't control instantiation and can't rely on injection - e.g. action filter attributes
public static class WebContainerManager
{
public static IKernel GetContainer()
{
var resolver = GlobalConfiguration.Configuration.DependencyResolver as NinjectDependencyResolver;
if (resolver != null)
{
return resolver.Container;
}
throw new InvalidOperationException("NinjectDependencyResolver not being used as the MVC dependency resolver");
}
public static T Get<T>()
{
return GetContainer().Get<T>();
}
}
Inside your controller, call your empty constructor like this with no parameters:
public SuggestionController() : this(WebContainerManager.Get<ISuggestionRepository>())
{
}
This should work.
This technique i got from the book on MVC4 by Jamie Kurtz #jakurtz.
You probably need to do some dependency injection so you can inject the ISuggestionRepository parameter on your SuggestionController constructor. To do that you need to override methods in the DefaultControllerFactory class to customize the creation of controllers. Since you are using NInject, you can have something like:
public class NInjectControllerFactory : DefaultControllerFactory
{
private IKernel kernel = new StandardKernel(new CustomModule());
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
return controllerType == null ? null : (IController)kernel.Get(controllerType);
}
public class CustomModule : NinjectModule
{
public override void Load()
{
this.Bind<ICompetitionRepository>().To(typeof(CompetitionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Competition>());
this.Bind<ISubmissionRepository>().To(typeof(SubmissionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Submission>());
this.Bind<IUserRepository>().To(typeof(UserRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<User>());
this.Bind<ISuggestionRepository>().To(typeof(SuggestionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Suggestion>());
}
}
}
Then in your Global.asax.cs, you can add a line to swap out the controller factory
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NInjectControllerFactory());
}