Form attribute not being resolved by controller - c#

I've got hard time understanding how [Form] attribute works.
Consider simple razor form:
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.Hidden("id", 1)
#Html.Hidden("g-recaptcha-response", "asdbasd")
<input type="submit">
}
And a controller that handles the request:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index([Form("id")] string id, [Form("g-recaptcha-response")] string response)
{
var explicitResponse = Request.Form["g-recaptcha-response"];
return View();
}
Now the problem I have is that response parameter that is resolved with assistance of Form attribute is null while the one I named explicitResponse evaluates correctly to asdbasd.
To make sure this works at all, I've added id property which is resolved correctly using the same attribute.
I'll be diving into source code of that attribute to seek the answer, but perhaps someone has already done it.
So my question is: Why this attribute doesn't resolve the same value as I get explicitly from Request.Form and if there's a workaround to avoid reimplementing that attribute.

This was just me not paying attention.
FormAttribute is located in System.Web.ModelBinding namespace and it can be used by WebForms.
The correct attribute to use here is BindAttribute that is located in System.Web.Mvc namespace.
Modified code looks like this and works as expected.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index([Bind(Prefix = "id")] string id, [Bind(Prefix = "g-recaptcha-response")] string response)
{
var explicitResponse = Request.Form["g-recaptcha-response"];
return View();
}

Related

ASP.NET MVC Core ViewModel post back including IFormFile

Is it possible to somehow not clear a file input when posting back a viewmodel in ASP.NET MVC Core? I know the IFormFile doesn't include a member that designates the directory of the file selected.
So, if the view model gets sent back in the situation that ModelState is invalid the file input is cleared. Is it possible to avoid this without resorting entirely to clientside validation?
Here's an example controller and template to more adequately explain what I am saying
Controller:
namespace Example
{
public class CreateVm
{
public IFormFile aFile{ get; set; }
public string someText{get; set;}
}
public class ExampleController : Controller
{
public IActionResult Action()
{
var vm = new CreateVm();
return View(vm);
}
[HttpPost]
public IActionResult Create(CreateVm vm)
{
// Something invalidates the model state
// The supplied viewmodel isn't valid, send it back
if (!ModelState.IsValid)
return View(vm);
// Do stuff here
return RedirectToAction(nameof(Create));
}
}
}
Template:
#model Example.CreateVm
<form asp-action="Create" method="post" enctype="multipart/form-data">
<input asp-for="someText"/>
<input asp-for="aFile"/>
<input type="submit" value="Submit">
</form>
From what I understand it is not possible to get the file directory in javascript so I don't think that's a viable way to go about it either.

Razor form not passing back to controller method

I am trying to pass a textbox's text back to a method in my controller, but the data is not being passed in the parameter
I am genuinely confused, i'm following another example but i'm getting a different result, ie - my method behaving as if no parameter is passed
Code
public ActionResult Index(string searchString)
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
var listOfAnimals = db.Animals.ToList();
if (!String.IsNullOrEmpty(searchString))
{
listOfAnimals = listOfAnimals.Where(a => a.AnimalName.ToLower().Contains(searchString.ToLower())).ToList();
}
return View(listOfAnimals);
}
and here is my razor form from my view page
#using(Html.BeginForm("Index", "Home"))
{
#Html.TextBox("searchString")
<input type="submit" id="Index" value="Index" />
}
Can anybody spot why this isn't working?
If more code is needed, please let me know but i think the issue is isolated to here
You code is correct.
Since you didn't add [HttpGet] or [HttpPost] before index method.
This method was called twice.
The first call ran when producing the page with form via url http://server/Home/Index. This call was an http get and searchString mapped from URL was null, which is correct.
The second call ran when you clicked submit button. Correct value would be mapped by MVC correctly.
You need to have 2 Index actions (two methods), one without decorations (GET verb) and another one decorated with HttpPost (POST verb). Basically, when you go to the index page, the GET action is executed. When you submit the form, a POST request is executed and the Index decorated with HttpPost is executed.
// GET
public ActionResult Index() { ... }
// POST
[HttpPost]
public ActionResult Index(string searchString) { ... }
Francisco Goldenstein wrote the recommended way. It means you can have two Index() actions:
// for GET
public ActionResult Index() { ... }
// for POST
[HttpPost]
public ActionResult Index(string searchString) { ... }
However it is possible to have one Index() method for handling both (GET and POST) requests:
public ActionResult Index(string searchString = "")
{
if(!string.IsNullOrEmpty(searchString)
{ /* apply filter rule here */ }
}
You wrote, your code is not working. Do you mean, your action method is not requested after click on the button? Consider to allow empty value Index(string searchString = "")
If your action method is fired but variable is empty, check the name on the View() side. Textbox must not be disabled, of course.

Only POSTs with both ActionLink and submit button; redirects otherwise

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.

Failed to pass routeValues to an action from partial view

I wanna add search functionality in my MVC application. This functionality should be available for all pages. So, I added it into the shared layout.
The only problem I have is that I'm not able to pass the routValues to the Search action in spite of I'm creating a new Form inside the partial view.
Target action:(performs the search)
[HttpPost]
public ActionResult Search(SearchModel keyword)
{
// keyword is always null
return RedirectToAction("SearchResult", keyword.keyword);
}
public class SearchModel { public string Keyword { get; set; } }
The Partial View:
#model DocuLine.Models.SearchModel
#using (Html.BeginForm("Search", "Home", FormMethod.Post))
{
#Html.EditorFor(model => model.Keyword)
<input type="submit" value="Search" />
}
Try
public ActionResult Search(SearchModel model)
Why the class is defined as Keyword and then view SearchResult you pass it as a keyword (lower case first letter)?
1 UpdateModel
You can update the class SearchModel with the method UpdateModel to check if you can make the correct assignment.
2 FormCollection
You can try to receive as parameter in the method Search form data (FormCollection) and check if you get what you require.
Finally, I solve it. The problem is that the SearchModel parameter name is keyword and it should be anything except keyword becuase there's already an html control is rendered with this name.
To solve it, it should only be named with another name:
public ActionResult Search(SearchModel model)
{
// model now has a value.
}

Can I use an AntiForgeryToken without AcceptVerbs tag?

I would like to use the AntiForgeryToken function but the AcceptVerbs post does not apply. I am getting the anti forgery error. Is there a way to do this without the post method?
public ActionResult Page1(string data)
{ //code with view that includes link to Edit }
public ActionResult Page2(string data)
{ //code with view that includes link to Edit }
public ActionResult Edit(string pageName)
{ //execution then redirect to Page1/Page2 }
The anti forgery token works by a cookie and a hidden input field in the form. They both hold the same encrypted value. When the controller handles an action decorated with [ValidateAntiForgeryToken] it checks if the values in the cookie and the hidden input field match. If they don't - you get a nice exception.
You can use code like this
View:
<% using (var form = Html.BeginForm("DoSomething", "Default")) { %>
<%:Html.ValidationMessageFor(x => x) %>
<%:Html.AntiForgeryToken() %>
<%:Html.Hidden("a", 200) %>
<input type="submit" value="Go"/>
<%}%>
Controller:
public class DefaultController : Controller
{
public ActionResult Index()
{
return View();
}
[ValidateAntiForgeryToken]
public ActionResult DoSomething(int a)
{
return View("Index");
}
}
But then the form generated gets an method="post" attribute. On the controller side you don't need to specify [AcceptVerbs(HttpVerbs.Post)]. So the answer to your question is that you can use AntiForgeryToken without the AcceptVerbs attribute. You just need to use the POST method in the form.
To continue with the sample, if you specify [AcceptVerbs(HttpVerbs.Get)] on the action and Html.BeginForm("DoSomething", "Default", FormMethod.Get), the example won't work, because the GET request does not contain the cookie only the hidden input value gets encoded in the query string.

Categories