ASP.NET MVC 3 Viewmodel Pattern - asp.net-mvc-3

I am trying to work out the best way of using a viewmodel in the case of creating a new object.
I have a very simple view model that contains a contact object and a select list of companies.
private ICompanyService _Service;
public SelectList ContactCompanyList { get; private set; }
public Contact contact { get; private set; }
public ContactCompanyViewModel(Contact _Contact)
{
_Service = new CompanyService();
contact = _Contact;
ContactCompanyList = GetCompanyList();
}
private SelectList GetCompanyList()
{
IEnumerable<Company> _CompanyList = _Service.GetAll();
return new SelectList(_CompanyList, "id", "name");
}
I then have contact controller that uses this viewmodel and enable me to select a related company for my contact.
[Authorize]
public ActionResult Create()
{
return View(new ContactCompanyViewModel(new Contact()));
}
My issue is with the create method on the controller.
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Contact _Contact)
{
try
{
_Service.Save(_Contact);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The problem is that the view returns an empty contact object, but! the company id is populated, this is because the dropdown list explicitly declares its field name.
#Html.DropDownList("parent_company_id",Model.ContactCompanyList)
The standard html form fields pass the objects values back in the format of contact.forename when using the HTML.EditorFor helper...
#Html.EditorFor(model => model.contact.forename)
I can access them if I use a FormCollection as my create action method paremeter and then explicitly search for contact.value but I cannot use a Contact object as a parameter to keep my code nice and clean and not have to build a new contact object each time.
I tried passing the actual view model object back as a parameter but that simply blows up with a constructor error (Which is confusing seeing as the view is bound to the view model not the contact object).
Is there a way that I can define the name of the Html.EditFor field so that the value maps correctly back to the contact object when passed back to the create action method on my controller? Or Have I made some FUBAR mistake somewhere (that is the most likely explanation seeing as this is a learning exercise!).

Your view model seems wrong. View models should not reference any services. View models should not reference any domain models. View models should have parameterless constructors so that they could be used as POST action parameters.
So here's a more realistic view model for your scenario:
public class ContactCompanyViewModel
{
public string SelectedCompanyId { get; set; }
public IEnumerable<SelectListItem> CompanyList { get; set; }
... other properties that the view requires
}
and then you could have a GET action that will prepare and populate this view model:
public ActionResult Create()
{
var model = new ContactCompanyViewModel();
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
and a POST action:
[HttpPost]
public ActionResult Create(ContactCompanyViewModel model)
{
try
{
// TODO: to avoid this manual mapping you could use a mapper tool
// such as AutoMapper
var contact = new Contact
{
... map the contact domain model properties from the view model
};
_Service.Save(contact);
return RedirectToAction("Index");
}
catch
{
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
}
and now in your view you work with your view model:
#model ContactCompanyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedCompanyId, Model.CompanyList)
... other input fields for other properties
<button type="submit">Create</button>
}

Related

mvc dropdownlist not showing data value

I'm having an issue displaying data from the database into drop-downlist.
controller
TowinsEntities db = new TowinsEntities();
public ActionResult TMakes()
{
//T_Make make_db = new Models.T_Make();
ViewBag.carMaker = new SelectList(db.T_Make, "Make");
return View();
}
view
#Html.DropDownList("carMaker", "Select Make")
model
public partial class T_Make
{
public string Make { get; set; }
}
The output of a view is:
You need to overload your DropDownList with the string field names you want for value/display. You're only passing the model and selected value. I guess you'd use Make for both value and display (though, most people would use an ID for a value)
ViewBag.carMaker = new SelectList(db.T_Make, "Make","Make");
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.selectlist.-ctor?view=aspnet-mvc-5.2#System_Web_Mvc_SelectList__ctor_System_Collections_IEnumerable_System_String_System_String_

Single property not getting bound on HttpPost

I'm working on the first MVC3 project at our company, and I've hit a block. No one can seem to figure out what's going on.
I have a complex Model that I'm using on the page:
public class SpaceModels : List<SpaceModel> {
public bool HideValidation { get; set; }
[Required(ErrorMessage=Utilities.EffectiveDate + Utilities.NotBlank)]
public DateTime EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
}
In the Controller, I create a SpaceModels object with blank SpaceModels for when Spaces get combined (this would be the destination Space).
// Need a list of the models for the View.
SpaceModels models = new SpaceModels();
models.EffectiveDate = DateTime.Now.Date;
models.DisplayEffectiveDate = true;
models.Add(new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
return View("CombineSpaces", models);
Then in the View, I am using that SpaceModels object as the Model, and in the form making a TextBox for the Effective Date:
#model Data.SpaceModels
#using (Html.BeginForm("CombineSpaces", "Space")) {
<div class="EditLine">
<span class="EditLabel LongText">
New Space Open Date
</span>
#Html.TextBoxFor(m => m.EffectiveDate, new {
size = "20",
#class = "datecontrol",
// Make this as a nullable DateTime for Display purposes so we don't start the Calendar at 1/1/0000.
#Value = Utilities.ToStringOrDefault(Model.EffectiveDate == DateTime.MinValue ? null : (DateTime?)Model.EffectiveDate, "MM/dd/yyyy", string.Empty)
})
#Html.ValidationMessageFor(m => m.EffectiveDate)
</div>
<hr />
Html.RenderPartial("_SpaceEntry", Model);
}
The Partial View that gets rendered iterates through all SpaceModels, and creates a containing the Edit fields for the individual SpaceModel objects. (I'm using the List to use the same Views for when the Spaces get Subdivided as well.)
Then on the HttpPost, the EffectiveDate is still back at it's DateTime.MinValue default:
[HttpPost]
public ActionResult CombineSpaces(SpaceModels model, long siteID, long storeID, DateTime? effectiveDate) {
// processing code
}
I added that DateTime? effectiveDate parameter to prove that the value when it gets changed does in fact come back. I even tried moving the rendering of the TextBox into the _SpaceEntry Partial View, but nothing worked there either.
I did also try using the #Html.EditorFor(m => m.EffectiveDate) in place of the #Html.TextBoxFor(), but that still returned DateTime.MinValue. (My boss doesn't like giving up the control of rendering using the #Html.EditorForModel by the way.)
There has to be something simple that I'm missing. Please let me know if you need anything else.
Looking at the source code for DefaultModelBinder, specifically BindComplexModel(), if it detects a collection type it will bind the individual elements but will not attempt to bind properties of the list object itself.
What model binding does is attempt to match the names of things or elements in the view to properties in your model or parameters in your action method. You do not have to pass all of those parameters, all you have to do is add them to your view model, then call TryUpdateModel in your action method. I am not sure what you are trying to do with SpaceModel or List but I do not see the need to inherit from the List. Im sure you have a good reason for doing it. Here is how I would do it.
The view model
public class SpacesViewModel
{
public DateTime? EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
public List<SpaceModel> SpaceModels { get; set; }
}
The GET action method
[ActionName("_SpaceEntry")]
public PartialViewResult SpaceEntry()
{
var spaceModels = new List<SpaceModel>();
spaceModels.Add(
new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
var spacesVm = new SpacesViewModel
{
EffectiveDate = DateTime.Now,
DisplayEffectiveDate = true,
SpaceModels = spaceModels
};
return PartialView("_SpaceEntry", spacesVm);
}
The POST action method
[HttpPost]
public ActionResult CombineSpaces()
{
var spacesVm = new SpacesViewModel();
// this forces model binding and calls ModelState.IsValid
// and returns true if the model is Valid
if (TryUpdateModel(spacesVm))
{
// process your data here
}
return RedirectToAction("Index", "Home");
}
And the view
<label>Effective date: </label>
#Html.TextBox("EffectiveDate", Model.EffectiveDate.HasValue ?
Model.EffectiveDate.Value.ToString("MM/dd/yyyy") : string.empty,
new { #class = "datecontrol" })
Sometimes you need to explicitly bind form data using hidden fields such as
#Html.HiddenField("EffectiveDate", Model.EfectiveDate.)
In order to bind the properties of the SpaceModel object you can add individual properties such as SiteID to the view model or add a SpaceModel property for a single SpaceModel. If you want to successfully bind a complex model, add it as a Dictionary populated with key-value pairs rather than a List. You should then add the dictionary to the view model. You can even add a dictionary of dictionaries for hierarchical data.
I hope this helps :)

MVC 3 Html.DropDownList error

I have that error
The ViewData item that has the key 'BookAttributesDDL' is of type 'System.String' but must be of type 'IEnumerable<SelectListItem>'.
in that code:
#Html.DropDownList("BookAttributesDDL", Model.BookAttributes_Items)
but the Model.BookAttributes_Items is type of the IEnumerable<SelectListItem> ! What's wrong ?
The ViewData.Keys property from the Immediate Window:
ViewData.Keys
Count = 2
[0]: "FCoookie"
[1]: "Title"
Try to avoid dynamic variables like ViewBag and ViewData. It will make your code unreadable and painful to maintain in future as it grows. ViewBag is like Magic strings !
Switch to the ViewModel approach.
Example, If you are creating a View to Create a Book, Create a Viewmodel (it is just a plain Class) for that like this
public class BookViewModel
{
public int BookId { set;get;}
public string BookName {set;get;}
public IEnumerable<SelectListItem> Attributes{ get; set; }
public int SelectedAttribute{ get; set; }
}
Now in your GET Action, Simply create an object of this class, Set the BookAttribbutes proeprties to your Dropdown items and pass this ViewModel object to the View
public ActionResult Create()
{
BookViewModel vm=new BookViewModel();
//The below code is hardcoded for demo. you mat replace with DB data.
vm.Attributes=new[]
{
new SelectListItem { Value = "1", Text = "F Cookie" },
new SelectListItem { Value = "2", Text = "Title" },
}
return View(vm);
}
Now in We will make our view strongly typed to this ViewModel class
#model BookViewModel
#using(Html.BeginForm())
{
#Html.TextBoxFor(x=>x.BookName)
#Html.DropDownListFor(x => x.SelectedAttribute,
new SelectList(Model.Attributes, "Value", "Text"), "Select Attribute")
<input type="submit" value="Save" />
}
Now you will get the Selected Dropdown value and the textbox value in your HttpPost action by accessing the corresponding properties of your ViewModel
[HttpPost]
public ActionResult Create(BookViewModel model)
{
if(ModelState.IsValid)
{
//check for model.BookName / model.SelectedAttribute
}
//validation failed.TO DO : reload dropdown here
return View(model);
}
Fully agree with View Model approach response to you (and its me who selected it as useful).
However, if you don't want to switch, but remain as is I bet your answer lays in this article.
Hope this help you.

To show Create and detail view in one view using mvc3

Hi i want to show a page where i'm allowing user to create a new record and show the other related records of same table below .....
i need to add data in Hobbydetail class:
public class HobbyDetail
{
public virtual HobbyMasters Hobbymaster { get; set; }
public virtual Course course { get; set; }
public virtual StudyMedium StudyMedium { get; set; }
public virtual decimal Fees { get; set; }
}
I want my view "Create" to let the user create a new record and to also to show existing record below it...
I Dont want to use a viewmodel...
Can sumbody help me
Thanx in advance
One way to accomplish this is: In your controller, create a child action that renders the list, then render that action in your "Create" view using Html.RenderAction (also see this). I have included some code below (I have not tested this, but it should give you the basic idea). Please note this is not the only way to accomplish this - You could use a partial view see this. Please also understand the difference between html.RenderAction and html.Action, see this.
//In HobbyDetail Controller
[HTTPGet]
public ActionResult Create()
{
var model = new HobbyDetail ();
return View(model);
}
[HTTPPost]
public ActionResult Create(HobbyDetail model)
{
if(ModelState.isValid)
{
//logic to persist model
}
else
{
//logic when validation fails...
}
}
[ChildActionOnly]
public ActionResult ListAll()
{
List<Hobbydetail> model = //query to DB, or Data store to get Hobbydetails
return View(model);
}
//View for ListAll
#model List<HobbyDetail>
{
Layout = null; //No layout here...
}
<ul>
#foreach(var h in Model)
{
<li>#h.HobbyMasters.Name</li> //for example...
}
</ul>
//View for Create
#model HobbyDetail
...
#{html.renderAction("ListAll");}

MVC 3 Ajax.ActionLink Pass Parent Model Property

I have a view which displays a model eg:
public class MyModel()
{
public string name {get;set;}
public IList<Note> notes {get;set;}
}
The view displays all the notes for the model, i am trying to use Ajax.ActionLink to delete a note, but in order to delete a note i need to pass my controller action result the ID of the model.
public ActionResult DeleteNote(int modelId, int noteId)
{
var franchise = _franchiseRepository.FindById(modelId);
Note note = new Note(noteId);
franchise.RemoveNote(note);
_franchiseRepository.SaveOrUpdate(franchise);
return View();
}
Ajax.ActionLink("Delete", "DeleteNote", new {id=item.id}, new AjaxOptions{HttpMethod="POST"})
Can this be accomplished with ajax.actionlink?
Thanks in advance
Your DeleteNote action expects two parameters. I think it should work if you change the ActionLink to:
Ajax.ActionLink("Delete", "DeleteNote", new { modelId = [modelId], noteId = [noteId] }, new AjaxOptions { HttpMethod = "POST" })

Resources