I have a controller which displays user profiles the Action for showing a profile is just under Index : its /User/123 to show user 123. This is set on route map like this:
routes.MapRoute(
name: "User",
url: "User/{id}",
defaults: new {controller = "User", action = "Index", id = UrlParameter.Optional}
);
Now my controller looks something like this:
public class UserController : Controller
{
[Authorize]
public ActionResult Index(int id)
{
*edited out for simplicity*
return View(model);
}
[Authorize]
[HttpPost]
public ActionResult Follow(int id)
{
*edited out for simplicity*
return RedirectToAction("Index", "User", new { id });
}
}
And my web form:
using (Html.BeginForm("Follow", "User", FormMethod.Post))
{
<input type="submit" value="Follow!" class="btn btn-success" />
}
Now the GET works perfectly but when I submit the POST it isn't handing over the user id from the url /user/123.
Do I need to perform an extra action in the Html.BeginForm if I am going from a GET on Index to a POST on Follow in order for it to hand over the {id} ?
You can use the BeginForm overload that takes RouteValues and pass the id from the Url using the RequestContext
Html.BeginForm("Follow","User", new { id = #Url.RequestContext.RouteData.Values["id"] }, FormMethod.Post);
Related
Edited the question as I've found out that the issue isn't inside the razor, but instead in the route
I have a very simple login form, but somehow, when the user presses Login the page goes tot Error404 and it simply does not hit the controller breakpoints for some reason.
#using (Html.BeginRouteForm("MyCustomRoute", new { controller = "login", action = "verify", FormMethod.Post }))
{
<fieldset class="clearfix">
<p><span style="float:none;color:black; font-size:20pt;"></span></p>
<p><span style="float:none;color:black; font-size:20pt;"></span></p>
<p><span class="fa fa-user"></span>#Html.TextBoxFor(m => m.UserName, new { #class = "form-control", placeholder = "Username", onkeydown = "convertTabtoEnter(this, event)", autofocus = "" })</p> <!-- JS because of IE support; better: placeholder="Username" -->
<p>
<span class="fa fa-lock"></span>#Html.PasswordFor(m => m.Password, new { #class = "form-control", placeholder = "Password", onkeyup = "convertTabtoEnter()" })
</p> <!-- JS because of IE support; better: placeholder="Password" -->
<div>
<span style="width:48%; text-align:left; display: inline-block;">
<a class="small-text" href="#">
#*Forgot
password?*#
</a>
</span>
<span style="width:50%; text-align:right; display: inline-block;"><input type="submit" value="Sign In"></span>
</div>
</fieldset>
<div class="clearfix"></div>
}
And Inside my login controller I have a simple ActionResult named Verify with the 2 params.
[RoutePrefix("Login")]
public class LoginController : Controller
{
// GET: Login
public ActionResult Index()
{
return View();
}
[HttpPost]
[Route("Verify")] //Matches GET login/verify
public ActionResult Verify(string username, string password)
{...}
What exactly am I doing wrong here? It's not like this is rocket science.
Edit2:
I've noticed that whenever I change the RouteConfig.cs back to default it works correctly. So, the problem isn't inside the form tags but within the routings.
So I've been trying to add a custom route in order to get this working using this sample: Using Html.BeginForm() with custom routes
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "TrailersOverview",
url: "{TrailersOverview}/{action}/{vendid}",
defaults: new { controller = "TrailersOverview", action = "Index", vendId = UrlParameter.Optional }
);
routes.MapRoute(
"MyCustomRoute", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "login", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
name: "Default",
url: "{*anything}",
defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional }
);
}
}
When I remove the routings and I simply bring everything back to default, the controller does get hit. Unfortunately I really need those routings for the rest of the app :(
There are several things that seems to be wrong:
Your Controller is missing in the route Config.
Action and Controller names are in wrong order.
RouteConfig:
A default route looks like that:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}/",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Form:
If you want to use Tag Helpers, then you have to change your code from:
<form action="#Url.Action("Login", "Verify")" method="post">
to:
<form asp-controller="Login" asp-action="Verify" method="post">
Have a look here.
#using (Html.BeginForm("Verify", "YourControllerName", FormMethod.Post))
{ your form here // }
please try this if it works !
One more thing add name attribute in your input fields which should be similar to the property name in your model like
#Html.TextBoxFor(m => m.UserName, new { #class = "form-control", id="username", name="USERNAME"})
And this USERNAME should be there in your model like:
Public class yourModel{Public string USERNAME{get;set;}}
And use your Model Object in your Action Method to fetch data.
Your custom route (the second in your RegisterRoutes code above) seems to be incorrect... based on what you stated above, it can/should be this:
routes.MapRoute(
"MyCustomRoute", // Route name
"Login/Verify", // (No parameters needed)
new { controller = "Login", action = "Verify" } // Parameter defaults
);
With this setup, alter the first line of your razor code to be this:
#using (Html.BeginRouteForm("MyCustomRoute", FormMethod.Post }))
{
<fieldset class="clearfix">
...
}
Using Html.BeginRouteForm will automatically use the defaults specified in your custom route; no need to add the defaults in Razor. Using FormMethod.Post as the second parameter will render your form's method as a POST.
Edit:
Let's fix your general Route problem. You're attempting to use Attribute Routing, which is described well here: https://www.dotnettricks.com/learn/mvc/understanding-attribute-routing-in-aspnet-mvc. I'd contend that it isn't necessary here.
First, fix your default Route (last route in your RegisterRoutes code) like so:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
// Change the 'controller' and 'action' parameters here to point to the Main page in your application.
Your default Route is very important, and should be configured as a catch-all for any requests can be simply mapped to a Controller/Action combination. I suspect that you experienced problems because your default Route was altered.
Next, comment out the Route and RoutePrefix attributes in your Login Controller... there's hardly a need for the [Route("Verify")] directive if you're using 'RegisterRoutes' properly.
// [RoutePrefix("Login")]
public class LoginController : Controller
{
// GET: Login
public ActionResult Index()
{
return View();
}
[HttpPost]
// [Route("Verify")] //Matches GET login/verify
public ActionResult Verify(string username, string password)
{...}
}
Now that the default Route is set up properly, the url '/Login' should take you to your Login screen, because the default action is "Index" (it's the default in your default Route above.)
Try adding routes.LowercaseUrls = true; to your route config as you seem to prefer lower-case versions of routes:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.LowercaseUrls = true;
routes.MapRoute(
name: "TrailersOverview",
url: "{TrailersOverview}/{action}/{vendid}",
defaults: new { controller = "TrailersOverview", action = "Index", vendId = UrlParameter.Optional }
);
routes.MapRoute(
"MyCustomRoute", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "login", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
name: "Default",
url: "{*}",
defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional }
);
}
}
And for the default route, I think you wanted to mention {*} for url to match everything else?
I have an issue, when I'm trying to build the url for action in controller, the value of vm after assigning is "/". If I try to create url with other action name then everything works fine, like Url.Action("Edit", "Contact").
public class ContactController : Controller
{
public ActionResult List()
{
string vm = Url.Action("Create", "Contact"); // equals "/"
string editUrl = Url.Action("Edit", "Contact"); // all is fine
return View("List", vm);
}
public ActionResult Create()
{
return HttpNotFound();
}
public ActionResult Edit()
{
return HttpNotFound();
}
}
What's wrong with that code?
It is because your route specifies them as defaults.
Your route is:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Contact", action = "Create", id = String.Empty }, null);
Essentially, it is because you specify the default values controller = "Contact", action = "Create". When you specify these as default you are saying if the value is not provided in the URL then use these.
For examples all these URLs are the same: /, /Contact & /Contact/Create. By default MVC generates you the shortest URL.
You could either change the default values or remove them like this:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { id = String.Empty }, null);
I'm trying to setup a simple form submission in MVC5, but I'm finding that my method doesn't get fired unless I have both an ActionLink and submit button.
I have a simple model:
public class LoginModel
{
public string username { get; set; }
}
Then I have two methods in my controller, one for when a form submission is available and one when not:
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel myModel)
{
var username = myModel.username;
// do something with username
return View();
}
Finally, my View creates a POSTing form:
#using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.TextBox("username", string.Empty)
#Html.ActionLink("Enter", "Login")
<input type="submit" name="submit" value="Enter" />
}
I don't really care whether I use an ActionLink or whether I have a submit button (MSDN implies it should be the latter), but if I have only one of them, my [HttpPost] method is not called, and my page is redirected with the username in the query string:
/Home/Login?ReturnUrl=%2F%3Fusername%3DmyUsernameHere
If I have both on the page, I can click the ActionLink and I see that the appropriate method is called with myModel.username containing the value I provided. The submit button, however, will still redirect.
I want this form method to be POST, not GET (which it is in the generated HTML), and for failures to not contain the key as a GET param. What's the problem with having only one of these form submission mechanisms? Why do they not trigger the POST as expected? Is there something more I need to do to 'register' my model with the view, even though it is submitted properly in my workaround scenario?
Edit -- My configured routes are typically as follows:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.RouteExistingFiles = true;
So normally, I'm going to /Login instead of /Home/Login.
Also, I do have some authentication setup, with my HomeController decorated with [Authorize] and my Login methods both with [AllowAnonymous]. When I remove all annotations, I still find that my [HttpPost] is not called, and username shows up as a GET parameter instead of being POSTed.
I believe that the application doesn't understand that you're trying to make a model with just username. So, you are sending a string username and attempting to place it into a model. I'm not entirely sure about that. Could you try binding your form to your model? Here's an example:
View:
#model YourApplication.Models.LoginModel
#using (Html.BeginForm("Login", "Home"))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.username, string.Empty)
<input type="submit" value="Enter" />
}
Controller:
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login([Bind(Include="username")] LoginModel myModel)
{
var username = myModel.username;
// do something with username
return View("Congrats");
}
Here's an alternate option. If you wanted to do something else, maybe try accepting the string "username" and creating your model after? Example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username)
{
LoginModel myModel = new LoginModel();
myModel.username = username;
// do something with username
return View("Congrats");
}
Either way, you should be using the submit button.
So, I am trying to submit a form on a List Page(http://example.com:3480/List) which is actually a Search implementation. So far I have done this:
index.cshtml
#using(Html.BeginForm("Search","ListController"))
{
<input id=query type=text name=query />
<input id=btnsearch type=submit value=Search />
}
ListController.cs
[HttpPost]
public ActionResult Search(FormCollection collection)
{
Response.Write("We are here");
// Get Post Params Here
string var1 = collection["query"];
Response.Write(var1);
return View();
}
Global.asax
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Details",
"Details/{id}/{orderid}",
new { controller = "Details", action = "Index", id = UrlParameter.Optional, orderid = UrlParameter.Optional }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
}
Upon Clicking it goes to http://example.com:3480/ListController/Search which seems fine.
Now I guess I need to define route in Global.aspx but not sure. What I want is to show result in same View file instead of creating a new one.
At this moment I am unable to get into Search method after POSTing form
Assuming you are currently just using the default route, the reason you are not reaching the action method is that the "Controller" suffix on your route is implicit - it shouldn't be part of your URL.
#using(Html.BeginForm("Search","List"))
Additionally, regarding:
What I want is to show result in same View file instead of creating a new one.
You can easily return a specific view from any controller action by specifying the name of the view in the call to the View method:
return View("Index");
I needed to get a data from the url on my post method. I have this routing on my asax:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Then on my Home Controller, under Get:
[HttpGet]
public ActionResult Index()
{
var id = ControllerContext.RouteData.GetRequiredString("id");
}
And on Post:
[HttpPost]
public ActionResult SomeNewNameHere(HomeModel homeModel)
{
var id = ControllerContext.RouteData.GetRequiredString("id");
}
My problem here is I need that id from the url on my post method. By debugging, I noticed that it gets the id on the get method, but when i post it, it returns me a null resulting to an error. So basically, RouteValues work on Get but not on my Post. Anything I missed here? Thanks!
Sample url:
http://localhost:1000/Controller/Action/12312121212
EDIT:
I also tried this but no luck:
var id = ControllerContext.RouteData.Values["id"];
The form on the view:
#using (Html.BeginForm("SomeNewNameHere", "Home", FormMethod.Post))
You can add id parameter to the post URL in your view:
#using (Html.BeginForm("SomeNewNameHere", "Home",new { id = Model.ID}, FormMethod.Post))
Add and int Id property to your HomeModel
then in your view, within your form:
#Html.Hiddenfor(m => m.Id)
This will post the Id to your action method
With the help of Ufuk Hacıoğulları, I came up with this solution on my form:
(Html.BeginForm("SomeNewNameHere", "Home",new { id = ViewContext.RouteData.GetRequiredString("id") }, FormMethod.Post))
So what happened here is it includes the id when it do a post.
Your Querystring values and Form values are automatically sent to the ActionResult at the same time, and the ASP.Net MVC Model binder will attempt to bind everything that it can.
So your GET Index ActionResult should be;
[HttpGet]
public ActionResult Index(int id)
{
// access id directly
}
And your POST Index ActionResult should be;
[HttpPost]
public ActionResult SomeNewNameHere(int id, HomeModel homeModel)
{
// access id directly
}
So your URL needs to be /Home/Index?id=1