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

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

Related

Submiting Ajax form in Net .Core

I created wizard with the jQuery-Steps lib with simple form for now. There is no Submit button. I submit form on finish step via jQuery.
I am already using jQuery calls all over the place, and I have included all scripts, and I needed form for uploading images and other stuff, it's easier with it.
Nothing happens, the controller action is not called.
I just getting redirected on start page with all this parameters in query string.
Like this:
https://localhost:44380/Dashboard?Name=Aaaa&Surename=Bbbb&City=Cccc
Dashboard is all made with Ajax and partial views. And from person option in menu I'm getting redirected on home view in Dashboard with all this parameters.
This is the form:
<form asp-controller="PersonSettings" asp-action="SaveForm" data-ajax="true" data-ajax-method="POST" id="personForm" class="steps-validation wizard-notification">
<!-- Step 1 -->
<h6>Person</h6>
<fieldset>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="Name">
Person Name :
<span class="danger">*</span>
</label>
<input autocomplete="off" type="text" class="form-control required" id="Name" name="Name">
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="Surname">
Person Surname:
<span class="danger">*</span>
</label>
<input autocomplete="off" type="url" class="form-control required" id="Surname" name="Surname">
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="City">
Person City:
<span class="danger">*</span>
</label>
<input autocomplete="off" type="url" class="form-control required" id="City" name="City">
</div>
</div>
</div>
</fieldset>
</form>
This is the Model:
public class PersonDto {
public string Name { get; set; }
public string Surname { get; set; }
public string City { get; set; }
}
Here is Action in PersonSettings controller:
[Route("SaveForm")]
[HttpPost]
public IActionResult SaveForm(PersonDto Person) {
//Do something with person model
return Ok();
}
And on finish button I have jQuery submit called on this form:
$('#personForm').submit();
EDIT:
I tried with this parameters:
<form action="PersonSettings/SaveForm" method="post" id="personFrom" class="steps-validation wizard-notification">
And this works.. it's good, but why does the first method not work? Because I need this in Ajax and finish method.
I didn't find any way to post image with form with classic Ajax call. Because I can't read image path (it's not exposed to the browsers).
I think that you didn't installed Tag Helpers. If it works with action attribute, it should work with Tag Helpers attribute. Because you're using Asp-Net-Controller attribute.
https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.TagHelpers
Please let me know.

Controller action not being hit

I have the following form that is trying to let a User SignIn:
<form method="post" asp-controller="Auth" asp-action="SignIn">
<div class="form-group row">
<div class="col">
<input class="form-control" type="email" placeholder="Email" id="email-input" name="email">
</div>
</div>
<div class="form-group row">
<div class="col">
<input class="form-control" type="password" placeholder="Password" id="password-input" name="password">
</div>
</div>
<div class="form-group row">
<div class="col">
<button class="btn btn-success" type="submit" name="button" style="width: 100%;">Login</button>
</div>
</div>
</form>
As you can see the form is using a Controller named Auth and an action on the Controller named SignIn. Here is a sample of the Controller:
public class AuthController : Controller
{
public IActionResult Index()
{
return View(new SignInViewModel());
}
[HttpPost]
public async Task<IActionResult> SignIn(SignInViewModel model)
{
if (ModelState.IsValid)
{
if (await _userService.ValidateCredentials(model.Email, model.Password, out var user))
{
await SignInUser(user.Email);
return RedirectToAction("Index", "Home");
}
}
return View(model);
}
}
I've set a breakpoint on the line if (ModelState.IsValid) and it never gets hit. When I click the login button the page simply refreshes. I really cannot see why this breakpoint is not being hit when the Controller and the Action seem OK to me.
EDIT: SignInViewModel:
public class SignInViewModel
{
[Required(ErrorMessage = "Please Enter an Email Address")]
public string Email { get; set; }
[Required(ErrorMessage = "Please Enter a Password")]
public string Password { get; set; }
}
Snippet from Chrome:
So I've made a new project in an attempt to replicate the issue and it works perfectly for me.
I'm assuming you're not using Areas also? If you are using areas, add this attribute to your controller class:
[Route("AreaNameHere")]
Although if you are able to get to the index page then this isn't the issue.
Are your breakpoints valid? Are symbols being loaded? It could be refreshing due to the ModelState being invalid or the details being invalid and so it's returning the same view. You could use fiddler to see if it's actually making a request at all.
EDIT:
Do you have this line "#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers" in your _ViewImports.cshtml file?

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)]

Trying to get data from a textbox in a form inside view to controller in asp.netmvc

I am trying to get data from a textbox in a form inside view to controller in asp.net-mvc.
My requirement is to compare Token_No. with some id and
Password with some existing password. How to get get those values at controller side on button click inside if condition.
I am not using razor syntax so how to accomplish this situation without using razor syntax and strongly bind with model.
Code in view
<form name="ctl00" id="ctl00" action="HomeController/Index" method="post" data-dpmaxz-fid="1">
<div class="input-group">
<span class="input-group-addon"> <i>Token_No.</i></span>
<div class="form-group is-empty">
<input name="txtToken" class="form-control" id="txtToken" type="text" placeholder="Token No..." data-dpmaxz-eid="5">
<span class="material-input"></span>
</div>
</div>
<div class="input-group"> <span class="input-group-addon">
<i class="material-icons">lock_outline</i> </span>
<div class="form-group is-empty">
<input name="txtPswd" class="form-control" id="txtPswd" type="password" placeholder="Password..." data-dpmaxz-eid="5">
<span class="material-input"></span>
</div>
</div>
<div class="footer text-center">
<input name="btn_Login" class="btn btn-primary" id="btn_Login" type="submit" value="Login" data-dpmaxz-eid="6">
</div>
</form>
Code in Controller
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(EmpDetails model)
{
if (model.token_no.="197418")
{
}
return View();
}
}
you can generate the view using controller method & create a strongly typed view. then you can choose a model class & select scaffold template as what you want..you can select create,edit,delete..etc as you want.then you can access the text box values using form collection.
[HttpPost]
public ActionResult authenticateUser(FormCollection formCollection)
{
int token = int.Parse(formCollection["txtToken"]);
string pswd = formCollection["txtPswd"];
return View();
}

Checkbox state always passed to controller as null

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!

Categories

Resources