Route by query string - asp.net-mvc-3

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

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 ?

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

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.

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

Resources