About multiple models in one view, again - c#

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;}
}

Related

How to retrieve multiple Models From Single view

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!

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).

ViewModel vs. InputModel in Razor View

As I found on 10 Good practices for asp.net mvc webapplications it is a good practice to split mvc models in ViewModel (Models representing the view) and InputModels (Representing data entered by user).
The ViewModel gets a property of Type InputModel. The InputModel carries the data that can be edited by the user.
public class EmployeeInputModel{
public string Name {get;set;}
public Id? DepartmentId{get;set;}
}
public class EmployeeCreateViewModel{
public IList<Department> Departments{get;set;}
public EmployeeInputModel EmployeeModel{ get;set;}
}
The Controller-methods look like:
public class EmployeeController : Controller{
public ActionResult Create(){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), new EmployeeInputModel());
return View(viewModel);
}
public ActionResult Create(EmployeeInputModel createModel){
try{
EmployeeService.Create(...);
return RedirectToAction("Index");
} catch (Exception ex){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), createModel);
return View(viewModel)
}
}
}
The View looks like:
#model EmployeeCreateViewModel
#Html.EditorFor(m => m.EmployeeModel)
The Editor Partial is just like:
#model EmployeeInputModel
#Html.TextBoxFor(m => m.Name)
#Html.DropDownFor(m => m.Department, ???)
This worked great for me, unless I came to the point of DropDownLists (in the sample see departments). Because the EditorTemplate doesnt know the ViewModel but just the InputModel.
I dont want to put the department list into the input model, because it is not the supposed place to be for this list (I would have to bind them). It has to be in the viewmodel. The properties of the input model should also not be in the ViewModel.
Does someone have any idea how to achieve a separation of viewmodel and input model in one view?
You would have to create a model abstraction of your departments. Something like this:
public class EmployeeInputModel{
public string Name {get;set;}
public List<DepartmentInputModel> Departments{get;set;}
}
public class DepartmentInputModel{
public int Id;
public string Name;
}
Then you can display just the name of the department in the drop down list. The value will then be the id of the department.
You can have a look at this SO question for a example.
You can use the ViewBag yo pass the list of departments to your partial.
Or use a partial view that accepts the view model as its model

Can a controller manage more than one model?

I want my controller to put modelA into viewA and modelB into viewB.
From what I know a controller can be associated with only one view using only one model.
Correct me please if I'm wrong.
A Controller-action can only produce 1 View at a time.
But it is possible to build some conditional logic into the Controller and decide which View(s) to
show.
a controller can be associated with only one view using only one model
No, a CRUD controller normally associates with 1 Model and produces List/Edit/Delete/Create Views.
So multiple Views is quite normal, so is multiple ViewModels. And a ViewModel often combines information form more than 1 Model entity.
I want my controller to put modelA into viewA and modelB into viewB.
BO classes:
class modelA{
...
}
class modelB{
...
}
Controller code:
...other using statements...
using MyProj.DAL;
using MyProj.BO;
public class MyController:Controller
{
//Create object of your Data Access Layer's MyDAL functionality
MyDAL DALobj = new MyDAL();
public ActionResult viewAList(){
modelA mobj = DALobj.FetchObjFromDB();
return View(mobj);
}
public ActionResult viewACreate(modelA newobj){
...check if modelstate is okay and tweak your model object here...
DALobj.SendDataToDB(newobj);
return RedirectToView("some other view | index");
}
public ActionResult viewBList(){
...same as viewAList() but with modelB this time...
}
public ActionResult viewBCreate(){
...same as viewACreate() but with modelB this time...
}
}//controller ends here
I guess what is asked is:
From what I know a controller can be associated with only one view
using only one model.
I translate it as :
There can be only one view consuming one model associated with a particular controller.
which I think looks like this:
public class PersonController : Controller
{
static List<Person> people = new List<Person>();
public ActionResult Index()
{
return View(people);
}
public ActionResult Details(Person person)
{
return View(person);
}
public ActionResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Person person)
{
if (!ModelState.IsValid)
{
return View("Create", person);
}
people.Add(person);
return RedirectToAction("Index");
}
}
Here is one controller, four Actions, one model. So, One Controller is associated with one model, but not only view (which you can get; by removing any other two Functionalities(not functions, Create-functionality has two functions, one for GET & POST each))
Now, question arises, can you use multiple models in 1 controller?
Answer: Yes. I just showed it in first code example.
Question: Can there be two+ models in 1 controller?
Answer: yes. Create a wrapper model, put other two models in it. and BAM!
How?
(Of course. sigh!)
MODEL | BO:
public class BigObjClass{
public A a;
public B b;
}
public class A{
public int serial{get;}
public int age{get;set;}
}
public class B{
public string Name{get;}
public string Address{get;set;}
}
Now, as in very first code example, use BigObjClass's object and pass it to the views.
Please let me know if any of this makes sense to you.

DropDownListFor and TryUpdateModel in ASP.NET MVC

I have two related POCOs
public class Parent
{
public Guid Id {get; set;}
public IList<Child> ChildProperty {get; set;}
}
public class Child
{
public Guid Id {get; set;}
public String Name {get; set;}
}
and I have a .cshtml Razor view with
<div>
#{
var children =
new SelectList(Child.FindAll(), "Id", "Name").ToList();
}
#Html.LabelFor(m => m.Child)
#Html.DropDownListFor(m => m.Child.Id, , children, "None/Unknown")
</div>
I'd like to do the following in my controller class:
[HttpPost]
public ActionResult Create(Parent parent)
{
if (TryUpdateModel(parent))
{
asset.Save();
return RedirectToAction("Index", "Parent");
}
return View(parent);
}
Such that if the user selects "None/Unknown", the child value of the parent object in the controller is null but if the user selects any other value (i.e. an ID of a child object retrieved from the database), the child value of the parent object is instantiated and populated with that ID.
Basically I'm struggling with how to persist a list of possible entities across the HTTP stateless boundary such that one of the entities is properly rehydrated and assigned via the default model binder. Am I just asking for too much?
Am I just asking for too much?
Yes, you are asking for too much.
All that's sent with the POST request is the ID of the selected entity. Don't expect to get much more than that. If you want to rehydrate or whatever you should query your database. The same way you did in your GET action to populate the child collection.
Oh and there's a problem with your POST action => you are calling the default model binder twice.
Here are the 2 possible patterns (personally I prefer the first but the second might be useful in some situations as well when you want to manually call the default model binder):
[HttpPost]
public ActionResult Create(Parent parent)
{
if (ModelState.IsValid)
{
// The model is valid
asset.Save();
return RedirectToAction("Index", "Parent");
}
// the model is invalid => we must redisplay the same view.
// but for this we obviously must fill the child collection
// which is used in the dropdown list
parent.ChildProperty = RehydrateTheSameWayYouDidInYourGetAction();
return View(parent);
}
or:
[HttpPost]
public ActionResult Create()
{
var parent = new Parent();
if (TryUpdateModel(parent))
{
// The model is valid
asset.Save();
return RedirectToAction("Index", "Parent");
}
// the model is invalid => we must redisplay the same view.
// but for this we obviously must fill the child collection
// which is used in the dropdown list
parent.ChildProperty = RehydrateTheSameWayYouDidInYourGetAction();
return View(parent);
}
In your code you've made some mix of the two which is wrong. You are basically invoking the default model binder twice.

Categories