Multiple TempData variables passed to same action - c#

I'm creating ASP.NET web application using MVC and Entity Framework. I have two different success messages I am passing to an index action for when a user clocks in or clocks out. The success message will print correctly when a user clocks in, but will not when a user clocks out for some reason.
The actions are very similar and I used all of the same conventions so I can't figure out why one would print and another would not. I've tried debugging and there are no red flags and everything updates in the database like it should. Is it not possible to pass multiple TempData variables to the same action?
Here is the relevant code:
Controller
// GET: TimeClocks
public ActionResult Index()
{
ViewBag.ClockInSuccess = TempData["ClockInSuccess"];
ViewBag.ClockOutSuccess = TempData["ClockOutSuccess"];
return View();
}
[HttpPost]
public ActionResult ClockIn(TimeClock timeClock)
{
if(db.TimeClocks.ToList().Count == 1)
{
ModelState.AddModelError("ExistsError", "You already clocked in at" + timeClock.ClockIn);
}
string currentUserId = User.Identity.GetUserId();
ApplicationUser currentUser = db.Users.FirstOrDefault(x => x.Id == currentUserId);
timeClock.ApplicationUser = currentUser;
timeClock.ClockIn = DateTime.Now;
if (ModelState.IsValid)
{
db.TimeClocks.RemoveRange(db.TimeClocks.ToList());
db.TimeClocks.Add(timeClock);
db.SaveChanges();
TempData["ClockInSuccess"] = "You clocked in successfully at " + timeClock.ClockIn;
return RedirectToAction("Index");
}
return RedirectToAction("Index", timeClock);
}
[HttpPost]
public ActionResult ClockOut(TimeClock timeClock)
{
timeClock = db.TimeClocks.FirstOrDefault();
if(timeClock.ClockIn == null)
{
ModelState.AddModelError("NullError", "You must clock in before you can clock out.");
return RedirectToAction("Index");
}
timeClock.ClockOut = DateTime.Now;
if (ModelState.IsValid)
{
db.Entry(timeClock).State = EntityState.Modified;
db.SaveChanges();
TempData["ClockOutSuccess"] = "You clocked out successfully at " + timeClock.ClockOut;
return RedirectToAction("Index");
}
return RedirectToAction("Index", timeClock);
}``
View
#model FinalProject.Models.TimeClock
#{
ViewBag.Title = "Create";
}
<h2>Employee Time Clock</h2>
#using (Html.BeginForm("ClockIn", "TimeClocks"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Clock In" class="btn btn-lg" />
</div>
</div>
</div>
}
#using (Html.BeginForm("ClockOut", "TimeClocks"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Clock Out" class="btn btn-lg" />
</div>
</div>
</div>
}
#{
if (#ViewBag.ClockInSuccess != "")
{
<p class="alert-success">#ViewBag.ClockInSuccess</p>
}
else if (#ViewBag.ClockOutSuccess != "")
{
<p class="alert-success">#ViewBag.ClockOutSuccess</p>
}
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>

TempData is lost once you read it so you need to do a Peek or a Keep:
ViewBag.ClockInSuccess = TempData.Peek("ClockInSuccess");
ViewBag.ClockOutSuccess = TempData.Peek("ClockOutSuccess");
http://www.codeproject.com/Articles/818493/MVC-Tempdata-Peek-and-Keep-confusion

Related

Cannot perform runtime binding on a null reference | Not Sure How The Reference Is Null

Context:
I'm trying to build a feature in my MVC Application where an Employer can take an Application submitted by a Student, and convert the information in the Application to create an Employee. However, I'm running into the Cannot perform runtime binding on a null reference error.
Controller:
public ActionResult Onboard (int id)
{
var application = db.Applications.
Include(a=>a.JobPosting.Employer).
Include(a=>a.Student)
.FirstOrDefault(a => a.ApplicationID == id);
return View(application);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Onboard([Bind(Include = "ApplicationID,JobPostingID,StudentID")] Application application)
{
{
if (ModelState.IsValid)
{
db.Employees.Add(new Employee()
{
Student = application.Student,
JobPosting = application.JobPosting,
Employer = application.JobPosting.Employer
});
db.SaveChanges();
return RedirectToAction("Details", "Employees");
}
}
return View(application);
}
View:
#model InTurn_Model.Application
#{
ViewBag.Title = "Onboard";
Layout = "~/Areas/Employers/Views/Shared/_Employers.cshtml";
}
<h2>Onboard</h2>
#using (Html.BeginForm("Onboard","Applications",FormMethod.Post,null))
{
#Html.AntiForgeryToken()
<input id="StudentID" type="hidden" value="#Model.StudentID" />
<input id="JobPostingID" type="hidden" value="#Model.JobPostingID" />
<input id="EmployerID" type="hidden" value="#Model.JobPosting.EmployerID" />
<div class="form-horizontal">
<h4>Employee</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.StudentID, "StudentID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<span>#Model.Student.FirstName #Model.Student.LastName</span>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.JobPostingID, "JobPostingID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<span> #Model.JobPosting.Position</span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Hire" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
When I run a debug, the breakpoint on
return View(application)
stores the relevant information from the 1st Onboard controller.
However, the breakpoint on public ActionResult Onboard([Bind(Include = "ApplicationID,JobPostingID,StudentID")] Application application), the 'application' is storing values of 0 for ApplicationID, JobPostingID and StudentID. It's not null technically, but they aren't the correct values.
My question is, what am I doing wrong that the controllers are not passing the correct data? How do I fix this issue?
Is there a better way to achieve this?
I am incredibly new to MVC, and this is the first application that I'm building from the ground up. Any help is appreciated. Thank you!
Looks like I didn't need to use the [HTTPPost] or [ValidateAntiForgerty]. My thought was treating this like a create- but I guess because I'm not actually creating new data- rather transferring one piece of data to another- I don't need to use that.
Here's what I wound up doing:
public ActionResult NewHire(int id)
{
var application = db.Applications.Find(id);
db.Employees.Add(new Employee()
{
StudentID = application.Student.StudentID,
JobPostingID = application.JobPosting.JobPostingID,
EmployerID = application.JobPosting.Employer.EmployerID
});
db.SaveChanges();
return View();
}

Authorization: users able to edit their own details when loggedin

I have an application where I want Volunteers to be able to help with a Ceremony.
I would like them to be log in and click a button, this brings them to a page where their details appear and a list of Ceremonies they can apply for. I have that page working but I have no way of getting the current user logged in.
Also, for clarification: I have a Volunteer entity which holds the data and then a separate user entity. The username for a Volunteer is the same as their user entity.
I want to:
Compare the Username to the Volunteer Username and get the
VolunteerId.
2.This will then be used to edit/join a ceremony for that particular volunteer.
Here's my Volunteer Controller methods:
// GET:
public ActionResult VolunteerCeremony(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
string userName = string.Empty;
var getVolunteerId = (from u in db.Volunteers
where WebSecurity.CurrentUserName == u.Username
select u.VolunteerId).SingleOrDefault();
Volunteer v = (Volunteer)(from k in db.Volunteers
where getVolunteerId == k.VolunteerId
select k).SingleOrDefault();
if (v == null)
{
return HttpNotFound();
}
PopulateAssignedCeremonyData(v);
return View(v);
}
// GET:
public ActionResult VolunteerHub()
{
return View();
}
// POST: /Player/VolunteerCeremony/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult VolunteerCeremony(int? id, string[] selectedOptions)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var getVolunteerId = (from u in db.Volunteers
where WebSecurity.CurrentUserName == u.Username
select u.VolunteerId).SingleOrDefault();
var v = (Volunteer)(from k in db.Volunteers
where getVolunteerId == k.VolunteerId
select k).SingleOrDefault();
try
{
UpdateVolunteerCeremonies(selectedOptions, v);
db.Entry(v).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateAssignedCeremonyData(v);
return View(v);
}
And then my Razor pages, the first which I want the user to click a link to bring them to the edit/join ceremony page:
#model PIMS.Entities.Volunteer
#{
ViewBag.Title = "VolunteerHub";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<li>#Html.ActionLink("Join Ceremony", "VolunteerCeremony", "Volunteers", null, new { id = #model.VolunteerId })</li>
This gives me an error on the new { id = #model.VolunteerId }
Then the page which I want to get to:
#model PIMS.Entities.Volunteer
#using Microsoft.AspNet.Identity
#{
ViewBag.Title = "VolunteerCeremony";
}
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<h2>Apply for Ceremony</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.VolunteerId)
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { #readonly = "readonly" })
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.VolunteerRole, "Volunteer Role", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.VolunteerRole, new { #readonly = "readonly" })
#Html.ValidationMessageFor(model => model.VolunteerRole)
</div>
</div>
</div>
<div class="row">
<div class="col-md-2"> </div>
<div class="form-group col-md-4">
<label class="control-label">Assigned Ceremonies</label>
#Html.ListBox("selectedOptions", (MultiSelectList)ViewBag.SelectedCeremonies, new { #class = "form-control" })
</div>
<div class="form-group col-md-1" style="text-align:center">
<div class="form-group">
<button type="button" id="btnRight" class="btn btn-warning btn-lg">
<span class="glyphicon glyphicon-arrow-right"></span>
</button>
</div>
<div class="form-group">
<button type="button" id="btnLeft" class="btn btn-success btn-lg">
<span class="glyphicon glyphicon-arrow-left"></span>
</button>
<div></div>
</div>
</div>
<div class="form-group col-md-4">
<label class="control-label">Available Ceremonies</label>
#Html.ListBox("availOptions", (MultiSelectList)ViewBag.AvailCeremonies, new { #class = "form-control" })
</div>
<input type="submit" id="btnSubmit" value="Save" class="btn btn-default" />
</div>
<div style="text-align:center;">
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/multisel")
}
Does anyone have any experience with this as it sort of key to my application!
in mvc there is something called session variables, its just like app.config variables, you declare them like this in the controller
Session["Volunteer"] = "volunteer1";
once declared they will remain declared for the entire session.
then you write code for the logoff to reset the Session variable to something that your code handles as no-one logged in or just null.

Post data from a partial view to another action

Just started with MVC and trying to do the following:
having a Details.cshtml with the following part on one of my tabs:
<!-- DETAILS TAB CONTENT -->
<div class="tab-pane profile active" id="details-tab">
#if (ViewBag.ScreenMode == Constants.ScreenMode.View)
{
#Html.Partial("_ViewDetails", Model)
}
else
{
#*#Html.Partial("_EditDetails", Model)*#
<div id="DetailsEdit">
#{Html.RenderPartial("_EditDetails");}
</div>
}
</div>
My _ViewDetails.cshtml shows detail information and has the following to go to the Edit mode:
#Html.ActionLink("Edit", "Details", new { id = Model.EmployeeId, screenMode = Constants.ScreenMode.Edit })
When clicked, indeed the tab shows the contents of the partial view _EditDetails. My _EditDetails.cshtml looks like this:
#using (Html.BeginForm("Edit", "Employee", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.EmployeeId)
#* fields with editable controls. Left it out here *#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
and having the following methods in my controller:
public ActionResult Details(Guid? id, Constants.ScreenMode? screenMode)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Employee employee = employeeManager.Get(id.GetValueOrDefault());
if (employee == null || employee.EmployeeId == null || employee.EmployeeId == Guid.Empty)
return HttpNotFound();
if (screenMode == null)
screenMode = Constants.ScreenMode.View;
ViewBag.ScreenMode = screenMode;
return View(employee);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Guid? id, HttpPostedFileBase upload)
{
// code here
return RedirectToAction("Details", "Employee", new { id = employeeToUpdate.EmployeeId, screenMode = Constants.ScreenMode.View });
}
However, when I press the save button from the edit partial view, it never hits my code Edit action in my controller. It only goes back to the Details action, which gets the info again. I thought by using the following at the beginning of my partial view, I could make the post go to my Edit action:
#using (Html.BeginForm("Edit", "Employee", FormMethod.Post, new { enctype = "multipart/form-data" }))
What do I miss or are my thoughts completely wrong?
Thanks in advance.
Aarghhhh, found it.
My Details.cshtml started with:
#using (Html.BeginForm("Details", "Employee", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
Removed this and now it's working.
Overlooked this for days.
Thanks.

Submit to a new page and download a file

I have code that allows users to create and edit records in a SQL database. This works fine. However, now I want it to output the SQL scripts for what is being done. I'm not sure how to do this. Here is my current code:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<div class="form-group">
<strong>ID:</strong>
<div class="col-md-10">
<p>
#Html.EditorFor(model => model.ID)
#Html.ValidationMessageFor(model => model.ID)
</div>
</div>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-primary">Submit <i class="fa fa-caret-right"></i></button>
</div>
</div>
</div>
}
public ActionResult Edit(int id)
{
using (var client = new XServiceClient())
{
X x = client.Find(id);
if (x == null)
{
return HttpNotFound();
}
return View(x);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(X x)
{
if (ModelState.IsValid)
{
using (var client = new XServiceClient())
{
client.Edit(x);
return Redirect("~/Home/Index");
}
}
return View(x);
}
I'm thinking in my controller I could do something like this:
using (var client = new XServiceClient())
{
var sessionID = Guid.NewGuid().ToString("N");
var filename = String.Format("Session-{0}.sql", sessionID);
var sqlString = "Update SQL String Stuff";
File(System.Text.UTF8Encoding.UTF8.GetBytes(sqlString), "text/plain", filename);
client.Edit(x);
return Redirect("~/Home/Index");
}
Most systems I have seen recommend a return on that File line. However, that won't work with the rest of the program.
How can I get my submit button to submit the data to the server and also download a file to the user?

Mvc ViewBag - Cannot convert null to 'bool' because it is a non-nullable value type

I want to set a bool to true in the controller when producing a certain view and then alter the header of the view accordingly. This should be dead simple but instead Im getting:
Cannot perform runtime binding on a null reference Exception Details:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot perform
runtime binding on a null reference
All I'm doing is in controller:
[AllowAnonymous]
public ActionResult Register()
{
ViewBag.IsRegistration = true;
return View();
}
and then in view:
#if (ViewBag.IsRegistration)
{
<legend>Register using another service.</legend>
}
else
{
<legend>Use another service to log in.</legend>
}
but it fails on:
#if (ViewBag.IsRegistration)
UPDATE
Relevant Controller Code:
[AllowAnonymous]
public ActionResult Register()
{
ViewBag.IsRegistration = "true";
return View();
}
The register view:
#model Mvc.Models.RegisterViewModel
#{
Layout = "~/Views/Shared/_AccountLayout.cshtml";
ViewBag.Title = "Register";
}
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
</hgroup>
<div class="row">
<div class="col-lg-6">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset class="form-horizontal">
<legend>Create a new account.</legend>
<div class="control-group">
#Html.LabelFor(m => m.UserName, new { #class = "control-label" })
<div class="controls">
#Html.TextBoxFor(m => m.UserName)
</div>
</div>
<div class="control-group">
#Html.LabelFor(m => m.Password, new { #class = "control-label" })
<div class="controls">
#Html.PasswordFor(m => m.Password)
</div>
</div>
<div class="control-group">
#Html.LabelFor(m => m.ConfirmPassword, new { #class = "control-label" })
<div class="controls">
#Html.PasswordFor(m => m.ConfirmPassword)
</div>
</div>
<div class="form-actions no-color">
<input type="submit" value="Register" class="btn" />
</div>
</fieldset>
}
</div>
<div class="col-lg-6"></div>
<section id="socialLoginForm">
#Html.Action("ExternalLoginsList", new { ReturnUrl = ViewBag.ReturnUrl })
</section>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The ExternalLoginsList partial:
#using Glimpse.Core.Extensions
#using Microsoft.Owin.Security
#model ICollection<AuthenticationDescription>
#if (Model.Count == 0)
{
<div class="message-info">
<p>There are no external authentication services configured</p>
</div>
}
else
{
using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = ViewBag.ReturnUrl }))
{
#Html.AntiForgeryToken()
<fieldset id="socialLoginList">
#if (!string.IsNullOrEmpty(ViewBag.IsRegistration))
{
<legend>Register using another service.</legend>
}
else
{
<legend>Use another service to log in.</legend>
}
<p>
#foreach (AuthenticationDescription p in Model) {
<button type="submit" class="btn" id="#p.AuthenticationType" name="provider" value="#p.AuthenticationType" title="Log in using your #p.Caption account">#p.AuthenticationType</button>
}
</p>
</fieldset>
}
}
Try:
#if (ViewBag.IsRegistration == true)
I know this is an old question, but I think I have an elegant answer, so in case anyone reads this after searching, here is mine:
#if (ViewBag.IsRegistration ?? false)
Try this:
Replace the line in your controller:
ViewBag.IsRegistration = true;
with
ViewBag.IsRegistration = new bool?(true);
and replace the line in your view:
#if (ViewBag.IsRegistration)
with
#if ((ViewBag.IsRegistration as bool?).Value)
Effectively you are putting a nullable bool in the ViewBag and then unwrapping it.
Simply check for null before checking for true:
if (ViewBag.IsRegistration != null && ViewBag.IsRegistration)
Try TempData instead of ViewBag.
Change your code from
Controller
ViewBag.IsRegistration=true;
to
TempData["IsReg"]=true;
and in View
#if((bool)TempData["IsReg"])
It seems that you are using the value in child partial view and you are adding the data in parent action.The values in viewbag cannot pass out data from one action to anothers action's view. While TempData use session it can be used to pass data to one action to another actions view.
Ok so as per Floods suggestion in comments, I need to pass the arguments around. The ViewBag from the parent View does not flow through to partial views.
So in the code for the Register View I needed to change from
<section id="socialLoginForm">
#Html.Action("ExternalLoginsList", new {ReturnUrl = ViewBag.ReturnUrl})
</section>
to
<section id="socialLoginForm">
#Html.Action("ExternalLoginsList",
new {ReturnUrl = ViewBag.ReturnUrl,
IsRegistering = #ViewBag.IsRegistering })
</section>
Then go into my account controller and change from:
[AllowAnonymous]
[ChildActionOnly]
public ActionResult ExternalLoginsList(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return (ActionResult)PartialView("_ExternalLoginsListPartial", new List<AuthenticationDescription>(AuthenticationManager.GetExternalAuthenticationTypes()));
}
to
[AllowAnonymous]
[ChildActionOnly]
public ActionResult ExternalLoginsList(string returnUrl, string isRegistering) {
ViewBag.IsRegistering = (isRegistering.ToLower() == "true");
ViewBag.ReturnUrl = returnUrl;
return (ActionResult)PartialView("_ExternalLoginsListPartial", new List<AuthenticationDescription>(AuthenticationManager.GetExternalAuthenticationTypes()));
}
Then in the ExternalLogins I can just:
#if (ViewBag.IsRegistering)
as necessary.
So Im effectively passing the IsRegistering bool from controller to main view then back up to action method on controller then into ViewBag which allow me to access the bool in the child partial view.
Many thanks.
Booleans in Viewbag are always tricky. Try this instead
[AllowAnonymous]
public ActionResult Register()
{
ViewBag.Registration = "x";//x or whatever
return View();
}
#if (!String.IsNullorEmpty(ViewBag.Registration))
{
<legend>Register using another service.</legend>
}
else
{
<legend>Use another service to log in.</legend>
}
Maybe so:
#if ((ViewBag.IsRegistration != null) && (ViewBag.IsRegistration is bool) && (bool)ViewBag.IsRegistration)
{
}

Categories