ASP.Netcore How to call model/ViewModel in another Controller like HomeIndexView - asp.net-core

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:

Related

How to bind the list which is present in the ViewModel to the input field like Radio Button

public class AssessmentResponseViewModel
{
public int Id { get; set; }
public int AssessmentType { get; set; }
public DateTime DateTime { get; set; }
public string TakenBy { get; set; }
public List<AssessmentQuestionViewModel> AllQuestions { get; set; }
}
public class AssessmentQuestionViewModel
{
public int Id { get; set; }
public string Question { get; set; }
public int Response { get; set; }
}
In the Post method of Assessment, I want All responses.
Controller
[HttpGet]
public IActionResult Assessment()
{
AssessmentResponseViewModel assessmentResponse = new AssessmentResponseViewModel();
assessmentResponse.AssessmentType = 4;
assessmentResponse.AllQuestions = _assessmentService.GetAssessmentQuestionsByType(4);
return View(assessmentResponse);
}
[HttpPost]
public IActionResult Assessment(AssessmentResponseViewModel Response)
{
return View();
}
Actually, this application is just like an assessment where different questions are present.
Here is the Code that how I'm currently binding
#model LectureSoft.ViewModel.AssessmentResponseViewModel
<div class="container-fluid mt-1">
<div class="col-12">
<div class="card">
<div class="card-body" style="color: black;">
<p class=" mt-2">Please Answer these questions based on your usual practice.</p>
<p class=" font-weight-bold">Estimate how often in your teaching and couses(s) that you do the following.</p>
<p class="font-weight-bold mt-2">GUIDE: Never-0%, Rarely-25%, Sometimes-50%, Often-75%, Always-100% of the time</p>
<hr>
<div class="mclass" style="background-color: white;">
<form asp-action="Assessment" asp-controller="Home" method="post" id="QuestionsForm">
#if (Model.AllQuestions != null)
{
int i = 1;
#foreach (var item in Model.AllQuestions)
{
<div class="row rclass">
<div class="col-lg-5 col-md-5 col-sm-12 qclass">
<h4 style="color: #08c7bf;">Question #i</h4>
<label class="text1">#item.Question</label>
</div>
<div class="col-lg-7 col-md-7 col-sm-12 oclass justify-content-center align-self-center">
<div class="row nclass">
<div class="col-lg-2 col-sm-4">
<h7>Never</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="1" class="margin-top" name="#item.Id">
</div>
<div class="col-lg-2 col-sm-4">
<h7>Rarely</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" checked value="2" class="margin-top" name="#item.Id">
</div>
<div class="col-lg-3 col-sm-4">
<h7>Sometime</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="3" class="margin-top" name="#item.Id">
</div>
<div class="col-lg-2 col-sm-4">
<h7>Often</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="4" class="margin-top" name="#item.Id">
</div>
<div class="col-lg-3 col-sm-4">
<h7>Always</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="5" class="margin-top" name="#item.Id">
</div>
</div>
</div>
</div>
<hr>
i++;
}
}
<div class="col-12 text-center">
<button type="submit" class="btn mb-1 btn-primary px-5" style="background-color: #08c7bf !important;
border-color: #08c7bf !important;">
Submit
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
But I'm getting Null in the List of "AllQuestions". Please let me know how I can bind all question with radio buttons.
Not sure what is the value and property you want to bind to the AllQuestions.
From you razor view, Question in AllQuestions cannot be bind to the backend because it is a label. If you want to bind it, you need set a hidden input element for it.
Then I see you use Id and Response as id and name attribute in radio button. You need to know the backend code receives value by getting value attribute in radio button, the value in your code is hard coded(value="1",value="2",value="3"...). And the model binding system in asp..net core binds the property by name attribute.
For example, your code use name="#item.Id" and value="1" in radio button, in this scenario you will get a model with a nested model named Item and in Item model contains Id property and the Id value is 1.
Actually asp-for in your code is useless. Because default asp-for will generate the id and name, but you specify the id and name, they will override the asp-for generated html.
Assume that you pass the hard coded value to backend and bind it to Response. Here is a working demo:
Change the name attribute(name="AllQuestions[#i-1].Response") in each radio button and add hidden input for Question:
#if (Model.AllQuestions != null)
{
int i = 1;
#foreach (var item in Model.AllQuestions)
{
<div class="row rclass">
<div class="col-lg-5 col-md-5 col-sm-12 qclass">
<h4 style="color: #08c7bf;">Question #i</h4>
<label class="text1">#item.Question</label>
<input asp-for="#item.Question" name="AllQuestions[#(i-1)].Question" hidden/>
</div>
<div class="col-lg-7 col-md-7 col-sm-12 oclass justify-content-center align-self-center">
<div class="row nclass">
<div class="col-lg-2 col-sm-4">
<h7>Never</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="1" class="margin-top"
name="AllQuestions[#(i-1)].Response">
</div>
<div class="col-lg-2 col-sm-4">
<h7>Rarely</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" checked value="2" class="margin-top"
name="AllQuestions[#(i-1)].Response">
</div>
<div class="col-lg-3 col-sm-4">
<h7>Sometime</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="3" class="margin-top"
name="AllQuestions[#(i-1)].Response">
</div>
<div class="col-lg-2 col-sm-4">
<h7>Often</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="4" class="margin-top"
name="AllQuestions[#(i-1)].Response">
</div>
<div class="col-lg-3 col-sm-4">
<h7>Always</h7><br>
<input type="radio" id="#item.Id" asp-for="#item.Response" value="5" class="margin-top"
name="AllQuestions[#(i-1)].Response">
</div>
</div>
</div>
</div>
<hr>
i++;
}
}
The result(I choose 1,2,3,4 in frontend):
Any way, you need remember one thing: The model binding bind the property by name in html and bind the value by value in html.

How can I put red border around input without JS?

<span asp-validation-for="Password" class="text-danger"></span>
<div class="form-group">
<label asp-for="Password">პაროლი</label>
#Html.TextBoxFor(x => x.Password, new { type = "password",#class = "form-control" })
<!--
<input asp-for="Password" class="form-control" />
-->
</div>
This is Sample input. I am doing Server-side validation only. How can I make input field border red when user input is not validated?
Model Validation feature adds the CSS Class input-validation-error to all the controls that have failed the validation checks .
Here is a example you could refer to :
1.Model
public class User
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]
public string Password { get; set; }
}
2.View
<style type="text/css">
.input-validation-error {
border-color: yellowgreen;
}
</style>
<div class="row">
<div class="col-md-4">
<form asp-action="Create" class="myForm">
<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="Password">პაროლი</label>
<input asp-for="Password" type="password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
#section Scripts
{
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
Result :
Reference : https://www.yogihosting.com/aspnet-core-model-validation/

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

AspNet Core - [Required] attribute not working on file input

I have an AspNet Core web app with a simple view to allow the user to upload a file.
The file property in the view model has a [Required] attribute. However, clicking the submit button will perform a post even if there is no file selected.
View Model:
public class DocumentUploadViewModel
{
[HiddenInput]
public string Id { get; set; }
[Required]
[FileExtensions(Extensions = "pdf")]
public IFormFile Document { get; set; }
}
View:
#using App.Models.DocumentViewModels
#model DocumentUploadViewModel
#{
ViewData["Title"] = "Upload Document";
}
<h2>Upload Document</h2>
<form method="post" enctype="multipart/form-data" asp-controller="Document" asp-action="Upload" >
<div class="form-horizontal">
<div class="form-group">
<input asp-for="Id" />
<label class="col-md-2 control-label">Document</label>
<div class="col-md-10">
<input class="form-control" type="file" asp-for="Document" />
<span asp-validation-for="Document" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input id="save" name="save" type="submit" value="Upload" class="btn btn-default pull-right" />
</div>
</div>
</div>
</form>
According to Client side validation section:
You must have a view with the proper JavaScript script references in place for client side validation to work as you see here:
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.0.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"></script>

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