I've created a partial view that allows a user to enter their email and get added to a subscriber list. Originally I had this working as standard post and everything worked fine.
However now I'm trying to make this an ajax call and for some reason, even though the form is posting to the correct controller, the model is always coming through as null and I can't figure out why.
Here are my views:
Parent view:
#Html.Partial("Shared/Newsletter/NewsletterForm", new NewsletterSubscriber())
Form partial:
#using (Ajax.BeginForm("NewsletterSignUp", "Newsletter", new AjaxOptions { HttpMethod = "POST" }))
{
<input type="text" name="EmailAddress" id="newsletter-email" class="basic-input" placeholder="Email Address">
<button id="submit-newsletter" class="basic-submit">Sign Up</button>
<p id="status"></p>
}
Controller:
[HttpPost]
public ActionResult NewsletterSignUp(NewsletterSubscriber model)
{
if (!ModelState.IsValid)
{
return Content("Please enter a valid e-mail address");
}
// Do logic...
}
The model:
public class NewsletterSubscriber
{
[Required]
[EmailAddress]
[StringLength(300)]
public string EmailAddress { get; set; }
}
Scripts:
<script src="/scripts/jquery.validate.min.js"></script>
<script src="/scripts/jquery.validate.unobtrusive.min.js"></script>
<script src="/scripts/jquery.unobtrusive-ajax.min.js"></script>
<script>
$(function() {
$('#submit-newsletter').on('click', function(e) {
e.preventDefault();
$.post('/umbraco/surface/Newsletter/NewsletterSignUp', function (data) {
console.log(data);
$('#status').show().text(data);
});
});
});
</script>
Like I said, when I step through the code the post is hitting the correct controller, however the model is always coming through as null.
Where am I going wrong?
I just realised I had the ajax form handling the submission for me and I was trying to manually post the form in another script. Once I removed the additional script it worked.
(It's the end of a long day and I missed this obvious mistake!)
Related
I'm using 2019 ASP.NET MVC with jquery 3.4.1. I'm trying to get the error message when I make a jquery ajax post. To simulate an error I mispelled the method in the controller being called by ajax. The best I could do was to display an alert box with some HTML code. It looks like the title tag contains the correct error message: "The resource cannot be found." But it's nested inside of a bunch of html code.
Is there a way to get that error message out of that html code and into a string?
Edit: I figured it out. I just added a span tag and set the html property of it.
Controller
using System.Web.Mvc;
namespace Test
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//view
return View();
}
[HttpPost]
public ActionResult DoSomething1111111111()
{
return Json("hello");
}
}
}
View
#{
Layout = null;
}
<input type="button" id="test-button" value="test" />
<span id="message"></span>
<script src="~/Scripts/jquery-3.4.1.js"></script>
<script>
//test-button click event
$(document).on("click", "#test-button", function () {
//post
$.ajax({
url: "/Home/DoSomething",
type: "POST",
success: function (res) {
alert("success");
},
error: function (res) {
$("#message").html(res.responseText);
}
});
});
</script>
This is my first MVC project and I've been trying to figure out how to refresh a Partial View after calling a controller method from AJAX.
My classes look like this and I want to add as many courses as I want for a semester in one view.
public class CourseViewModel
{
public int SemesterID {get; set;}
public List<Course> Courses {get; set;}
}
public class Course
{
public string CourseID {get; set;}
public string CourseTitle {get; set;}
}
An Example page looks like this:
In my view, I set up a typeahead for the Course textbox. When a user select a course from a list of typeahead suggestion, I call the SaveCourse method in the controller and it successfully saves. The problem is that I cannot refresh the Partial View after the save occurs.
My View (Index.cshtml):
#using Project.ViewModels;
#model Project.ViiewModels.CourseViewModel
<div id="divCourseTypeahead">
<input class="typeahead form-control" type="text" />
</div>
<div id="listCourses">
#{Html.RenderPartial("_CourseList");}
</div>
<script type="text/javascript">
$(document).ready(function () {
$('#divCourseTypeahead .typeahead').typeahead({
//setup typeahead
}).on('typeahead:select', function(obj, datum){
var semesterId = #Model.SemesterID
$.ajax({
type: "GET",
url: "/Course/SaveCourse/",
data: { semesterId: semesterId, courseId: datum.id },
success: function (result) {
alert(result);
}
});
});
</script>
What I've tried (1):
I tried return a PartialVeew from SaveCourse.
public PartialViewResult SaveCourse(int semesterId, string courseId)
{
//Insert course
CourseViewModel data = new CourseViewModel(semesterId);
return PartialView("_CourseList", data);
}
When I do this, the PartialView does not get refreshed and the alert(result); in ajax success function does not get called.
What I've tried (2):
public ActionResult SaveCourse(int semesterId, string courseId)
{
//Insert course
return RedirectToAction("Index", "Course", new {id=semesterId});
//in Index, I return `CourseViewModel`
}
When I do this, the alert(result); in AJAX success function gets called so I added $('#listCourses').html(result); in the success function then the PartialView does refresh but I get two textboxes like the image below.
I've tried many other options but I am so confused. Could someone please help me how I can achieve what I want to do?
You have a couple of problems in your document.ready function.
1. You're passing
courseId: datum.id
However, datum object can't be seen anywhere in the javascript function.
Maybe you're defining it somewhere else.
instead of alert line I suggest write
$('#listCourses').html(result);
Also, Remove #{Html.RenderPartial("_CourseList");}, because since _CourseList partial view requires a list model, and you're not providing it during render. So the page will not load.
I could achieve below result with these changes.
I have a controller which returns a Partial View with a Model.
The view that contains the partial View has a button, When clicking on the button, a function of the controller is called and it returns the same partial View with the model updated. The new model is loaded without any problem, but the page doesn't reload, the view is the same view as before the onclik.
The code of partial View
<div class="well">
#if (publication.Coments != null) {
foreach (var comments in publication.Coments) {
<div class="row">
<div class="col-md-12">
#comments.Nick
<span class="pull-right">#comments.DateComment.ToShortDateString()</span>
<p>#comments.Message</p>
</div>
</div>
}
}
</div>
The method of controller return the partial view with the next code :
ViewData["publication"] = publication;
return PartialView("details_comment");
I call the partial view in the view :
#Html.Partial("../Home/ListPublication")
I debugged the page and the model is reloaded ok, but the partial view doesn't reload.
I mentioned on the comments I was having the same issue but later today I figured at MSDN that this is an expected behaviour if you are returning the same model and view types after a POST. For my scenario I had to use ModelState.Clear() before changing any values on the returning view model. To explain a little better, I'll try to describe my case as close as I can remember to try to contextualize:
view models
// ~/Models/SomeFeatureModels.cs
public class SomeViewModel {
public int Id {get;set;}
public string SomeField{get;set;}
public string SomeOtherField{get;set;}
public DateTime CreatedOn{get;set;}
}
public class SomeOtherViewModel {
public int Id {get;set;}
public string SomeField{get;set;}
public string SomeOtherField{get;set;}
public DateTime CreatedOn{get;set;}
}
public class IndexViewModel {
public string FeatureTitle{get;set;}
}
templates
<!-- ~/Views/Some/SomeInfo.cshtml -->
#model.App.Models.SomeInfoViewModel
#using(Html.BeginForm("AddSomeInfo", "Some", FormMethod.Post, new { #id="frmAddSomeInfo" }) {
<div id="some-info">
#Html.DisplayFor(m=>m.SomeField)
#Html.EditorFor(m=>m.SomeField)
#Html.ValidatorFor...
<input type="submit">Go</input>
</div>
}
<!-- ~/Views/Some/SomeInfo.cshtml -->
#model.App.Models.SomeOtherInfoViewModel
#using(Html.BeginForm("AddSomeOtherInfo", "Some", FormMethod.Post, new { #id="frmAddSomeOtherInfo" }) {
<div id="some-other-info">
#Html.DisplayFor(m=>m.SomeField)
#Html.EditorFor(m=>m.SomeField)
#Html.ValidatorFor...
<input type="submit">Go</input>
</div>
}
<!-- ~/Views/Some/Index.cshtml -->
#model App.Models.IndexViewModel
#{
layout: "someLayout.cshtml"
}
<h2>Model.FeatureTitle</h2>
#{ RenderAction("SomeInfo") }
#{ RenderAction("SomeOtherInfo") }
#section scripts {
//bundle must include:
// jquery, jquery.unobtrusive.ajax, jquery.validate, jquery.validate.unobtrusive
<script>
$(function() {
$('#frmAddSomeInfo').submit(function(e) {
e.preventDefault();
var form = $(this);
if (form.valid()) {
$.ajax({
url: form.action,
type: form.method,
data: form.serialize()
}).done(function(payload) {
$('#some-info').html(payload);
}).fail(function(jqXHR, error, errorThrown) {
// handle
});
}
});
$('#frmAddSomeOtherInfo').submit(function(e) {
e.preventDefault();
var form = $(this);
if (form.valid()) {
$.ajax({
url: form.action,
type: form.method,
data: form.serialize()
}).done(function(payload) {
$('#some-other-info').html(payload);
}).fail(function(jqXHR, error, errorThrown) {
// handle
});
}
});
});
</script>
}
controller
// ~/Controllers/SomeController.cs
public class SomeController: Controller {
// This would be the landing view of a given controller
public ActionResult Index() {
// for this view model I have basically typed the things that
// are a concern of Index, like page title and other things
// but nothing related to the view models that I will be
// updating or inserting
var viewModel = somePrivateMethodToBuildMyViewModel();
return View(viewModel);
}
public PartialViewResult SomeInfo() {
// this is technically a fresh instance with normalized
// or localized default data that I will be sending
// when the index requests this partial view
var someViewModel = somePrivateMethodToBuildSomeViewModel();
return PartialView(someViewModel);
}
[HttpPost]
public PartialViewResult AddSomeInfo(SomeViewModel viewModel) {
// this is where I am checking if my view model is alright
// and then the "issue" will occur
if (!ModelState.IsValid) {
// ... handle
} else {
// I was doing "normal stuff" like
// translating view model to an entity (let's call it model)
// committing changes with some ORM and get and id and timestamp back
// and naturally assign these values to the view model
viewModel.Id = model.id;
viewModel.createdOn = model.created_on;
}
// then return the same view and same model **types** of the request
return PartialView("SomeInfo", viewModel);
}
}
This is the part that I had to use ModelState.Clear(). I've changed my POST action to this:
// ~/Controllers/SomeController.cs
public class SomeController: Controller {
// ...
[HttpPost]
public PartialViewResult AddSomeInfo(SomeViewModel viewModel) {
if (!ModelState.IsValid) {
// ... handle
} else {
// Included this line for valid model state handling
ModelState.Clear();
// then persist, etc
// then change view model
viewModel.Id = model.id;
viewModel.createdOn = model.created_on;
}
// then returning the view model to the same partial should now work
return PartialView("SomeInfo", viewModel);
}
}
Sorry this got a little too much, but I just wanted to show how I got it working on my scenario.
Try like below.
return PartialView("details_comment", publication);
You need to pass the model's object with the partial view to see the value, as your model will get bound with the helpers to create a view
return PartialView("details_comment",publication);
Updated
Instead of relative URL:
Try: #Html.Partial(#"~/Views/Home/ListPublication.cshtml") or use
#{Html.RenderPartial("ListPublication");}
The other thing I noticed is that you are using old ViewData. I am not saying you should not use it at all but a better approach is to use a strongly typed ViewModel. A View Model is a class that will have necessary properties and logic for your view to render. So you could have a class:
public class PublicationVM{public Publication publication {get; set;} }
and can have other necessary properties if needed by the view. SO has lot of information about using View Model. Please check What is ViewModel in MVC?
I have the following problem (I spend many hours looking for a solution).
The ‘Create’ button has a click event which calls the ‘Test’ action on the ‘Home’ controller.
All works fine.
When I hit the ‘Save’ button, to submit the form, that works fine to.
But after I have submitted the form, my ‘Create’ button stops working. The ‘Create’ button does have the click event, but the ‘Test’ action is unreachable?
index.cshtml
<script type="text/javascript">
$(document).ready(function () {
$("#create").click(function () {
$.ajax({
type: "POST",
traditional: true,
url: 'Home/Test',
dataType: "html",
success: function (data) {
alert('Succes!')
},
error: function () {
alert('A problem ocurred!!');
}
});
});
});
</script>
<input id="create" type="button" value="Create" />
#using (Html.BeginForm("SaveForm", "Home"))
{
<input type="submit" value="Save" />
}
HomeController.cs
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult Test()
{
return Content("Test result");
}
public ActionResult SaveForm()
{
return View("Index");
}
public ActionResult About()
{
return View();
}
}
All of your actions are GET only. Either add [HttpPost] (POST only) or [AcceptVerbs(HttpVerbs.Post, HttpVerbs.Get)] (GET or POST) attributes to your POST actions.
[HttpPost]
public ActionResult Test()
{
return Content("Test result");
}
[HttpPost]
public ActionResult SaveForm()
{
return View("Index");
}
Theres 2 problems:
1) you dont have [HttpPost] above your methods
2) You are not sending any data to your controller
add an id to your form by using an anonymous class:
#using (Html.BeginForm("SaveForm", "Home", new {id = "testform"}))
then rewrite the ajax request:
<script type="text/javascript">
$(document).ready(function () {
$("#create").click(function () {
$.ajax({
type: "POST",
data: $("#testform").serialize();
url: 'Home/Test',
dataType: "html",
success: function (data) {
alert('Succes!')
},
error: function () {
alert('A problem ocurred!!');
}
});
});
});
</script>
Let me know if it works :)
To create an entity, you have to submit your data to server, either by post back or ajax as in your case. Now there are some contradictions in your code :)
1. Why are you calling one action as form action and another through
ajax? Because since your button will post back, your ajax call won't
fire the success and error handlers. To solve this either use <button> or
$("#create").click(function (e) { e.preventDefault();//Some code});
2. If you want to use ajax, write it like this. Also write [HttpPost] on
your action. This indicates that this action can be called by post requests.
This is a required step, whether or not you are using ajax.
I hope this solved your problem.
Eventually I used Ajax.BeginForm instead of Html.BeginForm, added the [HttpPost] attributes to my actions and I used the RedirectToAction("Index") instead of PartialView. That solved my problem. Thanks again for your tips!
Feel like I'm missing something simple here - When I submit this form it takes me to a white page with my encoded JSON model, rather than staying on the page I was on.
Form in a partial view, loaded on every page:
#using (Ajax.BeginForm("Inquiry", "Home", new AjaxOptions { HttpMethod = "POST", OnSuccess = "success" }))
And my actions:
public ActionResult Inquiry()
{
return PartialView("_Inquiry",new Inquiry());
}
[HttpPost]
public JsonResult Inquiry(Inquiry model)
{
if (ModelState.IsValid)
{
db.Inquiries.Add(model);
db.SaveChanges();
}
return Json(model);
}
Make sure you have referenced the following script in your page:
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
It is what makes the Ajax.* helpers work.