Having this in Index.cshtml
<form asp-controller="Authentication" asp-action="Test" method="post">
<input type="text" name="titi" value="titi-val" />
<input type="text" name="toto" value="val-toto" />
<button type="submit">Test Submit</button>
</form>
this
this in the AuthenticationController.cs
[ApiExplorerSettings(IgnoreApi = true)]
[ApiController]
[Route("api/authentication")]
public class AuthenticationController : BaseApiController
{
[HttpPost]
public IActionResult Test(string toto, string titi) {
logger.LogInformation($"Test '{toto}', '{titi}'");
return Ok(toto+titi);
}
I get this:
The titi field is required
and
The toto field is required
{"errors":{"titi":["The titi field is required."],"toto":["The toto field is required."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1" , "title":"One or more validation errors occurred.","status":400,"traceId":"00-5a...03-00"}
PS.
Observed that this would work as expected
<form asp-controller="Authentication"
asp-action="Test"
asp-route-titi="titi-val"
asp-route-toto="val-toto"
method="post">
API controllers does not process "simple" values from form by default. From the docs:
[FromForm] is inferred for action parameters of type IFormFile and IFormFileCollection. It's not inferred for any simple or user-defined types.
You need either to post data in some other way (json body mapped to some model or via query string parameters) or add FromForm attribute:
[HttpPost]
public IActionResult Test([FromForm] string toto, [FromForm] string titi)
{
return Ok(toto + titi);
}
Related
I can post elements from my form if I make a direct reference to the named element in the controller parameters. I am trying to use FormCollection so I do not have to type every element from the form in the post ActionResult parameters.
The HTML form:
#using (Html.BeginForm("legallabels", "Reports", FormMethod.Post, new { id = "reportForm", #class = "report-form col-9" }))
{
<div class="col-12">
<b>Beginning </b><input type="text" class="form-control col-2" id="beginningDatePicker" name="beginningDate" value="#DateTime.Today.Date.ToString("MM/dd/yyyy")" />
</div>
<input type="submit" value="submit">
}
Controller using named parameter (beginningDate):
[HttpPost]
public ActionResult LegalLabels(string beginningDate)
{
return View();
}
When using FormCollection, it does not get passed to the controller:
[HttpPost]
public ActionResult LegalLabels(FormCollection form)
{
return View();
}
Using a breakpoint in the controller, I can see that the form is posting correctly and everything works fine when naming form elements (beginningDate) in the parameters. I've looked at similar samples of code that use FormCollection and they seem to work fine. Why are my FormCollection values not getting passed to the controller?
Tested your code, it works fine. If you see the snippet below you can loop through all posted values and check.
[HttpPost]
public ActionResult LegalLabels(FormCollection form)
{
StringBuilder sb = new StringBuilder();
foreach (var key in form.AllKeys)
{
sb.AppendLine(string.Format("Key: {0}. Value: {1}.<br>", key, form[key]));
}
ViewBag.FormData = sb.ToString();
return View();
}
On the cshtml
<div>
#Html.Raw(ViewBag.FormData)
</div>
After renaming FormCollection to IFormCollection, everything is working properly.
I am building a filtering area and I have checkboxes, when you click on it does a "get" request to the server to find anything that matches.
I am not sure how to pass these filters values to my get method.
I can't have
public IActionResult GetFilterOptions(string param1, param2, etc)
Can I pass it in through the body like in a post?
For a GET, you pass parameters in the query string.
Example:
public class DataController : Controller
{
//note: HttpGet attribute is only one of many ways to template the route
[HttpGet("/Data/FilterOptions")]
public IActionResult GetFilterOptions(string paramOne, string paramTwo) { }
}
then create a GET request with that looks like:
localhost:80/Data/FilterOptions?paramOne=abc¶mTwo=xyz
Have you tried using [FromQuery] and accept an object that contains the filtering logic?
You could look at Telerik’s DataSourceRequest as a reference on how the filter is build up and applied.
public IActionResult Search([FromQuery] DataSourceRequest request)
And the DataSourceRequest’s structure would be something like this:
{“Filters” : [{“FieldName” : “Username”, “Operator” : “eq”, “Value” : “John”}, {“FieldName” : ...}, ...]}
<input type="checkbox" name="newCheckboxes" value="1" />
<input type="checkbox" name="newCheckboxes" value="2" />
<input type="checkbox" name="newCheckboxes" value="3" />
$.ajax({
type:"POST",
url: "PostedHere",
data: { checkedValues: $("#newCheckboxes:checked").serialize() }
});
Use Post Rather than Get Method
[HttpGet]
public IActionResult GetFilterOptions([FromBody] CheckboxObject model)
{
foreach (item in selectedItems)
{
//Query Statement goes here
}
return Ok(new { result= test });
}
public class CheckboxObject
{
public List<string> CheckedValues;
}
I spent a good margin of time searching and working through this problem yesterday without coming up with a solution. Here is the guide I used as reference.
The problem is that the data from the form is not reaching my controller. My goal is to take the form data and pass it to my controller/ model so that I can use the values throughout my code and store them in the database. Below is what I have so far...
In my Browse.cshtml (View)
#model Collect
<form asp-action="Collect" asp-controller="Collect" method="post">
<input type="hidden" asp-for="GameId" name="#game.id"/>
<button type="submit" class="dropdown-item btn btn-block">Default</button>
</form>
In my CollectController.cs (Controller)
using System;
using GameLibrary.me.Models;
using Microsoft.AspNetCore.Mvc;
namespace GameLibrary.me.Controllers
{
public class CollectController : Controller
{
[HttpGet]
public IActionResult Collect()
{
return View();
}
[HttpPost, ValidateAntiForgeryToken]
public IActionResult Collect(Collect model)
{
Console.WriteLine("**********\n"+model.GameId+"\n**********");
return Content($"Hello {model.GameId}");
}
}
}
In my Collect.cs (Model)
namespace GameLibrary.me.Models
{
public class Collect
{
public int GameId { get; set; }
}
}
EDIT: Here is what my IDE is telling me...
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 POST https://localhost:5001/browse?game=eevee application/x-www-form-urlencoded 7
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 1.0139ms 200
info: Microsoft.AspNetCore.Server.Kestrel[32]
Connection id "0HLJHIUOU6AKO", Request id "0HLJHIUOU6AKO:00000003": the application completed without reading the entire request body.
Any guidance on what I am doing wrong would be great appreciated... Also can I send multiple values through the hidden field type, or should I make a new hidden field type for each value?
There was a lot of different help here, thanks especially to Kirk Larklin! There were three issues that was preventing my controller from picking up the data.
Browse.cshtml was missing the #addTagHelpers... I added the following:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper *, AuthoringTagHelpers
My CollectController.cs was missing a route... I added the following:
[HttpPost, ValidateAntiForgeryToken]
[Route("Index/Collect")]
Finally, I renamed my controller post method from 'Collect' which conflicting with another method to Index and updated the asp-action in my Browse.CSHTML file to match.
public IActionResult Index(Collect model)
Thanks for all the help!
-Travis W
First I would pass in the model to the view with the Id initialised:
public IActionResult Collect()
{
return View(new Collect { GameId = "5"});
}
Inside your view update the form to the following:
<form asp-action="Collect" asp-controller="Collect" method="post">
#Html.HiddenFor(m => m.GameId)
<button type="submit" class="dropdown-item btn btn-block">Default</button>
</form>
The Html helper will create the html coded for the field. Once you click submit the value will be correct.
In my case, I updated the login view with this line of code. that's fixed.
<form method="post" asp-controller="account" asp-action="login">
Based on your example I do not believe that you want custom actions, you should be able to achieve your goal with the default framework mapping.
public class CollectController : Controller
{
[HttpGet]
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index([FromBody]CollectionModel model) => Content(...);
}
To clarify the magic, the declared Post method I utilize a HttpBody attribute, otherwise the framework will expect the parameter contents to be defined via Query String in the URL. The framework by default looks for Index when a Controller has been hit, if you do not need the URL to represent that, then do not use it.
The routing pattern usually follows:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
URL:
http://www.sample.com/Collect ---> Initial index Get would be hit
I have a HomeController, which returns a View... In my View, I submit a form, which is handled by ServicesController and action SubmitDestination. (Error/problem is listed at bottom)
HomeController
public class HomeController : Controller
{
[ActionName("Index")]
public IActionResult Index()
{
return View();
}
}
View
#model ScheduleService.Models.Departure
<form asp-controller="Services" asp-action="SubmitDestination" method="post">
Departure time: <input asp-for="DepartureTime"/>
Number: <input asp-for="Number"/>
Destination<input asp-for="Destination"/>
<button type="submit">Submit</button>
</form>
ServicesController
[Route("api/[controller]")]
[ApiController]
public class ServicesController : ControllerBase
{
[HttpPost]
public Departure SubmitDestination(Departure departure)
{
Departure _departure = new Departure
{
DepartureTime = departure.DepartureTime,
Number = departure.Number,
Destination = departure.Destination
};
_db.DepartureDB.Add(_departure);
_db.SaveChanges();
return _db.DepartureDB.LastOrDefault();
}
}
PROBLEM: When I submit the form, this is the response that is returned in the browser:
{"":["The input was not valid."]}
Can anyone spot, why my input is invalid? Is there some special rules for API controllers? Thanks
Try using the [FromForm] attribute:
public Departure SubmitDestination([FromForm]Departure departure)
This instructs the model binder to read the data from x-www-urlencoded
As #Nkosi pointed out in the comments, the API Controller is expection Json, but I am trying to pass in data from the form (which is not Json). I didn't find a solution, but that was the problem
I recently came upon this question as I have a solution I need to work on.
I have a controller which does some logic, with some method:
public Controller1: MvcController
{
public ActionResult SomeLogic(Model model)
{
return view();
}
public ActionResult SomeLogic2(Model model)
{
return view();
}
}
And I have a second controller which does different logic:
public Controller2: MvcController
{
public ActionResult SomeLogic(Model model)
{
return view();
}
public ActionResult SomeLogic2(Model model)
{
return view();
}
}
Both are working on the same view:
MyView.cshtml
The view contains a begin form that works on submit:
#using(Html.BeginForm("SomeLogic", "Controller1", FormMethod.Post)
and 4 submit buttons:
<input type='submit' formaction='SomeLogic' value='submit' />
<input type='submit' formaction='SomeLogic2' value='submit2' />
<input type='submit' formaction='SomeLogic' value='submit3' />
<input type='submit' formaction='SomeLogic2' value='submit4' />
I know I can control the action to which I am redirecting the submit using the:
formaction attribute.
I there a way to change the controller as well? meaning that:
<input type='submit' formaction='SomeLogic' value='submit3' />
<input type='submit' formaction='SomeLogic2' value='submit4' />
Will submit to controller2 instead of Controller1 which is defined in my begin form.
This will solve my debate whether to create a new controller or use the existing one, which is already full of complex logic.
You need to include controller name in the formaction attribute.
Note that your controller names are not valid, but assuming your have
public FirstController: Controller
{
[HttpPost]
public ActionResult SomeLogic(Model model)
{
return view();
}
}
public SecondController: Controller
{
[HttpPost]
public ActionResult SomeLogic(Model model)
{
return view();
}
}
Then you can post to the correct controller using
// Post to the SomeLogic method of FirstController
<input type="submit" formaction="#Url.Action("SomeLogic", "First")" value="submit" />
// Post to the SomeLogic method of SecondController
<input type="submit" formaction="#Url.Action("SomeLogic", "Second")" value="submit3" />
Just intercept the submit with jQuery
You can add an ID to your form, to be able to select it easily in jQuery
#using(Html.BeginForm("SomeLogic", "Controller1", FormMethod.Post, new { #id = "yourForm" })
Then
$("#yourForm").submit(function(e){
e.preventDefault();
var url1 = '#(Url.Action("SomeLogic1", "SomeController", null, Request.Url.Scheme))';
var url2 = '#(Url.Action("SomeLogic", "SomeController2", null, Request.Url.Scheme))';
//Whatever logic should you do to select which URL you want to POST to
$.ajax({
type: 'POST',
url: url_selected_above,
data: $('#yourForm').serializeArray(),
success: function (xhr, response) {
//...
},
error: function (xhr, response) {
//...
}
});
});
This should work if all the methods expect the same view model.
Alternatively, if you do not want to mess around in the UI, you can do something as such in the action that generates the form.
var controllerName = "SomeController";
var actionName = "Action1";
//do your logic to select whichever controller/action.
viewModel.ControllerName = controllerName;
viewModel.ActionName = actionName;
And in the view that has the form:
#using(Html.BeginForm(#Model.ActionName, #Model.ControllerName, FormMethod.Post)
Again, if all the actions expect the same view model, that should work.