MVC 4 PartialViewResult caching on jQuery $.post - asp.net

I have a jQuery $.post back to a MVC 4 Controller that will return back a PartialViewResult with rendered using the data sent in the POST. When debugging the Partial View and Controller, the correct data is being received and sent to the Partial View as the View Model. The issue is, when analyzing the HTML sent back in the AJAX result it is containing seemingly "cached" data from the original page refresh.
I have seen a good amount of posts on here that are similar, but none that were the same as my issue.
I am aware that HTTP Post requests do not cache in the browser, so that is not the issue. I also have set the set the OutputCache attribute to NoStore = true, etc.
Controller
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public partial class MyController : Controller
{
...
[HttpPost]
public virtual ActionResult UpdatePartial(MyViewModel myVm)
{
return this.PartialView("My/_Partial", myVm);
}
}
JS
$('.someButton').click(function () {
$.post(myAjaxUrl, $('form').serialize(), function (data) {
$('#myContent').html(data);
});
});

I'm able to work around this by adding ModelState.Clear prior to doing any operations on the model.
[HttpPost]
public virtual ActionResult UpdatePartial(PersonViewModel model)
{
ModelState.Clear();
model.FirstName += "1";
model.LastName += "1";
model.Age += 1;
return this.PartialView("../My/_Partial", model);
}
This question has an answer by Tim Scott with more info an links.

By default JQuery will cache $.ajax XMLHttpRequests (unless the data type is script or jsonp). Since $.post is implemented via $.ajax, JQuery itself has cached your request. The following JS should work.
JS
$('.someButton').click(function () {
$.ajax{(
url: myAjaxUrl,
data: myAjaxUrl,
success: function (data) {
$('#myContent').html(data);
},
cache: false
});
});
You might also find it worthwhile to handle the error event in case the post doesn't succeed.

Related

Managing HTTP requests and ViewModel in ASP.NET

I have a CSHTML page and I'm having trouble finding the best way to structure my requests flow and maintaining a persistance of what is in the ViewModel with what is being displayed in the View.
I'm using an ASP.NET Web Service as API to connect the website to the database. This is how my Controller is populating the ViewModel before calling the View:
[AllowAnonymous]
[Route("company/sites")]
public async Task<IActionResult> CompanySites()
{
var client = _clientFactory.CreateClient("API");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", HttpContext.Request.Cookies[Startup._tokenCookieName]);
CompanyAccount user = JsonConvert.DeserializeObject<CompanyAccount>(HttpContext.Request.Cookies[Startup._companyInfoCookieName]);
if (user == null)
{
return RedirectToAction("Index", "Company");
}
CompanySitesViewModel viewModel = new CompanySitesViewModel ();
viewModel.LoggedInCompanyId = user.CompanyId;
// Populate viewModel...
return View(viewModel);
}
The ViewModel:
public class CompanySitesViewModel
{
public List<CompanyDTO> AdministratedCompanies { get; set; }
public CompanySitesViewModel ()
{
AdministratedCompanies = new List<CompanyDTO>();
}
}
And this is one of the places I access data from the ViewModel inside the View:
...
#if (Model.AdministratedCompanies.Count > 0)
{
<div class="list-group list-group-flush">
#for (int i = 0; i < Model.AdministratedCompanies.Count; i++)
{
<button class="list-group-item">
<div>
<h6>#Model.AdministratedCompanies[i].CompanyName</h6>
#if (#Model.AdministratedCompanies[i].CompanyCnpj != null)
{
<span><small class="text-muted">##Model.AdministratedCompanies[i].CompanyCnpj</small></span>
}
</div>
</button>
}
</div>
}
...
My problem starts when I want to manipulate the data displayed in this list. In instance, I want to remove one company from AdministratedCompanies. I'm currently doing a Ajax call directly to the API and, when receiving success, forcing a page refresh so the View gets updated without the deleted company.
This is the Ajax call:
function deleteCurrentSite() {
if (currentCompanyIdToDelete != null) {
$.ajax({
url: "#Startup._apiConnectionString" + "sites/" + currentCompanyIdToDelete ,
type: "DELETE",
success: function (e) {
showAlert('Company deleted. Refreshing page...', true);
// Has to reload page to refresh site list
document.location.reload(true);
},
error: function (e) {
showAlert('Error deleting company.', false);
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + '#Model.Token');
}
});
return false;
}
}
I have background in React development, and it is incredibly easy to just change the state and have the view to be updated, and I seems very unoptimal to have to reload the whole page because of a change.
I have some ideas in mind to solve this:
Find the deleted company's div and delete it manually. Not optimal, even less when I want to add a company and have to create and append the component.
Have the Controller to store the ViewModel and have the Ajax call to send the ID I want to delete to the Controller, that then manages the API calls. But from what I found, if I use RedirectToAction or call the View again after the deletion, the page will still be refreshed (even though this seems a better approach as having Ajax to call the API).
Found some references to this library BeginCollectionItem to create a more dynamic binding between the View and ViewModel, but when I saw it was updated 7 years ago I didn't investigate further.
So what is the best way of creating a dynamic binding between the View and ViewModel including CRUD operations and list redraw when changes occur?
Here's an example of some javascript (jQuery) for a simple get. The handler passes the URL of the "jq_pagename.cshtml":
function DisplayError(jqXHR, textStatus, error) {
errortext = error + jqXHR.responseText;
$('#status').html('');
$('#results').html(errortext);
}
function updateMainDiv(url) {
$('#results').html('');
data = "<img src='/images/waiting.gif'></img>";
$('#status').html(data);
$.get(url, function (data) {
$("#results").html(data);
$('#status').html('');
}).fail(function (jqXHR, textStatus, error) {
DisplayError(jqXHR, textStatus, error);
});
}
For this technique I usually use partials for static view controls on the page... search, top-level nav, etc... and then for main content. the main page includes those, the jq_ page does not. The "results" div is only in the main page. You are sending the GETs or POSTs to the jq_page via javascript and returning their HTML via javascript. This way the exact same controller can be used to update the data and the view. (You're just intercepting the returned HTML and putting it where you like.) Here's an example of processing a form (onsubmit, or onchange select, etc..):
function ProcessFormPost(formID, destURL) {
formresult = $('#' + formID).serialize();
$('#super').html('');
data = "<img src='/images/waiting.gif'></img>";
$('#status-super').html(data);
$.post(destURL, formresult, function (data) {
$("#super").html(data);
$('#status-super').html('');
}).fail(function (jqXHR, textStatus, error) {
DisplaySuperError(jqXHR, textStatus, error);
});
return false;
}

Alternative way to provide CSRF Token instead using #Html.AntiForgeryToken() in ASP.Net MVC

I'm working on an angular 2 app using a ASP.Net MVC backend. For this, the index.cshtml has included #Html.AntiForgeryToken(); so i get back a hidden input element with the Token as its value. In my frontend i can grab this token and use it for my requests. So far so good.
Now since user information is used to generate the token in the backend, i have to switch out the token on some occassions. The problem is described here in this question.
Now since i don't want to do a full page reload in my app i have to use a workaround. For this i created a partial view in the backend which just hands out this input using #Html.AntiForgeryToken(); as ActionResult with a simple method like this:
public ActionResult GetAntiForgery()
{
return PartialView("~/Views/Home/AntiForgery.cshtml");
}
After login / logout i call this function from my frontend and replace the value of my existing AntiForgery input element like this:
getAntiForgery(url:string) {
return this._http.get(url)
.map((response:any) => {
let originalXsrfElement = <HTMLInputElement>document.querySelector('[name=__RequestVerificationToken]');
let body = response._body;
if(body) {
let helperElem = document.createElement('div');
helperElem.innerHTML = body;
let helperInputElem = <HTMLInputElement>helperElem.firstChild;
originalXsrfElement.value = helperInputElem.value;
}
return response;
});
}
I can't even tell you what bugs me the most. I hate to make an extra request (but lets not dive into this here) but way more terrible for me is that i have to request something, get an html string back and have to extract the token string out of it.
If i were the backend guy, i would kill the frontend guy (me..) for even thinking about creating an extra partial view, creating an extra method and always doing two requests on login/logout instead of one.
Is there a better way? For example i would like to call a method which just hands out a JSON with the proper token instead of an HTML snippet. Even better, on existing JsonResult methods in the backend, i would like to add the new CSRF Token as a property.
I'm not a backend architect so there might be some stuff wrong in general how i do this, but my backend colleagues don't mind what i'm doing there so it shouldn't be so far off.
Any hints are appreciated.
You can use AntiForgery.GetToken in your action to set some property on the JSON being returned:
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
json.RequestVerificationToken = cookieToken + ":" + formToken;
Then, pass it back in the header of your next AJAX request:
$.ajax("/my/awesome/url", {
type: "post",
contentType: "application/json",
data: { ... },
dataType: "json",
headers: {
'RequestVerificationToken': requestVerificationToken
}
});
Since, it's no longer being handled by cookies, you can't just use the standard ValidateAntiForgeryToken attribute. You'll need to manually check for the header and validate it. However, you should be able to create a custom attribute you can use instead:
AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenFromHeaderAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var request = filterContext.HttpContext.Request;
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
}
Then, just decorate your actions with [ValidateAntiForgeryTokenFromHeader] instead.
[Code adapted from http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks]

JSON Post Request is not working in ASP.NET5 MVC but is working in ASP.NET4.5 MVC

I have an example which sends a JSON post request to MVC controller. This example works in ASP.NET 4.5 but won’t work in the newest ASP.NET 5 release. Do I miss anything in this example?
I created a model, but I didn’t bind it to database. It will be just the object created in the memory.
public class SalesOrder
{
public int SalesOrderId { get; set; }
public string CustomerName { get; set; }
public string PONumber { get; set; }
}
I use Visual Studio 2015 to create that model based controller and its associated CRUD views. However, in this example, I will only run the “Create” view.
Inside the controller, I hard coded a SalesOrder object list and it only contains an item. I will use it to work with the CRUD views.
private List<SalesOrder> salesOrderList =
new List<SalesOrder> (
new SalesOrder[]
{
new SalesOrder()
{
SalesOrderId = 1,
CustomerName = "David",
PONumber = "123"
}
});
Inside the controller, I also create a new function to process the “Save” request. The request will just change the CustomerName property of the model then bounce back with the JSON result.
[HttpPost]
public JsonResult Save(SalesOrder salesOrderViewModel)
{
salesOrderViewModel.CustomerName = "David returns";
return Json(new { salesOrderViewModel });
}
In the Create.cshtml, I created a button and attach JQuery script to its click event.
<p><button id="saveButton">Save</button></p>
This is the JQueryScript.
$(function () {
var clickFunc = function () {
var salesOrderViewModel = {
"salesOrderViewModel": {
"SalesOrderId": 123,
"CustomerName": "ab",
"PONumber": "2",
}
}
$.ajax({
url: "/SalesOrders/Save/",
type: "POST",
cache: false,
data: JSON.stringify(salesOrderViewModel),
contentType: "application/json; charset=utf-8"
});
}
$('#saveButton').click(clickFunc)
})
I click the "Save" button in Create.cshtml to trigger the post request.
I set the debug break point in the controller and verify the coming post request. In ASP.NET 4.5, the JSON deserialization is working, and it shows all the values.
However, in ASP.NET 5, an empty object is returned.
In ASP.NET 5 case, if press F12 to start the debugger in Microsoft Edge, then it shows the Post request does have the correct values, but for some reasons, they are not passed to MVC controller.
Please see these screen shots:
http://i63.tinypic.com/68vwnb.jpg
Do I miss anything?
Thanks for helping…
I don't think you need to specify the name of the parameter. Try:
var salesOrderViewModel = {
"SalesOrderId": 123,
"CustomerName": "ab",
"PONumber": "2",
}
I think you need to especify fromBody in the Controller.
[HttpPost]
public JsonResult Save([FromBody]SalesOrder salesOrderViewModel)
{
salesOrderViewModel.CustomerName = "David returns";
return Json(new { salesOrderViewModel });
}
Thanks for DCruz22 and Stephen Muecke.
The working version of the script is:
$(function () {
var clickFunc = function () {
var salesOrderViewModel = {
"salesOrderViewModel": {
"SalesOrderId": 0,
"CustomerName": "ab",
"PONumber": "2",
"MessageToClient": "POST to server"
}
}
$.ajax({
url: "/SalesOrders/Save/",
type: "POST",
cache: false,
data: salesOrderViewModel
});
}
$('#saveButton').click(clickFunc)
})
I found the parameter name "salesOrderViewModel" is not a problem. With it, the script is still working.
Actually, this question is a simplified version of my original problem. My originally problem is starting from the knockoutJs. Below is the script doing the data binding. After user clicks the "Save" button, the self.save function() is called and the script will send the post request including the data model to the MVC controller.
SalesOrderViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
self.save = function () {
$.ajax({
url: "/SalesOrders/Save/",
type: "POST",
cache: false,
data: ko.toJSON(self),
success: function (data) {
}
});
}
}
The problem is "ko.toJSON(self)" includes some other information. Below is the request body I captured from the Microsoft Edge debugger.
{"SalesOrderId":0,
"CustomerName":"chiang123",
"PONumber":"123",
"MessageToClient":null,
"__ko_mapping__":{
"ignore":[],
"include":["_destroy"],
"copy":[],
"mappedProperties":{
"SalesOrderId":true,
"CustomerName":true,
"PONumber":true,
"MessageToClient":true}}}
You can see all data starts from "ko_mapping" is KnockoutJs specific. Do I need to manually trim those data in order to make this work? Per information, obviously the same implementation should work in ASP.NET 4.5 but I just haven't try it yet. Many thanks...

Strange Behavior when using jQuery/asp.net WebApi

I have 2 Web API Projects:
Api1 is a testing-Environment for the JavaScript Front-End, but has a API
Back-end(the default ValuesController), also for testing.
Api2 is the "true" Back-end, from which the Experimental JavaScript UI schould pull Data. For Testing, i use the default ValuesController here too, because, i want to have the same Output.
Status Quo
The Api1-UI can query the Data from the ValuesController of the own API
The Api2 returns the Correct Data(tested in Firefox and with Fiddler)
The Code
JavaScript Client:
var _load = function (url) {
$.ajax({
url: url,
method: 'GET',
accepts: "application/json; charset=utf-8",
success: function (data) {
alert("Success: " + data);
},
error: function (data) {
alert("Error :" + data);
}
});
};
WebApi Controller method:
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Problem
The JavaScript UI of the experimental Front-End is not able to display, or even receive, the data from the API 2, which is, according to Fiddler, sent correct.
My first thought was, I am using the wrong Method, but i tried $.getJSON and $.ajax. But i always end up with an error. It just says statusText= "Error"
I don't get, why it can display Data from the own ApiController, but not from the "External"...
Thanks for any Help/Suggestions
You seem to be accessing data from X from a different domain Y using ajax. This seems to be a classic cross domain access issue.
You need to set Access-Control-Allow-Origin to value " * " in your response header.
Response.Headers.Add("Access-Control-Allow-Origin", "*")
There various ways you can solve this
defining this header in IIS
using a actionfilter attribute like below
FilterAttribute
public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null)
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
base.OnActionExecuted(actionExecutedContext);
}
}
Using Attribute on Controller Action
[AllowCrossSiteJson]
public Result Get(int id)
{
//return appropriate result
}

MVC3 Action as a simple web service

How could I use this action as a service?
public class HomeController : Controller {
public string GetSomeValue(){
return "This is some value";
}
}
If I navigate to this URL, http://mysite.com/Home/GetSomeValue, it returns a string, without any html or markup of any kind.
So, what is to keep me from using this method as a service that returned something meaningful, say json, that I could call from anywhere?
And if this is possible, how would I do this (Say from the code behind of another asp.net web site)?
Thanks in advance.
100% Nothing wrong with doing this.
A sample application - NerdDinner - does this very same thing to load dinners restfully.
See http://nerddinner.codeplex.com/SourceControl/changeset/view/70027#874260 for controller and http://nerddinner.codeplex.com/SourceControl/changeset/view/70027#874293 for javascript file ( look for NerdDinner.FindMostPopularDinners )
e.g.
C#
// AJAX: /Search/GetMostPopularDinners
// AJAX: /Search/GetMostPopularDinners?limit=5
[HttpPost]
public ActionResult GetMostPopularDinners(int? limit)
{
var dinners = dinnerRepository.FindUpcomingDinners();
// Default the limit to 40, if not supplied.
if (!limit.HasValue)
limit = 40;
var mostPopularDinners = from dinner in dinners
orderby dinner.RSVPs.Count descending
select dinner;
var jsonDinners =
mostPopularDinners.Take(limit.Value).AsEnumerable()
.Select(item => JsonDinnerFromDinner(item));
return Json(jsonDinners.ToList());
}
JS
NerdDinner.FindMostPopularDinners = function (limit) {
$.post("/Search/GetMostPopularDinners", { "limit": limit }, NerdDinner._renderDinners, "json");
}
This is essentially a RESTful service:
http://www.ibm.com/developerworks/webservices/library/ws-restful/
All you gonna need is to construct the http request to consume this service, you could use Hammock to construct such requests:
https://github.com/danielcrenna/hammock

Resources