ASP.NET MVC2: Update of a model for further modifications in a POST handler. - asp.net-mvc-2

Model:
public class Model
{
public ItemType Type { get; set; }
public int Value { get; set; }
}
public enum ItemType { Type1, Type2 }
Controller:
public ActionResult Edit()
{
return View(new Model());
}
[HttpPost]
public ActionResult Edit(Model model, bool typeChanged = false)
{
if (typeChanged)
{
model.Value = 0; // I need to update model here and pass for further editing
return View(model);
}
return RedirectToAction("Index");
}
And of course View:
<div class="editor-label"><%: Html.LabelFor(model => model.Type) %></div>
<div class="editor-field">
<%: Html.DropDownListFor(
model => model.Type,
Enum.GetNames(typeof(MvcApplication1.Models.ItemType))
.Select(x => new SelectListItem { Text = x, Value = x }),
new { #onchange = "$(\'#typeChanged\').val(true); this.form.submit();" }
)
%>
<%: Html.Hidden("typeChanged") %>
</div>
<div class="editor-label"><%: Html.LabelFor(model => model.Value) %></div>
<div class="editor-field"><%: Html.TextBoxFor(model => model.Value) %></div>
<input type="submit" value="Create" onclick="$('#typeChanged').val(false); this.form.submit();" />
The code in controller (with the comment) doesn't work as I expect. How could I achieve the needed behavior?

As I wrote here multiple times, that's how HTML helpers work and it is by design: when generating the input they will first look at the POSTed value and only after that use the value from the model. This basically means that changes made to the model in the controller action will be completely ignored.
A possible workaround is to remove the value from the modelstate:
if (typeChanged)
{
ModelState.Remove("Value");
model.Value = 0; // I need to update model here and pass for futher editing
return View(model);
}

Related

Simple Model Binding Not Working For Integer

I'm trying to figure out why this simple controller action isn't working. All I'm trying to do is increase Number after every POST.
Model
public class ViewModel
{
public int Number { get; set; }
}
View
<body>
<% using (Html.BeginForm("Test", "Invoice"))
{ %>
<%: Html.EditorFor(m => m.Number) %>
<%= Model.Number %>
<input type="submit" value="Submit" />
<% } %>
</body>
Controller
public ActionResult Test()
{
var viewModel = new ViewModel {Number = 1};
return View("Test", viewModel);
}
[HttpPost]
public ActionResult Test(ViewModel viewModel)
{
viewModel.Number = viewModel.Number + 1;
return View("Test", viewModel);
}
In my controller, viewModel.Number is increased to 2, but when the view is returned the text box contains 1 and Model.Number displays 2.
Am I missing something?
The Html helpers favor the values in the ModelState over the actual Model values.
So if you modify the Model in your action you need to clear the ModelSate before passing it to the view:
[HttpPost]
public ActionResult Test(ViewModel viewModel)
{
viewModel.Number = viewModel.Number + 1;
ModelState.Clear();
return View("Test", viewModel);
}
You can read more about this ASP.NET MVC feature in this great article:
ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

LINQ to Entities error when trying to bind a dropdownlist

I am trying to create a viewmodel and add a SelectListItem to allow me to bind a dropdown list.
I have a very basic viewmodel that looks like this
public class CreatePurchaseViewModel
{
public Product Product { get; set; }
public IEnumerable<SelectListItem> Products { get; set; }
public int SelectedProductId { get; set; }
public DateTime OrderDate { get; set; }
public bool OrderSent { get; set; }
}
My controller looks like this
[HttpGet]
public ActionResult Create()
{
var model = new CreatePurchaseViewModel
{
Products = context.Products.Select(x =>
new SelectListItem()
{
Text = x.ProductName,
Value = x.ProductID
})
};
return View(model);
}
However it complains that Value = x.Product cant convert type int to string. So if I add a .ToString it compiles ok but when I try load the view I get an error
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
My View
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>CreatePurchaseViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.SelectedProductId)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SelectedProductId,Model.Products)
#Html.ValidationMessageFor(model => model.SelectedProductId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.OrderDate)
</div>
<div class="editor-field">
#Html.TextBoxFor(model=>model.OrderDate)
#Html.ValidationMessageFor(model => model.OrderDate)
</div>
<div>
Sent
#Html.RadioButtonFor(model => model.OrderSent, "Sent", new { #checked = true })
Not Sent
#Html.RadioButtonFor(model=>model.OrderSent,"Not Sent")
Im pretty new to both entity framework and mvc so any help would be great.
Thank you
You haven't specified how does your Product model look like but we can assume that the ProductID property is integer so you might need to convert it to a string:
[HttpGet]
public ActionResult Create()
{
var model = new CreatePurchaseViewModel
{
Products = context.Products.Select(x =>
new SelectListItem
{
Text = x.ProductName,
Value = x.ProductID.ToString()
}
)
};
return View(model);
}

Textbox reverts to old value while Modelstate is valid on postback

Maybe I'm missing something but when I have a form that posts back to the same action, the textbox value reverts to the old value. The following example should increment the value in the textbox on each POST. This does not happen, the value on the model is incremented and the model is valid.
IF however I clear the modelstate in the HttpPost Action (the comment in the code), everything works as expected.
Am I missing something?
Here's the code:
Model:
public class MyModel
{
public int MyData { get; set; }
}
View:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Models.MyModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) {%>
<%: Html.TextBoxFor(m => m.MyData)%> (<%: Model.MyData%>)
<%: Html.ValidationMessageFor(m => m.MyData) %> <br />
State :<%: ViewData["State"] %> <br />
<input type="submit" />
<% } %>
</asp:Content>
Controller:
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(new MyModel { MyData = 0 });
}
[HttpPost]
public ActionResult Index(MyModel myModel)
{
// ModelState.Clear();
ViewData["State"] = "invalid";
if (ModelState.IsValid)
ViewData["State"] = "Valid";
var model = new MyModel { MyData = myModel.MyData + 1 };
return View(model);
}
}
I just found an answer to this online.
The trick is to clear the ModelState before returning the Model
[HttpPost]
public ActionResult Index(MyModel myModel)
{
// ModelState.Clear();
ViewData["State"] = "invalid";
if (ModelState.IsValid)
ViewData["State"] = "Valid";
var model = new MyModel { MyData = myModel.MyData + 1 };
ModelState.Clear();
return View(model);
}
For more detail read these 2 articles
http://forums.asp.net/p/1527149/3687407.aspx
Asp.net MVC ModelState.Clear
You should either use the Post-Redirect-Get pattern or not use the Html Helpers.
Reference: http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx
Basically MVC expects any redisplay from a post to be a validation error, and re-uses the posted data (view ModelState) for redisplay in preference to model data. The guidance is to not use ModelState.Clear().

Data is empty in Post method asp.net mvc 2

I`m trying to display classes that have properties of type some custom class inside it.
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
public ActionResult Testify(int id)
{
ComplexParent par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View("Testify", par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View("Testify", pComplexParent);
}
View:
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Fields</legend>
<%: Html.EditorFor(x => x.First) %>
<br />
<%: Html.EditorFor(x => x.Second.ChildName)%>
<br/>
<br/>
<br/>
<% Html.RenderPartial("SimpleChild", Model.First); %>
<p>
<input type="submit" value="Watch me :-)" />
</p>
</fieldset>
<% } %>
When it comes to Get it works just fine, I can see all the data. But on post pComplexParent parameter is empty (both properties of a complex class are nulls). Probably Im missing something here, but could not get this to work ...
Small addition: view part that only shows editor for name makes Second child not null and name is set to Bob. But I dont understand how to make it just with EditorFor or DisplayFor methods.
UPDATE: Thanks to Darin Dimitrov, who kindly went throught all my code and found what caused this problem. The exact problem was that if you are using display template, asp.net mvc 2 doesnt post any values back and if whole template is has nothing to post back object is null. I still thinking of the way how to get the data, even if you don`t want to edit it. But using editor template does the thing and I have all objects filled with proper data now.
Your view is a bit of a mess. You are using editor templates along with partials for the first child. It is not very clear what fields are included inside the form. I would recommend you using only editor templates:
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
[HandleError]
public class HomeController : Controller
{
public ActionResult Testify(int id)
{
var par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View(par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View(pComplexParent);
}
}
View:
<% using (Html.BeginForm()) { %>
<%: Html.EditorFor(x => x.First) %>
<%: Html.EditorFor(x => x.Second) %>
<input type="submit" value="Watch me :-)" />
<% } %>
Editor template for SimpleChild (~/Views/Home/EditorTemplates/SimpleChild.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SomeNs.Models.SimpleChild>" %>
<%: Html.HiddenFor(x => x.Id) %>
<%: Html.EditorFor(x => x.ChildName) %>
<%: Html.EditorFor(x => x.ChildDescription) %>
Now if you want to have different editor templates for the two child properties you could either specify the editor template name when including it:
<%: Html.EditorFor(x => x.First, "FirstChildEditor") %>
which corresponds to ~/Views/Home/EditorTemplates/FirstChildEditor.ascx or use an [UIHint] attribute at your model:
public class ComplexParent
{
[UIHint("FirstChildEditor")]
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
My recommendation is not to use Html.RenderPartial for generating input fields because their names will be hardcoded and won't bind properly depending on your objects hierarchy.

ModelBinder NHibernate Complex Object with Collections

Below, on initial load (Edit), everything displays fine. However, when I POST (Action below), it looks like it goes and tries to grab the ProductAttribute models separately, with the Product's id, which promptly fails. How to keep my Binder implementation from trying to re-bind Collections as separate entities?
Thanks!
Model
public class Product {
virtual public long Id { get; set; }
virtual public string Name { get; set; }
private IList<ProductAttribute> _productAttributes;
public virtual IList<ProductAttribute> ProductAttributes {
get{
if(_productAttributes == null){
_productAttributes = new List<ProductAttribute>();
}
return _productAttributes;
}
set{
_productAttributes = value;
}
}
}
View
<%using (Html.BeginForm(new {id = Model.Id > 0 ? (long?)Model.Id : null})) {%>
<table class="form">
<% for(var i=0; i < Model.ProductAttributes.Count; i++){
var pa = Model.ProductAttributes[i]; %>
<tr>
<th><%: Model.ProductAttributes[i].Name %></th>
<td>
<%: Html.Hidden("ProductAttributes.Index", i) %>
<% if(pa.CanSpecifyValueDirectly()){ %>
<%: Html.TextBoxFor(model => model.ProductAttributes[i].Value) %>
<% } else { %>
<%: Html.DropDownListFor(model => model.ProductAttributes[i].Value, new SelectList(pa.MarketAttribute.AttributeLevels, "id", "Name", pa.AttributeLevel)) %>
<% } %>
</td>
</tr>
<input type="submit" value="Save" />
</table>
<%}%>
Controller
public ActionResult Edit(long id, Product product) {
ViewData.Model = product;
if (ModelState.IsValid) {
var results = product.Update();
ViewData["results"] = results;
if (!results.Error) {
return RedirectToAction("Show", new { id = id });
}
}
return View();
}
Binder
public class StackModelBinder : DefaultModelBinder {
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
var modelInterface = modelType.GetInterface("IModel");
if (modelInterface == null) {
return base.CreateModel(controllerContext, bindingContext, modelType);
}
var value = bindingContext.ValueProvider.GetValue("id").RawValue.ToString();
if (string.IsNullOrEmpty(value)) {
return base.CreateModel(controllerContext, bindingContext, modelType);
}
var id = Convert.ChangeType(value, typeof (long));
var assembly = Assembly.GetAssembly(modelType);
var dao = assembly.GetType(string.Concat(assembly.GetName().Name, ".core.GlobalDao`1[", modelType.FullName, "]"));
if (dao == null) {
return base.CreateModel(controllerContext, bindingContext, modelType);
}
var method = dao.GetMethod("Get");
return method.Invoke(null, new[] {id});
}
}
Typically, it is a bad idea to push entities through the model binder--they tend to be a bit too complex for it to handle, never mind the exciting stuff that goes on in modern ORMs, such as dynamic proxies, that can give the ModelBinder or the ORM fits.
Your best bet here is to change the rules and build a dedicated class for taking the edits and transferring that to the controller. This class can be ModelBinder-friendly and you get the added benefit of separating the UI from the domain entities.