How do you hook up a textbox to a method in MVC5 using attribute routing, with areas?
This is view:
#using (Html.BeginForm())
{
#Html.TextBox("searchpara")
#Html.ActionLink("Search", "SearchMethod", "Home", new { area = "Timetables" }, null)
}
Controller:
[RouteArea("Timetables")]
[RoutePrefix("Home")]
public class HomeController : Controller
{
Method:
[Route("SearchMethod/{searchpara=Test}")]
public ActionResult SearchMethod(string searchpara)
{
It doesn't work. The problem may not be routing?
I believe you want a submit button, and not an action link and you may need to update the form to post to a specific action if it is not the current action.
#using (Html.BeginForm("SearchMethod", "Home", new { area = "Timetables" }))
{
#Html.TextBox("searchpara")
<button type="submit">Search</button>
}
Related
I've got this route configuration:
routes.MapRoute(
"routeB",
"routeB/{action}/{id}",
new { controller = "Sample", action = "IndexB", id = UrlParameter.Optional });
routes.MapRoute(
"routeA",
"routeA/{action}/{id}",
new { controller = "Sample", action = "IndexA", id = UrlParameter.Optional });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And my Sample controller contains these Action methods:
public ActionResult IndexA(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexA()
{
return RedirectToAction("Confirmation");
}
public ActionResult Confirmation()
{
return View("Confirmation");
}
public ActionResult IndexB(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexB()
{
return RedirectToAction("Confirmation");
}
If I land on the localhost/RouteA page and make a POST (via a button) it redirects me to localhost/RouteB/Confirmation.
How can I get the page to redirect to the RouteA/Confirmation page?
Thanks.
There are 2 issues here. As others have pointed out, your HTTP POST needs to be corrected. Since you are sharing a single view for 2 different actions, the simplest way to do that is to set the actionName parameter to null. This tells MVC to use the action name from the current request.
#{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; }
<h2>Index</h2>
#using (Html.BeginForm(null, "Sample", new { id = "OrderForm" }))
{
#Html.AntiForgeryToken()
<button type="submit" id="orderFormBtn">Extend my discount.</button>
}
The second issue is that the RedirectToAction call is ambiguous between routeA and routeB when generating the URL. Since the first match always wins, the URL you are redirecting to is always the top one in your configuration.
You can fix this problem by using RedirectToRoute to specify the route name (in addition to your existing matching criteria) explicitly.
public class SampleController : Controller
{
public ActionResult IndexA(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexA()
{
return RedirectToRoute("routeA", new { action = "Confirmation" });
}
public ActionResult Confirmation()
{
return View("Confirmation");
}
public ActionResult IndexB(string id)
{
return View("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult IndexB()
{
// Note that changing this one to RedirectToRoute is not strictly
// necessary, but may be more clear to future analysis of the configuration.
return RedirectToRoute("routeB", new { action = "Confirmation" });
}
}
Your problem is quite simple.
Take a look to your controller, the methods IndexA and IndexB, returns the same view; (from your comments)
#{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; }
<h2>Index</h2>
using (Html.BeginForm("IndexB", "Sample", new { #id = "OrderForm" })) {
#Html.AntiForgeryToken()
<button type="submit" id="orderFormBtn">Extend my discount.</button>
}
When you click on submit, you always do a post to IndexB
There are a lot of ways to correct such a simple error, for example you simple could use 2 different views IndexA and IndexB and change
using (Html.BeginForm("IndexB", "Sample", new { #id = "OrderForm" })) {
to
using (Html.BeginForm("IndexA", "Sample", new { #id = "OrderForm" })) {
The problem is that both your routeA and routeB routes will work for creating a link for an IndexA action in SampleController. As a result, BeginForm will just short-circuit and pick the first route that will work, which may or may not be the "correct" one you're looking for. To differentiate the routes, generate them via the route name:
#using (Html.BeginRouteForm("routeA", new { #id = "OrderForm" })) {
However, that will only ever let you get the "default" routeA route, i.e. /routeA/. There's no way to specify a different action other than the default one for the route.
For more flexibility, you can employ attribute routing, which will allow you to give a custom route name to each action, which you can then use to get the URL for that action.
Short of that, you will need to differentiate the two routes in some way, so that it's not ambiguous which should be used when generating a URL. That's often difficult to do with standard routing, which is why attribute routing is the much better approach if you're going to diverge from simply using the default route for everything.
Alternatively, you can restructure your project to keep the same URL structure, but make it much easier to differentiate routes. By employing areas, you can then specify the area where the route should be created from. For example, assuming you had RouteA and RouteB areas, you could then do:
#using (Html.BeginForm("IndexB", "Sample", new { area = "RouteA", #id = "OrderForm" })) {
That would then mean having a SampleController in each area, but you can employ inheritance to reuse code.
Ajax.BeginForm() not working when Html.EditorFor() contains special charactor <
It does not invoke the controller action on submit the form
Have you tried the following:
Index.cshtml
#using (Ajax.BeginForm("PostForm", "Home", new AjaxOptions()))
{
string myFormValue = "";
#Html.EditorFor(x => myFormValue)
<input type="submit" value="Submit"/>
}
HomeController.cs
public class HomeController : Controller
{
public ActionResult PostForm(string myFormValue)
{
return View("Index");
}
When i debug it seems to work:
May you post your code?
I have the following Controller
namespace ExampleProject.Areas.Plugins.Default.Admin.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
}
I have the following custom route in my PluginsAreaRegistration.cs
context.MapRoute(
"Plugins_Default_Admin",
"Plugins/Default/Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
namespaces: new[]{
"ExampleProject.Areas.Plugins.Default.Admin.Controllers"
}
);
So when I call the url "/Plugins/Default/Admin/Home/Index", I will get to the ActionResult in the controller Above.
What I want achieve is to call the Index ActionResult using Html.Action in a different View
Example:
#Html.Action("Index", "Home", new {area = "Plugins" })
I'm not sure how I can point the the specific home controller that I want? I have multiple HomeControllers in the Plugins Area, all with different routes.
Error I'm getting is "Multiple types were found that match the controller named 'Home'."
I have the following route defined
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Login", id = UrlParameter.Optional }
);
I am then trying to use Html.BeginForm as below
<% using (Html.BeginForm("Login", "Account", System.Web.Mvc.FormMethod.Post, new { #class = "login-form" }))
{ %>
But this renders me a form like below
<form class="login-form" action="/" method="post">
</form>
However if i change my defaults on me route to be something different like
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Login", id = UrlParameter.Optional }
);
My form then renders correctly, for info i am using Html.BeginForm() in a partial view that is returned from the login method on my account controller.
public class AccountController : Controller
{
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
//TODO: Login user
}
return View(model);
}
}
The behavior that you are noticing is expected and is also correct. When generating links MVC goes through the list of routes in the route collection(top-down) and sees which routes can match based on the route data that you are providing in Html.BeginForm. For example, you can imagine a request coming in like POST / and in this case your Account controller's Login action would be called because of the presence of defaults.
Actually it is expected behaviour. Actually routing system is pretty clever and it knows the request which is coming that is for the default values.(In your case default controller is Account and default action is Login and in your begin form you are using the same controller and action).
So routing system will replace it
by '/'.
You can verify it by just adding one another controller let say Admin and the same View Login. And now just replace the controller by new controller like
<% using (Html.BeginForm("Login", "Admin", System.Web.Mvc.FormMethod.Post,
new { #class = "login-form" }))
Now you will have link like
<form class="login-form" action="/Admin" method="post"></form>
There will be no action, because routing system will find the action is default action.
Thanks
Bear with me as I describe the issue.
An MVC3 application making use of partial views. Having trouble posting a Comments form in a partial view from its parent-model's Details view.
For reference ArticleViewModel has a collection of CommentsViewModel so there is a OTM relationship.
Details View
#model ArticleViewModel
// Renders the Article and its Comments - no forms, just display markup
// A nested _Comments partial is used to render the article's comments.
#Html.Partial("_Article", Model)
// Renders HTML and Javascript for the Pagedown editor - POST form inside.
#Html.Partial("_CommentEditor", new CommentViewModel())
#section scripts { /* code here for validation and the javascript editor */ }
_CommentEditor Partial View
#model CommentViewModel
#using (Html.BeginForm("Comment","Article",FormMethod.Post)) {
#Html.TextAreaFor(m => m.Content)
<input type="submit" value="Submit" />
<input type="reset" value="Clear" />
}
Article Controller
public ActionResult Details(string slug) {
return View(_articleService.Get(slug));
}
[HttpPost]
public ActionResult Comment(string slug, CommentViewModel comment) {
if(ModelState.IsValid) {
_articleService.AddComment(comment, slug);
}
return RedirectToAction("Details", new { Slug = slug });
}
Scenario / Issue
Http Request for /Article/Details/{slug} renders the article, its comments, and the editor form correctly.
The editor works as intended, but on clicking Submit, I noticed the Details action on my controller being called rather than the HttpPost Comment action.
As you can see the Razor Form helper specifies the Comment action on the Article controller using POST.
Question
Why is this happening? What am I missing?
Chump Award!
The answer is Routing.
Looking closer with Fiddler, I was actually sending a POST request to /article/comment, so I checked my routing... how I missed this, I don't know:
routes.MapRoute("Article-Create", "article/create", new { controller = "Article", action = "Create" });
routes.MapRoute("Article-Edit", "article/edit/{slug}", new { controller = "Article", action = "Edit" });
routes.MapRoute("Article-Delete", "article/delete/{slug}", new { controller = "Article", action = "Delete" });
routes.MapRoute("Article", "article/{slug}", new { controller = "Article", action = "Details" });
routes.MapRoute("Articles", "articles", new { controller = "Article", action = "Index" });
There is no explicit route for the Comment action. There is a catch-all REST-ish route for fetching an article (article/{slug}). So the Comment POST was being handled by it before hitting the default route...
My specific solution (i like explicit routing - even when it gets me in trouble) was to add a route for comments, just about the catch-all article/{slug} pattern:
routes.MapRoute("Article-Comment", "article/comment", new { controller = "Article", action = "Comment" });
Problem solved. Embarrassing.