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;
}
Related
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 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
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'm using ASP.Net MVC2. I'm trying to open a new window of view when call controller from Jquery Ajax post call.
here is my code..
in ascx page..
$('#DeleteButton').click(function () {
var isLineChecked = $(':checkbox:checked', '#providerSearchResultsTable').length;
if (isLineChecked == 0) {
alert("Please select at least one row ");
return false;
}
else {
var params = {
Id: gaiSelected.join(',')
};
alert(params);
$.ajax({
type: "Post",
url: "SelectProviderAndContact",
data: params,
success: function (html) {
**//$('#SelectProviderAndContact').html(html);**
}
});
}
});
here is my controller Action method
[SessionFilter]
public ActionResult SelectProviderAndContact(string Id)
{
try
{
List<ProviderBaseInfo> providerList = null;
string[] internalProviderIDs = Id.Split(",".ToCharArray());
//string[] billingProviderNames = billingProvider.Split(",".ToCharArray());
IStateBag stateBag = _commonModel.GetStateBag();
//stateBag.SetValue("InternalProviderId", Id);
List<Guid> internalProviderIds = new List<Guid>();
foreach (var a in internalProviderIDs)
{
internalProviderIds.Add(new Guid(a));
}
List<Contacts> providerContactList = _providerModel.GetProviderContactlist(internalProviderIds);
if (providerContactList.Count <= 0)
{
//IStateBag stateBag = GetStateBag();
List<ProviderBaseInfo> providers = (List<ProviderBaseInfo>)stateBag.GetValue(ProviderListCache);
if (providers == null)
{
providerList = _providerModel.GetProviderCompleteList(null, null, null, null, Id).ToList();
}
else
{
providerList = providers.Where(x => internalProviderIds.Contains(x.InternalProviderId)).ToList();
}
providerContactList = _providerModel.GetContactlistbyInsertingProviders(providerList);
}
ViewData["ProviderNotFound"] = false;
// ViewData["ProviderName"] = new SelectList(billingProvider.Select(x => new { value = x, text = x }), "value", "text");
var Provider = new[] {
new { ProviderId = "A", Providername = "A" }
//new DataContracts.RegionKeyValues { RegionId = "B", RegionValue = "B" },
//new DataContracts.RegionKeyValues { RegionId = "D", RegionValue = "D" }
};
ViewData["ProviderName"] = new SelectList(Provider, "ProviderId", "Providername");
**return View("SelectProviderAndContact",providerContactList);**
}
catch (FaultException<MedicareFault> ex)
{
if (ex.Code.Name == typeof(ArgumentException).Name)
{
ViewData["ProviderNotFound"] = true;
ViewData["Error"] = ex.Reason;
return View((object)null);
}
else
{
ViewData["Error"] = Errors.Common.UnknownError;
return View((object)null);
}
}
catch
{
ViewData["Error"] = Errors.Common.UnknownError;
return View((object)null);
}
}
and I have created SelectProviderAndContact.aspx in view.
Please any one help me to open another window with SelectProviderAndContact.aspx
from ajax post call.
Your jquery will have to do almost all the work in opening a window. Using the window.open() method in your javascript you can make your controller send back a specific argument that causes it to open a new window with a certain URL (i.e. TheSmallPopupPageThatViewsResults.aspx?resultId=12345 or something).
Your controller would just decide whether or not to tell the view to open the new window and the view would then open it if it is told to.
With your specific implementation, you may have to create a model or something that stores results in the database so that the controller can save the result and the action and view that are for the popup page can then access that result. Another way of doing it would be to have the arguments that the popup page is called with determine what is viewed on the page. This would eliminate the need for another model, but your urls could get really long really fast if you have a lot of data and I believe that there is generally a limit to how long those urls can be.
I would recommend using JSON or XML to return the data to the javascript so that you can extend the returned object as much as needed. The way I have done it in the past is made several XML tags like <alert>, <refresh>, <redirect>, <somePageSpecificAction>, etc that I have jquery parse using $(theEnclosingTag).each( function () { //...parse here }).
I use mainly MVC3 so I don't know if this is supported in MVC2, but changing ActionResult to JsonResult for the return type and using return this.Json(new { put = "data here" }); for your return statements makes it really easy to use json.
Also, It may be beneficial to use a different action method to process ajax requests. You would then have one action that displays the page, another action to process ajax requests from that page (it could be decorated with [HttpPost] or something), and another method for your popup page view. That would also keep your code short and easier to read. Having long controller methods can get really confusing later down the line when you try to find the location of a specific bug.
EDIT: A specific example: Assuming MVC3 (since that is what I use...I think you can find other ways of doing this...you could use an xml serializer and just output xml) you have your page (Action #1) that displays with all your buttons and stuff (this is where you javascript containing the $("#DeleteButton") and such goes as well). Your javascript makes its AJAX call to another action (SelectContactAJAX or something...this is action #2) with a few arguments which has a return type of JsonResult. Your javascript gets the response back from that ajax-specific action and the response tells it "open a window with the URL /SelectContactForm?choiceId=12345" (or something). The choiceId is a reference that would be used in the background for the SelectContactForm action (yet another separate action...action #3) to know what to display. Your ajax would then call window.open("/SelectContactForm?choiceId=12345") and when the window opens, it calls action #3 which looks up the reference for what it should be displaying and then shows that to the user.
As for getting feedback on what the user entered in the new window and having the original page react, there are various ways in javascript to listen for window closings and such, but I haven't ever had to use this in one of my applications and it is a bit out of the scope of this question.
use colorbox jquery plugin
http://www.jacklmoore.com/colorbox
write code in .aspx page to call .ascx page
parent.$.fn.colorbox({ href: '/IGTR/SaveIGTRPreference/' + id + '?t=' + Math.random(), height: "400", width: "800", overlayClose: false, escKey: false
});
Here SaveIGTRPreference is .ascx page
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.