TempData still available after subsequent http requests? - c#

My textbook says that "TempData gets destroyed immediately after it’s used in subsequent HTTP request", so I write a simple test to verify
below is my code:
// SimpleForm.cshtml is just a simple view that uses a form to send post request to ReceiveForm action method
//Result.cshtml is just a simple view that products an output
public class HomeController : Controller
{
public ViewResult Index() => View("SimpleForm");
[HttpPost]
public RedirectToActionResult ReceiveForm(string name, string city)
{
TempData["name"] = name;
TempData["city"] = city;
return RedirectToAction(nameof(Transfer));
}
public RedirectToActionResult Transfer()
{
string name = TempData["name"] as string;
string city = TempData["city"] as string;
return RedirectToAction(nameof(Data));
}
public ViewResult Data()
{
string name = TempData["name"] as string;
string city = TempData["city"] as string;
return View("Result", $"{name} lives in {city}");
}
}
so when the application runs, it goes to Index() action method first, I fill up the form with name and city and press submit button, then it goes to ReceiveForm() action method, which setup TempData and redirect to Transfer() action method.
In the Transfer() action method, I read TempData, so TempData should get destroyed and unavailable to read in the next http request according to the textbook.
But in the Data(), I find that I can still read TempData, see the screenshot below:
and I checked the chrome dev tool, there was one post request and two get requests, which is all good and correct. so when does TempData actually get destroyed ?
additional code:
SimpleForm.cshtml:
#{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Controllers and Actions</title>
<link rel="stylesheet" asp-href-include="lib/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 p-1">
<form method="post" asp-action="ReceiveForm">
<div class="form-group">
<label for="name">Name:</label>
<input class="form-control" name="name" />
</div>
<div class="form-group">
<label for="name">City:</label>
<input class="form-control" name="city" />
</div>
<button class="btn btn-primary center-block" type="submit">Submit</button>
</form>
</body>
</html>
Result.cshtml:
#model string
#{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Controllers and Actions</title>
<link rel="stylesheet" asp-href-include="lib/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 p-1">
Model Data: #Model
</body>
</html>

For your scenario, this is caused by RedirectToActionResult. For RedirectToActionResult, which is IKeepTempDataResult.
public class RedirectToActionResult : ActionResult, IKeepTempDataResult
SaveTempDataFilter is filter that saves temp data. It will call SaveTempData.
private static void SaveTempData(
IActionResult result,
ITempDataDictionaryFactory factory,
IList<IFilterMetadata> filters,
HttpContext httpContext)
{
var tempData = factory.GetTempData(httpContext);
for (var i = 0; i < filters.Count; i++)
{
if (filters[i] is ISaveTempDataCallback callback)
{
callback.OnTempDataSaving(tempData);
}
}
if (result is IKeepTempDataResult)
{
tempData.Keep();
}
tempData.Save();
}
For SaveTempData, it will check whether IActionResult result is IKeepTempDataResult. If it is, it will keep the tempData.
If you want to avoid keep tempData between request, you could change RedirectToAction to LocalRedirect like
public IActionResult Transfer()
{
string name = TempData["name"] as string;
string city = TempData["city"] as string;
return LocalRedirect("~/Home/Data");
//return RedirectToAction(nameof(Data));
}

Related

POST JSON object to C# ASP.NET WEB API

I'm unable to post JSON object in the body of my HTTP message.
I have even tried this
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
but it still didn't work
Here is my code:
<!DOCTYPE html>
<html lang="en">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="formCtrl">
<form novalidate>
type:<br>
<input type="number" ng-model="config.type"><br>
Id:<br>
<input type="text" ng-model="config.Id">
<br><br>
<button ng-click="submit()">Submit</button>
</form>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope,$http) {
$scope.config = {type:0, Id:"786"};
$scope.submit = function() {
var data = $scope.config;
//$http.post("http://localhost:8612/api/values/", data);
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "http://localhost:8612/api/values/", true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send(JSON.stringify(data));
alert("Done");
};
});
</script>
</body>
</html>
I am getting an error that is my object is empty in this method
// POST api/values
[HttpPost]
public IActionResult Post([FromBody]object c)
So the deserialization is failing probably. having the request model as object type is wrong; instead you should define a proper request model and use that rather like
public class ApiRequestModel
{
// define all your required properties client going to send
}
[HttpPost]
public IActionResult Post([FromBody] ApiRequestModel c)

Cant find page after POST redirect C#

I go to a View, submit data via POST, but the redirect cannot find the Controller method. What am I doing wrong here? After submitting the form I get:
404 error: cannot find page. URL is: http://localhost:52008/InternalController/UpdateCardFormPost
Snippet from InternalController.cs:
public ActionResult UpdateCardFormView()
{
var CardToUpdate = new CardView();
return View(CardToUpdate);//return implementation of Cards.cshtml with the empty model that was passed to it
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UpdateCardFormPost(CardView c)
{
CardModelIO.WriteCard(c);//#TODO: IMPLEMENT
return View("CardDetailView", c);
}
UpdateCardFormView.cshtml (the view with the form I am submitting):
#using LeanKit.API.Client.Library.TransferObjects
#model CardView
<!DOCTYPE html>
<html>
<!--Form used to change a card
STARTING DISPLAY called by in Internal/UpdateCardFormView
ENDING DISPLAY (post) called by UpdateCardForm in InternalController a specified below-->
<head>
</head>
<body>
#Html.BeginForm("UpdateCardFormPost", "InternalController", FormMethod.Post)
#Html.TextBoxFor(c => c.AssignedUserName);
<input type="submit" value="Submit Changes" />
</body>
</html>
Heres the CardDetailView.cshtml (the view I should be redirected to):
#using LeanKit.API.Client.Library.TransferObjects
#model IEnumerable<CardView>
<!--used for displaying an individual card in detail view
referenced in UpdateCardFormPost() method of Internal controller-->
<!DOCTYPE html>
<html>
<head>
</head>
<body>
CardView j = Model;
<p>j.AssignedUserId</p>
</body>
</html>
You've specified the controller name as InternalController but it's probably just called "Internal".
Try changing
#Html.BeginForm("UpdateCardFormPost", "InternalController", FormMethod.Post)
to
#Html.BeginForm("UpdateCardFormPost", "Internal", FormMethod.Post)
you are missing closing form tag
you should do it like
using (#Html.BeginForm("UpdateCardFormPost", "InternalController", FormMethod.Post))
{
...
}
#using(Html.BeginForm())
{
#Html.TextBoxFor(c => c.AssignedUserName);
<input type="submit" value="Submit Changes" />
}

sending integer value from controller to view in asp.net mvc

i am trying to send integer value from controller to view and display in view multiplied by 0.2
here the code in controller
public ActionResult Details()
{
ViewBag["salary"] = 1400;
return View();
}
and the code in view
#{
Layout = null;
int Salary=int.Parse(#ViewBag["salary"]);
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
</head>
<body>
<div>
<p> Salary: </p>
#(Salary / 0.2)
</div>
</body>
</html>
but it throw an exception
An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in
System.Core.dll but was not handled in user code
Additional information: Cannot apply indexing with [] to an expression
of type 'System.Dynamic.DynamicObject'
Use this code in your controller:
ViewBag.salary = 1400;
And this code in your view:
int Salary=(int)ViewBag.salary;
Using the ViewBag is typically considered poor practice, I'd recommend using a Model (Model-View-Controller). I'll also mention that adding logic in the view is also poor practice, your logic should be controlled by the Controller.
Model
public class DetailsVM
{
public decimal Salary { get; set ; }
}
Controller (method)
public ActionResult Details()
{
var model = new DetailsVM();
model.Salary = 1400 / 0.2;
return View(model);
}
View
#model DetailsVM
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
</head>
<body>
<div>
<p> Salary: </p>
#Model.Salary
</div>
</body>
</html>

How to render partial Views Sections in main layout file

I am using asp.net MVC 5.
I have a _layout file as follows:
<!DOCTYPE html>
<html lang="en">
<head>
#Html.Partial("_Head")
#RenderSection("styles", required: false)
</head>
<body>
#RenderBody()
#RenderSection("scripts", required: false)
</body>
</html>
I then have my main view:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
Title = "mainView"
}
<div class="partialcontents" data-url="/user/UserList"></div>
$(document).ready(function (e) {
$(".partialcontents").each(
function (index, item) {
var url = $(item).data("url");
if (url && url.length > 0) {
$(item).load(url, function () { $(this).css("background-image", "none"); });
}
}
);
});
and here is my partial view:
#{
}
<p> Partial View</p>
#section scripts
{
#Scripts.Render("~/bundles/script/datatable")
}
My controller that renders the partial view:
[Authorize]
public ActionResult UserList(UsersViewModel model, string sortOrder, string currentFilter, string searchString, int? page)
{
.... removed for simplicity
return PartialView("_UserList", new UsersViewModel()
{
Users = users.ToPagedList(pageNumber, pageSize)
});
}
So now my issue is that the section from the partial view is not being rendered in the main layout file. I understand why I think...
Its because I am rendering it in another controller. and then passing it along.
But how can I get this right?
You should place:
#section scripts
{
#Scripts.Render("~/bundles/script/datatable")
}
in the main view, because AJAX requests return only partial without layout so MVC doesn't know where it should render this script section. In this AJAX request #RenderSection("scripts", required: false) is never evaluated. If you omit #section scripts in your partial, it would probably work, but this is a not good approach to include JS in AJAX responses, so don't do that if you don't need too.

Verifying credentials without leaving view

I've got a user login page that I want to check if the inserted credentials are correct. Here's the controller:
EDITED:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SustIMS.Models;
namespace SustIMS.Controllers
{
public class MainController : Controller
{
public static string userName;
public static string password;
public ActionResult Index()
{
getCredentials();
if (Authenticate(userName, password))
{
return View();
}
ModelState.AddModelError("", "Could not authenticate");
return Redirect("../Home");
}
public void getCredentials()
{
if (Request["username"] != null && Request["password"] != null)
{
userName = Request["username"].ToString();
password = Request["password"].ToString();
}
}
}
}
Everything is OK if the inserted credentials are correct: the Authenticate function verifies that and if it returns true, the ActionResult returns the View.
However, if they are not correct, I set it to return null, going to a blank page instead.
I want it to stay on the same page and display some kind of message to the user informing that the credentials aren't correct (by showing a div, calling a javascript function with a popup window, ..., I don't really know).
How can I do that?
Thanks
EDIT
Here's my view:
#{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>#ViewBag.Message</title>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
</head>
<body>
<div id="outer-limit">
<div class="logo-main">
<a href="../Home/Index">
<img class="displayed" src="../../Images/logo.png" alt="SustIMS" /></a>
</div>
<section class="container">
#Html.Partial("LoginForm")
#Html.ValidationSummary()
</section>
</div>
</body>
</html>
And the partial LoginForm:
<div class="login">
<h1>#ViewBag.Login</h1>
<form method="get" action="../Main/Index">
<p>
<input type="text" name="username" value="" placeholder="Username" maxlength="30"></p>
<p>
<input type="password" name="password" value="" placeholder="Password" maxlength="25"></p>
<br />
<br />
<p class="submit">
<input type="submit" onclick="Refresh()" name="commit" value="Login"></p>
</form>
</div>
doing something like this should work. Checking the ModelState can/will validate required fields via Attributes on your view (if you've implemented them) otherwise you can manually take care of that in your action. Adding the model error before returning the view will cause the error to display on the page after it reloads. Upon successful authentication, we're redirecting to another Action.
public ActionResult Index(LoginModel model) {
if (ModelState.IsValid) {
if (AuthenticateUser(model)) {
return RedirectToAction("LoggedIn");
} else {
ModelState.AddModelError("", "Could not authenticate"); //or better error
}
}
return View(model);
}
be sure to also add an #Html.ValidationSummary() to your view to display the error message returned.
You should be able to tailor the above code to work with your methods, but ultimately, I would recommend strongly typing your views and passing a model back through a post.
EDIT
Redid my example based on your code above, rather than a general example
public ActionResult Index()
{
getCredentials();
if (Authenticate(userName, password))
{
return View();
}
ModelState.AddModelError("", "Could not authenticate");
return View();
}
My recommendation would be to RedirectToAction after successful authentication, rather than returning the same view.
One way to do that :
public ActionResult Index(string url)
{
getCredentials();
if (Authenticate(userName, password))
{
return View();
}
else
{
TempData["ErrorMessage"]="Wrong Credential";
return Redirect(url);
}
}
}
Then in your view Show the message in a div if it's not empty
#if(TempData["ErrorMessage"] != null)
{
<div class='alert alert-success alert-dismissable' >
#TempData["ErrorMessage"]
</div>
}
I believe you have wrong idea how ASP.NET MVC works. If you submit form, the whole new request is made, and its response will be shown - that is, you must return something to display back yo user - typically view. Never return as ActionResult. In your case, you return View() in both cases, just give it different data to display - succeess message or error message for example. You can use strongle typed model for this, or ViewBag - take a look at some tutorials.
If you want user to "stay on the same page" if credentials are wrong, you need some kind of ajax validation. ASP.NET MVC supports this also, take a look at http://msdn.microsoft.com/en-us/library/gg508808(vs.98).aspx

Categories

Resources