I have the following in my controller:
public ActionResult Details(string name)
{
Student student = db.Students.FirstOrDefault(x => x.FirstName == name);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
// GET: /Student/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
RouteConfig.cs:
// /Student/Details/Id
routes.MapRoute(
name: "StudentDetailsId",
url: "{Student}/{Action}/{id}",
defaults: new { controller = "Student", action = "Details", id = UrlParameter.Optional },
constraints: new { id = #"\d+" }
);
// /Student/Details/Name
routes.MapRoute(
name: "StudentDetailsName",
url: "{Student}/{Details}/{name}",
defaults: new { controller = "Student", action = "Details" }
);
// /Student/Name
routes.MapRoute(
name: "StudentName",
url: "{Student}/{name}",
defaults: new { controller = "Student", action = "Details" }
);
So pretty much I would like to have the same action name but fetched with either an id:int or string.
However I get the following:
The current request for action 'Details' on controller type 'StudentController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Details(System.String) on type MySuperAwesomeMVCApp.Controllers.StudentController
System.Web.Mvc.ActionResult Details(System.Nullable`1[System.Int32]) on type MySuperAwesomeMVCApp.Controllers.StudentController
Controller:
public ActionResult DetailsByName(string name)
{
Student student = db.Students.FirstOrDefault(x => x.FirstName == name);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
// GET: /Student/Details/5
public ActionResult DetailsById(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
Routes:
// /Student/Details/Id
routes.MapRoute(
name: "StudentDetailsId",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Student", action = "DetailsById", id = UrlParameter.Optional },
constraints: new { id = #"\d+" }
);
// /Student/Details/Name
routes.MapRoute(
name: "StudentDetailsName",
url: "{controller}/{action}/{name}",
defaults: new { controller = "Student", action = "DetailsByName" }
);
// /Student/Name
routes.MapRoute(
name: "StudentName",
url: "{controller}/{name}",
defaults: new { controller = "Student", action = "DetailsByName" }
);
In MVC, you can only have a maximum of 2 methods with the same name, and when you do, one of them must be GET and the other must be POST.
No, MVC cannot figure out which action method you want to invoke given a set of route data. Though your methods are valid C# overloads, MVC's action method selector is not strongly-typed. I have answered this question before, let me look up the link...
Update
Here is the link: https://stackoverflow.com/a/10668537/304832
Another Update
I only bring this up because other people have thanked me for mentioning it when I answer other routing questions.... Look into AttributeRouting.NET. It is so great that it's the most talked about feature of MVC5, though it started out as an open source side project and there is no reason why you can't use it in MVC4. Just download the NuGet package. With this library, you don't need to use MapRoute. Your action methods would look like this:
// GET: /Students/Details/5
[GET("students/details/{id:int}", ControllerPrecedence = 1)]
public ActionResult DetailsById(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
// GET: /Students/Details/Albert+Einstein
// or GET: /Students/Albert+Einstein
[GET("students/{name}", ActionPrecedence = 1)]
[GET("students/details/{name}")]
public ActionResult DetailsByName(string name)
{
Student student = db.Students.FirstOrDefault(x => x.FirstName == name);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
No MapRoute or cryptic regular expression constraints necessary. You just have to make sure your int method has precedence so that MVC doesn't try to pass numbers into your method that takes a string.
Related
I have this route:
routes.MapRoute(name: "Trads",
url: "test_3000/{action}/{traditional}",
defaults: new { controller = "Test_3000", action = "Subset", traditional = UrlParameter.Optional });
And a Test_3000Controller with this method:
// GET: Test_3000/Subset?traditional=(Chinese Character)
public ActionResult Subset(string traditional)
{
if (traditional == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Test_3000 test_3000 = db.Test_3000.Find(traditional);
if (test_3000 == null)
{
return HttpNotFound();
}
return View(test_3000);
}
This URL works:
server/test_3000/subset?traditional=的
This URL does NOT work:
server/test_3000/subset/的
In the latter case, 'traditional' is null.
"Traditional" is a column in an SQL table.
Have you tried this
Routes.MapRoute(name: "Trads",
url: "test_3000/{action}/{traditional?}",
defaults: new { controller = "Test_3000", action = "Subset", traditional = UrlParameter.Optional });
Notice the ? on traditional.
Also
public ActionResult Subset(string traditional = null)
{ ... }
So that traditional is explicitly set as an optional
routes.MapRoute(
name: "ChBoPinCritCji",
url: "charbopopincrits/subset/{Char}",
defaults: new { controller = "CharBopoPinCrits", action = "Subset", Char = typeof(string) }
);
This MapRoute, which is first, now works.
Thanks for the attention.
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 currently developing Edit page for one of the models, so I've set Edit page as default in Route Config. Problem is, that for some reason it's triggering Post event instead of Get. Example is very simple:
[HttpGet]
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
JobLine jobLine = db.JobLines.Find(id);
if (jobLine == null)
{
return HttpNotFound();
}
Mapper.CreateMap<JobLine, JobLineDTO>();
JobLineDTO joblineDTO = Mapper.Map<JobLine, JobLineDTO>(jobLine);
return View("Create",joblineDTO);
}
[HttpPost]
public ActionResult Edit(JobLineDTO jobLineDTO)
{
return View();
}
What could possibly be a reason for such behavior?
Route Config
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Joblines", action = "Edit", id = 1 /*UrlParameter.Optional*/ }
);
}
My routes:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" }
);
routes.MapHttpRoute(
name: "ApiControllerActionRoute",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
My methods:
// GET api/Set/All
[ActionName("All")]
public IEnumerable<Set> GetSets()
{
var sets = _repository.GetAllSets();
return sets;
}
// GET api/Set/TopLevelSets
[ActionName("TopLevelSets")]
public IEnumerable<Set> GetTopLevelSets()
{
var sets = _repository.GetTopLevelSets();
return sets.ToList();
}
// GET api/Set/Breadcrumbs/1
[ActionName("Breadcrumbs")]
public IEnumerable<Set> GetBreadCrumbs(int id)
{
var sets = _repository.GetBreadcrumbs(id);
return sets.ToList();
}
// GET api/Set/5
public Set GetSet(int id)
{
Set set = _repository.GetSet(id);
if (set == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return set;
}
// PUT api/Set/5
public HttpResponseMessage PutSet(Set set)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
_repository.UpdateSet(set);
return Request.CreateResponse(HttpStatusCode.OK);
}
// POST api/Set
public HttpResponseMessage PostSet(Set set)
{
if (ModelState.IsValid)
{
_repository.AddSet(set);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, set);
return response;
}
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
// DELETE api/Set/5
public HttpResponseMessage DeleteSet(int id)
{
_repository.DeleteSet(id);
return Request.CreateResponse(HttpStatusCode.OK, id);
}
At this point I'm trying to hit the localhost/api/set/1 - getSet method. Everything seems to line up with the routes but it's not working for some reason. What am I missing?
Your default route isn't setting a default action, so when you use the 'Get' Action the routing engine could not decide if you wanted GetTopLevelSets or GetSet. Adding a default will fix this:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetSet" },
constraints: new { id = #"^\d+$" }
);
Would have it default to your GetSet Action in your controller using: api/set (controller)/1 (id) route.
Are you missing the api portion of your query? localhost/api/set/1
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
}