Submit button action in MVC controller - asp.net-core

I'm trying to find out the best way to create a submit and add button action in a controller.
I have a HttpGet for Create (Submit) but not sure how to do a HttpPost or if Get or Post is even needed:
[HttpGet]
public IActionResult Create()
{
var drafList = _drService.GetDraft().ToList();
var IndexViewModel = new IndexViewModel();
IndexViewModel.Draft = draftList;
IndexViewModel.Published = _drService.GetPublished();
IndexViewModel.Current = _drService.GetCurrent();
return View(IndexViewModel);
}
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text- danger"></div>
<div class="form-group">
<div class="col-md-3">
<label for="asof">As of:</label>
</div>
<div class="col-md-9">
<input name="AsOf" type="date" title="AsOf" class="form-control" />
</div>
</div>
<div class="clearfix col-md-12"></div>
<div class="clearfix col-md-12"></div>
<div class="form-group">
<div class="col-md-2">
<label for="title">Title:</label>
</div>
<div class="col-md-9 col-md-offset-1">
<input type="text" class="form-control" id="title" />
</div>
<div class="col-md-6">
<input type="submit" value="Add" class="btn btn-primary" />
</div>
</div>
</form>
</div>
</div>
I expect when clicking the Add button to perform the actions in the controller and add the record.

I think you can make a controller action with the same name (create() in this case) but with the [httpPost] prefix on the action so that the form would call the post create action when submitted

GET is for non-destructive actions, i.e. the same GET request should return the same response when repeated. For a create, you need to use a POST. Basically, you need to add an action like:
[HttpPost]
public async Task<IActionResult> Create(IndexViewModel model)
{
if (!ModelState.IsValid)
return View(model);
// map the data posted (`model`) onto your entity class
var entity = new MyEntity
{
Foo = model.Foo,
Bar = model.Bar
};
_context.Add(entity);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}

Related

ASP.NET Core disable validation when button is pressed

On the razor page, I have two buttons of type 'submit'.
The first button is used to add data to a local table, and the second button is used to add all columns in the database.
I want to prevent the first button from checking the validation of fields, only the second button checks the fields.
How to prevent the first button from checking the validation of the fields?
(ASP.NET Core)
Add formnovalidate to the first button input. And formnovalidate attribute could skip the validation in client side,but could not skip validation in server side. So you could clear model state to skip validation in server side.
Below is a mvc demo, you can refer to it.
Custom.cs:
public class Custom
{
public string name{ get; set; }
public int Id { get; set; }
}
In HomeController.cs:
public IActionResult Submit()
{
return View();
}
[HttpPost]
public IActionResult Submit(Custom custom)
{
if (!ModelState.IsValid)
{
ModelState.Clear();//clear model state to skip validation in server side
return View("Submit");
}
return View("Submit");
}
View:
#model nnnn.Models.Custom
#{
ViewData["Title"] = "Submit";
}
<h1>Submit</h1>
<h4>Custom</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Submit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="name" class="control-label"></label>
<input asp-for="name" class="form-control" />
<span asp-validation-for="name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Id" class="control-label"></label>
<input asp-for="Id" class="form-control" />
<span asp-validation-for="Id" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" formnovalidate class="btn btn-primary" />
<input type="submit" value="Save" class="btn btn-secondary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Result:
I am sure there are many ways to handle this...
You can direct to different controllers with different buttons and one of them would be like in this link and there are differents ways in it that you can use.. Check this out. I hope this helps.
Disable Validation Link
You can create those two buttons like below.
The post request will be sent when the type="submit" button is triggered. In the other case, the get request can be executed and you can write your code inside the get related action method.
<button type="submit" class="btn btn-success" asp-action="Create" asp-controller="Category">Submit</button>
<a class="btn btn-success" asp-controller="Category" asp-action="Index" >Back</a>

ASP.Netcore How to call model/ViewModel in another Controller like HomeIndexView

I have a model called, "GetInTouch" (contact us form) with its controller and view all works fine.
My problem is that, when the app loads it has a 'Home/landing page' and I need to display this 'GetInTouch' view on the landing page which has a 'HomeController' rather than having it as a separate page. So upon 'submit' from the Landing page it should go to the 'GetInTouchController' and process the data (save and send emails) but it comes back with null.
Here is the GetInTouch MODEL
public class GetInTouch
{
[Key]
public int Id { get; set; }
[Display(Name = "Your Name")]
public string YourName { get; set; }
[Display(Name = "Email Address")]
public string EmailAddress { get; set; }
[Display(Name = "Your Message")]
public string YourMessage { get; set; }
}
And here is the GetInTouchController
public async Task<IActionResult> Create(GetInTouch intouch)
{
if (ModelState.IsValid)
{
_GetInTouch.GetInTouch.Add(intouch);
await _GetInTouch.SaveChangesAsync();
//await _emailService.SendTestEmail(options);
return RedirectToAction(nameof(Create), new { isSuccess = true, sendId = intouch.Id });
}
return View(intouch.Id);
}
And the GetInTouch View
#model project.Models.GetInTouch
<div class="container">
<h3 class="display-4">Get In Touch</h3>
<hr />
#if (ViewBag.IsSuccess == true)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<strong>Thank You for your enquiry!</strong> We'll be in touch with you asap <br />
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
}
<div class="row mt-5">
<div class="col-sm-12 col-md-12">
<h2 class="section-heading text-primary text-center no-after mb-4">
Contact Us
</h2>
<form method="post" class="form-contact" autocomplete="nope" id="contactForm" data-toggle="validator" asp-action="Create" asp-controller="GetInTouch">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row">
<div class="col-sm-6 col-md-6">
<div class="form-group">
<input type="text" asp-for="YourName" autocomplete="nope" class="form-control" id="p_name" placeholder="Your Name" required="">
<span asp-validation-for="YourName" class="text-danger"></span>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<input type="email" asp-for="EmailAddress" autocomplete="new_email" class="form-control" id="p_email" placeholder="Your Email" required="">
<span asp-validation-for="EmailAddress" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<textarea id="p_message" asp-for="YourMessage" class="form-control" rows="6" autocomplete="nope" placeholder="Your Message"></textarea>
<span asp-validation-for="YourMessage" class="text-danger"></span>
</div>
<div class="form-group">
<div class="text-center">
<div id="success"></div>
<input type="submit" value="SEND" class="btn btn-primary" />
</div>
</div>
</form>
</div>
</div>
</div
The above code works fine, now what I want to do is to display this 'form/view' as a section (just before the footer) in another view say Home Index View but when the user clicks 'send' it should of course go to the GetInTouchController create action and process.
So I copy the view from the GetInTouch Create View to the Home Index view just before the footer but the home page/view has a view model name, 'indexViewModel' which list data from another models but I know you can only pass one model to a view (so no way to say #model project.Model.GetInTouch, instead I have #model IndexViewModel in the HomePage) so all I want is once the user completes the form and hit 'Send' from the Home view it goes to the 'GetInTouchController' Create action and save all the details entered to the GetInTouch Model and send email (I got the email function) just need to save the data as it all comes back as null. Here is the IndexViewModel
public class IndexViewModel
{
//public IEnumerable<CategoryVM> Category { get; set; }
public GetInTouch GetInTouch { get; set; }
}
I don't know how to proceed, I tried copying the exact create method to the HomeController and point form to HomeController Create action but that didn't work either - any idea how to process a form in another view? Sorry about it being long but needed to explain what I want to achieve hope it's clear and thank you for your time.
As far as I know, if you want to post the data from another view, you could just set the form tag in the new view.
It will generate the form data according to the input tag and post to the GetInTouch view.
More details, you could refer to below test demo.
Notice: You should set the name in the input tag to match the GetInTouch controller:
Put below form into the index view:
IndexViewModel:
public class IndexViewModel
{
public GetInTouch GetInTouch { get; set; }
}
View:
<form method="post" class="form-contact" autocomplete="nope" id="contactForm" data-toggle="validator" asp-action="Create" asp-controller="GetInTouch">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row">
<div class="col-sm-6 col-md-6">
<div class="form-group">
<input type="text" asp-for="GetInTouch.YourName" name="YourName" autocomplete="nope" class="form-control" id="p_name" placeholder="Your Name" required="">
<span asp-validation-for="GetInTouch.YourName" class="text-danger"></span>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<input type="email" asp-for="GetInTouch.EmailAddress" name="EmailAddress" autocomplete="new_email" class="form-control" id="p_email" placeholder="Your Email" required="">
<span asp-validation-for="GetInTouch.EmailAddress" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<textarea id="p_message" asp-for="GetInTouch.YourMessage" name="YourMessage" class="form-control" rows="6" autocomplete="nope" placeholder="Your Message"></textarea>
<span asp-validation-for="GetInTouch.YourMessage" class="text-danger"></span>
</div>
<div class="form-group">
<div class="text-center">
<div id="success"></div>
<input type="submit" value="SEND" class="btn btn-primary" />
</div>
</div>
</form>
Result:

How to Implement HttpDelete and HttpPut in Asp.Net MVC Core 3.1 c#. Resolve HTTP ERROR 405?

Technology Info:
Framework = Asp.Net Core 3.1
IDE = VisualStudio 2019
Problem:
I have a controller with Update and Delete Action Methods. I have UpdateView and DeleteView from where I need to redirect to the respective controller. I have implemented a button that can submit the form. Still I'm facing HTTP ERROR 405 Issues with PUT & DELETE. Can somebody help me to resolve this issue. Thanks in advance
Controller:
[HttpPut]
[ActionName("ModifyEmployee")]
public IActionResult ModifyEmployee(int employeeId, Malips.Data.Employee employee)
{
if (ModelState.IsValid)
{
Malips.Data.Employee employeeDetail = _hrService.EmployeeSystem.UpdateEmployee(employee);
return View("GetEmployee", employeeDetail);
}
return View();
}
[HttpDelete]
public IActionResult DeleteEmployee(int employeeId)
{
_hrService.EmployeeSystem.DeleteEmployee(employeeId);
return View("Index");
}
UpdateView:
#model Employee
#{
ViewBag.Title = "Modify Employee";
}
<div>
<form asp-controller="Hr" asp-action="ModifyEmployee" asp-route-employeeId="#Model.EmpId">
<div class="form-group">
<div asp-validation-summary="All" class="text-danger">
</div>
</div>
#Html.HiddenFor(e => e.EmpId)
<div class="form-group row">
<label asp-for="FirstName" class="col-sm-2">First Name</label>
<input asp-for="FirstName" class="col-sm-10" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-info">Update</button>
</form>
</div>
DeleteView:
<form asp-controller="Hr" asp-action="DeleteEmployee" asp-route-employeeId="#Model.EmpId">
<div class="row card" style="width: 18rem;">
<div class="card-body">
<label hidden="hidden">#Model.EmpId</label>
<h5 class="card-title">#Model.FirstName #Model.LastName</h5>
<p class="card-text">#Model.Salary </p>
<button type="submit" class="btn btn-danger">Delete</button>
</div>
</div>
</form>
The current HTML5 does not support PUT or DELETE in forms. You can use it with ajax or httpclient only. Or you can try #Html.BeginForm razor page template if it is possible. #Html.BeginForm has post metod choice.
For now remove [ActionName("ModifyEmployee")], [httpput] and [httpdelete] from your action attributes.
And change
public IActionResult ModifyEmployee(int employeeId, Malips.Data.Employee employee)
to:
public IActionResult ModifyEmployee(Employee employee)
since you don't use and don't need emploeeId. And remove asp-route-employeeId="#Model.EmpId" from ModifyEmployee view too.
Like #Sergey said,You can use it with ajax.Below is a working demo.
UpdateView:
<div>
<form id="update" asp-controller="Hr" asp-action="ModifyEmployee">
<div class="form-group">
<div asp-validation-summary="All" class="text-danger">
</div>
</div>
#Html.HiddenFor(e => e.EmpId)
<div class="form-group row">
<label asp-for="FirstName" class="col-sm-2">First Name</label>
<input asp-for="FirstName" class="col-sm-10" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group row">
<label asp-for="LastName" class="col-sm-2">First Name</label>
<input asp-for="LastName" class="col-sm-10" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<button type="submit" id="submit" class="btn btn-info">Update</button>
</form>
</div>
#section scripts
{
<script>
$("#submit").click(function (e) {
e.preventDefault();
var data = $('#update').serialize();
$.ajax({
type: "PUT",
url: "/hr/Modifyemployee",
data: data,
success: function (response) {
window.location.href = response.redirectToUrl;
}
});
})
</script>
}
ModifyEmployee Action
[HttpPut]
[ActionName("ModifyEmployee")]
//remember add this.
[ValidateAntiForgeryToken]
public async Task<IActionResult> ModifyEmployee(Employee employee)
{
//....
return new JsonResult(new { redirectToUrl = Url.Action("Index", "Hr") });
}
DeleteView:
<div>
<form id="delete" asp-controller="Hr" asp-action="DeleteEmployee" asp-route-employeeId="#Model.EmpId">
<div class="row card" style="width: 18rem;">
<div class="card-body">
<label hidden="hidden">#Model.EmpId</label>
<h5 class="card-title">#Model.FirstName #Model.LastName</h5>
<button type="submit" id="submit" class="btn btn-danger">Delete</button>
</div>
</div>
</form>
</div>
#section scripts
{
$("#submit").click(function (e) {
e.preventDefault();
$.ajax({
type: "delete",
url: "/hr/DeleteEmployee?id=" + #Model.EmpId,
success: function (response) {
window.location.href = response.redirectToUrl;
}
});
})
</script>
}
DeleteEmployee Action
[HttpDelete]
public async Task<IActionResult> DeleteEmployee(int id)
{
//......
return new JsonResult(new { redirectToUrl = Url.Action("Index", "hr") });
}
Test Result:

Is parameter for controller method with name Message forbidden?

I have an application with feedback form which is processing by FeedbackController.
#model ClientMessageDto
...
...
<form class="contact-form form-horizontal" asp-action="Enrol" asp-controller="Feedback" asp-anti-forgery="true">
<div class="col-sm-6 col-sm-offset-3">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control">
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control">
</div>
<div class="form-group">
<label asp-for="Phone"></label>
<input asp-for="Phone" class="form-control">
</div>
<div class="form-group">
<label asp-for="Message"></label>
<textarea asp-for="Message" class="form-control" rows="2"></textarea>
</div>
<div class="form-group">
<button type="submit" name="submit" class="btn btn-primary btn-lg pull-right">
Send request
</button>
</div>
</div>
</form>
And I have a controller method which should receive DTO from my form
Route("enrol")]
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Enrol([FromForm]ClientMessageDto message)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index", "Home", new {area = ""});
}
return View(clientMessage);
}
This method always receives object with empty fields even if I use [FromForm] attribute.
But it works if I change parameter name to whatever except message (something like messageDto or clientMessage works properly).
Is it ok and 'message' word is forbidden or I should report it as a bug?
This isn't a bug with MVC, it simply due to the conflict between the message parameter that your action method is taking and the Message form field. That's confusing the default model binder. You could probably write your own custom model binder, but as you've already figured out, it's a lot easier to rename either the action method parameter or the form field.

ActionResult of partial makes Layout disappear

I'm trying to validate a changed password partial view in my Index page of my Manage section. Like so,
#inject UserManager<ApplicationUser> UserManager
#model IndexViewModel
#{
ViewData["Title"] = "Manage your account";
}
<div class="row settings">
<div class="col-sm-6">
<h4>Basic information</h4>
#await Html.PartialAsync("BasicInformation", new BasicInformationViewModel(UserManager))
</div>
<div class="col-sm-6">
<h4>Change Password</h4>
#await Html.PartialAsync("ChangePassword", new ChangePasswordViewModel())
</div>
Controller code
[HttpPost("ChangePassword")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return PartialView(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User changed their password successfully.");
ViewData["StatusMessage"] = TranslationService.TranslateManageMessage(_context, ManageMessageId.ChangePasswordSuccess, "Your password has been changed.");
return PartialView();
}
AddErrors(result);
return PartialView(model);
}
ViewData["StatusMessage"] = TranslationService.TranslateManageMessage(_context, ManageMessageId.Error, "An error has occured.");
return PartialView();
}
So when the user changes his password, the thing I actually want to happen is that the Action get completed and the statusmessage of the partialview ges updated with what happend (succes, error, etc..). So how i have implemented it now it's work as I want it to but the only thing that is bad about this setup is that this happens.
My layout just disappears and I have no idea why. I thought returning a partial view would just reload the page but seems like it's not that.
Could anyone point me in the right direction ? Or give a suggestion how to do this better ?
EDIT:
ChangePassword Partial
#model ChangePasswordViewModel
<form asp-controller="Manage" asp-action="ChangePassword" method="post" class="form-horizontal">
<p class="text-success">#ViewData["StatusMessage"]</p>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="OldPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Change password</button>
</div>
</div>
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
The thing is you are initially loading the partialview inside index view on a get request, when you post to changepassword action it is not an ajax partial post it is a full post and it is not the Index action so it is not using the index view and you are returning a partialview that is not inside any other view so there is no outer view and no layout
UPDATE: since you requested in comments to show how to make it use ajax:
In your index view wrap a div with an id around the partial like this:
<div id="changepassword">
#await Html.PartialAsync("ChangePassword", new ChangePasswordViewModel())
</div>
and you need to include jquery unobtrusive ajax
in the Scripts section which should be in the index view not in the partial
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script src="~/path-to-jqueryajaxunobtrusive"></script>
}
In your ChangePassword view change like this:
#model ChangePasswordViewModel
<form asp-controller="Manage" asp-action="ChangePassword" method="post" class="form-horizontal"
data-ajax="true"
data-ajax-method="POST"
data-ajax-mode="replace"
data-ajax-update="#changepassword">
<p class="text-success">#ViewData["StatusMessage"]</p>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="OldPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Change password</button>
</div>
</div>
by including the jquery unobtrusive ajax script and adding the data-* attributes as shown that should wire up the form to do an ajax post and the result should update the contents of the div with the id indicated