Web Api - multiple actions? - asp.net-web-api

I have those methods, using webapi:
//Responsible for getting or updating a template and it's shifts
public class AdminTemplateDefinitionController : AdminApiController
{
public HttpResponseMessage Get()
{
}
public HttpResponseMessage Get(int Id)
{
}
public HttpResponseMessage Post([FromBody]ENTemplateDefinitions EnTemplateDefinitions)//add shifts to template
{
}
public HttpResponseMessage Put([FromBody]ENTemplateDefinitions EnTemplateDefinitions) // add skills to template.
{
}
public HttpResponseMessage Delete(ENSkillInTemplate EnSkillInTemplate)// remove skiilId from template
{
}
}
For some reason I get this error:
Multiple actions were found that match the request.
My RouteConfig is:
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 }
);
}
Any idea ?

Related

How can I define two routes for the same controller

I have a controller named PaymentsController in a Payments area.
it works when I request 'localhost/payments/payments/index' but I want call with 'localhost/payments/pa/index' or just 'localhost/payments'
I need both names.
I try somthing like
using System.Web.Mvc;
namespace Execplan.MVC.Areas.Payments
{
public class PaymentsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Payments";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Payments_default",
"Payments/{controller}/{action}/{id}"
);
context.MapRoute(
"Payments_pa",
"Payments/Pa/{action}/{id}"
, new { controller = "payments", action = "Index", id = UrlParameter.Optional }
);
}
} }

How do you conditionally route to an action if the request originates from an AJAX call?

Using APS.NET routing, how do you conditionally route to separate actions based on whether or not the request originated as an AJAX call?
For instance on a controller I may have two actions:
public ActionResult List() { return View(); }
and
public ActionResult ListJSON() { return Content(...); }
I'd like both actions to have the same URL, but ListJSON() should get called if the request originated as an AJAX call.
Why using 2 separate actions when the code is the same? It's the view result that differs.
How about:
public ActionResult List()
{
var model = ...
if (Request.IsAjaxRequest())
{
return View(model);
}
return Json(model);
}
Obviously if you had to do this in every controller action this would quickly become a complete nightmare.
So you could externalize this logic into a custom action filter:
[MyFilter]
public ActionResult List()
{
var model = ...
return View(model);
}
where you could define the MyFilterAttribute like so:
public class MyFilterAttribute: ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var result = filterContext.Result as ViewResultBase;
if (result != null)
{
filterContext.Result = new JsonResult
{
Data = result.Model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
}
You could also register this action filter as a global action filter to avoid the need of putting it on each controller and action that requires it.
UPDATE:
As explained in the comments section it seems that the OP requires 2 different actions. For this purpose you could use a custom route:
public class MyRoute : Route
{
public MyRoute(string url, object defaults) :
base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
if (httpContext.Request.IsAjaxRequest())
{
rd.Values["action"] = rd.GetRequiredString("action") + "json";
}
return rd;
}
}
which will be registered in Application_Start:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
"Default",
new MyRoute(
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
)
);
}
It turns out that you can easily define your own route constraints by implementing IRouteConstraint.
Something like this:
public class AjaxRouteConstraint : IRouteConstraint {
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
return httpContext.Request.IsAjaxRequest();
}
}
Then simply use this constraint on a route. The trick is that the route with this constraint must come before the route without the constraint, otherwise the ajax route will never be called (the not-ajax route will always match).
There may be some consequences to doing this when it comes to URL generation. But I have not significantly tested those.

Route by query string

What do I do if I want a url like below (which is not defined by me, so I can't change it):
http://localhost/Something?cmd=Open&a=1&b=2
Maps to MyController.Open() action?
public class MyController : Controller
{
public ActionResult Open(int a, int b)
{
//.....
}
public ActionResult Close(string c)
{
//.....
}
}
Note: there are more than one possible cmd values, each mapped to an action of the same controller.
You could write a custom route:
public class MyRoute : Route
{
public MyRoute()
: base(
"something",
// TODO: replace the name of the controller with the actual
// controller containing the Open and Close actions
// What you have shown in your question is not an MVC controller.
// In ASP.NET MVC controllers must derive from the Controller class
new RouteValueDictionary(new { controller = "home" }),
new MvcRouteHandler()
)
{ }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
var cmd = httpContext.Request.QueryString["cmd"];
if (!string.IsNullOrEmpty(cmd))
{
rd.Values["action"] = cmd;
return rd;
}
return null;
}
}
and then register this custom route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// register the custom route before the default route,
// to ensure that it handles requests to /something
routes.Add("something", new MyRoute());
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

Area controllers are being called from root request

I have some area in my ASP.NET MVC3 Application:
namespace MyProject.Areas.myarea
{
public class myareaAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "myarea";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"myarea_default",
"myarea/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
this area contains "Hello" controller with "Smile" action.
In global.asax file for whole project I have:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
So, when I request "localhost/myarea/hello/smile" it calls appropriate controller as expected.
BUT! When I request "localhost/hello/smile" it calls hello controller STILL! With that, it looks for the Views not in the myarea/Views folder, but in a ~/Views folder for "root" (non-area) level of project.
How can I fix this, so server will throw an 404 exception, that resource is not found, just like I requested non-existing controller?
UPD: Controllers in area are in namespace:
namespace MyProject.Areas.myarea.Controllers
{
public class HelloController : Controller
...
}
Controllers in "root"-level are in namespace:
namespace MyProject.Controllers
{
public class AnotherRootController : Controller
...
}
So I tried this in global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new [] { "MyProject.Controllers" } //Namespace
);
}
I thought this would restrict this route to "root"-level controllers only, since they are in MyProject.Controllers namespace. That DID NOT work. Area-controllers are still being called with request without areaname in it.
May be someone can explain, why?
You could set the UseNamespaceFallback=false datatoken when registering the default route in your Global.asax by restricting it to look only for controllers in the given namespace. You may take a look at the following blog post.
So to put that into action add a namespace restriction to your area registration:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"myarea_default",
"myarea/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "MyProject.Areas.myarea.Controllers" }
);
}
and in your Global.asax set the UseNamespaceFallback data token to false when registering the default route in order to constrain it to the given namespace:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "MyProject.Controllers" }
).DataTokens["UseNamespaceFallback"] = false;
}

Ninject multiple modules

I didnt think this would be a problem originally, but as I keep getting exceptions thought I would post here incase im being an idiot...
I have 2 module classes, one sets up NHibernate and one sets up MVC Controllers, now the problem I have is that I have something like below:
public class NHibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionManager>().To<SessionManager>();
}
}
public class ControllerModule : NinjectModule
{
public override void Load()
{
Bind<SomeController>().ToSelf()
.WithConstructorArgument("sessionManager", Kernel.Get<ISessionManager>());
}
}
Whenever I try to use the controller, it just bombs out telling me that its having problems binding the sessionManager argument. I make sure the list has the Nhibernate module in before the Controller module when I create the kernel.
Is there anything immediately stupid in what im doing above?
Assuming:
public class SomeController : Controller
{
private readonly ISessionManager _sessionManager;
public HomeController(ISessionManager sessionManager)
{
_sessionManager = sessionManager;
}
public ActionResult Index()
{
return View();
}
}
The following should be sufficient:
public class NHibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionManager>().To<SessionManager>();
}
}
public class ControllerModule : NinjectModule
{
public override void Load()
{
Bind<SomeController>().ToSelf();
}
}
and in Global.asax:
public class MvcApplication : NinjectHttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
var modules = new INinjectModule[]
{
new NHibernateModule(),
new ControllerModule()
};
return new StandardKernel(modules);
}
}

Resources