ASP.NET MVC: How to use multiple models in one View? - c#

I have the following ViewModel that includes 2 other models:
public class ViewModel
{
public ViewModel1 viewModel1 { get; set; }
public ViewModel2 viewModel2 { get; set; }
}
My View looks like this:
#Html.TextBoxFor(m => m.viewModel1.NameOfCustomer)
#Html.TextBoxFor(m => m.viewModel2.ProductCategory)
And finally the controller:
public ActionResult CreateNewOrder(ViewModel viewModel)
{
Model1 myModel1 = new Model1()
{
NameOfCustomer = viewModel.viewModel1.NameOfCustomer
};
db.Orders.Add(myModel1);
db.SaveChanges();
return View(viewModel);
}
The problem is, if I want to take the data from my ViewModel to pass it to my actual model, it does not work. It just shows null as value. If I do the following change to my controller data is there but this does not work as it should:
public ActionResult CreateNewOrder(ViewModel viewModel)
{
// that works
viewModel.viewModel1.NameOfCustomer = "John John";
Model1 myModel1 = new Model1()
{
myModel1.NameOfCustomer = viewModel.viewModel1.NameOfCustomer
};
db.Orders.Add(myModel1);
db.SaveChanges();
return View(viewModel);
}
So I guess the problem is, my data from my view is not being send correctly to my ViewModel. I hope you guys can give me a hint what Im doing wrong.
Regards,

Edited following #Stephen Muecke's comment, doesn't sound like this is the case. However, I'll leave this answer here, as my first port of call would still be to check out the POST payload in the request to make sure it was going across the wire as expected, so this part may be useful
It looks like it's because the data coming into your MVC action, is essentially a flattened view model, whereas the model expected in the action is hierarchical.
If look at the network tab within your browser's dev tools, find the POST request being made, and check the payload being sent to the server.
Sounds like this is being posted:
{"NameOfCustomer": "SomePerson", ....}
Which won't be autobinding server-side to your model, because ViewModel does not itself have a NameOfCustomer property - instead, it's on a child object held in the viewModel1 property. So, autobinding would work if the following was being posted:
{"viewModel1": {"NameOfCustomer" : "SomePerson", ....}}
Solutions include:
1) construct the payload to post from JS yourself, ensuring the structure reflects the nested objects correctly as above
2) Create a flattened viewmodel, so all properties from ViewModel1 are held directly at top level in the ViewModel class
3) Create a custom model binder

Your code should properly carry the information to the controller(see a similar fiddle here)
The piece:
Model1 myModel1 = new Model1()
{
NameOfCustomer = viewModel.viewModel1.NameOfCustomer
};
db.Orders.Add(myModel1);
db.SaveChanges();
Are you sure you are doing the right thing here? You are creating a Model1 type variable and add it to the Orders in your database. Are you getting any errors? Maybe the right code should be something like:
Order myModel1 = new Order ()
{
NameOfCustomer = viewModel.viewModel1.NameOfCustomer
};
db.Orders.Add(myModel1);
db.SaveChanges();

Related

ASP.MVC: Access Data in Controller from another controller

"Please feel free to edit the title if it is misleading, since I am not sure if that's the correct way to ask"
I am new to ASP.MVC I am running into a seemly easy problem and having a hard time doing it.
What I am trying to do: I have two sets of scaffolds: View A, Controller A, Model B. and View B, Controller B, Model B.
Controller A looks like this:
public ActionResult LogIn(FormCollection formValues)
{
ModelA Model = new ModelA();
Model.EmailAddress = formValues["EmailAddress"];
}
I want to pass this formValues["EmailAddress"] from controller A to controller B.
In Controller B:
public ActionResult Initiate(FormCollection formValues, string phone, string method)
{
var ModelB = new ModelB();
var ModelA = new ModelA(); ---> This is null.
ModelB.Email = ModelA.EmailAddress --> This is null.
var userId = ModelB.dosomething(ModelB.Email, phone, method);
}
Is there away of doing that?
If the logic of ModelA and ModelB are going to be intertwined, I would recommend creating a new ViewModel, View, and Controller:
ModelAB
public class AB {
public ModelA ModelA { get; set; }
public ModelB ModelB { get; set; }
}
ModelABController
public ActionResult LogInAndInitiate(FormCollection formValues, string phone, string method)
{
var ModelB = new ModelB();
var ModelA = new ModelA(); // passed in, so its not null!
ModelA.EmailAddress = formValues["EmailAddress"];
ModelB.Email = ModelA.EmailAddress // passed in too!
var userId = ModelB.dosomething(ModelB.Email, phone, method);
}
Save the Model in TempData in Controller A like this
ModelA Model = new ModelA();
Model.EmailAddress = formValues["EmailAddress"];
TempData["ModelA"]=Model;
and Then You can Acces it in Controller B like this
ModelA modelA= TempData["ModelA"] as ModelA;
However TempData has very short life and can not be used in subsequent requests
to keep the TempData persistant for subsequent request you can use
TempData.Keep();
If the controller actions are completely unrelated (i.e., they do not call each other) you have two options:
1) Render the data to the client into a form and let the data be re-posted on the next request. This is of course only suitable for small amounts of data AND if you are sure it is neither sensitive nor a problem if the user tampers with the data.
2) Use sessions. This is what sessions are for, i.e., shopping cart contents, etc. You can opt to save this data in a database or have it in-memory in a session object only.
I wouldn't recommend using TempData for this, the idea behind TempData is to contain data which is useful for the very next request only. Think validation messages and that kind of data.

Using ViewModel to setup MVC Form but want to bind to object of the viewmodel in the postback. Is this possible?

Just need to know if this is possible to do or what exactly is standard practice in MVC as this is my first large scale MVC application.
So I've got a form I want the user to be able to edit and on that form page to pull up all the necessary data I need I'm bringing in a viewmodel,
public class Tier2IssueFormViewModel
{
public Tier2IssueDTO Tier2Issue { get; set; }
public IEnumerable<SelectListItem> VersionList { get; set; }
public IEnumerable<SelectListItem> BugList { get; set; }
public IEnumerable<SelectListItem> IssueStatusList { get; set; }
}
Then once I've finished collecting the form data from the user using things like,
#Html.TextBoxFor(m => m.Tier2Issue.Tier2Notes, new { #class = "form-control"})
#Html.DropDownListFor(m => m.Tier2Issue.FishbowlVersion, Model.VersionList, "Select Application Version")
#Html.HiddenFor(m => m.Tier2Issue.ID)
I want to post back to this action with the following signature for my model to bind to,
[HttpPost]
[Route("Issues/{id}/Edit")]
public ActionResult EditIssue(Tier2IssueDTO model)
{
...
// Update DB with the DTO model
...
}
But so far nothing really gets bound to this object. I thought the model binder might be smart enough to pair the two but I'm guessing this logic is incorrect. So I'm currently doing a workaround by using this,
[HttpPost]
[Route("Issues/{id}/Edit")]
public ActionResult EditIssue(Tier2IssueFormViewModel model)
{
...
// Get Tier2IssueDTO values from viewmodel
// Update DB with the DTO model
...
}
I mean it works, but it seems odd to me that you would model bind to a view model. Is this standard practice or is there a way to bind to an object contained within the viewmodel directly?
This will not work because the input text box names are differnt from the model inside your action, ex: the text box will have a name Tier2Issue.Tier2Notes while the model parameter in your action is expecting a property name Tier2Notes only without the Tier2Issue prefix.
You can overcome this issue by either making the model the same as the action parameter or give an explicit name and value to the text box, ex:
#Html.TextBox("Tier2Notes",Model.Tier2Issue.Tier2Notes, new { #class = "form-control"})
This should make it work
You have the right of it. It often seems pretty repetitive to have a viewmodel, dto and entity that all seem to have the same properties, but they all do different jobs an usually end up diverging a bit. A dto could act as a viewmodel, but it's a square peg in a round hole. If you're not using automapper to map these objects to one an other (this may be opinion baesed but it's broadly shared) - then use automapper to save you mindless keystrokes.

How to validate multiple ViewModels in single view by condition using ModelState.IsValid

I have a problem with validation of one view with multiple ViewModels. My situation is, that I have one Basic form, which is same for lot of pages. By ID parameter, I render new external fields to this Basic form. These external fields are type of ActionResult, using own ViewModel and own Controller. In Main controller on Post action I want to control if ModelState.IsValid, but I have problem - it validate all ViewModels of all external fields, but I want to validate only ViewModel of active external fields (and Basic form too).
It looks like this:
ViewModel of all view models
public class AllFieldsVm
{
public BasicFormVm BasicFormVm { get; set; }
public ExternalFieldXyVm ExternalFieldXyVm { get; set; }
public AnotherExternalFieldVm AnotherExternalFieldVm { get; set; }
}
In controller of external fields I create new instance of AllFieldsVm and in this create new instance of ExternalFieldXyVm (if I need, I prefill these fields). This I render whitout layout like partial view (using #{Html.RenderAction("Action", "Controller", new {#someOptionalData = value});} ), when some condition is true.
In controller of Basic form on Post action I have something like this and I want to use something like this code if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)):
[POST("someurl-id{someId}")]
public ActionResult SaveFormData(int someId, AllFieldsVm model)
{
//Here I want something like
//if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)) or something like that...
var se = new SomeEntity();
se.property1 = model.property1;
se.property2 = model.property2;
using (var dbc = _db.Database.BeginTransaction())
{
try
{
_db.Add(se);
_db.SaveChanges();
//My Condition - when save external data
if (someId == (int) MovementTypes.SomeEnumInt)
{
var rd = new ExternalFieldEntity
{
PropertyA = se.property0,
PropertyB = Convert.ToDateTime(model.ExternalFieldXyVm.SomeExternalFieldName)
};
_db.Add(rd);
_db.SaveChanges();
}
dbc.Commit();
}
catch (Exception)
{
dbc.Rollback();
}
}
return RedirectToAction("Action", "Controller");
}
So, my question is, how can I validate ExternalFieldXyVm separatly based on some conditions?
Is it possible, or I have to create all own validators, without using basic DataAnnotations or FluentValidation? I have no experience with these types of forms, so please be patient...
Thanks to all for help!!
Great, I got it. I play with this for two days, don't know how it is possible that I didn't see that.
Result is: When view with own view model which is included in main viewmodel, isn't rendered into view, this viewmodel is not validate on post action. So my Basic form is validate everytime, and ExternalFields are validate only when are rendered. So sorry, for so stupid question....

Sending IEnumerable to a view that already contains a model

This is the view:
#model tgpwebged.Models.sistema_DocType
...
this model is an entity used with textBoxFor and others html helpers
This is the controller.
public ActionResult AdminSettingAddTipo()
{
IEnumerable<string> indices;
using (tgpwebgedEntities context = new tgpwebgedEntities())
{
var obj = from u in context.sistema_Indexes select u.idName;
indices = obj.ToList();
}
return PartialView(indices);
}
I have all I need here, I am using a model to create with the view so I am not allowed to send ´indices´ as a model because it´s not allowed to have 2 models in one view.
I don´t want to use ´Tupe´ now a parent view. I just want to know how is the best way to send my IEnumerable to the view.
I was thinking of ViewBag for the last option but I am avoiding ViewBag.
thanks
ViewBag is not a good choice. Create ViewModel using your list and your current Model:
public class YourViewModel
{
public sistema_DocType Type { get; set; }
public IEnumerable<string> Indices {get;set;}
}
Hope,it will help.
If you don't want to use ViewBag for whatever reason, you could create a Model specifically for the view that contains the info from the old model and the new indices you want. This is a common pattern in MVC development. You can even have the ViewModel be a Decorator for your current Model.
http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx
Use strongly defined where you can, apply this to a model and send that model:
Model
public class MyModel{
public List<sistema_Indexes> indecies {get;set;}
}
Controller
MyModel model = new MyModel();
model.indecies = context.sistema_Indexes.Select(u=> u.idName).ToList();

How to Post Form from View strongly Typed to ViewModel?

I have a view model as such:
public class MyViewModel
{
public MyObject myObject{ get; set; }
public List<MyList> myList{ get; set; }
}
I have a view with a form strongly typed to MyViewModel
This view allows you to enter values for the properties of MyObject, as well as create a list of MyList objects. The List part works fine although I thought that would be the more difficult of the two.
Assuming MyObject has a property Description I create a textbox to enter the value as such:
#Html.EditorFor(x => x.myObject.Description);
The text box renders with an id of MyObject_Description...The problem is when I post this to my controller action, MyObject does not get bound at all(althought the list items do as they recieve the appropriate IDs of "MyViewModel_MyList[guid].myListValue")
What am I doing wrong here??
EDIT: more info
The first line of the view is:
#model MyApp.ViewModels.MyViewModel
And the Action method:
[HttpPost]
public ActionResult Create(MyViewModel myViewModel)
{
}
I am passing a new MyViewModel into the partial view to begin...
public ActionResult Create()
{
MyViewModel model = new MyViewModel();
return PartialView(model);
}
EDIT 2
Ok When I render my partial view that contains the forms I call :
#{Html.RenderAction("Create", "MyController");}
this is called from within a View of type #model IEnumerable<MyApp.Models.MyObject>
(this view displays a list of currently existing MyOjects, and at the bottom the partial is rendered to allow the user to add another MyObject to the DB)
If you are not already doing so, try creating a editor template (e.g., Views->Shared->EditorTemplates) called MyObject.cshtml. Move your partial view content to this view and then call
#Html.Editor("myObject").
from your parent partial view.
Change your ViewModel to have the Description directly
public class MyViewModel
{
public string Description { get; set; }
public List<MyList> myList{ get; set; }
}
then bind accordingly
#Html.EditorFor(x => x.Description);
I would expect the top line of your view to look something like this:
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<MyViewModel>" %>
This tells the view that the model it is supplied is of type MyViewModel (a la <T> style).
I don't think the out of the box model binding knows how to bind to complex objects. You're probably going to have to write up some sort of custom model binder.
I'm afraid it's not something I've done since MVC1 so I'm a bit hesitant to give you any sample code because the mechanism may well have changed completely since then. A quick google did turn up this article http://www.learnxpress.com/asp-net-mvc-hosting-6-tips-for-asp-net-mvc-model-binding-2.html and this article http://bradwilson.typepad.com/blog/2010/10/service-location-pt9-model-binders.html.
Edit: I've just seen this answer which might help Retrieving data from view, should I use model binder?

Categories