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
}
Related
Dont't vote Negative if cant solve the prob because i know what you answer thats why iam here at the end.
Controller
[Route("{Name}")]
public ActionResult Details(int? id, string Name)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Menu menu = db.Menus.Find(id);
if (menu == null)
{
return HttpNotFound();
}
return View(menu);
}
Views
#Html.ActionLink("Details", "Details", new { id = item.Id, Name = item.MenuName })
Route.config.cs
route.MapMvcAttributeRoutes();
Output:
how to get output like this
localhost:2345/Blog instead of localhost:2345/Details/id=1?Name=Blog
You can't because the url in the RouteConfig has a specific format:
{controller}/{action}/{id}
To get the url you want, you may create public ActionResult Blog()
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "NoScript",
url : "noscript",
defaults : new { controller = "Home", action = "EnableJavaScript"}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",//the specific format
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Assuming you have a UserController with the following methods
// match http://..../arrivaler
public ActionResult Index(string username)
{
// displays the home page for a user
}
// match http://..../arrivaler/Photos
public ActionResult Photos(string username)
{
// displays a users photos
}
Route.config.cs
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "User",
url: "{username}",
defaults: new { controller = "User", action = "Index" },
constraints: new { username = new UserNameConstraint() }
);
routes.MapRoute(
name: "UserPhotos",
url: "{username}/Photos",
defaults: new { controller = "User", action = "Photos" },
constraints: new { username = new UserNameConstraint() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Test", action = "Index", id = UrlParameter.Optional }
);
}
public class UserNameConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
List<string> users = new List<string>() { "Bryan", "Stephen" };
// Get the username from the url
var username = values["username"].ToString().ToLower();
// Check for a match (assumes case insensitive)
return users.Any(x => x.ToLower() == username);
}
}
}
If the url is .../Arrivaler, it will match the User route and you will execute the Index() method in UserController (and the value of username will be "Arrivaler")
If the url is .../Arrivaler/Photos, it will match the UserPhotos route and you will execute the Photos() method in UserController (and the value of username will be "Arrivaler")
Note that the the sample code above hard codes the users, but in reality you will call a service that returns a collection containing the valid user names. To avoid hitting the database each request, you should consider using MemoryCache to cache the collection. The code would first check if it exists, and if not populate it, then check if the collection contains the username. You would also need to ensure that the cache was invalidated if a new user was added.
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.
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.
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 = "" }
);
In my Global.asax.cs file in RegisterRoutes method, I put
routes.MapRoute("messages",
"Message/{id}",
new { controller = "Archive", action = "Message", id = 0 });
Then I created this controller:
namespace TestingApp.Controllers
{
public class ArchiveController : Controller
{
public string Message(int id)
{
return "testing: you will receive the message: " + id.ToString();
}
}
}
But in my browsser when I go to:
http://.../Message/34
I get a 404.
What else do I need to define so that the routing finds my controller?
Try defining your specific route before the Default one:
routes.MapRoute(
"messages",
"Message/{id}",
new { controller = "Archive", action = "Message", id = 0 });
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" });
I think your Message method should return an ActionResult instance:
public ActionResult Message(int id)
{
return new ContentResult {
Content = "testing: you will receive the message: " + id.ToString()
};
}