ASP.NET MVC 2 edit controller doesn't work using viewmodel - asp.net-mvc

Hey, the problem is that edit controller in ASP.NET MVC 2 doesn't work. I tried many ways and nothing works.
Here's a sample code:
[Authorize]
public ActionResult Edit() {
var edit = new UserViewModel {
User = Database.Users.Single(u => u.UserName == User.Identity.Name)
};
return View(edit);
}
[Authorize]
[HttpPost]
public ActionResult Edit(FormCollection formCollection) {
var edit = new UserViewModel {
User = Database.Users.Single(u => u.UserName == User.Identity.Name)
};
// TODO: try, catch
UpdateModel(edit, "User");
Database.SaveChanges();
return View(edit);
}
Here's a view model class:
public class UserViewModel {
public User User { get; set; }
}
What should I do to update this user model to database? A the moment I'm using only Email field:
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) {%>
<div>
<div class="UserFieldLeft"><%: Html.LabelFor(model => model.User.Email) %></div>
<div class="UserFieldRight"><%: Html.TextBoxFor(model => model.User.Email, new { style="width: 200px" }) %></div>
<div class="UserFieldHelper"><%: Html.ValidationMessageFor(model => model.User.Email) %></div>
<p><input class="UserFieldInput" type="submit" value="ZmieƄ email" /></p>
</div>
<% } %>
If I work on native user model it doesn't work too. What's wrong? Where did I made a mistake?
By the way, I've to use view model to add (in future) some checkboxes (hair color, length, etc.) to my user.
Thank you for your time and help.

You don't need the prefix "User".
UpdateModel(edit);
should work. In the formsCollection their should be a key with User.Email. This should map to the Email property in the User Object.

Related

MVC Binding to checkbox

I have found so many questions about this, but none of them go over or seem to go over my scenario. I have a model:
public class CheckBoxModel
{
public int Id{ get; set; }
public bool IsSelected { get; set; }
}
In then try and bind my IsSelected bool to a checkbox like this:
<%= Html.CheckBox("IsSelectedCheck",Model.IsSelected)%>
I have lots of items on a page, they all have a checkbox next to them, all i am trying to do is pass back to the controller all the id's of the items and which ones have been selected.
At the moment, the value of IsSelected is always false. Should Html.CheckBox set the value of Model.IsSelected each time the user toggles the checkbox.
Thanks
Try like this:
<%= Html.CheckBoxFor(x => x.IsSelected) %>
Also if you want to pass along the id don't forget to do so:
<%= Html.HiddenFor(x => x.Id) %>
And if you had a collection of those:
public class MyViewModel
{
public CheckBoxModel[] CheckBoxes { get; set; }
}
you could:
<% for (var i = 0; i < Model.CheckBoxes.Length; i++) { %>
<div>
<%= Html.HiddenFor(x => x.CheckBoxes[i].Id) %>
<%= Html.CheckBoxFor(x => x.CheckBoxes[i].IsSelected) %>
</div>
<% } %>
which will successfully bind to:
[HttpPost]
public ActionResult MyAction(MyViewModel model)
{
// model.CheckBoxes will contain everything you need here
...
}
An alternative to Darin's fantastic answer
I definitely recommend following Darin's approach for returning classes which will be most of the time. This alternative is a 'quick' and dirty hack if all you need is the checked Ids:
<% foreach (var cb in Model.CheckBoxes) { %>
<div>
<input type="checkbox"
value="<%= cb.Id %>"
<%= cb.IsSelected ? "checked=\"checked\"" : "" %>
name="ids" />
</div>
<% } %>
Will bind to the int[] ids parameter in the following action:
[HttpPost]
public ActionResult MyAction(int[] ids)
{
// ids contains only those ids that were selected
...
}
The benefit is cleaner html as there is no hidden input.
The cost is writing more code in the view.
In MVC 4.0 (Razor 2.0) you can use the following syntax in your view:
<input type="checkbox" value="#cb.Id" checked="#cb.IsSelected" name="ids" />

Asp.net MVC2 ModelBindingContext.ModelName empty

I'm not even quite sure where to start explaining this problem. I've been working on this for about the past 10 hours without a clue as to what the root cause is. If any additional details are needed, I'd be happy to provide. I'm just guessing at what is relevant at this point.
I have an MVC2 site with routes set up by by Steve Hodgkiss' wonderful RestfulRouting package, using the default route setup with nested controllers (e.g. /customer/{custid}/location/{locid} and such).
In this, I have one particular model that is giving me issues. For some reason, when the create page post's the data back to my server, the ModelName property in the ModelBindingContext object passed to the DefaultModelBinder (well, my custom class inherited from DefaultModelBinder, to handle grabbing objects from a repository). This happens only for this one model. And I can't spot the differences at all.
The broken model
public class RemedialItem : Entity
{
public virtual int Id { get; set; }
....
A working model:
public class Customer : Entity
{
public virtual int Id { get; set; }
....
Entity is just an empty class used as a marker for Reflection use.
The broken controller method in RemedialItemController.cs
[HttpGet]
public ActionResult New(int? locationId, int? applianceId)
{
var model = ViewModelFactory.Create<CreateRemedialItemViewModel>();
model.Categories = (from c in repository.Query<RemedialItemCategory>()
orderby c.Name
select c).ToList();
model.RemedialItem = new RemedialItem();
return View(model);
}
A working controller method in CustomerController.cs
[HttpGet]
public ActionResult New()
{
var viewModel = ViewModelFactory.Create<SingleCustomerViewModel>();
viewModel.Customer = new Customer();
return View(viewModel);
}
ViewModelFactory is an injected class that handles setting up some basic properties common to all view models (mainly is the user logged in and user details right now)
A broken viewmodel:
public class CreateRemedialItemViewModel : ViewModelBase
{
public RemedialItem RemedialItem { get; set; }
public IList<Location> Locations { get; set; }
public IList<Appliance> Appliances { get; set; }
public IList<RemedialItemCategory> Categories { get; set; }
}
A working ViewModel:
public class SingleCustomerViewModel : ViewModelBase
{
public Customer Customer { get; set; }
}
ViewModelBase contains a handful of properties populated by the ViewModelFactory.
The broken form in thew New view for RemedialItem:
<% using(Html.BeginForm("Create","RemedialItem",FormMethod.Post))
{%>
<%: Html.AntiForgeryToken() %>
<fieldset>
<legend>General</legend>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Category) %>
<%:Html.DropDownListFor(m=>m.RemedialItem.Category.Id, new SelectList(Model.Categories,"Id","Name")) %>
</div>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Item) %>
<%: Html.TextAreaFor(m=>m.RemedialItem.Item) %>
</div>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Note) %>
<%: Html.TextAreaFor(m=>m.RemedialItem.Note) %>
</div>
<input type="submit" value="Create Item" />
</fieldset>
<%}%>
A working New view:
<% using (Html.BeginForm("Create","Customer",FormMethod.Post)) {%>
<%: Html.ValidationSummary(true) %>
<%:Html.AntiForgeryToken() %>
<fieldset>
<legend>Fields</legend>
<p>
<%: Html.LabelFor(m=>m.Customer.Name) %>
<%: Html.TextBoxFor(m=>m.Customer.Name) %>
</p>
<p>
<%: Html.LabelFor(m=>m.Customer.Street) %>
<%: Html.TextBoxFor(m=>m.Customer.Street) %>
</p>
[...tl;dr...]
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
Both produce similar field names:
Broken:
<label for="RemedialItem_Item">Item</label>
<textarea cols="20" id="RemedialItem_Item" name="RemedialItem.Item" rows="2">
</textarea>
Working:
<label for="Customer_Name">Name</label>
<input id="Customer_Name" name="Customer.Name" type="text" value="" />
I apologize for the overly long code dump, in short:
The working set of stuff, when posted back on the create form, has the ModelName set to Customer. The broken stuff is an empty string
Is there something I'm missing? Has anyone encountered something like this before?
I found the issue. In the ViewModel the property that held the instance of RemedialItem to display was called RemedialItem. In the action it posted to, the parameter that took the RemedialItem instance was called item, and that broke everything.
In short, when using ViewModels, make sure the parameter name that takes an object from the ViewModel is the same as the property name in the viewmodel.
There went my day.

Problem returning ViewModel from edit

I trying to return the same model back to the view that edited the model. Making it sort of like Word or something with ctrl+s functionality for saving the mode. This works fine though the model that is returned to the view contains a bunch of nulls for some stupid reason. Is it because things were not serialized properly when the controller got the view model back or am I handling MVC the wrong way?
This is the Model
public class EditInvoiceModel
{
private readonly IEnumerable<Customer> _customers;
public EditInvoiceModel()
{
CreateProduct = new Product { Invoice = Invoice };
CreateWorkday = new Workday { Invoice = Invoice };
}
public EditInvoiceModel(Invoice invoice, IEnumerable<Customer> customers)
{
Invoice = invoice;
_customers = customers;
Customers = _customers.Select(x =>
new SelectListItem
{
Selected = x.Id == Invoice.CustomerID,
Text = x.Name,
Value = x.Id.ToString()
});
Products = Invoice.Products;
Workdays = Invoice.Workdays;
CreateProduct = new Product {Invoice = Invoice};
CreateWorkday = new Workday { Invoice = Invoice };
}
public IEnumerable<SelectListItem> Customers { get; set; }
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Workday> Workdays { get; set; }
public Product CreateProduct { get; set; }
public Workday CreateWorkday { get; set; }
public Invoice Invoice { get; set; }
}
And this is the controller action that returns the model back to the same view.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(EditInvoiceModel invoiceModel)
{
try
{
_repository.UpdateInvoice(invoiceModel.Invoice);
}
catch (Exception ex)
{
Log.Error(ex);
}
return View(invoiceModel);
}
All properties except the Invoice is null when this is returned to the view. I have no idea why this happens. Hope someone can help.
The problem that occurs (in the view) is the following: This is not because of a typo since it is working fine the first time the view is run. This must be to a problem with the modelbinder or my usage of the model binder.
The ViewData item that has the key 'Invoice.CustomerID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The ViewData item that has the key 'Invoice.CustomerID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
Source Error:
Line 28: <div class="editor-field">
Line 29: <%: Html.DropDownListFor(x => x.Invoice.CustomerID, Model.Customers)%>
Line 30: <%: Html.ValidationMessageFor(x => x.Invoice.CustomerID)%>
Line 31: </div>
Lastly part of the view that displays the view model.
<%# page language="C#" masterpagefile="~/Views/Shared/Site.Master"
inherits="System.Web.Mvc.ViewPage<FakturaLight.WebClient.Models.EditInvoiceModel>" %>
<asp:content id="Content2" contentplaceholderid="MainContent" runat="server">
<%= Html.ValidationSummary() %>
<% using (Html.BeginForm())
{ %>
<%= Html.AntiForgeryToken() %>
<div class="content-left">
<%: Html.EditorFor(x => x.Invoice) %>
<div class="editor-label">
<%: Html.LabelFor(x => x.Invoice.CustomerID)%>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(x => x.Invoice.CustomerID, Model.Customers)%>
<%: Html.ValidationMessageFor(x => x.Invoice.CustomerID)%>
</div>
<% } %>
</div>
<div class="content-right" id="details" style=" clear:both;">
<div id="workdays">
<%: Html.DisplayFor(x => x.Workdays) %>
</div>
<div id="products">
<%: Html.DisplayFor(x => x.Products) %>
</div>
</div>
</asp:content>
Darin is correct. Remember that you are still working with a disconnected client. This is just HTML and HTTP under the covers. The model binder is only able to bind values that are pushed to the server in the HTTP POST. All other properties for the class will receive no assignment, so if you want a more complete model pushed back to the browser in response in the
return View(invoiceModel);
you will need to complete those property assignments on the server side within your controller or with your repository's update method perhaps.
The reason why only the Invoice property is populated is because in your form you are only having input fields and dropdown lists for it. The model binder populates properties from what's sent in the request. You are posting a form which contains values only for the Invoice. As far as the Workdays and Products properties are concerned you are only displaying them (Html.DisplayFor) and they are never sent to the server. Also the model binder invokes the default constructor of your model which doesn't initialize those properties neither, so they are null at postback.

MVC Validation Not Working In Web Forms Project

I have the following code in my aspx view page:
<% using (Html.BeginForm())
{
%>
<div>
CustomerCode:
<%= Html.TextBoxFor(x=> x.CustomerCode) %>
<%= Html.ValidationMessageFor(x => x.CustomerCode)%>
and this code in my model:
public class MyModel
{
[Required(ErrorMessage="customer code req")]
[StringLength(2,ErrorMessage="must be 2 u idiot")]
public string CustomerCode {get; set;}
Though if I enter more than 2 charachters in the textbox and submit the page, in the controller when I do:
if (ModelState.IsValid)
It always says its valid? What am I missing? I have put this MVC project inside a Web Forms project but the MVC project works fine, its just the validation which is not working, any ideas? Thanks.
Make sure that the controller action accepts the model as parameter:
public ActionResult SomeAction(MyModel model)
{
if (ModelState.IsValid)
{
}
return View();
}
Now if you invoke:
http://example.com/myapp/home/someaction?customercode=123
The model should not be valid.
Hmm, it works for me on a test page with the following
public ActionResult Test()
{
MyModel model = new MyModel();
return View(model);
}
[HttpPost]
public ActionResult Test(MyModel model)
{
if (ModelState.IsValid) { }
return View(model);
}
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.CustomerCode) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.CustomerCode) %>
<%: Html.ValidationMessageFor(model => model.CustomerCode) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
public class MyModel
{
[Required(ErrorMessage = "customer code req")]
[StringLength(2, ErrorMessage = "must be 2 u idiot")]
public string CustomerCode { get; set; }
}

Roles Provider - AccountModel

I'm adding the Roles provider to the built in AccountModel but having some problems adding GetAllRoles in my view using the Register View Model.
View Model from AccountModel
public class RegisterModel
{
UserName, Email Etc....
[Required]
[DisplayName("AllRoles")]
public SelectList AllRoles { get; set; }
}
Roles Service added to AccountModel
public interface IRolesService
{
SelectList GetAllRoles();
}
public class RolesService : IRolesService
{
public SelectList GetAllRoles()
{
var AllRoles = new SelectList(Roles.GetAllRoles());
return AllRoles;
}
}
Register View Page Inherits RegisterModel
Form...
<div class="editor-label">
<%= Html.LabelFor(m => m.ConfirmPassword) %>
</div>
<div class="editor-field">
<%= Html.PasswordFor(m => m.ConfirmPassword) %>
<%= Html.ValidationMessageFor(m => m.ConfirmPassword) %>
</div>
<%= Html.DropDownListFor(m => m.AllRoles)%>
I'm not sure how to populate the DropDown list with all the Roles from the View Model.
Any help would be really great!!
I think you need properties for the selected role and the full list of roles. The list of roles will be used to populate the dropdown, the selected role will be populated on post with the selected value.
public class RegisterModel
{
UserName, Email Etc....
[Required]
[DisplayName("Role")]
public string Role { get; set; }
[ScaffoldColumn(false)]
public SelectList AllRoles { get; set; }
}
...
public ActionResult Register()
{
var roleService = new RoleService();
var model = new RegisterModel
{
AllRoles = roleService.GetAllRoles(),
// Role = "User" if you want to choose a default
}
return View( model );
}
<div class="editor-label">
<%= Html.LabelFor(m => m.ConfirmPassword) %>
</div>
<div class="editor-field">
<%= Html.PasswordFor(m => m.ConfirmPassword) %>
<%= Html.ValidationMessageFor(m => m.ConfirmPassword) %>
</div>
<%= Html.DropDownListFor(m => m.Role, Model.AllRoles, "--select--", null )%>

Resources