Weird data binding behavior in ASP.NET MVC3? - asp.net-mvc-3

I have the following domain objects:
public class Foo
{
public Id {get; set;}
**public BarId {get; set;}**
public virtual Bar {get; set;}
public string Name {get; set;}
}
public class Bar
{
public Id {get; set;}
public string Name {get; set;}
}
The view model used by the view looks like this:
public class FooEditorModel
{
public Foo {set; get;}
public SelectList Bars { get; set; }
}
The view looks like this:
#model FooEditorModel
#Html.ValidationSummary( true )
#Html.HiddenFor( model => model.Foo.Id )
#Html.LabelFor( model => model.Foo.BarId, "Bar" )
#Html.DropDownListFor( model => model.Foo.BarId, Model.Bars )
#Html.ValidationMessageFor( model => model.Foo.BarId )
The controller looks like this:
[HttpPost]
public virtual ActionResult Save( FooEditorModel model )
{
if ( ModelState.IsValid )
{
Foo foo = MakeAModel( model );
FooRepository.Save( foo );
}
}
Foo MakeAModel( model )
{
Foo foo = MakeAFoo();
**foo.BarId = model.Foo.BarId;**
return foo;
}
Quite standard code, a bit simplified.
The problem is that if I use a Bar in Foo, instead of BarId, the ModelState is not valid (because of Bar.Name).
I find it very poorly designed to be forced to have both a child Id and a reference to the child in the root object.
Is this the only way for a data binding to work in MS MVC 3?

Related

Html.EditorFor method for interface attributes

I'm a beginner ASP.NET MVC 3 web-developer and have this problem:
There are several view models, that have similar logic and I've come to idea to have one common EditorTemplate for them to be rendered by Html.EditorFor.
The template is named "ExistOrCreateNewInput.cshtml" and is strongly-typed with IExistOrCreateNewInput interface class:
public interface IExistOrCreateNewInput
{
int? existEntId { get; set; }
IUnapprovedNewEntityCreateInput createInput { get; set; }
}
Template's content is something like:
#model IExistOrCreateNewInput
<h2>Add or choose</h2>
#* here put some common js code *#
Html.EditorFor(o => o.existEntId)
Html.EditorFor(o => o.createInput)
Suppose I have some sort of models that implement this interface, for example:
public class FirstModelInput : IExistOrCreateNewInput
{
[Display(Name="First")]
[UIHint("Lookup")]
public int? existEntId {get; set;}
[UIHint("PaperCreateInput")]
public PaperCreateInput paperCreateInput {get; set;}
public IUnapprovedNewEntityCreateInput createInput
{
get
{
return paperCreateInput;
}
set { }
}
}
public class SecondModelInput : IExistOrCreateNewInput
{
[Display(Name="Second")]
[UIHint("Lookup")]
public int? existEntId {get; set;}
[UIHint("ThesisCreateInput")]
public ThesisCreateInput thesisCreateInput {get; set;}
public IUnapprovedNewEntityCreateInput createInput
{
get
{
return thesisCreateInput;
}
set { }
}
}
public class ThirdModelInput : IExistOrCreateNewInput
{
...
}
Where PaperCreateInput and ThesisCreateInput classes implement IUnapprovedNewEntityCreateInput interface.
So, I want my view model
public class SomeGlobalViewModel
{
[Required]
string name {get; set;}
[UIHint("ExistOrCreateNewInput")]
FirstModelInput firstModel {get; set;}
}
to render correctly the attribute "firstModel" with Html.EditorFor(o => o.firstModel) method.
Now I know that EditorFor method is dealing with Metadata, so probably my question should be like "how can I pass metadata of attributes to base interface attributes". Correct me, if I'm wrong.
Anyway, I need those helper methods
Html.EditorFor(o => o.existEntId)
Html.EditorFor(o => o.createInput)
in my editor template (strongly-typed with interface) to render my Models' attributes as I declared them in implementing classes:
[UIHint("Lookup")]
public int? existEntId {get; set;}
[UIHint("PaperCreateInput")]
public PaperCreateInput createInput {get; set;}
Thanks in advance.
Sorry for my bad English.

How to change default behavior of .Where method in EntityFramework 4.1?

I'd like to change the default behavior of .Where method for specific case(s).
All my Business Objects inherit from BaseObject that has property int ID {get; set;}
// Base class
public abstract class BaseObject
{
public abstract int ID {get; set;}
}
I have 2 classes:
public partial class User : BaseObject
{
public override int ID {get; set;}
public string Username { get; set; }
public int ProfileID { get; set; }
public virtual Profile Profile { get; set; }
}
public partial class Profile : BaseObject
{
public override int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public static Profile GetAdminProfile()
{
return new Profile(){ID = 3, Name = "Admin profile"};
}
}
I would like to write
// This throws Unable to create a constant value of type 'Profile'... exception
User admin = Users.Where(user.Profile == Profile.GetAdminProfile()).FirstOrDefault();
instead of
User admin = Users.Where(user.Profile.ID == Profile.GetAdminProfile().ID).FirstOrDefault();
Is there a way to achieve this?
This is a known problem in Entity Framework. You will have follow the second approach.

Get Id and Type from Html.DropDownList to Controller

I have a class called
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Country Country { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Type { get; set; }
}
My MVC View Page is Strongly typed to Person and there is a dropdownlist which show the list of countries.
In My Controller Index
public ActionResult Index()
{
LoadCountryList();
return View(Person);
}
private void LoadCountryList()
{
IEnumerable<CountryList> countryList = CountryListService.GetCountryList();
ViewData["CountryList"] = new SelectList(country, "Id", "Type", 0);
}
Code in the html
<%: Html.DropDownListFor(model => model.Country.Id, (IEnumerable<SelectListItem>)ViewData["CountryList"], "--Select--")%>
When the page is submitted Create method is called in the controller
public ActionResult Create(Person person)
{
// person.Country.Id has the value
// person.Country.Type is null
}
I am getting only the Country Id from the object person in the Create Method. The Country Id is loaded inside the Person Object under Country.
Is there any way I can get both the Id and Type of the country when passed from the Page to the Controller?
I know I am passing Html.DropDownListFor(model => model.Country.Id .... from here.
Is there any Solution so that I get Id and Type in the controller.
Thanks
Passing it through the person object is not the best way to do it. Instead, assign an ID to your dropdown list like this:
<%: Html.DropDownListFor(
model => model.Country.Id,
(IEnumerable<SelectListItem>)ViewData["CountryList"], "--Select--")
new { id = "CountryID" }
%>
and then put that in as a parameter to your Create method:
public ActionResult Create(Person person, int CountryID)
{
var country = CountryListService.GetCountryList().Where(x => x.id == CountryID);
person.Country = country;
...
}
ASP .NET MVC will look for a control that has the same ID name as the parameter in the method call and pass it through.

How would I add properties to my view model that are not part of my model using automapper?

I am not sure if this is possible but here is my situation.
Say I have a model like this:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
My View model looks like this:
public class ProductModel
{
public int Id { get; set; }
public string Name { get; set; }
public string CustomViewProperty { get; set; }
}
I am using my ProductModel to post back to a form and I don't care or need the Custom View Property. This mapping works fine as automapper drops the unknown properties.
What I would like to do is map my custom properties in only one direction. i.e.
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty //???This is where I am stuck
What ends up happening is when I call "ToModel", automapper dumps my unknown properties and nothing comes over the wire.
Like this.
var product = _productService.GetProduct();
var model = product.ToModel;
model.CustomViewProperty = "Hello World"; //This doesn't go over the wire
return View(model);
Is this possible? Thanks.
You should ignore unmapped properties:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.Ignore());
or map them:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.MapFrom(product=>"Hello world"));

How to bind a ViewModel that has sub-viewmodel properties?

I am curious to know the best way to approach the following scenario:
public class LittleThingViewModel
{
public string Id{ get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class BigThingViewModel
{
public LittleThingViewModel little1 {get; set; }
public LittleThingViewModel little2 {get; set; }
public string propA {get; set; }
public string propB {get; set; }
public string propC {get; set; }
}
In the initial CREATE action of my controller, I populate two LittleThingViewModels, assign them to a new BigThingViewModel and pass it to the strongly-typed view. I then use EditorFor to render edit controls for BigThing properties A, B, and C.
What is the best technique for automatically rendering the LittleThingViewModels to the form such that when I post back to my controller it auto-binds? (My goal is to only display the Name and keep the other properties hidden)
public ActionResult Create(BigThingViewModel b)
I found that I could render A, B, and C properties in addition to each individual property of the two LittleThing sub-viewmodels and it successfully bind BigThingViewModel during POST:
Html.DisplayFor(model=>model.little1.Name)
Html.HiddenFor(model=>model.little1.Name)
Html.HiddenFor(model=>model.little1.Id)
Html.HiddenFor(model=>model.little1.Location)
Is there a technique that would allow me to render both labels and hidden elements automatically instead of having to do them each explicitly each time?
Something like:
Html.RenderFor(model=>model.little1)
Thanks
Create a partial view that is strongly typed to the LittleThingViewModel.
LittleThingView.ascx (partial view)
Html.DisplayFor(model=>model.Name)
Html.HiddenFor(model=>model.Name)
Html.HiddenFor(model=>model.Id)
Html.HiddenFor(model=>model.Location)
In main View
<%
Html.RenderPartial("LittleThingView",model.little1);
Html.RenderPartial("LittleThingView",model.little2);
%>
To make it look better you can make a collection for these objects in the big ViewModel. So if one of them is null or you need more than one, it wouldn't need any changes.
public class BigThingViewModel
{
public List<LittleThingViewModel> littles=new List<LittleThingViewModel>();
public string propA {get; set; }
public string propB {get; set; }
public string propC {get; set; }
}
And in the view:
<%
foreach(var littleThingViewModel in model.littles)
{
Html.RenderPartial("LittleThingView",littleThingViewModel);
}
%>

Resources