Verifying credentials without leaving view - c#

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

Related

How to retain search results when clicking browser's back button - ASP.NET Core MVC

I am creating an ASP.NET Core 3 MVC application that has a Customers tab in addition to the Home tab. On the Customers tab there is an input box where the user adds a search criterion (number of days) and a Search button. When the button is clicked then a list of Customer Ids is shown underneath (using jQuery and a Partial View). When the user clicks on a customer Id then the customer information is shown in a different page. However when I click on the browser's back button or on the 'Customers' tab then the criterion added and the search results disappear.
I have tried using the ResponseCache attribute to retain the search results but I could not make it work. I have also tried using the Cache Tag Helper but again was not successful. Anyone can help?
CustomersController
public class CustomersController : Controller
{
private readonly DbContext _context;
public CustomersController(DbContext context)
{
_context= context;
}
public IActionResult Index()
{
return View();
}
public IActionResult DisplayCustomerIdList(string searchText)
{
List<CustomerDetailViewModel> customers = _context.GetAll().ToList();
CustomerIndexViewModel model = new CustomerIndexViewModel()
{
Customers = customers
};
return PartialView("_CustomerIdListView", model);
}
public IActionResult Detail(decimal? Id)
{
Customer customer = _context.GetCustomerById(Id);
CustomerDetailViewModel model = new CustomerDetailViewModel(customer);
return View(model);
}
}
Index.cshtml
#{
ViewData["Title"] = "Customers Page";
}
#section Scripts {
<script type="text/javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
<script>
var url = '#Url.Action("DisplayCustomerIdList", "Customers")';
$('#search').click(function () {
var keyWord = $('#NumberOfDays').val();
$('#searchResults').load(url, { searchText: keyWord });
return false;
})
</script>
}
<body>
<div class="input-group mb-3 w-50">
<input type="text" class="form-control mr-2" placeholder="Number of days" autocomplete="off" id="NumberOfDays">
<button id="search" class="btn btn-outline-info mb-2">Search</button>
</div>
<div id="searchResults"></div>
</body>
_CustomerIdListView.cshtml
#model MyProject.Models.CustomerIndexViewModel
<div class="card border-info mb-3 shadow" style="width:220px; height: 625px; overflow-y: scroll;">
<div class="card-header">Customer Ids</div>
<div class="list-group">
#foreach (CustomerDetailViewModel customerdetails in Model.Customers)
{
<a asp-controller="Customers" asp-action="Detail" asp-route-id="#customerdetails.CustomerId" class="list-group-item list-group-item-action">
#customerdetails.CustomerId
</a>
}
</div>
</div>
Detail.cshtml
#model MyProject.Models.CustomerDetailViewModel
<h3>Customer Information</h3>
<ul>
<li>#Model.CustomerId</li>
<li>#Model.FullName</li>
</ul>
Do the search via a GET request (rather than post). That way, the actual URL the user is sent to includes the query.
<form action="/foo" method="get">
I have figured out why this was not working and thought to add it here in case someone else has the same issue.
It turns out that the jQuery .load() method creates a POST request when the input parameter is an object (and a GET request when it is a String). So, because the ResponseCache attribute does not work with POST requests, the caching was not working.

TempData still available after subsequent http requests?

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));
}

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" />
}

Post from IFRAME and Return to Partial View in Parent

My apologies if this is a dupe, but I haven't been able to find an exact solution to my problem.
Goal:
Upload a file, do work, return results. Easy right?
Problem:
I've been working on this for a few days without any luck. I've tried XmlHttpRequest, but due to the browser limits (can't get away with forcing end-users and clients to use IE10 or higher) that doesn't seem to be an option.
What I've spent most of my time on is uploading via iframe. I've gotten the upload piece working fine. What I need to happen is after doing work with the file, results should be returned back to the parent window and a partial view.
----------------------Index--------------------
Partial View Data Entry----Partial View Results
-----Upload iframe----------Results from file--
Here's what I have for code:
DataEntry.cshtml
<div>
...textboxes, radiobuttons, etc...
<iframe id="uploadFrame" class="seamless" frameborder="0" src='#Url.Action("UploadFile")'></iframe>
</div>
UploadFile.cshtml
<script type="text/javascript">
$(document).ready(function () {
$("#uploadFile").click(function () {
$("#field1").val(window.parent.document.getElementById("field1").value);
$("#field2").val(window.parent.document.getElementById("field2").value);
...other fields...
$("#fileForm").submit();
});
$("#file").change(function () {
if ($("#file").val() != "") {
$("#uploadFile").removeAttr("disabled");
}
else {
$("#uploadFile").attr("disabled", "disabled");
}
});
});
</script>
<form id="fileForm" action='#Url.Action("UploadFile")' method="post" enctype="multipart/form-data">
<div>
Please use this template (link) to upload a list of employees and dependents.
</div>
<div class="center">
<br />
<input type="hidden" id="field1" name="field1" />
<input type="hidden" id="field2" name="field2" />
<input type="file" id="file" name="file" /><br /><br />
<input type="button" disabled="disabled" id="uploadFile" name="uploadFile" value="Upload File" class="greenButton" />
</div>
</form>
HomeController.cs
public ActionResult UploadFile()
{
return View();
}
[HttpPost]
public ActionResult UploadFile(String field1, String field2, HttpPostedFileBase file)
{
...do work...
//return View("UploadFile", object);
//return View("Result", object);
//return ?
}
The return part is where I'm stuck. What can I do to return the object to the partial view, without having the partial view load within the iframe?
Any ideas or at least a point in the right direction would be appreciated. Even a link to a duplicate!
The Partial View would always be refreshed when it is returned. It is how things work. Unless you do the upload differently using AJAX.
Please refer to the link below:
Ajax.BeginForm in MVC to upload files
Alternatively by using the same logic described in your question, you could put some additional logic in your view, like for example using TempData as flag which is set in the action controller, to determine the partial view is for upload or showing result.
Then, in your partial view, using that flag to render the UI accordingly.
Hope it helps.
I ended up finding a solution based on the concepts from these questions:
Get JSON text from HTML iframe
How to display action result of iframe in parent window
Basically, I modified the HomeController > UploadFile action to return JSON text
JsonResult result = new JsonResult();
result.Data = listOfEmployeesWithRates.ToList();
result.ContentType = "text/plain";
return result;
Then in jQuery, I check to see if the iframe contains the JSON when it loads.
//uploadFrame
$("#uploadFrame").load(function(){
if ($("#uploadFrame").contents().find("pre").html() != null) {
//pass json via ajax call to Result partial view
//refresh iframe
$("#uploadFrame").attr('src', $("#uploadFrame").attr('src'));
}
});

Uploading file through an action of a controller - two forms in one view

The problem is I have two forms in the same view. The controller is Users and I have two actions for the two forms: Edit and UploadPhoto.
I have been using the Edit portion for awhile and it's working. Now I want to allow users to upload their photo on the same page. There is a separate button for saving the user information and another for saving the picture.
The page model is #model sportingbiz.Models.ViewModels.UserEditViewModel
The second form
<fieldset title="Upload photo">
<legend>Change Profile Picture</legend>
<div>
#using(Html.BeginForm("UploadPhoto", "Users", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<table>
<tr>
<td>
<img src="#Url.Content("~/Content/images/noprofilepic.jpg")"
</td>
<td>
<input type="file" name="photoFile" />
<br /><br />
<input type="submit" value="Upload" />
</td>
</tr>
</table>
}
</div>
</fieldset>
Controller
public class UsersController : Controller
{
UsersRepository usersRepo = new UsersRepository();
[Authorize]
[HttpPost]
public void UploadPhoto(HttpPostedFile photoFile)
{
if (photoFile == null)
{
return;
}
if (photoFile.ContentLength <= 0)
{
return;
}
string filename = Path.GetFileName(photoFile.FileName);
if (Path.GetExtension(filename) == "")
return;
}
}
When I click upload my page navigates to http://localhost/mvcapp/Users/UploadPhoto with a blank screen. What I want is to return to the Edit page and probably show errors to the user that file was not uploaded. Features like ModelState.IsValid or ModelState.AddModelError
There is also no option of returning the same view e.g. return View(model)
Am I doing this wrong way? Some suggestion is to use Ajax uploader or Uploader. Am also thinking of separating the forms putting the Upload portion in its own view. Can PartialView help in this respect
[Authorize]
[HttpPost]
public void UploadPhoto(HttpPostedFile photoFile)
{
An Action with a void return is very strange. Make it return an ActionResult and determine on each branch where you want to go.
[Authorize]
[HttpPost]
public void UploadPhoto(HttpPostedFile photoFile)
{
if (photoFile == null)
{
return RedirectToAction("ErrorPage");
}
...
var viewModel = ...; // but your've lost the Edit part
return View(viewModel);
}
try to validate the using jquery like below, which will make sure that user are force to select some file...
lets us assume you a view like below
<input type="file" name="Document" id="imgFile" style="height:25px;"/>
<input type="submit" value="Submit" id="btnSubmit"/>
below is the jquery to validate
$('#imgFile').change(function() {
if ( ! $('#imgFile').val() ) {
alert('Chose a file!');
return false;
}
});
or you can do this same on the Button click like below
$('#btnSubmit').click(function() {
if ( ! $('#imgFile').val() ) {
alert('Chose a file!');
return false;
}
});
hope this might help you...
you uplode your image like that
public void UploadPhoto(HttpPostedFile photoFile)
{}
i think you should little bit change you code like this
public void UploadPhoto(HttpPostedFileBase photoFile)
{}

Categories

Resources