Model values not being preserved - asp.net-mvc-4

I'm very new to ASP.NET MVC and I'm having trouble with something that seems like it should be a no-brainer.
With this ViewModel:
public enum Step
{
One = 1,
Two = 2,
Three = 3
}
public class TestViewModel
{
public string Description
{
get
{
return "Current step is " + this.Step;
}
}
public Step Step { get; set; }
public string Dummy{ get; set; }
public TestViewModel()
{ }
public TestViewModel(Step step)
{
this.Step = step;
}
}
and this view:
#using MvcApplication1
#model TestViewModel
#using (Html.BeginForm("Test", "Home"))
{
if (Model.Step == Step.One)
{
#Html.HiddenFor(m => m.Step)
#Html.HiddenFor(m => m.Dummy)
<p>#Model.Description</p>
}
else if (Model.Step == Step.Two)
{
#Html.HiddenFor(m => m.Step)
#Html.HiddenFor(m => m.Dummy)
<p>#Model.Description</p>
}
else if (Model.Step == Step.Three)
{
#Html.HiddenFor(m => m.Step)
#Html.HiddenFor(m => m.Dummy)
<p>#Model.Description</p>
}
<input type="submit" value="Continue" />
}
and this controller:
public ActionResult Test()
{
TestViewModel model = new TestViewModel(Step.One);
return View(model);
}
[HttpPost]
public ActionResult Test(TestViewModel model)
{
Debug.Print("Enter: Step = {0}", model.Step);
switch (model.Step)
{
case Step.One:
model.Step = Step.Two;
model.Dummy = "2";
break;
case Step.Two:
model.Step = Step.Three;
model.Dummy = "3";
break;
case Step.Three:
model.Step = Step.One;
model.Dummy = "1";
break;
}
Debug.Print("Enter: Step = {0}", model.Step);
return View(model);
}
On the first click of the button the controller sets model.Step to Step.Two and my view is updated correctly.
But on the second (and any subsequent) click of the button model.Step is read as Step.One instead of Step.Two so nothing is updated on my view.
Is there anything obvious that I'm missing here? Why are the values not being read/saved correctly?

You don't need if else blocks in your view. You are basically doing the same thing. This will also work:
#using (Html.BeginForm("Test", "Home"))
{
#Html.HiddenFor(m => m.Step)
<p>#Model.Description</p>
<input type="submit" value="Continue" />
}
After posting the form, you are returning a view in the same action. ASP.NET MVC only uses values from the POST request in HTML helpers, ignoring the updated values in your action. You can see it in HTML after you make the first request and here's the reason why it's implemented that way.
I would suggest implementing Post-Redirect-Get pattern. After updating the value, make a redirection to other action.
[HttpPost]
public ActionResult Test(TestViewModel model)
{
Debug.Print("Enter: Step = {0}", model.Step);
switch (model.Step)
{
case Step.One:
model.Step = Step.Two;
break;
case Step.Two:
model.Step = Step.Three;
break;
case Step.Three:
model.Step = Step.One;
break;
}
Debug.Print("Enter: Step = {0}", model.Step);
return RedirectToAction("SomeAction", model);
}
This will serialize the model into querystring. Better way would be to pass some ID as a parameter.

Related

MVC Core DropDownList selected value ignored

I am trying to access my page at: https://localhost:44319/Analyze/Index/6
The problem is that my drop down list always selects the first item in the list instead of the one provided by ID. While stepping through the debugger, before the View() is returned, I see that the SelectList was populated correctly.
AnalyzeController.cs
public IActionResult Index(int? Id)
{
return Index(Id ?? getStatementEndingById(Id).StatementEndingId);
}
[HttpPost]
public IActionResult Index(int StatementEndingId)
{
var statementEnding = getStatementEndingById(StatementEndingId);
ViewBag.StatementEndingId = new SelectList(
_context.StatementEnding.OrderByDescending(s => s.StatementEndingId),
"StatementEndingId",
"Name",
statementEnding);
return View(getPayments(statementEnding));
}
private StatementEnding getStatementEndingById(int? statementEndingId)
{
StatementEnding statementEnding;
if (statementEndingId.HasValue)
{
statementEnding = _context.StatementEnding.FirstOrDefault(s => s.StatementEndingId == statementEndingId);
}
else
{
statementEnding = _context.StatementEnding.OrderByDescending(s => s.StatementEndingId).FirstOrDefault();
}
return statementEnding;
}
Setting DropDownList in Razor
#Html.DropDownList("StatementEndingId", null, new { #class = "form-control mb-2 mr-sm-2" })
I am using ASP.NET Core 2.1.
Any suggestions are much appreciated. Thanks in advance.
First i would recomend to create a typed model, something like this one :
public class StatementViewModel
{
public int StatementEndingId { get; set; }
public List<SelectListItem> StatementEndings { get; set; }
}
Second fill the Model with all dropdown options (StatementEndings) and the selected one (StatementEndingId)
public IActionResult Index()
{
var model = new StatementViewModel();
model.StatementEndingId = getStatementEndingById(Id).StatementEndingId;
model.StatementEndings = _context.StatementEnding.OrderByDescending(s => s.StatementEndingId).Select(p => new SelectListItem() { Text = p.Name, Value = p.StatementEndingId }).ToList();
return View(model);
}
And for the last, in the view
#model StatementViewModel
#Html.DropDownListFor(m => m.StatementEndingId, Model.StatementEndings, null, new { #class = "form-control mb-2 mr-sm-2" })

MVC4 model changes not showing

I know what you're thinking, but hear me out.
I have a model:
public partial class DealerBudget
{
public int DealerBudgetID { get; set; }
public int DealerID { get; set; }
public int BudgetYr { get; set; }
public int BudgetMonth { get; set; }
public decimal BudgetAmt { get; set; }
public bool Confirmed { get; set; }
public short BudgetTypeID { get; set; }
}
A list of these is in a vm:
public class DealerBudgetVM
{
public List<CNHi.StMarys.ND.DBP.Web.Models.DealerBudget> YrBudget { get; set; }
}
They're filled in a Get:
public ActionResult Add()
{
DealerBudgetVM result = new DealerBudgetVM(); //initialised to have all 24 months
List<DealerBudget> dbList = db.DealerBudgets.Where(x => x.DealerID == dbp.dealerID && x.BudgetTypeID == (short)2 && (x.BudgetYr == DateTime.Now.Year || x.BudgetYr == DateTime.Now.Year + 1)).ToList();
//use the data from the DB if it exists
foreach (DealerBudget bud in result.YrBudget)
{
foreach (DealerBudget budDB in dbList)
{
bud.DealerID = dbp.dealerID;
bud.BudgetTypeID = 2; //Service
if (budDB.BudgetYr == bud.BudgetYr && budDB.BudgetMonth == bud.BudgetMonth)
{
bud.DealerBudgetID = budDB.DealerBudgetID;
bud.BudgetAmt = budDB.BudgetAmt;
bud.Confirmed = budDB.Confirmed;
}
}
}
return PartialView(result);
}
And displayed in a view. If each month's value is Confirmed, the textbox is disabled:
#for( int i = 0; i < 12; ++i)
{
#Html.HiddenFor(model => model.YrBudget[i].DealerBudgetID)
#Html.HiddenFor(model => model.YrBudget[i].BudgetTypeID)
#Html.HiddenFor(model => model.YrBudget[i].DealerID)
#Html.HiddenFor(model => model.YrBudget[i].BudgetYr)
#Html.HiddenFor(model => model.YrBudget[i].BudgetMonth)
#Html.HiddenFor(model => model.YrBudget[i].Confirmed)
<div class="pure-u-1-3 pure-u-md-1-6 pure-u-lg-1-12 s-box">
<span class="pure-u-1">#Html.DisplayFor(model => model.YrBudget[i].MonthDesc)</span>
#Html.TextBoxFor(model => model.YrBudget[i].BudgetAmt, (Model.YrBudget[i].Confirmed == false) ? (object)new {type="number", Value=Model.YrBudget[i].BudgetAmt.ToString("0.00")} : (object)new {disabled = "disabled", Value=Model.YrBudget[i].BudgetAmt.ToString("0.00")} )
</div>
}
The user enters new budget figures and saves:
[HttpPost]
public ActionResult Added(DealerBudgetVM vm)
{
if (ModelState.IsValid)
{
this.ModelState.Clear();
foreach (DealerBudget budVM in vm.YrBudget.Where(x => x.Confirmed == false && x.BudgetAmt > 0).ToList())
{
//If it's not in the DB, add it.
if (budVM.DealerBudgetID == 0)
{
DealerBudget budNew = new DealerBudget { BudgetYr = budVM.BudgetYr, DealerID = budVM.DealerID, BudgetMonth = budVM.BudgetMonth, BudgetTypeID = budVM.BudgetTypeID, BudgetAmt = budVM.BudgetAmt };
if (budNew.BudgetAmt > 0)
{
budNew.Confirmed = true;
budVM.Confirmed = true;
}
db.DealerBudgets.Add(budNew);
db.SaveChanges();
budVM.DealerBudgetID = budNew.DealerBudgetID;
}
else
{
//update and confirm
DealerBudget budDB = db.DealerBudgets.Where(x => x.DealerBudgetID == budVM.DealerBudgetID).FirstOrDefault();
if (budDB == null)
{
}
else
{
budDB.BudgetAmt = budVM.BudgetAmt;
budDB.Confirmed = true;
db.SaveChanges();
budVM.Confirmed = true;
}
}
}
return RedirectToAction("Index", "ServicePerformance");
}
else
{
return PartialView(vm);
}
}
...and nothing changes on the view. The new (confirmed) budget textboxes should be disabled, and they aren't.
"No problem!" you say. "this guy just needs to..."
Use ModelState.Clear(). Tried it, didn't work.
Remove keys from the ModelState. Tried it, didn't work.
Use Post-Redirect-Get. Tried it(both redirecting to the partial and to the master. Current code above still using it). Didn't work.
Clear the cache. Tried many, many methods and iterations. Nothing worked.
After the Post and save, the subsequent Get has all the correct saved values being sent to the View in the VM, but the old ones (ie user-entered values with confirmed flag=false) are still shown.
If the user then goes to another page and then returns, the page displays as expected.
All the posts I've seen here (and elsewhere) confidently declare that one of the four solutions above will fix it. I've wasted a day now banging my head against a wall trying to disable a stupid textbox.
Any other ideas? Is it because of nesting or partial views or something?

MVC 4 multiple buttons in form - why isn't this code working

I am trying to use a variation of the code from this page:
Multiple button in MVC
But everytime I click on the buttons it goes to the index actionresult method and not one of the button methods. Index is the view name but the button clicks are happening in a partial view called "P_NewPersonForm.cshtml"
P_NewPersonForm.cshtml
#using (Html.BeginForm())
{
<div id="divClaimType">
#Html.Label("Claim type:")
#Html.DropDownListFor(m => m.modelClaim.ClaimType, new List<SelectListItem>
{
new SelectListItem{ Text="Legal", Value = "Legal" },
new SelectListItem{ Text="Immigration", Value = "Immigration" },
new SelectListItem{ Text="Housing", Value = "Housing" }
})
</div>
<div id="divClaimStatus" style="padding: 5px;">
#foreach(var item in Model.LinkerStatusOfClaim)
{
#Html.Label("Claim status:")
#Html.DropDownListFor(m => m.LinkerStatusOfClaim[0].ClaimStatusID, new SelectList(Model.modelClaimStatus, "ClaimStatusID", "ClaimStatus"))
#Html.LabelFor(m => m.LinkerStatusOfClaim[0].Notes)
#Html.TextAreaFor(m => m.LinkerStatusOfClaim[0].Notes)
#Html.LabelFor(m => m.LinkerStatusOfClaim[0].StartDate)
#Html.TextBoxFor(m => m.LinkerStatusOfClaim[0].StartDate, new { #id = "datepicker", #Value = DateTime.Now, #readonly = true, Style = "background:#cccccc;" })
<br />
#Html.ValidationMessageFor(model => model.LinkerStatusOfClaim[0].StartDate)
<br />
}
<input type="submit" value="Add another status to this claim..." name="action:add"/>
<input type="submit" value="Delete status." name="action:remove"/>
#* #Ajax.ActionLink("Add another status to this claim...", "AddClaim", "Client", new AjaxOptions { HttpMethod = "POST"})*#
</div>
}
</div>
I have one button for adding to the collection of claims and another to remove one from the collection.
ClientController
public ActionResult Index()
{
var Model = new modelPersonClaim();
// Add one item to model collection by default
LinkerStatusOfClaim LinkerStatusOfClaim = new LinkerStatusOfClaim();
Model.LinkerStatusOfClaim.Add(LinkerStatusOfClaim);
DataLayer.RepositoryClient RC = new RepositoryClient();
Model.isValidModel = true;
RC.GetClaimTypes(Model, PersonTypes.NewPerson.ToString());
return View(Model);
}
[HttpPost]
public ActionResult P_NewPersonForm(modelPersonClaim Model)
{
DataLayer.RepositoryClient RC = new RepositoryClient();
RC.GetClaimTypes(Model, PersonTypes.NewPerson.ToString());
Model.isValidModel = ModelState.IsValid;
if (ModelState.IsValid)
{
RC.CreatePerson(Model);
Model.SuccessfulInsert = true;
Model.InsertString = "Person data has been successfully inserted into the database.";
if (Model.modelClaim.ClaimMade)
{
RC.CreateClaim(Model);
}
}
else
{
Model.SuccessfulInsert = false;
Model.InsertString = "Person data could not be inserted into the database. Missing key fields.";
}
return View("Index", Model);
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited = true)]
public class MultiButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
[HttpPost]
[MultiButtonAttribute(Name = "action", Argument = "Add another status to this claim...")]
public ActionResult AddClaimStatus(modelPersonClaim Model)
{
Model.LinkerStatusOfClaim.Insert(Model.LinkerStatusOfClaim.Count, new LinkerStatusOfClaim());
return View("Index", Model);
}
[HttpPost]
[MultiButtonAttribute(Name = "action", Argument = "Delete status.")]
public ActionResult RemoveClaimStatus(modelPersonClaim Model)
{
// Can't remove IF only 1
if (Model.LinkerStatusOfClaim.Count == 1)
{
}
else
{
Model.LinkerStatusOfClaim.RemoveAt(Model.LinkerStatusOfClaim.Count);
}
return View("Index", Model);
}
When I click one the buttons it hits the public override bool IsValidName twice. Once for each button. But then because the action name is always index, it goes to the index method and not one of the button methods.
Does anyone have any ideas how to fix this?
Something is wrong with this part:
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
Your attribute is this:
[MultiButtonAttribute(Name = "action", Argument = "Add another status to this claim...")]
So in that case keyValue will become: "action:Add another status to this claim..." while your HTML states: <input type="submit" value="Add another status to this claim..." name="action:add"/>, so I think Argument in your attribute should be add.

Multiple models in view error

I have a view pages that have different partial views with different models. I created a model class that will call other classes so i can use it on the main view page. But my problem is that when i try to change the password it gives me an error that i am passing in a model which i need to pass in another model. I believe i have my structure right but not sure what is causing this issue.
Main view:
#model Acatar.Models.ProfileModel
#{
ViewBag.Title = "ProfileAccount";
}
#{ Html.RenderAction("_PlayNamePartial"); }
#{ Html.RenderAction("_UsernamePartial", "Account");}
#{ Html.RenderAction("_TalentsPartial", "Account");}
#if (ViewBag.HasLocalPassword)
{
#Html.Partial("_ChangePasswordPartial")
}
else
{
#Html.Partial("_SetPasswordPartial")
}
Profile Model: containing models that i have created
public class ProfileModel
{
public LocalPasswordModel LocalPasswordModel { get; set; }
public PlayNameModel PlayNameModel { get; set; }
public UsernameModel UsernameModel { get; set; }
public TalentsModel TalentsModel { get; set; }
}
Controller:
public ActionResult Profile(ManageMessageId? message)
{
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: "";
ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
ViewBag.ReturnUrl = Url.Action("Profile");
return View();
}
POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Profile(LocalPasswordModel model)
{
bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
ViewBag.HasLocalPassword = hasLocalAccount;
ViewBag.ReturnUrl = Url.Action("Profile");
if (hasLocalAccount)
{
if (ModelState.IsValid)
{
// ChangePassword will throw an exception rather than return false in certain failure scenarios.
bool changePasswordSucceeded;
try
{
changePasswordSucceeded = WebSecurity.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword);
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("Profile", new { Message = ManageMessageId.ChangePasswordSuccess });
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
}
}
else
{
// User does not have a local password so remove any validation errors caused by a missing
// OldPassword field
ModelState state = ModelState["OldPassword"];
if (state != null)
{
state.Errors.Clear();
}
if (ModelState.IsValid)
{
try
{
WebSecurity.CreateAccount(User.Identity.Name, model.NewPassword);
return RedirectToAction("Profile", new { Message = ManageMessageId.SetPasswordSuccess });
}
catch (Exception e)
{
ModelState.AddModelError("", e);
}
}
}
return View(model);
}
View Page for password change:
#model Project.Models.LocalPasswordModel
#using (Html.BeginForm("Profile", "Account")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<legend>Change Password Form</legend>
#Html.LabelFor(m => m.OldPassword)
#Html.PasswordFor(m => m.OldPassword)
#Html.LabelFor(m => m.NewPassword)
#Html.PasswordFor(m => m.NewPassword)
#Html.LabelFor(m => m.ConfirmPassword)
#Html.PasswordFor(m => m.ConfirmPassword)
<br/>
<input class="btn btn-small" type="submit" value="Change password" />
</fieldset>
The Error i am getting:
The model item passed into the dictionary is of type 'Project.Models.LocalPasswordModel', but this dictionary requires a model item of type 'Project.Models.ProfileModel'.
Try specifying model in #Html.Partial method. (Excuse my syntax, I dont have an IDE now)
#if (ViewBag.HasLocalPassword)
{
#Html.Partial("_ChangePasswordPartial",Model.LocalPasswordModel)
}
else
{
#Html.Partial("_SetPasswordPartial",Model.LocalPasswordModel)
}
(I guess second view also use same model)
But I couldn't see any model passed into your view from your controller, You should pass an model to view
public ActionResult Profile(ManageMessageId? message)
{
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: "";
ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
ViewBag.ReturnUrl = Url.Action("Profile");
var ProfileModel = new ProfileModel();
ProfileModel.LocalPasswordModel = populateFromDB();
return View(ProfileModel);
}
or consider creating an action result to Render this partial view as you have done for other partial views like this.(If there are no other intentions using Html.partial here)
#if (ViewBag.HasLocalPassword)
{
#Html.RenderAction("_ChangePasswordPartial")
}
else
{
#Html.RenderAction("_SetPasswordPartial")
}

Displaying Error in a View

Is there any standard practice to display errors in a view? Currently it is being displayed from TempData.
I implemented a derived a class from Base Controller and used that derived class in every one of my controller. Then assign error or success messages from controller.
public class TestController : Controller
{
public string ErrorMessage
{
get { return (string) TempData[CommonHelper.ErrorMessageKey]; }
set
{
if (TempData.ContainsKey(CommonHelper.ErrorMessageKey))
{
TempData[CommonHelper.ErrorMessageKey] = value;
}
else
{
TempData.Add(CommonHelper.ErrorMessageKey,value);
}
TempData.Remove(CommonHelper.SuccessMessageKey);
}
}
public string SuccessMessage
{
get { return (string)TempData[CommonHelper.SuccessMessageKey]; }
set
{
if(TempData.ContainsKey(CommonHelper.SuccessMessageKey))
{
TempData[CommonHelper.SuccessMessageKey] = value;
}
else
{
TempData.Add(CommonHelper.SuccessMessageKey, value);
}
TempData.Remove(CommonHelper.ErrorMessageKey);
}
}
}
CommonHelper Class
public class CommonHelper
{
public const string SuccessMessageKey = "successMessage";
public const string ErrorMessageKey = "errorMessage";
public static string GetSuccessMessage(object data)
{
return data == null ? string.Empty : (string) data;
}
public static string GetErrorMessage(object data)
{
return data == null ? string.Empty : (string) data;
}
}
Then created a partial view having this
#using Web.Helpers
#if (!string.IsNullOrEmpty(CommonHelper.GetSuccessMessage(TempData[CommonHelper.SuccessMessageKey])))
{
<div class="alert alert-success">
#CommonHelper.GetSuccessMessage(TempData[CommonHelper.SuccessMessageKey])
</div>
}
else if (!string.IsNullOrEmpty(CommonHelper.GetErrorMessage(TempData[CommonHelper.ErrorMessageKey])))
{
<div class="alert alert-success">
#CommonHelper.GetErrorMessage(TempData[CommonHelper.ErrorMessageKey])
</div>
}
And in every view, the partial view is rendered.
<div>
#Html.Partial("_Message")
</div>
Here is a pretty common implementation of displaying errors.
Controller
public class UserController : Controller
{
[HttpPost]
public ActionResult Create(User model)
{
// Example of manual validation
if(model.Username == "Admin")
{
ModelState.AddModelError("AdminError", "Sorry, username can't be admin")
}
if(!ModelState.IsValid()
{
return View(model)
}
}
}
Model
public class User
{
[Required]
public string Username {get; set;}
public string Name {get; set; }
}
View
#Html.ValidationSummary(true)
#using(Html.BeginForm())
{
// Form Html here
}
You don't need all of the infrastructure you created. This is handled by the framework. If you need a way to add success messages you can checkout the Nuget Package MVC FLASH
I prefer to use ModelState.AddModelError()