View isnot updated from model in mvc - asp.net-mvc

I have a simple MVC controller and view and model (I removed all other codes to figure out the problem)
Model is a simple one property class:
public class SiteFileUploadModel
{
[Required]
public int ActivePage { get; set; }
}
View is also very simple:
#model Models.SiteFileUploadModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#if (Model != null)
{
using (this.Html.BeginForm("Index", "SiteFileUpload"))
{
#Html.Hidden("ActivePage",Model.ActivePage)
#Model.ActivePage
switch (Model.ActivePage)
{
case 1:
<p>Page 1</p>
break;
case 2:
<p>Page 2</p>
break;
}
<button type="submit" name="actionButtons">Previous</button>
<button type="submit" name="actionButtons">Next</button>
}
}
The controller has only one method:
public class SiteFileUploadController : Controller
{
//
// GET: /FileUpload/SiteFileUpload/
[HttpGet]
public ActionResult Index()
{
var model = new SiteFileUploadModel();
model.ActivePage = 1;
return View(model);
}
[HttpPost]
public ActionResult Index(SiteFileUploadModel model, string actionButtons)
{
if (actionButtons == "Next")
{
model.ActivePage++;
}
if (actionButtons == "Previous")
{
model.ActivePage--;
}
return View(model);
}
}
When I am running it and press next, I can see that model.activePage became 2 and it also shows on output (it shows 2 and page 2), but the hidden value is still 1. In fact the hidden value is always 1 and it doesn't follow the real value of ActivePage. I also tested it with generating hidden field using HiddenFor(m=>m.ActivePage) with the same effect! What is the problem?

See this answer.
In brief, you need to clear the ModelState before redisplaying the view, as the Html Helper will use the model state rather than the Model for its data.
Add the following statement to your controller action:
ModelState.Clear();

Related

Sitecore MVC - how to handle multiple forms on page

I've been looking at Sitecore MVC but I'm stuck at how to handle a case where my page has two controller renderings and each contains a form. I want the individual controllers to handle their HttpPost and return the whole page after post.
I've set up a simple example. Both controllers are similar:
public class ExampleController : Sitecore.Mvc.Controllers.SitecoreController
{
public override ActionResult Index()
{
return View("Index");
}
[HttpPost]
public ActionResult Index(string formPostData)
{
ViewBag.SaveForLater = formPostData;
return Index();
}
}
The views look like this:
#using Sitecore.Mvc
#using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, FormMethod.Post))
{
#Html.AntiForgeryToken()
var term = ViewBag.SaveForLater as string;
if (!string.IsNullOrEmpty(term))
{
<p>Submitted: #term</p>
}
<p>
#Html.Sitecore().FormHandler("Example", "Index")
<input type="text" name="formPostData" placeholder="Enter something" />
<input type="submit" name="submit" value="Search" />
</p>
}
With this setup both forms submit their data but the returned page consists only of the partial view and not the whole page.
If I replace the line #Html.Sitecore().FormHandler("Example", "Index") with #Html.Sitecore().FormHandler() then the whole page is returned but the post action for both forms is processed.
Neither scenario is ideal. I must be missing something and would appreciate a pointer.
Unfortunately there are multiple ways how you can integrate with Sitecore MVC and Sitecore doesn't provide many best practice examples. One example you can find here.
In our projects we do it a bit different, because we want to use as much as possible the conventions etc. from default ASP.NET MVC. I try to include a complete simple example in this post.
We add two different hidden fields to the form with the current action and the current controller, the view looks like this:
#model Website.Models.TestViewModel
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.Text)
#Html.TextBoxFor(model => model.Text)
<input type="submit" value="submit" />
<input type="hidden" name="fhController" value="TestController" />
<input type="hidden" name="fhAction" value="Index" />
}
With this simple ViewModel:
namespace Website.Models
{
public class TestViewModel
{
public string Text { get; set; }
}
}
Then we have created a custom attribute which checks if the current controller/action is the same as posted:
public class ValidateFormHandler : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var controller = controllerContext.HttpContext.Request.Form["fhController"];
var action = controllerContext.HttpContext.Request.Form["fhAction"];
return !string.IsNullOrWhiteSpace(controller)
&& !string.IsNullOrWhiteSpace(action)
&& controller == controllerContext.Controller.GetType().Name
&& methodInfo.Name == action;
}
}
The controller actions then gets the new attribute:
namespace Website.Controllers
{
public class TestController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
[ValidateFormHandler]
public ActionResult Index(TestViewModel model)
{
return View(model);
}
}
}
We always return the view resolved by ASP.NET MVC. By convention this is the view with the same name as the action within the folder with the same name as the controller.
This approach works very well for us. If you would like to add the AntiForgeryToken, this also works fine.
You should create the main Index view having two partial view ,
like this:
You can define main view model like this
public class MainViewModel
{
public LoginViewModel LoginModel { get; set; }
public RegisterViewModel RegisterModel { get; set; }
}
then the separate model:
public class RegisterViewModel
{
// Your model properties
}
public class LoginViewModel
{
// Your model properties
}
then define your action for main view as:
public ActionResult MainView()
{
MainViewModel model = new MainViewModel
{
LoginModel = new LoginViewModel(),
RegisterModel = new RegisterViewModel()
};
return View(model);
}
Your main view as
#Html.Partial("_Login", Model.LoginModel)
#Html.Partial("_Register", Model.RegisterModel)
after that you can saperately create your views ,thanks
Sitecore Habitat does it similarly as above, but using a unique rendering ID.
public class ValidateRenderingIdAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var ignoreCase = StringComparison.InvariantCultureIgnoreCase;
var httpRequest = controllerContext.HttpContext.Request;
var isWebFormsForMarketersRequest = httpRequest.Form.AllKeys
.Any(key => key.StartsWith("wffm", ignoreCase) && key.EndsWith("Id", ignoreCase));
if (isWebFormsForMarketersRequest)
{
return false;
}
string renderingId;
if (!httpRequest.GetHttpMethodOverride().Equals(HttpVerbs.Post.ToString(), ignoreCase) || string.IsNullOrEmpty(renderingId = httpRequest.Form["uid"]))
{
return true;
}
var renderingContext = RenderingContext.CurrentOrNull;
if (renderingContext == null)
{
return false;
}
Guid id;
return Guid.TryParse(renderingId, out id) && id.Equals(renderingContext.Rendering.UniqueId);
}
}
Link to repo

getting values View data in controller

i did this all but now how to get values being typed in Textbox, password box etc in CONTROLLER. I defined all necessary methods, boxes and buttons etc. So the only problem is to get values in controller and then to send them to model for accessing db data
.csHtml
#using (Html.BeginForm("register","Home", FormMethod.Post, new {id="submitForm"}))
{
<div>
<i>#Html.Label("Name:")</i>
#Html.TextBox("txtboxName")
</div>
<div>
<i>#Html.Label("Email:")</i>
#Html.TextBox("txtboxEmail")
</div>
<div>
<i>#Html.Label("Password:")</i>
#Html.Password("txtboxPassword")
</div>
<div>
<button type="submit" id="btnSubmit" name="Command" value="Submit">Submit</button>
</div>
}
Controller code:
namespace LoginSys.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Registration";
return View();
}
[HttpPost]
public ActionResult register(string command, FormCollection formData )
{
if (command == "submit")
{
var name = formData["txtboxName"];
var email = formData["txtboxEmail"];
}
return View();
}
}
}
i'm intentionally using this way of coding it instead of complex and advance one. Just help me to get values in controller
[HttpPost]
public ActionResult register(YOURMODEL model)
{
//db operation
return View();
}
NOTE: make sure your textbox name should be same as your model name
You should use viewmodels. create a model for the view that can be posted to the action. However, if you wish to continue your current approach you need to change the controller action to something like this:
[HttpPost]
public ActionResult register(string btnSubmit, string txtboxName, string txtboxEmail, string txtboxPassword)
{
if (command == "submit")
{
}
return View();
}
if this doesn't work, you can test it by using this:
[HttpPost]
public ActionResult register(FormCollection form)
{
if (command == "submit")
{
}
return View();
}
When you debug you can check the 'form' parameter and see that your fields exists in the form, and get the proper names for the parameters you need.

MVC 4 URL bypassing

I am creating a web application where there are five steps.
Home Page1 Page 2 Review Confirmation.
In the url, it goes like localhost:22112/Home/Page1 Page 2 and so forth.
My problem is if someone copies localhost:22112/Home/Page2, then it skips everything
and jumps to page 2 directly. So, how can I stop that? I did the following but its not working properly.
Any suggestions would be really helpful.
In the controller
private bool IsFromIndexPage()
{
if (Session["IsFromIndex"] != null)
{
return true;
}
else
{
return false;
}
}
And for each page actionresult, I am writing it like this.
[HttpGet]
public ActionResult Page1()
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}
[HttpPost]
public ActionResult Page1(Information model, string command)
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}
[HttpGet]
public ActionResult Page2()
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}
[HttpPost]
public ActionResult Page2(Information model, string command)
{
if (!IsFromIndexPage())
{
return RedirectToAction("Index");
}
.....other methods..
}
If you're using session to store the progress through the steps you should be checking your session variables to validate the request is for the given page otherwise redirect the user to the first/current completed page.
You can write a custom request handler for this to keep your session validation code separate your controller code
see this Question about how to implement the basic functionality to what you want to do
EDIT:
switch(currentStep){
case 1:
return Step1(model)
break;
case 2:
return Step2(model)
break;
default:
return new HttpNotFoundResult();
break;
}
Here is a bit different approach, on how to make a wizard with asp.net MVC using ajax.
Your url will be /Home/Wizard on every step. Since using the AjaxOnly attribute, it will not be possible to visit Step1, Step2 etc (see reference in the bottom for AjaxOnly)
Controller:
public ActionResult Wizard()
{
return View();
}
[AjaxOnly]
public ActionResult Step1()
{
return PartialView("Step1");
}
[AjaxOnly]
public PartialViewResult Step2(FormCollection coll)
{
Session["FullName"] = coll["FullName"]!= null ? coll["FullName"].ToString() : string.Empty;
return PartialView("Step2");
}
[AjaxOnly]
public PartialViewResult Confirm(FormCollection coll)
{
WizardModel model = new WizardModel() { Name = Session["FullName"].ToString(), Phone = coll["Phone"] != null ? coll["Phone"].ToString() : string.Empty };
return PartialView("Confirm", model);
}
Model for last step:
public class WizardModel
{
public string Phone { get; set; }
public string Name { get; set; }
}
Make sure you reference jquery.unobtrusive-ajax in your page/layout page
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
Wizard.cshtml
#{
ViewBag.Title = "Wizard";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Wizard - Overview</h2>
#using (Ajax.BeginForm("Step1", new AjaxOptions { HttpMethod="Get", UpdateTargetId = "wizardcontainer" }))
{
<input type="submit" value="Start wizard" />
}
<div id="wizardcontainer"></div>
Step1.cshtml
<div>
<h2>Wizard - Step 1</h2>
<br />
#using(Ajax.BeginForm("Step2", new AjaxOptions { UpdateTargetId = "wizardcontainer" }))
{
#Html.Label("FullName")
#Html.TextBox("FullName")
<input type="submit" value="Next >>" />
}
</div>
Step2.cshtml
<div>
<h2>Wizard - Step 2</h2>
#using(Ajax.BeginForm("Confirm", new AjaxOptions { UpdateTargetId = "wizardcontainer" }))
{
#Html.Label("Phone")
#Html.TextBox("Phone")
#Ajax.ActionLink("<< Previous", "Step1", new AjaxOptions { UpdateTargetId = "wizardcontainer" })
<input type="submit" value="Next >>" />
}
</div>
Confirm.cshtml
#model MvcApplication2.Controllers.WizardModel
<div>
<h2>Wizard - Final Stage</h2>
Name: #Model.Name
<br />
Phone: #Model.Phone
#Ajax.ActionLink("<< Previous", "Step2", new AjaxOptions { UpdateTargetId = "wizardcontainer" })
</div>
Look here for the AjaxOnly attribute:
http://helios.ca/2009/05/27/aspnet-mvc-action-filter-ajax-only-attribute/

Model change in post action not visible in Html.TextBoxFor?

This must be something very obvious but for me it looks very strange. I have simple controller, model with one property, and view which displays value of property and renders editor for that property. When I click the button, form is posted and exclamation mark is appened to property. This exclamation mark is visible in my view but only in p tag, not in input tag rendered by Html.TextBoxFor().
Why Html.TextBoxFor() ignores that I updated my model in post action?
Is there any way to change this behavior of Html.TextBoxFor()?
View
#model ModelChangeInPostActionNotVisible.Models.IndexModel
#using (Html.BeginForm())
{
<p>#Model.MyProperty</p>
#Html.TextBoxFor(m => m.MyProperty)
<input type="submit" />
}
Model
namespace ModelChangeInPostActionNotVisible.Models
{
public class IndexModel
{
public string MyProperty { get; set; }
}
}
Controller
namespace ModelChangeInPostActionNotVisible.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new IndexModel { MyProperty = "hi" });
}
[HttpPost]
public ActionResult Index(IndexModel model)
{
model.MyProperty += "!";
return View(model);
}
}
}
HTML after clicking on submit button
<form action="/" method="post"> <p>hi!</p>
<input id="MyProperty" name="MyProperty" type="text" value="hi" /> <input type="submit" />
</form>
This is by design.
The helper methods are using the ModelState, thus if the response of your request is using the same Model, it will display the value that was posted.
This is to allow you to render the same view in the situation where the validation would have failed.
To make sure you display the new information add : ModelState.Clear(); before you return.
Read more here : http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx
namespace ModelChangeInPostActionNotVisible.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new IndexModel { MyProperty = "hi" });
}
[HttpPost]
public ActionResult Index(IndexModel model)
{
model.MyProperty += "!";
ModelState.Clear();
return View(model);
}
}
}
Yan Brunet is absolutely correct that the variable needs to be removed from the ModelState in order to be modified in the controller. You don't have to clear the entire ModelState, though. You could do the following to remove just the variable to want to modify:
ModelState.Remove("MyProperty");
This would be useful in case you wanted to retain other values which the user had entered.

In MVC view, when form is posted, request contains form values while all model fields are 0

Using Visual Studio 2010, MVC project
When my form is submitted (currently via javascript, but same results with a submit button), the action is getting an empty model with both of the fields in it being zero instead of containing the value I entered into the textbox. The Request object does contain the correct name/value pair in the Form collection.
Model values going the other way work fine - so based on my [HttpGet] CallDisplayHome() action, the form loads with the textbox value being 1.
If anyone has a clue as to why it would not work coming back via POST, I would sure appreciate it.
Model being used:
namespace TCSWeb.Models
{
public class CallDisplayModel
{
public int SelectedRowIndex;
public int SelectedLineID;
}
}
View:
#model TCSWeb.Models.CallDisplayModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<body>
/*
There a Jscript datatable here and a bunch of scripts for working with it in the header I am skipping because I am hoping they are not relevant
*/
<div>
#using (Html.BeginForm("Testing", "CallDisplay", FormMethod.Post, new { name = "submitSelLine" }))
{
#Html.TextBoxFor(m => m.SelectedLineID)
<p>
<input type="submit" value="Log On" />
</p>
}
</div>
<button onclick="SubmitSelCallRecord()">#LangRes.Strings.calldisplay_opencallrecord</button>
My controller actions:
[HttpGet]
public ActionResult CallDisplayHome()
{
TCSWeb.Models.CallDisplayModel temper = new CallDisplayModel();
temper.SelectedLineID = 1;
temper.SelectedRowIndex = 1;
return View(temper);
}
[HttpPost]
public ActionResult Testing(TCSWeb.Models.CallDisplayModel cdmodel)
{
return RedirectToAction("CallDisplayHome"); //breaking here, cmodel has zero for selectedlineid
}
You need to declare your CallDisplayModel variables as properties:
public int SelectedRowIndex { get; set; }
[Required]
public int SelectedLineID { get; set; }
You can also add a little bit of validation to make sure that the user provides the correct information.
Change your post method to the following:
[HttpPost]
public ActionResult Testing(TCSWeb.Models.CallDisplayModel temper)
{
//check if valid
if(ModelState.IsValid)
{
//success!
return RedirectToAction("CallDisplayHome");
}
//update error! redisplay form
return View("CallDisplayHome", temper);
}
And display the errors in your view like so:
#Html.ValidationMessageFor(m => m.SelectedLineID)
#Html.TextBoxFor(m => m.SelectedLineID)
I'm unsure what your submitSelCallRecord button is doing, as it is referencing the javascript that was omitted.

Resources