ActionResult of partial makes Layout disappear - asp.net-core

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

Related

How to display validation summary below the input controls?

I have the following form:
<form asp-controller="Manage" asp-action="RemoveDetails" method="post" class="form-horizontal">
#if (Model.RequirePassword)
{
<div id="password-container">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" id="password" autocomplete="current-password" aria-required="true" />
<small>
<span asp-validation-for="Password" class="text-danger"></span>
</small>
</div>
</div>
</div>
}
<div class="col-6">
<hr class="mt-2 mb-3">
<button type="submit" class="btn btn-style-1 btn-danger">Confirm</button>
</div>
<div class="row">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</div>
</form>
If I leave the password empty, a validation message (the model uses DataAnnonations) is displayed right below the control. This is fine.
If I enter the wrong password, the Post action validates it and adds an error to the ModelState. This error is displayed below the form.
Is it possible to display such model errors below the relevant controls?
Try this code in Controller: ModelState.AddModelError("Password", "validation summary error."); The error message won't display in <div asp-validation-summary="ModelOnly"></div>, but it can display error message in #Html.ValidationSummary(false, "", new { #class = "text-danger" }).
<form asp-controller="Home" asp-action="RemoveDetails" method="post" class="form-horizontal">
<div id="password-container">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" id="password" autocomplete="current-password" aria-required="true" />
<small>
#*<span asp-validation-for="Password" class="text-danger"></span>*#
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Description"></label>
<input asp-for="Description" class="form-control" id="description" autocomplete="current-password" aria-required="true" />
<small>
<span asp-validation-for="Description" class="text-danger"></span>
</small>
</div>
</div>
</div>
<div class="col-6">
<hr class="mt-2 mb-3">
<button type="submit" class="btn btn-style-1 btn-danger">Confirm</button>
</div>
<div class="row">
<div asp-validation-summary="ModelOnly"></div>
</div>
</form>
[HttpPost]
public IActionResult RemoveDetails(RemoveDetail rdl)
{
if(ModelState.IsValid)
{
var pwd = rdl.Password;
//do something here and find the password is wrong
//code below won't display error message in <div asp-validation-summary="ModelOnly" class="text-danger">
//but can display error message in #Html.ValidationSummary(false, "", new { #class = "text-danger" })
ModelState.AddModelError("Password", "validation summary error.");
return View(rdl);
}
return View(rdl);
}

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 hide some textfields in razor view in asp.net core

I'm implementing a project by asp.net core. I have a selectlist item which its options are loaded from a model and it has 2 items. My problem is, I want in the razor view, according to the selected option in selectlist, some TextFields be shown to the user. For example if the user choose the first option, three of the fields be shown to the user and if the user selects the second option, other fields be shown to the user meanwhile the other three field be hidden. For doing this. I have tried some code like below in my Create view:
#model CSDDashboard.Models.ApplicantViewModel
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.js"> </script>
<script>
$(document).ready(function () {
$('#applicantvm.ApplicantType').change(function () {
var value = $(this).val();
if (value == '1') {
$(".legal").hide();
} else {
$(".person").show();
}
});
});
</script>
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Applicant</h4>
<hr />
<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">
<label asp-for="applicantvm.ApplicantType" class="control-label"></label>
<select asp-for="applicantvm.ApplicantType" class="form-control" asp-items="ViewBag.ApplicantType"></select>
</div>
<div class="form-group">
<label asp-for="applicantvm.Address" class="control-label"></label>
<input asp-for="applicantvm.Address" class="form-control" />
<span asp-validation-for="applicantvm.Address" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="applicantvm.Description" class="control-label"></label>
<input asp-for="applicantvm.Description" class="form-control" />
<span asp-validation-for="applicantvm.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="applicantvm.Name" class="control-label"></label>
<input asp-for="applicantvm.Name" class="form-control" />
<span asp-validation-for="applicantvm.Name" class="text-danger"></span>
</div>
<div class="form-group legal">
<label asp-for="legalapplicantvm.EconomicCode" class="control-label"></label>
<input asp-for="legalapplicantvm.EconomicCode" class="form-control" />
<span asp-validation-for="legalapplicantvm.EconomicCode" class="text-danger"></span>
</div>
<div class="form-group legal">
<label asp-for="legalapplicantvm.NationalCode" class="control-label"></label>
<input asp-for="legalapplicantvm.NationalCode" class="form-control" />
<span asp-validation-for="legalapplicantvm.NationalCode" class="text-danger"></span>
</div>
<div class="form-group legal">
<label asp-for="legalapplicantvm.RegisterNo" class="control-label"></label>
<input asp-for="legalapplicantvm.RegisterNo" class="form-control" />
<span asp-validation-for="legalapplicantvm.RegisterNo" class="text-danger"></span>
</div>
<div class="form-group person">
<label asp-for="personapplicantvm.BirthCertificateNo" class="control-label"></label>
<input asp-for="personapplicantvm.BirthCertificateNo" class="form-control" />
<span asp-validation-for="personapplicantvm.BirthCertificateNo" class="text-danger"></span>
</div>
<div class="form-group person">
<label asp-for="personapplicantvm.IssuePlace" class="control-label"></label>
<input asp-for="personapplicantvm.IssuePlace" class="form-control"/>
<span asp-validation-for="personapplicantvm.IssuePlace" class="text-danger"></span>
</div>
<div class="form-group person">
<label asp-for="personapplicantvm.NationalCode" class="control-label"></label>
<input asp-for="personapplicantvm.NationalCode" class="form-control" />
<span asp-validation-for="personapplicantvm.NationalCode" class="text-danger"></span>
</div>
<div class="form-group person">
<label asp-for="personapplicantvm.Username" class="control-label"></label>
<input asp-for="personapplicantvm.Username" class="form-control" />
<span asp-validation-for="personapplicantvm.Username" class="text-danger"></span>
</div>
}
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back To List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
After running the project, the view is the same as before and all the fields will be visible. I appreciate if anyone suggest me a solution.
asp-for="applicantvm.ApplicantType" render the id and name in the html like id="applicantvm_ApplicantType" name="applicantvm.ApplicantType" ,you could use the developer tools to check the Source code.
So you should change your javascript as shown:
<script>
$(document).ready(function () {
$(".person").hide();
$(".legal").hide();
$('#applicantvm_ApplicantType').change(function () {
var value = $(this).val();
if (value == '1') {
$(".legal").show();
if ($(".person").show()) {
$(".person").hide();
}
} else {
$(".person").show();
if ($(".legal").show()) {
$(".legal").hide();
}
}
});
});
</script>
Result:
In Simple terms,
Write ONCHANGE Event in the drop down and invoke a JavaScript function.
From JavaScript, based on the drop down values, show and hide the controls.

Submit button action in MVC controller

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");
}

IFormFile is null, Razor

I'm trying to bind an image uploading but facing the problem with sending this image to the controller.
I'm using Razor and .net core 2.2.
My Controller
[HttpPost(nameof(MealController))]
[Authorize(Roles = UserRoles.Moderator)]
public IActionResult Update(MealModel meal, IFormFile pic)
{
if (ModelState.IsValid)
{
try
{
var changedMeal = _mapper.Map<MealModel, MealDTO>(meal);
_mealService.Update(changedMeal, pic);
}
catch (Exception e)
{
return NotFound();
}
return RedirectToAction("Index");
}
return View(meal);
}
My form
<form class="form" method="post" asp-controller="Meal" asp-action="Update">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Продукт</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="product">
<div class="product-img">
<div class="selector">
<img src="#Model.ImagePath" class="img-thumbnail" id="selectedImg" alt="Product Image"/>
<div class="form-group">
<input type="file" class="custom-file-input" id="pic-input" name="pic"
onchange="readURL(this, 'selectedImg')">
#* <label class="custom-file-label" for="pic-input"></label> *#
</div>
</div>
</div>
<div class="product-info">
<div class="form-group name">
<label for="name">Назва продукту</label>
<input class="form-control" type="text" placeholder="Назва" id="name" name="name" asp-for="Name"
value="#Model.Name">
</div>
<div class="form-group">
<label for="name">Ціна</label>
<input class="form-control" placeholder="Price" id="price" type="number" asp-for="Price"
value="#Model.Price">
</div>
<div class="form-group">
<label for="name">Вага</label>
<input class="form-control" id="weight" type="number" asp-for="Weight"
value="#Model.Weight">
</div>
<div class="comments">
<textarea class="form-control" rows="3" cols="5" placeholder="Склад" asp-for="Description" value="#Model.Description"></textarea>
</div>
<input type="hidden" asp-for="MealGroupId" value="#Model.MealGroupId"/>
<input type="hidden" asp-for="Id" value="#Model.Id"/>
#* <input type="hidden" asp-for="ImagePath" value="#Model.ImagePath"/> *#
</div>
</div>
</div>
<div class="modal-footer">
<div class="action-button">
<button type="submit"
class="btn btn-success ok-button">
<span>Додати</span>
</button>
<button type="reset"
class="btn btn-danger remove-button">
<span>Відмінити</span>
</button>
</div>
</div>
</div>
</form>
When I debug, IFormFile is always null. I tried using [FromForm] but it didn't help. I googled and found out that my name in form and parameter name was different but it had effect. Could you, please give me a little hint on how I can solve it. Thank you and have a good day!
You have to specify <form>'s enctype attribute set to multipart/form-data to be able to send files to server
<form class="form" method="post" asp-controller="Meal" asp-action="Update" enctype="multipart/form-data">