I have a pretty simple controller:
public class HomeController : Controller
{
public ActionResult Index()
{
Session["SomeData"] = "123";
return View();
}
[HttpPost]
public ActionResult LongTest()
{
Thread.Sleep(5000);
return Json(new { Text = DateTime.Now.ToString("hh:mm:ss.fff") + " - LongTest"});
}
[HttpPost]
public ActionResult CantAnswer()
{
return Json(new { Text = DateTime.Now.ToString("hh:mm:ss.fff") + " - CantAnswer"});
}
}
I use these methods from the client's side this way:
<script type="text/javascript">
$(document).ready(function () {
$('#btnLongOperation').click(function () {
$.post("/Home/LongTest", null, function (data) {
$('#result').text(data.Text);
}, "json");
});
$('#btnWnotWork').click(function () {
$.post("/Home/CantAnswer", null, function (data) {
$('#result').text(data.Text);
}, "json");
});
});
</script>
<div>
<input id="btnLongOperation" type="button" value="Long operation"/>
<input id="btnWnotWork" type="button" value="Won't work"/>
</div>
<div id="result">
If I click the first button and then without waiting for 5 seconds click the second button my second action won't be called.
If you remove the string with using session in the Init method you will see that the actions are able to be called without waiting for each other. However, once you use session object you will not see a result of second action untill the first one is finished.
Can anyone explain this behavior of asp.net mvc?
This is caused by session locking. In essence, each request that uses session state places a lock on it until it is done reading. Subsequent requests cannot access Session until the previous request releases the lock.
The purpose of this is to ensure the integrity of session data. For example, what would happen if the request A needed to write to the session and request B needed to read, but the requests were issued simultaneously? The data that B reads is unpredictable as you have no way of knowing whether it will be pre or post-write.
Read here for more information:
http://www.timvasil.com/blog14/post/2008/04/16/Handling-multiple-simultaneous-requests-from-a-user-in-ASPNET.aspx
You can use asynchronous controllers for different behaviour. See this link
Related
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;
}
I am working on a logout feature in the application we are using ASP.NET Identity login. I can login successfully but when I logout and then try to login again I get the following message:
The provided anti-forgery token was meant for a different claims-based user than the current user.
Here is my logout code:
public ActionResult Logout()
{
SignInManager.Logout();
return View("Index");
}
**SignInManager.cs**
public void Logout()
{
AuthenticationManager.SignOut();
}
After the user press the logout button he is taken to the login screen. The url still says "http://localhost:8544/Login/Logout". Since we are on the login screen maybe it should just say "http://localhost:8544/Login".
What worked for me was switching the order of the middlewares used. Add first app.UseAuthentication() and then the antiforgery stuff. This is how I did it:
app.UseAuthentication();
app.Use(next => ctx =>
{
var tokens = antiforgery.GetAndStoreTokens(ctx);
ctx.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
return next(ctx);
});
Doing it the other way around creates a token that is not meant for authenticated users.
You are returning a View, rather than calling RedirectToAction(). So what is happening is the view is running under the context of the logout request, where the user is still logged in. They won't be logged out until the request finishes.
So, try
public ActionResult Logout()
{
SignInManager.Logout();
return RedirectToAction("Index", "Home");
}
I found that users were experiencing this issue when they would submit the login page when already authenticated. I replicated this error by:
Opening two tabs when logged in,
Logging out from one,
Reloading both,
Logging in to one,
Trying to log in with the other. The error occurred before entry to the POST: /Account/Login action.
The majority of my users use the web app on a mobile device, so it made sense that they had bookmarked the login page and pulled it up and submitted when they had a tab opened in the background already logged in. I also surmised that sometimes they would have a dormant tab loaded with the login form and just pull that tab up and submit.
I realize that there are many ways to solve this issue. I solved this with two changes:
I added a check on User.Identity.IsAuthenticated to my "GET: /Account/Login" action:
if (User.Identity.IsAuthenticated)
{
try
{
return RedirectToLocal(returnUrl);
}
catch
{
return RedirectToAction("index", "Home");
}
}
In my controller I created a "check if logged in" action:
[AllowAnonymous]
public JsonResult CheckLogedIn()
{
try
{
return Json(new { logged_in = User.Identity.IsAuthenticated }, JsonRequestBehavior.AllowGet);
}
catch
{
return Json(new { logged_in = false }, JsonRequestBehavior.AllowGet);
}
}
And I called it repeatedly in the view to redirect all open login forms away from the login page when already logged in:
<script type="text/javascript">
setInterval(function () {
$.ajax({
url: '#Url.Action("CheckLogedIn", "Account")',
type: "GET",
}).done(function (data) {
if (data.logged_in) {
window.location = '/';
}
});
}, 5000);
</script>
This worked well for me. Hope it helps you.
Try this:
public ActionResult Logout()
{
AuthenticationManager.SignOut();
Session.Abandon();
return RedirectToAction("Index");
}
That will reload your login page which will provide you a new CSRF token.
I've been getting this same error on the login for a LONG time now, but haven't been able to work out why. Finally I found it, so I'm posting it here (although it's a slightly different cause) in case someone else has it.
This was my code:
//
// GET: /login
[OutputCache(NoStore = true, Location = System.Web.UI.OutputCacheLocation.None)]
public ActionResult Login()
{
return View();
}
//
// POST: /login
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
if (!ModelState.IsValid)
{
return View(model);
}
//etc...
This worked fine for 99.99% of the logins, but every now & then I got the above-mentioned error, although I couldn't reproduce it, until now.
The error only happens when someone clicks the login button twice in quick succession. However, if I remove the AuthenticationManager.SignOut line in the Login action, then it's fine. I'm not sure why I put that line in there, but it's causing the issue - and removing it fixes the problem.
I didn't have the AuthenticationManager.SignOut command as Sean mentioned in my Login method. I was able to reproduce by clicking on the login button more than once before hte next View loads. I disabled the Login button after the first click to prevent the error.
<button type="submit" onclick="this.disabled=true;this.form.submit();"/>
Try this:
public ActionResult Login(string modelState = null)
{
if (modelState != null)
ModelState.AddModelError("", modelState );
return View();
}
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model)
{
AuthenticationManager.SignOut();
return RedirectToAction("Login", "Controller", new { modelState = "MSG_USER_NOT_CONFIRMED" });
}
I have an action Create and method Save. The create action simply displays the view as shown below
public ActionResult Create()
{
return view();
}
In create view, i get few fields like Name, Address etc. Once the user enters the data and click on the save button, i call the Save method using ajax call.
In Save method, i am validating the data:
[HttpPost]
public bool Save(UserModel User)
{
if (ModelState.IsValid)
{
// save the data
return true;
}
else
{
return false;
}
}
the response of this method is used in ajax of Create View:
$(function () {
$("#btnSave").click(function () {
$.ajax({
type: "POST",
contentType: "application/json;charset=utf-8",
url: "/Contoller/Save",
data:
// parameters
async: false,
success: function (data_l) {
if (data_l == true)
{
alert("record has been saved in database");
window.location.href = "#Url.Action("Index","Controller")";
}
else{
alert ("Invalid Entry");
window.location.href = "#Url.Action("Create","Controller")";
}
},
error: function () {
console.log("there is some error");
}
});
});
What i want to achieve is based on the response from the Save method, i should perform two operation. If the data is properly validated, data is saved and index page is loaded back. If validation is failed, i want to display the create view with fields entered along with validation messages.
I am sure that data annotation attributes are properly used in the model. Can someone please help me in solving this.
I suggest you perform validation on server side i.e. in your controller. Perform input validation first, if all fine, save the data on persistent store. If the data is saved successfully load index view or list view (whichever view you want by either return View() or RedirectToResult()). If there are any problem add an error in the ModelState object and return the same view. At client the error will be displayed along with the data already entered by user.
For an example refer below code snippet (there might be other ways however this is what we use at the moment):
public ActionResult AddNewSearch(SearchViewModel searchModel)
{
if (User.Identity.IsAuthenticated)
{
if (ModelState.IsValid)
{
var organizationUser = this.GetUser();
if (organizationUser == null)
{
ModelState.AddModelError("RecordNotFound", string.Format("No organization user found!!!"));
return RedirectToAction("LogOff", "Account");
}
if (this.searchService.GetUserSearch(organizationUser.Id, searchModel.Name) != null)
{
ModelState.AddModelError("RecordAlreadyExists", string.Format("Search already exists!!!"));
return View(searchModel);
}
var userSearchDomainModel = mappingService.Map<SearchViewModel, Search>(searchModel);
searchService.AddUserSearch(organizationUser, userSearchDomainModel);
return RedirectToAction("Index", "Search");
}
}
return RedirectToAction("Index", "Search");
}
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.
I am trying to use an Ajax (I think) call to update my model value and then have that new value reflected in the view. I am just using this for testing purposes for the moment.
Here's the overview:
MODEL
public class MyModel
{
public int Integer { get; set; }
public string Str { get; set; }
}
CONTROLLER
public ActionResult Index()
{
var m = new MyModel();
return View("Test1", m);
}
[HttpPost]
public ActionResult ChangeTheValue(MyModel model)
{
var m = new MyModel();
m.Str = model.Str;
m.Str = m.Str + " Changed! ";
m.Integer++;
return View("Test1", m);
}
VIEW
#model Test_Telerik_MVC.Models.MyModel
#using Test_Telerik_MVC.Models
#{
ViewBag.Title = "Test1";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Test1</h2>
#if (false)
{
<script src="~/Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery-ui.min.js" type="text/javascript"></script>
}
<h2>
ViewPage1
</h2>
<div>
<input type="button" onclick="changeButtonClicked()" id="changeButton" value="Click Me!" />
<input type="text" value="#Model.Str" class="txt" id="str" name="Str"/>
<div></div>
</div>
<script type="text/javascript">
function changeButtonClicked() {
var url = '#Url.Action("ChangeTheValue", "Test1")';
var data = '#Model';
$.post(url, data, function (view) {
$("#Str").value = '#Model.Str';
});
}
</script>
Basically the view renders a button with a textbox. My sole aim is to simply display the value of my model (Str property) in the textbox.
I have tried various combinations of the changeButtonClicked() function to no avail. Test1 is the name of my controller. What I don't understand is when I debug it, the controller action fires and sets my values correctly. If I place a breakpoint on the "#Model.Str" section of the input tag, it shows me that my #Model.Str is equal to Changed! which is correct. However, as soon as my success function fires in the javascript, the value reverts back to it's original value.
I can make it work by changing the input type to submit and wrapping it in a #Html.BeginForm() section but I am wondering if/how to do it like this? Or is a Submit the only way to accomplish it?
Thanks
First thing in the jQuery the proper way to set a value of an input is to use:
$("#Str").val(#Model.Str);
Next we'll look at the controller. In the POST action result you are returning the entire View in your AJAX call. That means all the HTML, script references, and JavaScript are being returned in your jQuery post request. Since all you are trying to update is the value of the input named Str, I would just return that value as JSON and nothing else.
[HttpPost]
public ActionResult ChangeTheValue(MyModel model)
{
var m = new MyModel();
m.Str = model.Str;
m.Str = m.Str + " Changed! ";
m.Integer++;
return Json(m.Str);
}
Next I would place your HTML inputs in a <form> so you can have jQuery serialize your model for you and then you can change your jQuery post code to be:
function changeButtonClicked() {
var url = '#Url.Action("ChangeTheValue", "Test1")';
$.post(url, $('form').serialize(), function (view) {
$("#Str").val(view);
});
}
All the serialization is doing is encoding the inputs in your form into a string and if everything is named properly ASP.NET will bind that back to your model.
If you need to have your route handle both AJAX calls and full requests you could use ASP.NET's IsAjaxRequest function to test the request and return different results depending on if the request is AJAX or not. You would do something like this in your controller:
[HttpPost]
public ActionResult ChangeTheValue(MyModel model)
{
var m = new MyModel();
m.Str = model.Str;
m.Str = m.Str + " Changed! ";
m.Integer++;
if (Request.IsAjaxRequest) {
return Json(m.Str);
}
else {
return View("Test1", m);
}
}
In the ActionResult above you are doing everything you did before, but now are testing the request type and if it's AJAX you return a JSON result of your string value. If the request was not from an AJAX call then the full View (HTML, scripts, etc) are returned to be displayed in the browser.
I hope this is helps you out and is what you were looking for.
You can update the view, just not the model. The model in a razor page is compiled on the server in order to render the view; you would need to recompile the razor page after every ajax request.
Only real option is to return json from server and manually update DOM/View.