Ajax.BeginForm does not insert partial view - ajax

Running ASP.NET MVC 2015 with C#.
Trying to add a partial view as a result of #using (Ajax.BeginForm(...)
All it does is to open a new page with my partial view in it. I found some answers in stackoverflow, but none works for me, unless I missed something.
Scenario:
index page here
Type A, B or C and partial view is expected below the current page
new page unexpected here
But it does open a new page instead
Controler:
public class TestController : Controller
{
public ActionResult Index()
{
return View();
}
[ChildActionOnly]
public ActionResult TestPartial()
{
ChoiceViewModel vm = new ChoiceViewModel();
return View(vm);
}
[HttpPost]
public ActionResult SelectChoice(ChoiceViewModel vm)
{
ModelState.Clear();
if (vm.MyChoice == "A")
return PartialView("APartial");
if (vm.MyChoice == "B")
return PartialView("BPartial");
if (vm.MyChoice == "C")
return PartialView("CPartial");
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
Index.cshtml:
<h2>Index</h2>
<div>
#Html.Partial("TestPartial")
</div>
<br/>
<div id="GoesHere">
</div>
TestPartial.cshtml:
#model TestPartial.ViewModels.ChoiceViewModel
#using (Ajax.BeginForm("SelectChoice", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "GoesHere", InsertionMode = InsertionMode.Replace }))
{
<div class="form-group">
<p>Give your choice please: A,B or C</p>
#Html.TextBoxFor(x => x.MyChoice, new { #class = "form-control" })
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Select">
</div>
}
APartial.cshtml (as well as B and C...)
<p>This is A choice</p>
Notes:
Yes, web.config has UnobtrusiveJavaScriptEnabled=true and ClientValidationEnabled=true
Yes, I installed the jquery.unobtrusive-ajax package
Can you tell me what I missed or did wrong ?
Thanks

The answer was that I was missing the unobtrusive-ajax inclusion line in my cshtml :
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>

Related

ASP Core Ajax Posting From Partials

I have a requirement to separate parts of one page into Partial Views and one of those parts contains a form to submit data. I've been playing around with this and have managed to get the form to submit without reloading the page.
However I have two problems:
The form fields don't clear after a successful post
If validation is broken, those validation messages don't appear when returning the result.
I'll admit i'm not too familiar with AJAX in ASP to begin with but hopefully someone can hope. Here's my code:
Model
using System.ComponentModel.DataAnnotations;
namespace MVCValidation.Models
{
public class Thing
{
public int Id { get; set; }
[Required]
public string Value { get; set; }
public string OtherValue { get; set; }
}
}
Main View (_Index.cshtml)
#model MVCValidation.Models.Thing
#{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Ajax Partial Test</h1>
</div>
<div class="row">
<div class="col">
<form asp-controller="Home" asp-action="Edit" data-ajax="true" data-ajax-method="POST">
#await Html.PartialAsync("_Form", Model)
</form>
</div>
</div>
Partial View (_Form.cshtml)
#model MVCValidation.Models.Thing
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new {#class = "text-danger"})
#Html.HiddenFor(m => m.Id)
<div class="form-group">
#Html.LabelFor(m => m.Value, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.Value, new {htmlAttributes = new { #class = "form-control" }})
#Html.ValidationMessageFor(m => m.Value, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="submit" class="btn btn-success"/>
</div>
</div>
</div>
Controller (HomeController)
namespace MVCValidation.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public Thing GetThing()
{
return new Thing(){Id = 1, OtherValue = "Other"};
}
public IActionResult Index()
{
return View(GetThing());
}
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult Edit(Thing thing)
{
if(ModelState.IsValid)
{
ModelState.Clear();
return PartialView("_Form", GetThing());
}
return PartialView("_Form", thing);
}
}
}
In my _Layout view I have the jquery.unobtrusive-ajax.min.js referenced and it's loading fine. Please can anyone suggest where I'm going wrong?
So, I eventually found this article: https://damienbod.com/2018/11/09/asp-net-core-mvc-ajax-form-requests-using-jquery-unobtrusive/ and saw what I was doing wrong.
I expanded my tag to look like below:
<form asp-controller="Home" asp-action="Edit"
data-ajax="true"
data-ajax-method="POST"
data-ajax-mode="replace"
data-ajax-update="#result">
<div id="result">
#await Html.PartialAsync("_Form", Model)
</div>
</form>
the PartialAsync call now takes place inside a div that ultimately will be the target for the result to populate... so it effectively replaces itself.
I also had to change the controller method to this:
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult Edit(Thing thing)
{
if(ModelState.IsValid)
{
return RedirectToAction(nameof(Index));
}
return PartialView("_Form", thing);
}
This correctly returns the partial view when the model is invalid, and allows the page to be used again if it is valid.

Ajax.Beginform refreshing whole page

It redirects to whole new page instead of just updating the current one, I have unobtrusive scripts and web.config stuff. Why is this not working?
<div id="preview-image-placeholder"></div>
#foreach (var item in Model)
{
using (Ajax.BeginForm("PreviewImage/" + #item.Id.ToString(), "Music", null, new AjaxOptions { HttpMethod = "Post", InsertionMode = InsertionMode.Replace, UpdateTargetId = "preview-image-placeholder" },
new { #enctype = "multipart/form-data", #item.Id }))
{
#Html.AntiForgeryToken()
<input type="file" class="upload-image" id="upload-image-file_#item.Id" name="file" accept="image/*">
<button class="btn btn-default" id="btn-preview_#item.Id" style="display: none" type="submit"></button>
}
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
Controller:
[ValidateAntiForgeryToken]
[HttpPost]
public PartialViewResult PreviewImage(HttpPostedFileBase file, int id)
{
//Do other code stuff
return PartialView("_DisplayImage", song);
}
_DisplayImage partial view:
#model IEnumerable<MusicSite.Models.UploadedSong>
#using MusicSite.CustomHtmlHelpers;
#foreach(var item in Model)
{
if (#item.CoverImageBytes != null)
{
#Html.ImageFor(x => item.CoverImageBytes, #item.SongName + " Image", new { #id = "preview-image" })
}
}
Besides unobtrusive scripts you must add Microsoft Ajax libraries too (MicrosoftAjax.js and MicrosoftMvcAjax.js) so that Ajax.BeginForm works correctly.
Regards,

Saving multiple partial views from one main page

Here is my requirement :
I am designing a page to add a vehicle to the database :
Normal vehicle information [Model - Inventory]
Some other features [Model - IList]
Here is my index.cshtml page
#model Model.ViewModel.VehicleViewModel
<div>
<div class='col-md-12'>
<div class="form-group">
<input id="mainFormSubmit" type="button" value="Save" class="btn btn-default" />
</div>
</div>
#{Html.RenderPartial("~/Views/Shared/_InventoryPartial.cshtml", Model.InventoryVM);}
#{Html.RenderPartial("~/Views/Shared/_StandardFeaturePartial.cshtml", Model.StandardFeatures);}
</div>
<script type="text/javascript">
$('#mainFormSubmit').click(function () {
$('#InventoryForm').submit();
$("#StandardFeatureForm").submit();
});
</script>
This is my view model class
public class VehicleViewModel
{
public InventoryViewModel InventoryVM { get; set; }
public IList<StandardFeature> StandardFeatures { get; set; }
}
The Inventory partial view [_InventoryPartial.cshtml]
#model Model.ViewModel.InventoryViewModel
#{
var options = new AjaxOptions() { HttpMethod = "Post" };
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Ajax.BeginForm("InventorySave", "AddVehicle", options, new { id = "InventoryForm" }))
{
<fieldset>
<legend>Inventory Info</legend>
<div class='col-md-6'>
<!-- VIN input-->
<div class="form-group">
#Html.LabelFor(x => x.VIN, new { #class = "col-md-4 control-label" })
<div class="col-md-7">
#Html.TextBoxFor(x => x.VIN, new { #class = "form-control", #placeholder = "VIN" })
</div>
</div>
</div>
</fieldset>
}
The standard feature partial view [_StandardFeaturePartial.cshtml]
==
#model IEnumerable<Model.DomainModel.StandardFeature>
#{
var options = new AjaxOptions() { HttpMethod = "Post" };
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Ajax.BeginForm("StandardFeatureSave", "AddVehicle", options, new { id = "StandardFeatureForm" }))
{
When I am clicking on index page SAVE button, only
$('#InventoryForm').submit();
$("#StandardFeatureForm").submit();
last one(StandardFeatureForm) is executing.
Please let me know if this process is correct, and what could be the reason of this issue.
You should not call the submit method twice. Depending of the browser you can face different issues :
the form submission causes the browser to navigate to the form action and the submission
of the first may prevent the submission of the second
The browser could detected there are two requests and discards the
first submit.
In your case it will be easier to wrap your two partial views inside a unique form.
#using (Ajax.BeginForm("InventorySave", "AddVehicle", FormMethod.Post, new { id = "InventoryForm" }))
{
#{Html.RenderPartial("~/Views/Shared/_InventoryPartial.cshtml", Model.InventoryVM);}
#{Html.RenderPartial("~/Views/Shared/_StandardFeaturePartial.cshtml", Model.StandardFeatures);}
}
However when the partial views render they are not generating the correct name attributes for the larger modelModel.ViewModel.VehicleViewModel you want to use :
public void InventorySave(VehicleViewModel vehicleViewModel) {}
In this case you should use EditorTempmlate instead of partial views. It's simple to do from your partial views and this post should help you :Post a form with multiple partial views
Basically, drag your partials to the folder ~/Shared/EditorTemplates/
and rename them to match the model name they are the editor templates
for.
Finally something like :
#model Model.ViewModel.VehicleViewModel
#using (Html.BeginForm("InventorySave", "AddVehicle", FormMethod.Post, new { id = "InventoryForm" }))
{
#Html.EditorFor(m => m.InventoryVM);
#Html.EditorFor(m => m.StandardFeatures});
}
The Ajax.BeginForm helper already has a submit event associated to it which creates an Ajax POST request. When you are manually submitting your form using $('#InventoryForm').submit();, you're calling both and the submit events which can have strange side effects.
There are a few ways around this. Here is one solution
Change your forms to a regular HTML form using the Html.BeingForm helper.
Amend your script to create ajax requests and use the form data
$('#InventoryForm').submit(function(e) {
e.preventDefault();
$.post($(this).attr("action"), $(this).serialize(), function(r) {
//Do something
});
});
$('#StandardFeatureForm').submit(function(e) {
e.preventDefault();
$.post($(this).attr("action"), $(this).serialize(), function(r) {
//Do something
});
});
Hope this helps

Can't add file upload capability to generated MVC3 page

I'm new to MCV and I'm learning MVC3. I created a model and a controller and view was generated for me. The generated code makes perfect sense to me. I wanted to modify the generated view and controller so that I could upload a file when I "create" a new record. There is a lot of good information out there about how to do this. Specifically I tried this: http://haacked.com/archive/2010/07/16/uploading-files-with-aspnetmvc.aspx
The problem is that even when I select a file (not large) and submit, there are no files in the request. That is, Request.Files.Count is 0.
If I create the controller and and view from scratch, in the same project (no model), the example works just fine. I just can't add that functionality to the generated page. Basically, I'm trying get the Create action to also send the file. For example, create a new product entry and send the picture with it.
Example Create view:
#model Product.Models.Find
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm("Create", "Find", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Find</legend>
<input type="file" id="file" />
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Example Controller:
[HttpPost]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
if (Request.Files.Count > 0 && Request.Files[0] != null)
{
//Not getting here
}
db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(find);
}
This will create the record just fine but there are not files associated with the Request.
I've also tried a controller action like this:
[HttpPost]
public ActionResult Create(HttpPostedFileBase file)
{
if (file.ContentLength > 0)
{
//Not getting here
}
return RedirectToAction("Index");
}
I'm wondering if maybe you can't post a file at the same time as posting form fields? If that is the case, what are some patterns for creating a new record and associating a picture (or other file) with it?
Thanks
Create a ViewModel which has properties to handle your image and Product deatils
public class ProductViewModel
{
public string ImageURL { set;get;}
public string Title { set;get;}
public string Description { set;get;}
}
And in your HTTPGET Action method, return this ViewModel object to your strongly typed view
public ActionResult Create()
{
ProductViewModel objVM = new ProductViewModel();
return View(objVM);
}
And in your View
#model ProductViewModel
<h2>Add Product</h2>
#using (Html.BeginForm("Create", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(m => m.Title) <br/>
#Html.TextBoxFor(m => m.Description ) <br/>
<input type="file" name="file" />
<input type="submit" value="Upload" />
#Html.HiddenFor(m => m.ImageURL )
}
Now in your HttpPost action method, accept this ViewModel and File
[HttpPost]
public ActionResult Create(HttpPostedFileBase file, ProductViewModel objVM)
{
if(file==null)
{
return View("Create",objVM);
}
else
{
//You can check ModeState.IsValid if you have to check any model validations and do further processing with the data here.
//Now you have everything here in your parameters, you can access those and save
}
}
You will have to create a ViewModel for Product (maybe ProductViewModel) and add a HttpPostedFileBase field with the same name as the field of the form and use that instead of the Product in the action of the controller.
A ViewModel is nothing but a model used for specific views. Most of the times, with extra data to generate the view or to decompose and form the model on the controller action.
public ProductViewModel
{
public string Cod { get; set; }
// All needed fields goes here
public HttpPostedFileBase File{ get; set; }
/// Empty constructor and so on ...
}

How i can submit multiple objects inside the form post values to be added

i have a create view to add answers to a question, currently the user can only add one answer at the same time when he clicks on the submit button, instead of this i want the user to be able to insert multiple answers objects into the same view and then the system to add all these new answer objects to the database after the user click on the submit button, my current view looks as the follow:-
#model Elearning.Models.Answer
#{
ViewBag.Title = "Create";
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<div id = "remove">
#using (Ajax.BeginForm("Create", "Answer", new AjaxOptions
{
HttpMethod = "Post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "remove"
}))
{
<div id = "returnedquestion">
#Html.ValidationSummary(true)
<fieldset>
<legend>Answer</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
</fieldset>
<input type= "hidden" name = "questionid" value = #ViewBag.questionid>
<input type= "hidden" name = "assessmentid" value = #ViewBag.assessmentid>
<input type="submit" value="Add answer" />
</div>
}
</div>
and the action methods look as the follow:-
public ActionResult Create(int questionid)//, int assessmentid)
{
ViewBag.questionid = questionid;
Answer answer = new Answer();
return PartialView("_answer",answer);
}
//
// POST: /Answer/Create
[HttpPost]
public ActionResult Create(int questionid, Answer a)
{
if (ModelState.IsValid)
{
repository.AddAnswer(a);
repository.Save();
return PartialView("_details",a);
}
return View(a);}
so how i can modify the above code to be able to insert multiple answer objects at the same view and then submit these answers objects at the same time when the user click on the submit button?
Try Post a List
Add input by javascript when user click "Add Answer".
And when submit the form ,it will post all answer data to binding to List
<script>
$(document).ready(function () {
var anwserCount = 1;
$("#addbutton").click(function () {
$("#AnwsersDiv")
.append("<input type='text' name='Anwsers[" + anwserCount + "]'/>");
anwserCount += 1;
});
});
</script>
#using (Html.BeginForm())
{
<div id="AnwsersDiv">
<input type="text" name="Anwsers[0]" />
</div>
<input id="addbutton" type="button" value="Add answer" />
<input type="submit" value="submit" />
}
Model
public class Answer
{
public List<String> Anwsers { get; set; }
}
When submit the form
I think this is what you are looking for
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Conclusion: you should make the post action with ICollection<Answer> Parameter, then it will be easy to get them when you post your main form, and create the appropriate QUESTION object, then save them all with only one submit.

Resources