How can I define two routes for the same controller - model-view-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 }
);
}
} }

Related

Web Api - multiple actions?

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 ?

Custom Filter Attribute or call in Action Method - Earlier in Request Pipeline but Performance?

I have a HomeController with about 8 or 9 Action Methods.
About 7 of these methods require a check to see if the User has a special setting or not to see if they are allowed to access these Methods and related Views.
If they are not they are redirected back to a Common Action Method and View.
public class HomeController : Controller
{
public ActionResult Index() {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
return View(p);
}
public ActionResult PunterList() {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
if (p.isPunter) {
return RedirectToAction("Index", "Home");
} else {
return View(p);
}
}
}
The check in 'PunterList' is done in other Action Methods, I was thinking about creating a FilterAttribute to do this check. As per the following:
public class NoPunterAttribute : FilterAttribute, IActionFilter {
public void OnActionExecuting(ActionExecutingContext filterContext) {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
if (p.isPunter) {
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } });
}
}
public void OnActionExecuted(ActionExecutedContext filterContext) { }
}
then put this attribute on the Action method this type of user cannot access.
[NoPunter]
public ActionResult PunterList() {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
return View(p);
}
this puts this code in 1 place, However the UserManager.GetPunter is called twice if the User.isPunter=false. Perhaps this is not such a good idea for Performance or Memory conservation of the MVC web application.
The benefit is does the check earlier in the Request pipeline, but perhaps a method called inside of the action method would mean .GetPunter would be called only once, but further along the Request pipeline. Not sure about this, kind of split on earlier vs Performance/Memory issues.
Any suggestions or ideas would be interesting to hear. Presumably it would depend on what is done inside UserManager.GetPunter. There is some caching inside this call but it does requery the cache.
You could write a custom authorization attribute which will inject the Punter as a parameter of your action:
public class HomeController : Controller
{
public ActionResult Index()
{
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
return View(p);
}
[NoPunterAuthorize]
public ActionResult PunterList(Punter punter)
{
return View(punter);
}
}
and the custom authorization attribute:
public class NoPunterAuthorize: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
return false;
}
var um = new UserManager();
var p = um.GetPunter(httpContext.User.Identity.Name);
var routeData = httpContext.Request.RequestContext.RouteData;
routeData.Values["punter"] = p;
return !p.IsPunter;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Home" },
{ "action", "Index" }
}
);
}
}

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 }
);
}

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