Why are form field entries disappearing after submitting? - c#

I have form that a user submits. For some reason, in our production environment when they click submit, the form is cleared and they lose all that they filled in when they click submit. I've been lost on this for awhile so any input would really help.
I've tried on multiple browsers and this occurs on each one (Chrome, Safari).
The first action creates the view model for the form, the second one takes that data/model and submits an application:
[Authorize(Roles = "Applicant")]
public IActionResult Application()
{
var vm = _applicantManager.GetApplicationViewModel(CurrentUser.UserName);
return View(vm);
}
[HttpPost]
//[ValidateAntiForgeryToken]
[Authorize(Roles = "Applicant")]
public IActionResult Application(ApplicationViewModel model)
{
var saved = false;
model = _applicantManager.RebuildApplicationViewModel(model);
if (ModelState.IsValid)
{
saved = _applicantManager.SubmitApplication(model, CurrentUser.UserName);
return RedirectToAction("Index");
}
return View(model);
}
I'm thinking if I remove the ValidateAntiForgeryToken that maybe this would solve issue, but am not sure.
Is it possible that the first action needs a route? On testing, it goes to the action with the HttpPost attribute when I click submit on my local environment.
Note: This is a problem on the production server, this does not occur on the local. This leads me to believe that may a possible IIS setting that needs changed?
JavaScript on the Page:
<script>
require(['jquery', 'jqueryui'], function($, jqueryui) {
$(function() {
$("#preHealthAreaDiv input:radio").click(function () {
var selectedVal = $("#preHealthAreaDiv input:radio:checked").val();
if (selectedVal == "Yes") {
$("#preHealthAreaDetail").show();
}
else {
$("#preHealthAreaDetail").hide();
}
})
});
});
</script>

Related

Action called from both the same and other controller via RedirectToAction()

I'm integrating an online payment platform inside a currently existing eCommerce platform. This is a B2B platform and customers may pay beforehand (using the aforementionned online payment plaform) or place their orders and are billed at the end of the month.
The following action is located in OrdersController and contains the logic that is triggered once the customer want to confirm its order:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Resume(ResumeViewModel viewModel)
{
if (!_orderService.CanConfirmOrder(UserId, viewModel.TemporaryOrderId))
{
return Resume(viewModel.TemporaryOrderId, _settingsRepository.OrderTooLateMessage());
}
if (_customerService.MustPayDirectly(PaniproId))
{
return RedirectToAction("Initialize", "Payments", new { orderId = viewModel.TemporaryOrderId, amount = viewModel.OrderViewModel.Total });
}
return RedirectToAction("Confirm", new { id = viewModel.TemporaryOrderId });
}
The PaymentsController is responsible for redirecting to the Confirm action if the payment has been successful on the remote payment platform:
public ActionResult Finalize(int orderId)
{
var finalizeResult = _saferpay.FinalizeTransaction((string)_tokens[orderId.ToString()]);
if (finalizeResult.IsSuccess)
{
return RedirectToAction("Confirm", "Orders", new { id = orderId });
}
else
{
LogTools.AddLog(finalizeResult.Error.ToString());
return RedirectToAction("Resume", "Orders", new { id = orderId, errorMessage = finalizeResult.Error.Description });
}
}
So ultimately, both "flows" end in the action below which finally confirms the order and redirect to display its confirmation:
public ActionResult Confirm(int id)
{
return HandleResult(_orderService.ConfirmOrder(UserId, id), order => RedirectToAction("Confirmation", new { id = order.ID }));
}
My worry about the action above is twofold:
I am modifying state in a GET action which should be a stateless operation
Invoking manually the URL /Orders/Confirm/{id} would bypass the payment and is also a very bad thing for the business
Ideally I'd like to do something like adding attributes [HttpPost] and [ValidateAntiForgeryToken] to this action but then it becomes impossible to do a redirect to it. How can I solve this problem ? Is my design flawed and needs a refactor ?
I suggest moving the actual task of confirming the order outside the "confirm" action. That action should simply be for displaying some UI in response to a successful confirmation.
Instead put the logic in some other class which you can call from the other flows directly. Once those flows know that confirmation has been recorded in the database they can redirect to the "confirm" action just to display a message to the user.
And that way if someone decides to visit the "confirm" URL manually then it will just display an incorrect message to them, but won't actually do anything harmful.

Viewmodel binding for Edit View

Having some trouble with the "GET" part of the Edit View, and can't really find anything online. So far, this is my POST section:
[HttpPost]
public ActionResult Edit(ContactsEditViewModel viewModel)
{
if (ModelState.IsValid)
{
var Contacts = TheContactContext.tblContacts.Find(viewModel.ID);
Contacts.Company = viewModel.Company;
Contacts.Contact = viewModel.Contact;
Contacts.Contact2 = viewModel.Contact2;
Contacts.Email1 = viewModel.Email1;
Contacts.Email2 = viewModel.Email2;
Contacts.IsSupplier = viewModel.IsSupplier;
Contacts.Telephone = viewModel.Telephone;
Contacts.Link = viewModel.Website;
Contacts.Notes = viewModel.Notes;
TheContactContext.SaveChanges();
return RedirectToAction("~/Contacts/Index");
}
return View(viewModel);
}
I've only ever done this using EntityFramework and letting it scaffold everything, so it's the first time using Viewmodels to do it personally.
Any help in whether my POST action is correct, and some guidance on the GET action would be appreciated :)
I believe you're on the right track with POST. GET is much more simplier:
public ActionResult Create()
{
return View(new ContactsCreateViewModel() { ... your initial settings, may be contained within constructor of view model directly ... });
}
The GET request requests server to provide empty form, which is filled by user, and filled data are sent back via POST, and processed within your provided function.
EDIT
If you are talking about Edit, then it is similar with one more step:
public ActionResult Edit(int id)
{
var data_model = TheContactContext.tblContacts.Get(id); // get model probably from database
var view_model = new ContactsCreateViewModel() {
Company = data_model.Company,
...
}; // copy all data into view model
return View(view_model); // and display view
}
When your page loads for a first time it sends GET request and retrieves model with collection of items from Db. After you've updated some of these items your app sends post request (most likely using Ajax) containing json data. You update your database in controller method and now it's time to refresh your page data. Simplest way is to use ajax.
$.ajax({
url: "http://" + location.host + "/CTRL/Action",
type: "POST",
data: yourdata,
}).done(function (html) {
location.reload(); (or other method to update UI)
}).fail(function (err) {
alert(err.statusText);
});
It's for client side. Server side is smth like :
lock (obj)
{
try
{
update database here...
}
catch(Exception ex)
{
return new HttpStatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable, ex.Message);
}
return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK, "Update completed");
}

AngularJs $http.post does not render new View

I'm having this AngularJs http.post-function. In my view I have a list of Id:s. And when I click on any of these Id:s this function gets hit.
$scope.GoToSinglePost= function (id) {
console.log(id);
if (confirm("Go to blogPost with " + id + " ?")) {
$http.post("/Home/SingleBlogPost", { postId: id });
}
};
Everything works fine here and the post gets me to my mvc c# actionresult witch is this one below:
public ActionResult SingleBlogPost(int? postId)
{
var singlePost = _repo.GetSinglePost(postId);
var viewModel = new SingleBlogPostViewModel()
{
Post = singlePost
};
return View(viewModel);
}
In this ActionResult the postId i've sent from my angularPost gets in to the parameter int? postId
And the actionResult starts to render and it's if I debug it, I can see that it will go in to its view (SingleBlogPost.cshtml)
But what happens if I watch it in my browser, everything stays the same. I'm still in the same view where I was from the beginning. Why doesn't my SingleBlogPost Actionresult-view renders in the browser? Why I'm I still on the same page even though the SingleBlogPost-method gets hit and goes thuogh it's view when I debug?
//Thanks
try adding a success or then once the operation is complete to perform further actions there, something like:
$http.post("/Home/SingleBlogPost", { postId: id }).success(function(data) {
//data is your response from server to the request;
//do something with data here, like changing to correct view;
});

RedirectToAction and HTML.ActionLink pages in browser history

I am using the following code in global.asax, in MVC 4, to restrict pages from being in browser history and it works as expected:
protected void Application_BeginRequest()
{
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetAllowResponseInBrowserHistory(false);
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
Response.Cache.SetNoStore();
}
However, pages that are loaded via RedirectToAction and #Html.ActionLink are still accessible via the browser history. For example, my login code in the controller validates the user and if valid redirects to an action called Cycles which returns a view (Constants.cyclesActionName="Cycles"):
[Authorize]
public ActionResult SubmitLogin(LoginViewModel model)
{
Login userLogin = new Login();
bool loginSuccessful = userLogin.LoginUser(Constants.domainName, model.UserName, model.Password);
if (string.IsNullOrEmpty(model.Password) || (string.IsNullOrEmpty(model.UserName)))
{
return RedirectToAction(Constants.loginActionName);
}
if (loginSuccessful)
{
return RedirectToAction(Constants.cyclesActionName);
}
else
{
ViewData[Constants.messageBoxIndex] = Constants.loginFailureString;
return View(Constants.loginActionName, model);
}
}
Here is the Cycles action:
[Authorize]
public ActionResult Cycles()
{
SortOrder initSortOrder = new SortOrder();
System.Web.HttpContext.Current.Session[Constants.sortOrderSessionName] = initSortOrder;
List<Cycle> Cycles = new FileMethods().GetCycles(ConfigurationManager.AppSettings[Constants.cycleFileLocationFromWebConfig]);
CycleViewModel model = new CycleViewModel();
model.Cycles = Cycles.OrderByDescending(x => x.StartDate).ToList();
System.Web.HttpContext.Current.Session[Constants.cycleSessionName] = Cycles;
return View(Constants.cyclesActionName, model);
}
And here is the code in my view:
#Html.ActionLink("Return to Cycle List", "Cycles", "NSQIP")
I've seen plenty of posts about hiding browser history in general but none that address this issue with RedirectToAction or Html.ActionLink.
My question is, how can I restrict those views from the browsing history as well?
Thanks very much!!!

Asp.Net MVC - Blank model not returning blank data

I have a form which a user can fill in x times with the data they want too. The form is posted to the following Action.
[HttpPost]
public ActionResult Manage(ProductOptionModel DataToAdd)
{
if (!ModelState.IsValid)
{
return View(DataToAdd);
}
var ProdServ = new ProductService();
if (DataToAdd.ID != 0)
{
//Edit Mode.
DataToAdd = ProdServ.EditProductOption(DataToAdd);
ViewData["Message"] = "Option Changes Made";
}else
{
//Add
DataToAdd = ProdServ.AddProductOption(DataToAdd);
ViewData["Message"] = "New Option Added";
}
var RetModel = new ProductOptionModel() {ProductID = DataToAdd.ProductID};
return View(RetModel);
}
So at the bottom I blank the model (Leaving just the required field) and then return to the view. However the view holds the data from the previously submitted form.
Any ideas why? I have debugged the code and checked that the RetModel variable is empty.
Html helpers work this way when a view is returned on HTTP POSTs. They prefer post data over model values.
Use Post/Redirect/Get
That's why I suggest you use the Post/Redirect/Get pattern that's very well supported in Asp.net MVC. Your controller actions should redirect to some GET action after POST has successfully completed as it is in your case.
public ActionResult Process()
{
return View(new Data { Id = -1 });
}
[HttpPost]
public ActionResult Process(Data data)
{
if (!this.ModelState.IsValid)
{
return View(data);
}
new MyService().ProcessData(data);
return RedirectToAction("Process");
}
And if you display all previously entered data you can provide those in in the GET action or transfer them from POST to GET action using TempData dictionary.
This is because the build in input helpers will look at the posted data first and use those values if they exist. Then it will look at the model.

Categories