I have a controller called Entry that has three ActionResults: View, New, and Edit.
The View action accepts a string parameter representing a specific Entry object.
I'm trying to figure out how to not show the word "View" in the URL. In other words, I'd like it to act like a default action.
Ideally I would like the URLs read as:
/entry/2DxyJR for a given entry
/entry/new to create a new entry
/entry/edit/2DxyJR to edit a given entry
I believe this can be accomplished with a custom route but am unsure how to actually do it. This route works for hiding "View", however /new and /edit don't work.
routes.MapRoute(
name: "Entry",
url: "entry/{id}",
defaults: new { controller = "Entry", action = "View", id = UrlParameter.Optional }
);
Sorry for the extreme noobishness of this, but I'm still trying to wrap my head around how routing works.
You'll need to be sure you put the more specific ones on top, so the entry/id will have to be last, because it appears you have string based id's.
This will match the most explicit (new) first, then if there's edit in the url, then if not fall through to the view action.
routes.MapRoute(
name: "Entry",
url: "entry/new",
defaults: new { controller = "Entry", action = "New" }
);
routes.MapRoute(
name: "Entry",
url: "entry/edit/{id}",
defaults: new { controller = "Entry", action = "Edit", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Entry",
url: "entry/{id}",
defaults: new { controller = "Entry", action = "View", id = UrlParameter.Optional }
);
I think a route constraint implementation that passes all strings behind "entry/" but excepts the words like view, edit and new, so the "Default" route can handle the. Something like:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"EntryView",
"Entry/{identifier}",
new { controller = "Entry", action = "View" },
new { identifier = new NotEqual(new string[]{"View", "Edit" , "New"}) }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Here is the NotEqual class:
public class NotEqual : IRouteConstraint
{
private string[] _match;
public NotEqual(string[] match)
{
_match = match;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
for (int i = 0; i < _match.Length; i++)
{
if (String.Compare(values[parameterName].ToString(), _match[i], true) == 0)
{
return false;
}
}
return true;
}
}
I tested it and it worked, I found it as I needed it on http://stephenwalther.com/archive/2008/08/07/asp-net-mvc-tip-30-create-custom-route-constraints.aspx
Related
Im trying to get my urls to look nice and seo friendly with slugs. I tought I suceeded but then my default routing stopped working.
When I go to this example.com/location/viewlocation/528 then the url ends up like example.com/528/a-nice-location
So thats good!
But now my normal stuff dosent work.
Typing in example.com/home/index results in the error
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult ViewLocation(Int32, System.String)' in 'Oplev.Controllers.LocationController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
I have tried different solutions but I a missing something. Cant get it to work.
My code:
RouteConfig
routes.MapRoute(
name: "view_location",
url: "{id}/{slug}",
defaults: new { controller = "Location", action = "ViewLocation", id = UrlParameter.Optional, slug = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Location controller
public ActionResult ViewLocation(int id, string slug)
{
if (string.IsNullOrEmpty(slug))
{
slug = "a-nice-location"; // testing..
return RedirectToRoute("view_location", new { id = id, slug = slug });
}
return View();
}
Home controller
public ActionResult Index()
{
return View();
}
Your first route matches anything with 0, 1 or 2 segments in the url. For example it matches ../Home/Index. You need some way to distinguish it, for example you could make it
routes.MapRoute(
name: "view_location",
url: "ViewLocation/{id}/{slug}",
defaults: new { controller = "Location", action = "ViewLocation", slug = UrlParameter.Optional }
);
or you could add a route constraint
Note also that only the last parameter can be marked as UrlParameter.Optional, but in your case the id is not optional anyway
Ok so I somehow ended up with something that works!
Changed routing for view_location to this:
routes.MapRoute(
name: "view_location",
url: "{id}/{slug}",
defaults: new { controller = "Location", action = "ViewLocation", slug = UrlParameter.Optional},
constraints: new { id = #"\d+" }
);
This is meant to supplement the already given answers. Attribute routing with route constraints will also work.
First make sure attribute routing is enabled in RouteConfig
//Attribute routing
routes.MapMvcAttributeRoutes(); // placed before convention-based routes
//convention-based routes
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Next use the necessary Route attributes on the controller
[HttpGet]
[Route("{id:int}/{*slug?}", Name = "view_location")] // Matches GET 528/some-nice-location
public ActionResult ViewLocation(int id, string slug) {
if (string.IsNullOrEmpty(slug)) {
slug = "a-nice-location"; // testing..
return RedirectToRoute("view_location", new { id = id, slug = slug });
}
return View();
}
We're currently making a small CMS module for our application where existing routes and controllers are highly prioritize, while dynamically created pages through the CMS will only be loaded if the provided URL does not exists in the default route.
I already looked at this one: Dynamic Routes from database for ASP.NET MVC CMS
but it prioritizes the dynamically created page before the routes.
This is our default route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Store", action = "Index", id = UrlParameter.Optional }
);
And aside from the default route, we have other routes that are using prefixes like this one:
routes.MapRoute(
name: "Media",
url: "m/{*url}",
defaults: new { controller = "Content", action = "GetContent", contentlibrary = "Media" },
constraints: new { url = #"(.*?)\.(png|jpg|pdf|mpeg|mp3|mp4)" }
);
UPDATE 1:
I created an IRouteConstraint for validating if the Controller exists for the default route. Here is my code:
public class ControllerValidatorConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var tempRequestContext = httpContext.Request.RequestContext;
IController controller = null;
try
{
controller = ControllerBuilder.Current.GetControllerFactory().CreateController(httpContext.Request.RequestContext, values["controller"].ToString());
}
catch (Exception ex)
{
}
return controller != null;
}
}
then on routing:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Store", action = "Index", id = UrlParameter.Optional },
constraints: new { url = new ControllerValidatorConstraint() }
);
routes.MapRoute(
name: "Page",
url: "{*url}",
defaults: new { controller = "Content", action = "GetPage" },
constraints: new { url = #"(.*?)(|.cshtml)" },
namespaces: new[] { "AppCore.Controllers" }
);
This already works as what I intend. The only remaining issue is the Match() method. The Match() method is currently creating an instance of the controller, I wrapped it with a Try-Catch block because it throws an error related to the provided path that does not exists as temporary solution but this is still wrong.
I'm almost there, I just have to find a proper way to check if the controller exists. Is reflection a bad choice? Any lighter way to check them? Thanks!
routes.MapRoute(
name: "MyRoute",
url: "{Product}/{name}-{id}",
defaults: new { controller = "Home", action = "Product", name = UrlParameter.Optional , id = UrlParameter.Optional }
);
my routemap and i want my url in product action be like = http://localhost:13804/Wares/Product/name-id
but now is like =
http://localhost:13804/Wares/Product/4?name=name
When defining a route pattern the token { and } are used to indicate a parameter of the action method. Since you do not have a parameter called Product in your action method, there is no point in having {Product} in the route template.
Since your want url like yourSiteName/Ware/Product/name-id where name and id are dynamic parameter values, you should add the static part (/Ware/Product/) to the route template.
This should work.
routes.MapRoute(
name: "MyRoute",
url: "Ware/Product/{name}-{id}",
defaults: new { controller = "Ware", action = "Product",
name = UrlParameter.Optional, id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Assuming your Product action method accepts these two params
public class WareController : Controller
{
public ActionResult Product(string name, int id)
{
return Content("received name : " + name +",id:"+ id);
}
}
You can generate the urls with the above pattern using the Html.ActionLink helper now
#Html.ActionLink("test", "Product", "Ware", new { id = 55, name = "some" }, null)
I know its late but you can use built-in Attribute Routing in MVC5. Hope it helps someone else. You don't need to use
routes.MapRoute(
name: "MyRoute",
url: "{Product}/{name}-{id}",
defaults: new { controller = "Home", action = "Product", name = UrlParameter.Optional , id = UrlParameter.Optional }
);
Instead you can use the method below.
First enable attribute routing in RouteConfig.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
}
Then in WaresController
[Route("Wares/Product/{name}/{id}")]
public ActionResult Product(string name,int id)
{
return View();
}
Then to navigate write code like this in View.cshtml file
Navigate
After following above steps your URL will look like
http://localhost:13804/Wares/Product/productname/5
I got a site with only this Route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Image", action = "Image", id = UrlParameter.Optional }
);
}
This is the controller:
public class ImageController : Controller
{
public ActionResult Image(int? id)
{
if (id == null)
{
// Do something
return View(model);
}
else
{
// Do something else
return View(model);
}
}
}
Now this is the default action so i can access it without an ID just by directly going to my domain. For calling the id it works just fine by going to /Image/Image/ID. However what i want is calling this without Image/Image (so /ID). This doesn't work now.
Is this a limitation of the default Route or is there a way to get this to work?
Thanks
Create a new route specific for this url:
routes.MapRoute(
name: "Image Details",
url: "Image/{id}",
defaults: new { controller = "Image", action = "Image" },
constraints: new { id = #"\d+" });
Make sure you register the above route before this one:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Otherwise it will not work, since the default route will take precedence.
Here I'm stating that if the url contains "/Image/1" then the ImageController/Image action method is executed.
public ActionResult Image(int id) { //..... // }
The constraint means that the {id} parameter must be a number (based on the regular expression \d+), so there's no need for a nullable int, unless you do want a nullable int, in that case remove the constraint.
I am trying to register a route as follows :
routes.MapRoute(
"SaleReport", // Route name
"SaleReport/GetDataConsolidated/{type}",
new { controller = "SaleReport",
action = "GetDataConsolidated",
type = UrlParameter.Optional});
and in controller
public ActionResult GetDataConsolidated(string type)
{
return Content("Report Type = " + type);
}
i am calling it like : localhost:56674/SaleReport/GetDataConsolidated/Sale
but the problem is the value of type is always null.
what am i doing wrong ?
It's probably just order of .MapRoute(...) calls.
Put your "SaleReport" .MapRoute(...) call before "Default" {controller}/{action} .MapRoute(...) call, since it's more specific.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "SaleReport",
url: "SaleReport/GetDataConsolidated/{type}",
defaults: new { controller = "SaleReport", action = "GetDataConsolidated", type = UrlParameter.Optional });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Is there any specific need to define another map route?
It should work with default route,
routes.MapRoute(
"SaleReport", // Route name
"SaleReport/GetDataConsolidated/{type}",
new { controller = "SaleReport",
action = "GetDataConsolidated",
type = UrlParameter.Optional});
Remove Above route,
Just change action methos like below
public ActionResult GetDataConsolidated(string id)
{
return Content("Report Type = " + id);
}
This will work,Thanks.