ASP.NET MVC partial view refresh on button click - model-view-controller

I'm using VS 2013, MVC 5.
Here is the content of my partial view (_Sales.cshtml):
#model IEnumerable<SomeModel>
<div id="outer">
<div id="inner1">
#(Html.Kendo().Chart<SomeModel>(Model)
...
)
</div>
<div id="inner2">
<table>
<tr>
<td>Total Sales </td>
<td>#Model.First().TotalSales.ToString("C")</td>
</tr>
<tr>
<td>Total Discount </td>
<td>#Model.First().TotalDiscount.ToString("C")</td>
</tr>
</table>
</div>
</div>
Below is an action method used while loading first time:
public ActionResult _Sales()
{
IEnumerable<SomeModel> salesList = null;
SearchCriteriaObject criteria = null;
salesList = getting data as list;
return PartialView(salesList);
}
So far, all work fine as expected. That's my partial view is rendering fine with initial data.
Now my requirement is I need to refresh my partial view as user specify search criteria and hit search button.
Here is the search button specific action method:
public ActionResult Get_BulletChartData_Updated(SearchViewModel criteriaModel)
{
IEnumerable<SomeModel> salesList = null;
SearchObject criteria = new SearchObject();
if (ModelState.IsValid)
{
if (criteriaModel != null)
{
//populating criteria here
}
salesList = //Getting data in list format
}
return PartialView(salesList);
}
On search button click event handler in javascript, I do this:
$("#btnSearch").click(function () {
...
var Url = $('#Url').val(); //Getting action method url from hidden field
$.ajax({
type: "POST",
dataType: 'HTML',
data: JSON.stringify(SearchViewModel),
url: Url, //#Url.Action("Get_SalesDataFiltered", "Sales")
contentType: "application/json; charset=utf-8",
success: function (result)
{
alert('success');
//$("#outer").load(result);
},
error: function ()
{
alert("error");
}
});
On search button click, I always get error alert message.
Could you please guide me the correct way to achieve this.
I'm new to MVC. Please feel free to ask for more info.
If you provide me with code, it'd be great.
Thanks.

I think that your problem is that you post a json object, while your post method has as a parameter a SearchViewModel object.
I believe that If you change this
data: JSON.stringify(SearchViewModel)
to this
data: $("#yourFormId").serialize()
you will get the expected result.

Related

ASP.NET Core MVC - VIew is not updated correctly - Ajax?

I have an ASP.NET Core 6 MVC application.
On one page I have a table; I want to support drag'n'drop for its rows. Afterwards the User is able to click on "Submit" and make the changes permanent.
The changes are sent to the controller and persisted to the database, however when I redirect to the GET to show the page again, a part of it is wrong!
#model MyViewModel
<form>
#Html.HiddenFor(y=>y.Id)
<table id="orderTable" class="table">
<thead>
<tr>
<th>
Name
</th>
<th>
Order
</th>
</tr>
</thead>
<tbody>
#foreach (var data in Model.Data)
{
<tr id='#data.Id'>
<td>#data.Name</td>
<td>#data.Order</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" id="SaveOrderButton" />
</div>
</form>
<script>
$(document).ready(function() {
$('#orderTable tbody').sortable();
$("#SaveOrderButton").click(function(e) {
e.preventDefault();
var newOrder = $('#orderTable tbody').sortable('toArray');
$.ajax({
url: '/Controller/Update',
type: 'POST',
data: { rowOrder: newOrder, id: #Html.Raw(Model.Id) },
success: function(response) {
console.log(response);
},
error: function(xhr,status, error){
console.log("An error occurred: " + xhr.responseText);
}
});
});
});
</script>
Backend:
[HttpGet]
public async Task<IActionResult> Order(int id)
{
var data= await context.Data
.AsNoTracking()
.Where(x => x.Id== id)
.ToListAsync();
data = data.OrderByDescending(y => y.Order.HasValue)
.ThenBy(y => y.Order)
.ToList();
var viewModel = new MyViewModel()
{
Data = data,
Id = id,
};
ModelState.Clear(); // found on SO, but does not change anything
return View(viewModel);
}
[HttpPost]
public async Task<IActionResult> Update(int[] rowOrder, int id)
{
var data= await context.Data
.Where(y => rowOrder.Contains(y.Id))
.ToListAsync();
for (int i = 0; i < rowOrder.Count(); i++)
{
data.First(y => y.Id == rowOrder[i]).Order = i;
}
try
{
context.UpdateRange(data);
await context.SaveChangesAsync();
}
catch (Exception ex)
{
logger.LogError("..........");
return Json(500, "Could not update new order.");
}
return RedirectToAction(nameof(Controller.Order), new { id= id});
}
Okay, so I go the the view with GET and everything is shown correctly, then I change something and click on "Save". Everything in the POST will be correctly done. The database is updated.
I then redirect to the GET method again, there everything is loaded correctly from the database and in the correct order.
Then I set a breakpoint in the View and there the stuff in the for is correct too.
However, when I look in the browser, the "Order" column is wrong. The table still shows the data how it looked like after I reordered it and before I clicked on "Save".
What is happening here? Is the sortable lib using a cache in the background that I have to invalidate?
I don't use a cache anywhere in my project, btw.
Also, when I go to the console after a POST, the whole website's HTML is in there.
When I now reload the page with the GET, everything is shown how it is supposed to be.
Has it something to do with Ajax? I have already removed the success and error events, which doesn't change anything.
Has it something to do with Ajax? I have already removed the success
and error events, which doesn't change anything.
Yes, the issue relates the Ajax method.
As we all known, when we use Ajax to update the part of view page, after calling the action method, it will return the response result to the success function, then in the success function, we get the updated data from the response, and then dynamic populate the table to update the page and achieve the part of page refresh behaviors.
So, in your scenario, you can try to use the following methods to display the updated data.
Method 1:
In the Update Action method, return the updated data as result, instead of redirect to another action result. Then, in the Ajax success function, get the data from response, then clear the table content first and re-populate it using the response data.
Method 2:
In the Ajax success function, use location.reload(); method to reload current page, or use window.location.href to refresh the current page.

MVC Core ajax and return result is a view

MVC Core, NET 5, not razor pages.
On a view I have three select components (bootstrap-select). I populate them via ViewModel.
"Get request -> Controller -> return View(viewModel);"
What I want...
When I changed value in any select component I do a post request (ajax) to the same controller (other method) and return view with repopulated data.
"'Post request -> Controller -> return View(changedModel);"
As I understood when I did ajax request I should handle it result in success and other cases.
What I should to do to reload page with new data?
Is it possible to achive this with this approach?
Yes, this is possible and you do not need to reload the page, just append the returned html to wherever you want it.
$.ajax({
type: "POST",
url: {your_url},
dataType: "html",
success: function (html) {
$("#someDiv").html(html);
}
});
What I should to do to reload page with new data?
If the post action return the same view as the get action and you want to reload the whole page, I think there is no need to use ajax. You can just redirect to post action with a form submission. If the view returned by the post action is a partialview you want render to the current view, you can use it like that in #cwalvoort answer.
Based on advices of cwalvoort and mj1313
I did:
Render main page with partials. ViewModel transfered to a partial as a parameter
On main page I added eventListners to controls with JS.
When control changes - ajax request to backend happens Controller/GetPartialView
Result from ajax replace html in partial section
Programmatically show needed components, re-add eventListners
PS Really need to learn Blazor or UI Framework :)
Code samples:
// JS
document.addEventListener("DOMContentLoaded", function (event) {
BindSelectActions();
});
function BindSelectActions() {
$('#selectGroups').on('hidden.bs.select', DoPartialUpdate);
$('#selectCompanies').on('hidden.bs.select', DoPartialUpdate);
$('#selectPeriods').on('hidden.bs.select', DoPartialUpdate);
}
function DoPartialUpdate(e, clickedIndex, isSelected, previousValue) {
// ToDo: Implement common script with "CallBackend" function
$.ajax({
type: "POST",
url: 'https://localhost:44352/TestController/TestGetPartial',
// no data its a stub at the moment
// data: $('#form').serialize(),
success: function (data, textStatus) {
$("#testControls").html(data);
$('#selectGroups').selectpicker('show');
$('#selectCompanies').selectpicker('show');
$('#selectPeriods').selectpicker('show');
BindSelectActions();
}
});
}
// Controllers
[HttpGet]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public async Task<IActionResult> Main()
{
// ViewModel = _helper -> _mediator -> query -> context
return await Task.Run(() => View(new TestViewModel()));
}
[HttpPost]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public IActionResult TestGetPartial(TestViewModel model)
{
// ViewModel = _helper -> _mediator -> query -> context
var result = new TestViewModel();
result.IsPageReload = "yes";
result.TestCollection = new string[] { "A", "B", "C" };
result.Companies = new List<SelectListItem> { new SelectListItem { Value = "999",
Text = "Test" } };
// ModelState.Clear();
return PartialView("_TestPartial", result);
}
// Main and partial views
#model TestViewModel
#{
ViewData["Title"] = "Test";
}
<div id="testControls">
#await Html.PartialAsync("_TestPartial", Model)
</div>
#section Scripts {
<script type="text/javascript" src="~/js/test.js" asp-append-version="true">
</script>
}
#model TestViewModel
<form>
<div class="d-flex flex-row justify-content-between mt-4">
<div><select id="selectGroups" asp-for="Groups" asp-items="Model.Groups"
class="selectpicker" data-live-search="true" data-style="btn-outline-dark"
title="Group"></select></div>
<div><select id="selectCompanies" asp-for="Companies" asp-items="Model.Companies"
class="selectpicker" data-live-search="true" data-style="btn-outline-dark"
title="Company"></select></div>
<div><select id="selectPeriods" asp-for="Periods" asp-items="Model.Periods"
class="selectpicker" data-live-search="true" data-style="btn-outline-dark"
title="Period"></select></div>
<div><button type="button" class="btn btn-outline-dark">Import</button></div>
</div>
</form>
<div>
#{
if (null != Model.TestCollection)
{
foreach (var item in Model.TestCollection)
{
<p>#item</p>
<br>
}
}
}
</div>

JQuery AJAX Post to Controller data incorrect

Here is what I am trying to do:
My goal is to display a list of Trending Opinions (A custom Model) from the page's model when the page loads. If a user clicks the "Show more Trending Opinions" button, it uses ajax to call a method on a controller that will then retrieve an additional number of items, come back to the page and display them. Then it adds say 20 more. Then they can repeat the process and click it again, etc.
Exactly the same as a normal site does when you click "Show More" on a list of items.
If the way I am approaching this is incorrect and you know of any tutorial (or just out of your head) showing the correct way to do this in MVC 4, please let me know. I am not dead-set on the way I am doing it at the moment, this is just the "correctest" way I have found.
I followed the answer to a similar question: How to Update List<Model> with jQuery in MVC 4
However, the data coming through to my controller is incorrect and I can't figure out what the issue is.
Let me put as much info as I can, because I have no idea where the error may be.
Model for page (OpinionModel has a few public properties):
public class IndexModel
{
public IList<OpinionModel> TopTrendingOpinions { get; set; }
}
The View:
<div id="TrendingOpinions">
<p>What is trending at the moment</p>
#using (Html.BeginForm("LoadMoreTrendingOpinions", "AjaxHelper",
method: FormMethod.Post,
htmlAttributes: new { #class = "form-horizontal", id = "LoadTrendingOpinionsForm" }))
{
#Html.EditorFor(x => x.TopTrendingOpinions)
<input type="submit" value="Load More Trending Opinions" />
}
<script type="text/javascript">
$('#LoadTrendingOpinionsForm').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: {
topTrendingOpinions: $(this).serialize()
},
success: function (result) {
alert(result);
}
});
return false;
});
</script>
</div>
**There is also an EditorTemplate for my model.
The Controller:**
[HttpPost]
public ActionResult LoadMoreTrendingOpinions(IList<MyGoldOpinionMVC.Models.OpinionModel> topTrendingOpinions)
{
var dataHelper = new Data.DataHelper();
var moreTrendingOpinions = dataHelper.LoadMoreTrendingOpinions(topTrendingOpinions.LastOrDefault().Id);
// var partialView = PartialView("../PartialViews/_ListOfPostedOpinion", moreTrendingOpinions);
return View(moreTrendingOpinions);
}
So here is the order of events:
When running the site, the form shows a list of OpinionModels (Using the Editor Template displaying correct data). When I click the SUBMIT button, it goes to the controller (I have a breakpoint) and the data for the "topTrendingOpinions" parameter is a List with one item in it, but that item is null. So in other words, it is not passing through the list that is clearly being used to populate the form.
The only way I have been able to get a list to post back to the controller is to build it manually with jquery. my understanding is this.serialize on a form click is going to try to serialize the whole from which would get very ugly. How I would do this is
<input type="button" class="btnMore" value="Load More Trending Opinions" />
$('.btnMore').on('click', function () {
$.ajax({
url: '#Url.Action("LoadMoreTrendingOpinions", "AjaxHelper")',
type: 'post',
contentType: 'application/json; charset=utf-8',
data: {
Id: '#ViewBag.Id'
},
success: function (result) {
//add results to your table
}
});
});
and set the id of the last record sent through the view bag on your controller so you have a reference to go off of for pulling the next chunk. Let me know if you have any questions
When posting lists you have to be really careful that your inputs are named correctly. If they are not, the default model binder fails to parse them into classes when posted resulting the object being null in the controller.
In your case you are posting a list of models inside a model, but not the whole model. I'd use PartialView instead of editortemplate, just to make working with field names easier. In my example we are posting a list of FooModels contained in IndexModel
Model
public class FooModel
{
public string Foo { get; set; }
public string Bar { get; set; }
}
public class IndexModel
{
public IList<FooModel> Foos { get; set; }
}
View
#using (Html.BeginForm("LoadMoreTrendingOpinions","AjaxHelper",
method: FormMethod.Post,
htmlAttributes: new { #class = "form-horizontal", id = "LoadTrendingOpinionsForm" }))
{
#Html.Partial("FooModelsPartial", Model.Foos)
<input type="submit" value="Load More Trending Opinions" />
}
FooModelsPartial
#model IList<FooModel>
#for (int i = 0; i < Model.Count(); i++)
{
#Html.EditorFor(model => model[i].Foo)
#Html.EditorFor(model => model[i].Bar)
}
Notice how we are using for instead of foreach loop. This is because editors in foreach loop are not named correctly. In this case we want our fields to be [0].Foo, [0].Bar, [1].Foo, [1]. Bar etc.
Controller:
[HttpPost]
public ActionResult LoadMoreTrendingOpinions(IList<FooModel> topTrendingOpinions)
{
// do something with toptrending thingy
var model = new IndexModel();
model.Foos = topTrendingOpinions;
return View("Index", model);
}
Now the real question in my opinion is do you really want to post the whole list of models to get bunch of new ones related to one of them? Wouldn't it be more convenient to post the id of opinion you'd want to read more of, returning partialview containing the requested more trending opinions and appending that to some element in the view with jquery?
Html:
#using (Html.BeginForm("LoadMoreTrendingOpinions","AjaxHelper",
method: FormMethod.Post,
htmlAttributes: new { #class = "form-horizontal", id = "LoadTrendingOpinionsForm" }))
{
<div id="more">#Html.Partial("FooModelsPartial", Model.Foos)</div>
<input type="submit" value="Load More Trending Opinions" />
}
Javascript:
$('#LoadTrendingOpinionsForm').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: {
id: 1 /* The id of trending item you want to read more of */
},
success: function (result) {
$("#more").html(result)
}
});
return false;
});
Controller:
[HttpPost]
public ActionResult LoadMoreTrendingOpinions(int id)
{
var moreTrendingOpinions = dataHelper.LoadMoreTrendingOpinions(id);
return PartialView("FooModelsPartial", moreTrendingOpinions);
}

How do I get the value of a button to pass back to the server when using JQuery & AJAX?

I am using MVC3, Razor, C#, JQuery and AJAX.
I am using an Ajax call to postback a form to the server. I get all the form element values being passed back to the controller in:
[HttpPost]
public ActionResult Edit(Product myProduct, string actionType)
if (actionType == "Save")
save....
And in the View I have:
#using (Html.BeginForm("Edit", "RA", FormMethod.Post, new { #class = "editForm", #id = "frmEdit" }))
Form Elements:
<td>
#Html.HiddenFor(p=>p.Id)
<button type="submit" name="actionType" value="Save" >Save</button>
</td>
<td>#Html.EditFor(p=>p.Name)</td>
Some Ajax:
$('.editForm').on('submit', function () {
$.ajax({
url: this.action,
type: this.method,
data: $('#frmEdit').serialize(),
context: $('button', this).closest('tr'),
success: function (result) {
$(this).html(result);
}
});
return false;
});
Now I think the problem line is since I have seen quite a few posts about problem with JQuery and submitting button values:
data: $('#frmEdit').serialize(),
But I cannot get the button to submit an actionType of "Save". I just get null.
Thoughts greatly appreciated.
Thanks.
UPDATE:
My code seems to interfere with my JQuery listener ?? My code is:
<input type="submit" id="btn" name="btn" value="Save" onclick="document.getElementById('actionType').value = 'Save';"/>
From the documentation:
No submit button value is serialized since the form was not submitted using a button.
However you can add it by hand:
data: $('#frmEdit').serialize() + '&actionType=Save',
or
data: $('#frmEdit').serialize()
+ '&'
+ encodeURIComponent(button.name)
+ '='
+ encodeURIComponent(button.value),
where button is the <button> DOM element.

Partial view in MVC3 Razor view Engine

I have an view in MVC3 Razor view engine like following image. Now i want to Confirm Connection Action Output show under this link text not New page. How can i done this work?
Please explain with example code.
My View Like this :
#model ESimSol.BusinessObjects.COA_ChartsOfAccount
#{
ViewBag.Title = "Dynamic Account Head Configure";
}
<h2>Dynamic Account Head Configure</h2>
<table border="0">
<tr>
<td> Select an Server Connection </td>
<td style="width:5px">:</td>
<td>#Html.DropDownListFor(m => m.DBConnections, Model.DBConnections.Select(x => new SelectListItem() { Text = x.ConnectionName, Value = x.DBConnectionID.ToString()}))</td>
</tr>
<tr>
<td> </td>
<td style="width:5px"></td>
<td>#Html.ActionLink("Confirm Connection", "ConformConnection")</td>
</tr>
</table>
AND My Controller action Like following :
public ActionResult ConfirmConnection()
{
return PartialView();
}
I'm a big fan of using jquery and ajax for this kind of thing ...
http://api.jquery.com/jQuery.ajax/
If you are following the typical MVC model then you can add an action link to the page using something like ...
#Html.ActionLink("controller", "action", args);
but I would go for the ajax driven approach ...
<script type="text/javascript">
var ajaxBaseUrl = '#Url.Action("yourController", "ConformConnection", new { args })';
$(link).click(function () {
var currentElement = $(this);
$.ajax({
url: ajaxBaseUrl,
data: { any other queryString stuff u want to pass },
type: 'POST',
success: function (data) {
// action to take when the ajax call comes back
}
});
});
});
</script>
First move your markup to a partial view. After that define an action method that renders your partial view.
[ChildActionOnly]
public ActionResult ConfirmConnection(COA_ChartsOfAccount model)
{
return PartialView("MyPartialView", model);
}
ChildActionOnly attribute makes sure this action method cannot be called by a HTTP request.
Then you can display it whenever you want using Html.Action method.
#Html.Action("ConfirmConnection", "MyController", new { model = Model })
Ignore passing the model as a parameter if it doesn't change by the page you display it. You can retrieve it in your action method.

Resources