Is it possible to specify Action names dynamically at run time? - asp.net-mvc

string actionName = "Users";
[HttpGet]
[ActionName(actionName)]
public ActionResult GetMe()
{
}
...gives: An object reference is required for the non-static field, method, or property
That was just a test though, is there a way to do this? If so, I could re-use the same Controller and possibly create new URIs on the fly... right?

Assuming you have the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
you could write a custom route handler:
public class MyRouteHander : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var rd = requestContext.RouteData;
var action = rd.GetRequiredString("action");
var controller = rd.GetRequiredString("controller");
if (string.Equals(action, "users", StringComparison.OrdinalIgnoreCase) &&
string.Equals(controller, "home", StringComparison.OrdinalIgnoreCase))
{
// The action name is dynamic
string actionName = "Index";
requestContext.RouteData.Values["action"] = actionName;
}
return new MvcHandler(requestContext);
}
}
Finally associate the custom route handler in your route definitions:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
).RouteHandler = new MyRouteHander();
Now if you request /home/users it's the Index action of the Home controller that will be served.

You can just take another routing argument in and do a switch statement.

Related

Right way to Redirect a URL segment to MVC Controller

I have an existing Controller
public class HomeController : Controller
{
public ActionResult Index()
{
return Redirect("/Scorecard");
}
[OutputCache(Duration = 18000)]
public ActionResult Scorecard()
{
return View();
}
}
This currently Maps to http://siteurl/Home/Scorecard . I wanted to the segment http://siteurl/scorecard to redirect to this Controller Action . What would the best wayt to do this . I tried checking the RequestUrl in Session_Start in Global.aspx but the redirects dont seem to be happening . The other alternative I thought of was using a Different Controller like "ScorecardController" and then having a RedirectToAction("Scorecard","Home") in the Index view there.
you could add a FilterAccess class on your App_Start folder to do something like this:
public class FilterAcess : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//Redirect
if (HttpContext.Current.Request.Url=="http://siteurl/scorecard"){
context.HttpContext.Response.Redirect("~/Home/Scorecard");
}
}
}
RedirectToAction is better way to do it, because, in case you change routing table later, redirect URL will be in adapted.
public class HomeController: Controller
{
public ActionResult Index()
{
return RedirectToAction("Scorecard");
}
[OutputCache(Duration = 18000)]
public ActionResult Scorecard()
{
return View();
}
}
You should also update RouteTable with additional route, before "Default" route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.LowercaseUrls = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "NoHomeSegmentInUrl",
url: "{action}/{id}",
defaults: new { controller = "Home", id = UrlParameter.Optional });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
And, for lower case routes you need line routes.LowercaseUrls = true;

MVC Route static value

I'm not sure if this is possible using mvc routing, I haven't been able to find a similar example.
I have about 5~ controller actions that are the same method, so I'd like to refactor them into a single action. I'd like to pass an enum value to the controller to tell it what path it should pass to lower layers.
Example:
public ActionResult ViewPage(int id, PageEnum page) {
var model = MyService.GetModelForTemplate(id, page);
return ("ViewPage", model);
}
Then the user could access this either through /PagesTypeOne/ViewPage/, or /PagesTypeTwo/ViewPage/. Both routes leading to the same endpoint.
Route table attempt:
routes.MapRoute(
name: "typeOne",
url: "PagesTypeOne/{action}/{id}",
defaults: new { controller = "Pages", action = "ViewPage", id = UrlParameter.Optional, page = PageEnum.TypeOne, }
);
routes.MapRoute(
name: "typeTwo",
url: "PagesTypeTwo/{action}/{id}",
defaults: new { controller = "Pages", action = "ViewPage", id = UrlParameter.Optional, page = PageEnum.TypeTwo, }
);
This obviously isn't working.
Is there a way I can do something like this? It would make my code much more concise.
screen != page, so if the property on the anonymous type matches the parameter it will work:
public ActionResult ViewPage(int id, PageEnum screen) {
var model = MyService.GetModelForTemplate(screen);
return ("ViewPage", model);
}
Updated: Created a empty application and it work flawlessly:
namespace MvcApplication6
{
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Test1",
"PagesTypeOne/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional,
page = PageEnum.PageOne } // Parameter defaults
);
routes.MapRoute(
"Test2",
"PagesTypeTwo/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional,
page = PageEnum.PageOne } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
}
public enum PageEnum
{
Undefined,
PageOne,
PageTwo
}
Controller:
namespace MvcApplication6.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult ViewPage(int id, PageEnum page)
{
var debug = 1; // break point
return new EmptyResult();
}
}
}
you can make it completely dynamic with one route definition:
// the route must be defined as the first route
routes.MapRoute(
name: "typeTwo",
url: "{page}/ViewPage/{id}",
defaults: new { controller = "Pages", action = "ViewPage", id = UrlParameter.Optional },
new { page= getPageTypes() }
);
the getPageTypes method:
private static string getPageTypes()
{
var pageTypes = Enum.GetNames(typeof(PageEnum));
return string.Join("|", pageTypes );
}
but PagesTypeOne/ViewPage/4 part must match the enum's name.

how to pass querystring in friendly url in asp.net mvc

I have the following action. I can hit this with
/basket/address?addressId=123
However i wonder how i can hit it with
/basket/address/123
public ActionResult Address(int addressId)
{
return RedirectToAction("Index");
}
my routes
routes.MapRoute(
"Default", // Route name
"{controller}.aspx/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Change:
public ActionResult Address(int addressId)
to
public ActionResult Address(int id)

MVC : Separate admin controllers

I was wandering if there is option to do the following
If I call
"admin/Category" - to call "CategoryAdminController"
If I call
"Category" - to call "CategoryController"
It is very easy to do this via routing and custom controller factory. Here is the solution:
// add route
routes.Add(new Route("{culture}/admin/{controller}/{action}/{*id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "", culture = LocalizationManager.DefaultCulture.Name, controllerSufix = "Admin" }),
Constraints = new RouteValueDictionary(new { culture = new CultureRouteConstraint() })
});
Than create custom controller factory
public class CmsControllerFactory : DefaultControllerFactory
{
RequestContext _requestContext;
protected override Type GetControllerType(string controllerName)
{
if (_requestContext.RouteData.Values.ContainsKey("controllerSufix"))
{
string sufix = (string)_requestContext.RouteData.Values["controllerSufix"];
Type type = base.GetControllerType(String.Concat(controllerName, sufix));
if (type != null)
return type;
}
return base.GetControllerType(controllerName);
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
_requestContext = requestContext;
return base.CreateController(requestContext, controllerName);
}
}
I would like if anybody know some different/better solution.
You can do this quite simply with two route handlers:
routes.MapRoute(
"Admin",
"/admin/category/{id}",
new { controller = "CategoryAdminController", action = "Index", id = "" }
);
and then:
routes.MapRoute(
"Standard",
"/category/{id}",
new { controller = "CategoryController", action = "Index", id = "" }
);

MVC Dynamic Routes

I would like to create dynamic urls that route to controller actions with an Id value. I've created the following route using a catch-all parameter
routes.MapRoute(
"RouteName",
"{id}/{*Url}",
new { controller = "Controller", action = "Action", id = "" }
);
This works as expected and allows me to use the following Urls:
"http://website.com/1/fake/url/path" (1 being the id that gets passed to the action method)
Does anyone know a way to achieve it this way instead without creating my own http module?:
"http://website.com/fake/url/path/1"
Thanks - Mark
That's a really difficult one, for me anyway.
Given the following route:
routes.MapRoute("Default", "{*token}",
new { controller = "Home", action = "Index", token = 0 });
Your controller and supporting classes would be something like this:
[HandleError]
public class HomeController : Controller
{
public ActionResult Index([ModelBinder(typeof(IndexReqquestBinder))] IndexRequest request)
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = String.Format("We're looking at ID: {0}", request.ID);
return View();
}
}
public class IndexRequest
{
public Int32 ID { get; set; }
public IndexRequest(Int32 id)
{
this.ID = id;
}
}
public class IndexReqquestBinder : IModelBinder
{
public ModelBinderResult BindModel(ModelBindingContext bindingContext)
{
if ( null != bindingContext.RouteData.Values["token"] ) {
foreach ( String v in bindingContext.RouteData.Values["token"].ToString().Split('/') ) {
Int32 id = 0;
if ( Int32.TryParse(v, out id) ) {
return new ModelBinderResult(new IndexRequest(id));
}
}
}
return new ModelBinderResult(new IndexRequest(0));
}
}
Routes redirection is based on route entry in the route table. It must be in systematic way. For instance if you have route as "customurl/id" after {controller}/{action}/{id}(default of mvc) when you will enter "customurl" in the url box it will take it as default route and no page found exception will occur. So if you wan to use custom route then first remove the default route. I do this like.
RouteCollection routes = RouteTable.Routes;
if (routes["rname"] != null)
{
RouteTable.Routes.Remove(routes["rname"]);
}
routes.Remove(routes["Default"]);
routes.MapRoute(
name: "newname",
url: url + "/{customId}",
defaults: new { controller = "Search", action = "Index", customId = UrlParameter.Optional }
);
//default route value
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I would suggest overriding of DefaultControllerFactory
> public class CustomControllerFactory : DefaultControllerFactory
> {
> public override IController
> CreateController(System.Web.Routing.RequestContext
> requestContext, string controllerName)
> {
> try
> {
> return base.CreateController(requestContext,
> controllerName);
> }
> catch (Exception exception)
> {
> // collect route data
> string id = (string)requestContext.RouteData.Values["id"];
> string action = (string)requestContext.RouteData.Values["action"];
> // set full path as routing "page" parameter
> VirtualPathData path = requestContext.RouteData.Route.GetVirtualPath(requestContext,
> requestContext.RouteData.Values);
> requestContext.RouteData.Values["id"]
> = path.VirtualPath; // or anything you need
> // use controller page by default
> controllerName = "MyController";
> // set basuc routing data
> requestContext.RouteData.Values["controller"]
> = controllerName;
> requestContext.RouteData.Values["action"]
> = "index";
> // call base method to create controller
> return base.CreateController(requestContext,
> controllerName);
> }
> }
>}
And than just register this as default controller factory in global.asax.cs file
protected void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(new MyNameSpace.CustomControllerFactory ());
RegisterRoutes(RouteTable.Routes); // this already exists by default
}

Resources