Checkbox state always passed to controller as null - c#

Similar questions have been asked a couple times, but I can't seem to find an answer.
I am POSTing some values to a controller action and all the parameters are being passed successfully based off their names, except the bool value of my checkbox. It is always passed as null.
Form:
#using (Ajax.BeginForm("AddNote", "Home", FormMethod.Post, new AjaxOptions { OnSuccess = "function noteAdded();", OnFailure = "alert(xhr.responseText)" }))
{
<!-- New Note Modal -->
<div class="modal fade" id="note-add-modal" tabindex="-1" role="dialog" aria-labelledby="note-add-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="note-add-modal-Label">Add A Note</h4>
</div>
<div class="modal-body">
<textarea placeholder="Description..." class="form-control" rows="5" id="comment" name="comment"></textarea>
<textarea name="assignTo" id="assign-to" rows="1" class="form-control" placeholder="Assign To..." data-autocomplete-url="#Url.Action("AutoCompleteUsername")"></textarea>
<br/>
<label id="follow-up-date">Follow-Up Date: <input name="alertDate" class="form-control-date" placeholder="dd/mm/yyyy" id="datepicker" type="text" /></label>
<label class="complete-label">Complete:</label>
<input type="checkbox" id="complete" name="complete" checked="checked"/>
<label for="complete"></label>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" id="note-form-submit" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
}
The checkbox has the name: complete.
Controller Action:
[HttpPost]
public void AddNote(string comment, DateTime? alertDate, bool complete, string assignedTo)
{
// Unrelated Code
}
I thought you were able to pass the checkbox state to a controller as a bool value through the name. Or it is absolutely necessary to use #Html.Checkbox?

The binding will check for value attribute since there isn't any in your checkbox it will always be false. One way you can do is to explicitly get complete from your FormCollection of the current request like following:
public void Submit(bool test)
{
var complete = Request.Form["complete"];
//complete == null if checkbox was not checked
//complete == "on" if checkbox was checked
}
The other easier way is to just use #Html.Checkbox("complete")

If you make a few changes, I think you will have more success. However, if you prefer your controller action to have many arguments instead of a single viewmodel, you may not like my solution.
Create a complex viewmodel instead of having several loose arguments. This tends to be easier to maintain over time.
public class Note
{
public string Comment {get;set;}
public DateTime? AlertDate { get; set; }
public bool Complete { get; set; }
public string AssignedTo { get; set; }
}
Change your Action to take the new viewmodel
[HttpPost]
public void AddNote(Note viewModel)
{
// Unrelated Code
}
Set your #model in the CSHTML
#model Company.Product.Note
While Html.CheckBoxFor(x => x.Complete) is the preferred approach, you can still do equivalent HTML manually. However, if the viewmodel changes at all, Html.CheckBoxFor will give you a more meaningful error due to name mismatches.
HTH!

Related

Id vs Object as parameters in Delete HTTP Post. Which is better?

I am trying to implement a simple CRUD using ASP.net Core as a beginner. My issue is with an HTTP post called DeletePlayer(). Please consider my code below:
Model:
public class Player
{
public int PlayerId { get; set; }
//... deleted unrelated code for shorter post ...
}
Controller:
public class PlayerController : Controller
{
private readonly DataContext _database;
public PlayerController(DataContext database)
{
_database = database;
}
//... deleted unrelated code for shorter post ...
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeletePlayer(int? id)
{
if (id == null || id == 0)
{
return NotFound();
}
var obj = _database.Players.Find(id);
if (obj == null)
{
return NotFound();
}
_database.Players.Remove(obj);
_database.SaveChanges();
return RedirectToAction("PlayerIndex");
}
}
View:
#model Player_Simulation.Models.Player
<form method="post" asp-action="DeletePlayer">
<input asp-for="PlayerId" hidden />
<div class="border p-3">
<div class="form-group row">
<h2 class="text-black-50 pl-3">Delete Player</h2>
</div>
<div class="row">
<div class="col-12">
<div class="form-group row">
<div class="col-6">
<label asp-for="PlayerName"></label>
</div>
<div class="col-6">
<label asp-for="PlayerRating"></label>
</div>
</div>
<div class="form-group row">
<div class="col-6">
<input asp-for="PlayerName" disabled class="form-control" />
</div>
<div class="col-6">
<input asp-for="PlayerRating" disabled class="form-control" />
</div>
</div>
<div class="form-group row">
<div class="col-8 offset-2 row">
<div class="col">
<input type="submit" class="btn btn-danger w-75" value="Delete" />
</div>
<div class="col">
<a asp-action="PlayerIndex" class="btn btn-success w-75">Back</a>
</div>
</div>
</div>
</div>
</div>
</div>
Graphics:
The DeletePlayer(int? id) I have in my PlayerController doesn't work and it gives me HTTP ERROR 404. However, if I change that to:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeletePlayer(Player obj)
{
_database.Players.Remove(obj);
_database.SaveChanges();
return RedirectToAction("PlayerIndex");
}
The delete works! So my questions are:
Why doesn't DeletePlayer(int? id) work while DeletePlayer(Player obj) work?
What must I do if I want DeletePlayer(int? id) to work?
I would appreciate anyone who could help me with my confusions!
1.Why doesn't DeletePlayer(int? id) work while DeletePlayer(Player obj) work?
Try to set a breakpoint in the DeletePlayer(int? id) HttpPost method, whether this breakpoint is hit when you click the Delete button?
If this breakpoint doesn't hit, it means there have a route error, the route doesn't find this action method, you need to check the request url and the action name.
If the breakpoint is hit, check the id value, perhaps the id is null, so the if condition will return a NotFound error. The reason for the empty id could be that the request url (route template) doesn’t contains the ID value, and the submit form doesn’t contains the id field. So, the id will be null.
You are using a form submit for your delete and that will attempt to send the entire model, so DeletePlayer(Player obj) work.
2.What must I do if I want DeletePlayer(int? id) to work?
The solution is what you see in the example, which is to add the ActionName("Delete") attribute to the DeletePlayer method. That attribute performs mapping for the routing system so that a URL that includes /Delete/ for a POST request will find the DeletePlayer method.
result:
From the above sample, we can see that in the Delete Get page, the request url is https://localhost:5001/Player/Delete/2, after clicking the delete button, the submit request url will be https://localhost:5001/Player/Delete/2, so in the HttpPost method, we can get the id value from the request url (route data), instead of the submitted form.
refer to:https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/details?view=aspnetcore-5.0

How to prevent model binding of disabled or readonly input fileds in Razor view HTML in ASP.NET Core?

So I have complex object that should be displayed on the page. Some properties of this object should be disabled (or readonly) for users in certain access roles. However, I noticed that if I edit this <input disabled .../> in devtools and remove disabled property and add arbitrary text, then model binder will happily take this value and put it in form model when submitted via OnPost action.
Here is example code:
<form method="post">
<div class="form-group col-sm-6">
<label asp-for="FormModel.FirstName" class="control-label">First Name</label>
<input asp-for="FormModel.FirstName"
disabled="disabled"
class="form-control"
/>
</div>
<div class="form-group col-sm-6">
<label asp-for="FormModel.LastName" class="control-label">Last Name</label>
<input asp-for="FormModel.LastName"
class="form-control"
/>
</div>
<input type="submit" value="Save" class="btn btn-primary" />
</form>
And the page model:
public class IndexModel : PageModel
{
private readonly MyDbMock _myDb;
[BindProperty]
public FormModel FormModel { get; set; }
public IndexModel(MyDbMock myDb)
{
_myDb = myDb;
}
public void OnGet()
{
FormModel = new FormModel
{
FirstName = _myDb.FormModel.FirstName,
LastName = _myDb.FormModel.LastName
};
}
public IActionResult OnPost()
{
_myDb.FormModel = FormModel;
return RedirectToAction(nameof(OnGet));
}
}
public class FormModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class MyDbMock
{
public FormModel FormModel { get; set; } = new FormModel
{
FirstName = "John",
LastName = "Smith"
};
}
When I open this page for the first time I get John in the disabled input with First name label. This is fine. Then when I click Save my OnPost method will get FormModel object where FirstName is null. This is reflected in the updated page. Can this be prevented and tell model binder just to not touch this property? Ie. leave it as John when form is submitted and not set it to null because of it being disabled?
The other issue I have is that an advanced user can remove disabled attribute from the First Name input and just submit arbitrary value. Can this also be prevented by telling model binder same thing as above? Eg. leave First Name field as-is when it was sent in GET request, no updates.
Here is the demo of all this:
I know I can handle this in backend code by preventing updating of the disabled fields. However I am looking some more elegant and declarative way if it exists. In my main project the form model is much complex and has objects within objects so ideally I'd like to skip adding additional logic to action handler for this.
Try to add a hidden input to bind FormModel.FirstName,and change the name of the disabled input,so that the value of disabled input will not be binded to FormModel.FirstName.
<form method="post" >
<div class="form-group col-sm-6">
<label asp-for="FormModel.FirstName" class="control-label">First Name</label>
<input asp-for="FormModel.FirstName" name="FirstName"
disabled="disabled"
class="form-control" />
<input asp-for="FormModel.FirstName" hidden/>
</div>
<div class="form-group col-sm-6">
<label asp-for="FormModel.LastName" class="control-label">Last Name</label>
<input asp-for="FormModel.LastName"
class="form-control" />
</div>
<input type="submit" value="Save" class="btn btn-primary" />
</form>
result:

.Net Core Ajax form submit not returning non binded model property

I have .Net core web project with form ajax submit. While loading the form, I am setting all property value to the view model. But while posting, it returns only property that binded to the html input. The other values are coming null. How can I get all the view model properties I passed to the form.
Controller
public IActionResult General(string templateId)
{
return View(new TemplateGeneralViewModel { Id = "12-3", Name = "Name", Code = "code" });
}
[HttpPost]
public IActionResult General(TemplateGeneralViewModel model)
{
return View();
}
Cshtml
<form asp-area="AdminPanel" asp-controller="Template" asp-action="General"
method="post" data-ajax-begin="OnFormAjaxBegin" data-ajax-complete="OnFormAjaxComplete"
data-ajax-failure="OnFormAjaxFailed" data-ajax-success="OnFormAjaxSuccess"
data-ajax="true" data-ajax-method="POST">
<div class="form-group">
<label for="tf1">Id</label>
<input type="email" asp-for="Id" class="form-control" id="tf1" aria-describedby="tf1Help" placeholder="e.g. johndoe#looper.com">
<small id="tf1Help" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label class="form-control-label" for="tfValid">Name</label>
<input type="text" asp-for="Name" class="form-control is-valid" id="tfValid">
<div class="valid-feedback"> Success! You've done it. </div>
</div>
</form>
View Model
public class TemplateGeneralViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
Here the property 'Code' is null on submit. How can I get this value without using any hidden field ?
Keep in mind that the model returned in the Post action is not the same instance of the model returned in the Get action. Is built anew with the data present in the html. Where is the field for the code property in your cshtml? Nowhere so when the model is created anew to pass it back in the post action no value could be given.
Just add the field and if you don't want to show it, add the style to hide it
<div class="form-group">
<label class="form-control-label" for="tfValid">Name</label>
<input type="text" asp-for="Name" class="form-control is-valid" id="tfValid">
<div class="valid-feedback"> Success! You've done it. </div>
</div>
<div class="form-group">
<input type="text" asp-for="code" class="d-none">
</div>
You can try to pass the other data in query string with asp-route-{value},you can refer to the official doc of anchor tag helpers.Here is a piece of sample code which passes Id,Name and Code to action:
<form asp-area="AdminPanel" asp-controller="Template" asp-action="General"
method="post" data-ajax-begin="OnFormAjaxBegin" data-ajax-complete="OnFormAjaxComplete"
data-ajax-failure="OnFormAjaxFailed" data-ajax-success="OnFormAjaxSuccess"
data-ajax="true" data-ajax-method="POST" asp-route-Code="#Model.Code">
<div class="form-group">
<label for="tf1">Id</label>
<input type="email" asp-for="Id" class="form-control" id="tf1" aria-describedby="tf1Help" placeholder="e.g. johndoe#looper.com">
<small id="tf1Help" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label class="form-control-label" for="tfValid">Name</label>
<input type="text" asp-for="Name" class="form-control is-valid" id="tfValid">
<div class="valid-feedback"> Success! You've done it. </div>
</div>
</form>

How to pass the value entered in a text box from a modal window to another component using Blazor server?

I am working on a Blazor server project and created this modal window. This is a component itself
<div class="modal #ModalClass" tabindex="-1" role="dialog" style="display:#ModalDisplay">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" #onclick="() => Close()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<label for="FirstName">Enter FirstName:</label>
<input type="text" id="FirstName" name="FirstName"><br><br>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" #onclick="() => Done()">Done</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal" #onclick="() => Close()">Close</button>
</div>
</div>
</div>
</div>
#if (ShowBackdrop)
{
<div class="modal-backdrop fade show"></div>
}
#code {
public Guid Guid = Guid.NewGuid();
public string ModalDisplay = "none;";
public string ModalClass = "";
public bool ShowBackdrop = false;
public void Open()
{
ModalDisplay = "block;";
ModalClass = "Show";
ShowBackdrop = true;
StateHasChanged();
}
public void Close()
{
ModalDisplay = "none";
ModalClass = "";
ShowBackdrop = false;
StateHasChanged();
}
public void Done()
{
}
}
I am using the component like this. I want to be able to use the FirstName entered in the Modal
in this component below to do other things when the user clicks on Done button.
#page "/modaltest"
<MappingPoc.UI.Pages.MyModal #ref="Modal"></MappingPoc.UI.Pages.MyModal>
<button #onclick="() => Modal.Open()">Open Modal</button>
#code {
private MappingPoc.UI.Pages.MyModal Modal { get; set; }
}
How can I pass the value of the first name entered in the modal to this component?
You can use an EventCallback to achieve this.
In your Modal component, bind the first name input textbox to a field using the #bind attribute:
<div class="modal-body">
<label for="FirstName">Enter FirstName:</label>
<input #bind="firstName" type="text" id="FirstName" name="FirstName"><br><br>
</div>
In the #code section of the same component, define a field called firstName of type string:
private string firstName;
This firstName will be bound to the textbox and any value update in the textbox will also set the value of firstName to the same value.
Secondly, in the same code section, add a public property of type EventCallback<string> as a parameter to the Modal component(by decorating it with the [Parameter] attribute:
[Parameter] public EventCallback<string> OnDoneCallback { get; set; }
In this code section, invoke the EventCallback using its InvokeAsync() method in the Done() method:
public void Done()
{
// ...
await InvokeAsync(() => OnDoneCallback.InvokeAsync(firstName));
}
This is all you need in your child Modal component.
In the #code section of the parent component, add a field to hold the first name from the child component:
private string _firstName;
Now, include a callback method to be executed in response to when the Done method of the child component calls the EventCallback:
private void OnModalDone(string firstName)
{
_firstName = firstName;
}
Now in your parent component where you are including the Modal component, pass the OnModalDone method as parameter for the EventCallback:
<MappingPoc.UI.Pages.MyModal #ref="Modal" OnDoneCallback="OnModalDone"></MappingPoc.UI.Pages.MyModal>
<h1>#_firstName</h1>
I have also added an <h1> tag to display the first name. Now, when you click the Done button of the modal, it will call the Done method. This Done method will call OnModalDone of the parent, with the parameter set as the value entered in the textbox of the modal. The entered value is available in OnModalDone as the parameter firstName.
You don't need to call StateHasChanged for event callbacks, it is called automatically.
See this working in Blazor fiddle: https://blazorfiddle.com/s/rbsg30wv
There are a few different ways you can do this depending on your needs:
Cascading Parameters: A good video for the basics of it - https://www.youtube.com/watch?v=ZmDMKp1Q8kA
An AppState approach: A good video for it covering two different ways: https://www.youtube.com/watch?v=BB4lK2kfKf0
Or you can pass it in as a parameter to the component: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-5.0 (scroll down to the 'Binding with component parameters' section
I included videos as I believe they are more helpful but let me know if it is not clear.

IFormFile always null (ASP.NET Core with MVC/Razor)

I have an ASP.NET Core MVC app attempting to upload an IFormFile. However, the IFormFile is always null. None of the other solutions I've found have solved this issue. What am I doing wrong?
Model
public class EmailForm
{
[Display(Name = "Add a picture")]
[DataType(DataType.Upload)]
[FileExtensions(Extensions = "jpg,png,gif,jpeg,bmp,svg")]
public IFormFile SubmitterPicture { get; set; }
}
Controller
public async Task<ActionResult> Contribute([Bind("SubmitterPicture")] EmailForm model)
{
if (ModelState.IsValid)
{
//Do some stuff
}
}
View
<form method="post" asp-action="Contribute" asp-antiforgery="true" enctype="multipart/form-data" >
<div class="form-group" >
<div class="col-md-2">#Html.LabelFor(m => m.SubmitterPicture)</div>
<div class="col-md-4">
<input type="file" name="SubmitterPicture" id="SubmitterPicture" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Contribute" />
</div>
</div>
<form method="post" enctype="multipart/form-data">
</form>
enctype="multipart/form-data"
The multipart form data is the key.
You can alternatively get the file from the HttpContext.Request.Form.Files and get rid of the IFormFile interface in your model. I recommend this method as I believe that files have nothing to do with data models.
The example would be:
public IActionResult Index()
{
//Please note that if no form data is posted
// HttpContext.Request.Form will throw an exception
if (HttpContext.Request.Form.Files[0] != null) {
var file = HttpContext.Request.Form.Files[0];
using (FileStream fs = new FileStream("Your Path", FileMode.CreateNew, FileAccess.Write, FileShare.Write)) {
file.CopyTo(fs);
}
}
return View();
}
If this method also fails, that means there is something wrong with the multipart request.
After sitting with the same problem for hours I found the solution.
The Problem:
Submitting a single input in a form.
The Conclusion:
Basically in your html form this won't work if you only have one input besides the submit button. My property was no longer null as soon I added another property on my viewModel and another input in my form.
I hope this helps someone else.
Razor Page HTML:
#page
#model SomeModel
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input asp-for="Input.SingleInput" type="file" class="form-control" />
<input asp-for="Input.AnotherInput" class="form-control" />
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Upload" />
</div>
</div>
</form>
Razor Page Code Behind:
public class SomeModel: PageModel
{
[BindProperty]
public SomeViewModel Input { get; set; }
public async Task OnGetAsync()
{
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
// This won't be null.
var showMeSomething = Input.SingleInput;
return RedirectToPage();
}
}
SomeViewModel:
public class SomeViewModel
{
public IFormFile SingleInput{ get; set; }
public string AnotherInput{ get; set; }
}
I found that by providing names on my html form, I was able to map to parameters (ASP.NET Core 2.1):
Client side:
<form method="post" enctype="multipart/form-data" action="...">
<input type="file" name="myFile" required />
<input type="password" name="myPass" required />
<input type="submit" value="post" />
</form>
Server side:
[HttpPost()]
public async Task<IActionResult> Post(IFormFile myFile, [FromForm]string myPass)
{
//...
}
Similarly, if anyone working in .Net Core 2.1 and are using asp-for inorder bind the model for your input elements, then do not give name & id properties for that <input> elements. Ideally, the InputTagHelper upon the rendering on the browser generates the name and id properties with their values. If you give the same value for name & id w.r.t Model class, then everything works fine. Else, system doesn't know to which model property it should bind.
Better approach is not to give id and name on the <input>
Below is the sample code.
<form id="uploadForm" enctype="multipart/form-data" name="uploadForm" asp-action="UploadExcel" method="post">
<div class="form-group form-group-lg form-group-sm row ">
<div class="col-sm-12 col-md-10 col-lg-10 uploadDiv" style="display: flex !important">
<label asp-for="FileName" class="col-sm-12 col-md-10 col-lg-10" style="font-size: 15px; max-width: fit-content ">File Name :</label>
<input asp-for="FileName" class="form form-control fileName"
type="text"
placeholder="Enter your file name" />
<!--File upload control-->
<label asp-for="FileName" class="col-sm-12 col-md-10 col-lg-10" style="font-size: 15px; max-width: fit-content ">Attachment :</label>
<input asp-for="File" required class="form-control" type="file" placeholder="File Name" />
</div>
</div>
<div class=" form-group form-group-lg form-group-sm row">
<span asp-validation-for="FileName" class="text-danger"></span>
</div>
<div class=" form-group form-group-lg form-group-sm row">
<small>Please upload .xls or .xlxs or json or xml formatted files only</small>
</div>
<div class="form-group form-group-lg form-group-sm row">
<div class="col-sm-12 col-md-10 col-lg-10">
<input type="submit" class="btn btn-primary" name="submit" id="fileUploadButton" value="Upload" />
<input type="reset" class="btn btn-Gray" name="result" id="resetButton" value="Reset" />
</div>
</div>
<a asp-action="DownloadTemplate" asp-controller="Download" title="Click to download template">Import Batch Transactions Template</a>
</form>
Model.cs
public class ExcelUploadViewModel
{
/// <summary>
/// Gets or Sets the FileName
/// </summary>
[Required(ErrorMessage = "FileName is required")]
public string FileName { get; set; }
[Required(ErrorMessage = "File is required")]
[DataType(DataType.Upload)]
[FromForm(Name = "File")]
public IFormFile File { get; set; }
}
Upon Submit
Thank you.
Your code looks perfectly fine and it should work as long as you are using the same version of code you posted on the question. I have a very strong feeling that you are getting false for the ModelState.IsValid expression and hence seeing the some sort of unexpected behavior.
The FileExtensions data annotation is supposed to be used with String type properties, not with IFormFile type properties. Because of this reason, the IsValid returns false.
So remove that from your view model.
public class EmailForm
{
[Display(Name = "Add a picture")]
[DataType(DataType.Upload)]
public IFormFile SubmitterPicture { get; set; }
}
Here is a relevant GH issue and explanation from one of the team member, for your reference.
FileExtensions Data annotation invalid ModelState #5117
I had this issue and the fix was to make sure the element of "input type="file"" had an id and name set. Then, in the controller, set the IFormFile parameter name to be exactly the same, case as well.
G-luck
Adding the below to my controller fixed the issue for me. It was because my file was large. Adjust the limits as needed.
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]

Categories

Resources