How to retrieve multiple Models From Single view - c#

Is there a way to obtain a model object from the view into the controller without using strongly typed HTML helpers. The View uses the Account entity But need to post the form and retrieve the account entity of the variable running in a foreach loop used to generate the table/grid of values. The information from the dropdown is used for a different entity hence why its not strongly typed.
public class HomeController : Controller
{
// GET: Home
ModelContext model = new ModelContext();
[HttpGet]
public ActionResult Index()
{
return View(model.accounts.ToList());
}
[HttpPost]
public ActionResult Index(Account account,List<string> status,string name)
{
// return student object
return View();
}
}
The rest of the code in the View runs on a foreach loop to generate an html table enclosed in a #HTML.BeginForm
<td>
#Html.DropDownList("status",
new SelectList(Enum.GetValues(typeof(ProjectName.Models.Code))),
"...",
new { #class = "form-control" })
</td>
<td>
#Html.TextArea("comment");
</td>

You should be created a parent view model that includes both of these view models that you'll be needed in your view. For example:
public class ViewModel
{
public ProjectName.Models.Code Code {get; set;}
public ProjectName.Models.Account Account {get; set;}
}
Also, please have a look MVC - Multiple models in a view. I think it's good for you.
Hope this help!

Related

MVC Pass display data between views

I have a view model that is used to display a form on one view, and then is also used to represent the POST data to an action. The action then displays another view model that contains much of the same data from the first view model. However, the first view model has several "display only" properties that are also required on the second view model (for display only on the second view also).
I am wondering what the best way to pass this "display only" data to the second view would be. Currently, the best solution I have come up with is to have a bunch of hidden form fields that contain the display only property values, and then the model gets auto-populated for the action that handles the form POST. However, using hidden form fields seems very "hackish", and there seems like there should be a better solution to passing this data to another view The action doesn't need the display only information, it is only accessing it to populate the properties of the second view model that is passed to the second view.
Let me just explain my question with code, as what I am after is probably better understood through code than words.
Models:
public class SearchFilters
{
// ...
}
public class SearchResult
{
public int Id { get; set; }
public bool Selected { get; set; }
public string SomeDisplayValue1 { get; set; }
public string SomeDisplayValue2 { get; set; }
// ...
}
public class ResultsViewModel
{
public IList<SearchResult> Results { get; set; }
// ...
}
public class DoSomethingWithSelectedResultsViewModel
{
public IList<SearchResult> SelectedResults { get; set; }
public string SomeOtherProperty { get; set; }
// ...
}
Controller:
[HttpPost]
public ActionResult Results(SearchFilters filters)
{
ResultsViewModel results = new ResultsViewModel();
// ...
return new View(results);
}
[HttpPost]
public ActionResult DoSomethingWithSelectedResults(ResultsViewModel model)
{
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
View: Results.cshtml
#model ResultsViewModel
#using (Html.BeginForm("DoSomethingWithSelectedResults", "Search"))
{
<table>
for (int i = 0; i < Model.Results.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(m => Model.Results[i].Selected)
#* I would like to eliminate these hidden inputs *#
#Html.HiddenFor(m => Model.Results[i].Id)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue1)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue2)
</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue1)</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue2)</td>
<tr>
}
</table>
<button type="submit">Do Something With Selected Results</button>
}
As far as I know, one of the best way to pass data from View to another View through a Controller is to use ViewBag, ViewData or TempData. As an example, you can pass the data retrieved from View I as shown below:
TempData[DataToBePassed] = model.CustomData;
And then retrieve this data in View II similar to that:
#if(TempData[DataToBePassed] != null)
{
var dataFromFirstView = TempData[DataToBePassed];
}
For more information take a look at When to use ViewBag, ViewData, or TempData in ASP.NET MVC 3 applications.
You could put the model in the TempData property of the controller, that way it's automatically available in the next request.
More here
Found what I was looking for, I just hadn't worked with MVC enough yet to know about it. The Controller.UpdateModel method does exactly what I was looking for.
Example (using the code from the question):
[HttpPost]
public ActionResult DoSomethingWithSelectedResults()
{
// Load initial model data here, in this case I had simply cached the results in
// temp data in the previous action as suggested by Emeka Awagu.
ResultsViewModel model = (ResultsViewModel)TempData["results"];
// Call UpdateModel and let it do it's magic.
UpdateModel(model);
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
Using this method I was able to eliminate all the hidden form fields and did not have to write any custom copy logic, since UpdateModel deals with it automatically.
Note: I did have to implement some custom model binders to get things to work correctly with dictionaries and collections (see here, here, and here).

About multiple models in one view, again

I'm trying to create a one view where I'll could edit data from 2 different model. I read this manual: Multiple models in a view and some other topics on SO, and I successful got data from 2 models in my view, but I can't understand why I can't edit it in my view.
So, in my view I have smth like:
#model Tuple<GroupProjectsModel,InfrastructureModel>
#Html.EditorFor(m => m.Item1.Data)
#Html.EditorFor(m => m.Item2.Data)
Then, when I'm trying to save data, some mistakes happened. I have following code in controller:
[HttpPost]
[InitializeEditPageAttribute]
public ActionResult Edit(GroupProjectsModel get, InfrastructureModel getInf)
{
if (ModelState.IsValid)
{
....
return ReturnView(get.Id, NameModule);
}
var tuple = new Tuple<GroupProjectsModel, InfrastructureModel>(get, getInf);
return View(tuple);
}
In this case variable ModelState.IsValid is equal false anyway. But I'm trying another case:
[HttpPost]
[InitializeEditPageAttribute]
public ActionResult Edit(Tuple<GroupProjectsModel, InfrastructureModel> tupleFromModel)
{
if (ModelState.IsValid)
{
...
return ReturnView(tupleFromModel.Item1.Id, NameModule);
}
var tuple = new Tuple<GroupProjectsModel, InfrastructureModel>(tupleFromModel.Item1, tupleFromModel.Item2);
return View(tuple);
}
and got mistake too. I have no idea what to do.
Just created another class that holds both classes.
public class EditViewModel
{
public GroupProjectsModel groupProjectsModel {get; set;}
public InfrastructureModel infrastructureModel {get; set;}
}

MVC4 model binding - Passing Custom View Models & values from view to controller

I have a strongly typed view with the following model.
public class ProductViewModel
{
public Product Product { get; set; }
public List<ProductOptionWithValues> ProductOptionsWithValues { get; set; }
}
public class ProductOptionWithValues
{
public ProductOption ProductOption;
public List<AllowedOptionValue> AllowedOptionValues;
}
I'm using this Model To populate a form where a user can select the options they want for a product.
This is the view.
#model AsoRock.Entities.ViewModels.ProductViewModel
#{
ViewBag.Title = "Details";
}
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<h3>
#Html.DisplayFor(model => model.Product.ProductName)
----> #Html.DisplayFor(model => model.Product.Price)
</h3>
<br/>
foreach (var item in Model.ProductOptionsWithValues)
{
<b>#Html.DisplayFor(modelItem => item.ProductOption.Option.OptionName)</b>
<br/>
#Html.DropDownListFor(m => m.ProductOptionsWithValues,
new SelectList(item.AllowedOptionValues,
"Id", "DisplayString",
item.AllowedOptionValues.First().Id))
<br/>
}
<input type="submit" value="Add to cart" />
}
In my controller I am trying to pass the model back. When I set a break point in the controller it hits it but the Product view model is empty, any ideas how I can get the values that are selected in the view back in to my controller?
[HttpPost]
public ActionResult Details(ProductViewModel ProductViewModel)
{
return View();
//return View();
}
As mentioned in the comments, you need to change the name of the viewmodel parameter from ProductViewModel to something else e.g.
[HttpPost]
public ActionResult Details(ProductViewModel viewModel)
{
}
Now it's very odd that the viewModel param is not set to an instance of the class. The MVC model binder will still create an instance of ProductViewModel even if none of it's properties are set to anything. You're not using a custom model binder by any chance?
Also I would very strongly suggest that your viewmodel class does not have a Product property. Instead, create properties in the viewmodel specifically for the Product properties you intend to use e.g.
public class ProductViewModel
{
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
public List<ProductOptionWithValues> ProductOptionsWithValues { get; set; }
}
Using Product in the viewmodel sort of defeats the point of having a viewmodel. The viewmodel should contain only the bare minimum that the view needs. Including Product means the viewmodel is now bloated with extra data it does not use/need.
EDIT:
In your shoes, I would strip down the view itself, using only little bits of the viewmodel, and POST to the controller to see what happens. If the viewmodel calss is not NULL, go back to the view and add another bit back. Keep doing this until the viewmodel is NULL again. Doing this bit by bit should help.

how to Bind dropdownList using Entity Framework in MVC 3

i want to show a dropdownList on a page using Entity Framework in my MVC app, but i am just stuck here to do this using using HTML Helper. so if anyone having knowledge of entity framework, help me...
my dataContext partial class is Entities, in which an entity named MemberInfo have some fields including MemberID & MemberName, so my dropdownList should show the field MemberName & behind this the value should be MemberID,
the code i tried yet--->
#Html.DropDownListFor(Model => Model.MemberID, MemberInfo)
in controller i am returning the Model--->
var MemberNameList = FinanceDBContext.MemberInfoes.Select(x => new { x.MemberID, x.Name });
return View(MemberNameList);
but its not working (errors).
You need to pass in all of your objects as the "model". Best practice is to use a ViewModel which will contain the list of data and a property to store the selected item.
ViewModel
public class MyViewModel
{
// The drop-down list and variable to get selection
public List<Member> Members { get; set; }
public int SelectedMemberId { get; set; }
}
Controller
[HttpGet]
public ActionResult Index()
{
var viewModel = new MyViewModel();
viewModel.Members = FinanceDBContext.MemberInfoes.ToList();
return View(viewModel);
}
[HttpPost]
public ActionResult Index(MyViewModel viewModel)
{
string debug = string.Format("You selected member: {0}", viewModel.SelectedMemberId);
return View(viewModel);
}
Finally, in your view (these lines need to be inside a BeginForm { ... } and ensure your View is strongly typed to MyViewModel
#Html.DropDownList("SelectedMemberId", new SelectList(Model.Members, "MemberID", "Name"))
<input type="submit" value="Save etc..." />
In this example you could put a break-point on the HttpPost action and check the debug string to check the correct Member is returned and proceed as required.

What Is The Best Practice To Deal With MVC Post Models?

I'm pretty new to MVC and still confused what the best and proper way for 2 cases for with same result.
Let's say some user should add new sub category for specific root category.
Case 1:
SubCategory is a mapped class by EF where all properties not nullable.
Controler:
[Authorize]
public ActionResult Create()
{
SubCategory subCategory = new SubCategory();
subCategory.RootCategoryID = 1;
return View(subCategory);
}
[Authorize]
[HttpPost]
public ActionResult Create(SubCategory thisSubCategory)
{
if (ModelState.IsValid)
{
//And some BL logic called here to handle new object...
}
}
View:
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.RootCategoryID)
<h3>Sub Category Name: </h3>
#Html.EditorFor(model => model.CategoryName)
#Html.ValidationMessageFor(model => model.CtaegoryName)
<input id="btnAdd" type="submit" value="Add" />
Case 2:
Add helper class as controller's model and populate EF object after post
Controler:
class SubCategoryHelper
{
public string Name { get; set; }
}
[Authorize]
public ActionResult Create()
{
SubCategoryHelper subCategory = new SubCategoryHelper();
return View(subCategory);
}
[Authorize]
[HttpPost]
public ActionResult Create(SubCategoryHelper thisSubCategory)
{
if (ModelState.IsValid)
{
SubCategory newSubCategory = new SubCategory();
newSubCategory.RootCategoryID = 1;
newSubCategory.CtaegoryName = thisSubCategory.Name;
//And some BL logic called here to handle new object...
}
}
View:
Sub Category Name:
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
<input id="btnAdd" type="submit" value="Add" />
Both ways makes the same, but the first way looks less secure because of the hiddens that could be changed on the client side.
The second way much longer, imagine the same way for rich objects such as Client or Product...
What should I choose? Or there is some other way?
The first case is good for simplicity. If you extend your model you will have to do changes on less places. It is not less secure. You can bypass creating or binding the hidden input fields by several ways.
Use BindAttribute to bypass binding of property:
ActionResult Create([Bind(Exclude = "RootCategoryId")]
SubCategoryHelper thisSubCategory) {//....}
Or ScaffoldColumnAttribute on the model class property (e.g. when you use edit templates):
[ScaffoldColumn(false)]
public int RootCategoryId {get; set;}
Or just simple do not expose it (as you did in your example using the Html.HiddenInput helper).
The second approach, you have described, is often called ViewModel pattern. It encourages seperation of your Presentation and Domain layer. The advantage is, that your domain model will not be polluted with presentation layer specific code (like various display attributes etc.). Hovewer it brings another overhead of mapping between domain models and view models.
There is probably no genarel rule of thumb. It depends on type of your appliction.
If it is basically some simple data driven CRUD application, you can easily stay with the first one. Nevertheless, when your application becomes larger, you will definetly appreciate the freedom of your hands on separate layers. And if your BLL code is used with some other kind of "client" (web service, desktop etc.) except the ASP MVC I would defintely go with the second option.
I also suggest to read this great article: Is layering worth mapping
I always go with Case 2, whether it's a small project I work on or a big project, I always separate my data layer (entity framework) and my UI layer. Especially if you are using Entity Framework, because those objects can get huge, and it's a lot of crap that you are passing around that you more often don't need.
Instead of calling it a Helper class, call them ViewModel or Model. In your case, SubCategoryViewModel.
public class SubCategoryViewModel
{
public int Id {get;set;}
public int RootCategoryId {get;set;}
[Required]
public string Name { get; set; }
}
[Authorize]
public ActionResult Create()
{
var subCategoryViewModel = new SubCategoryViewModel();
return View(subCategoryViewModel);
}
[Authorize]
[HttpPost]
public ActionResult Create(SubCategoryViewModel viewModel)
{
if (ModelState.IsValid)
{
var subCategory = new SubCategory();
subCategory.RootCategoryID = 1;
subCategory.CategoryName = viewModel.Name;
//And some BL logic called here to handle new object...
}
}

Categories