I have two nearly identical scenarios regarding model binding on a controller action. One works, and one doesn't. I can't figure out why.
This works:
Given this ViewModel class:
Public Class SeasonCreateViewModel
Public Property Season As Season
End Class
We have these actions
Function Create() As ActionResult
Dim seasonVM As New SeasonCreateViewModel()
Return View("Create", seasonVM)
End Function
<HttpPost()>
<ValidateAntiForgeryToken()>
Function Create(seasonVM As SeasonCreateViewModel) As ActionResult
End Function
And everything binds perfectly. seasonVM.Season contains the values posted from the form.
HOWEVER, this doesn't work:
Given this ViewModel class:
Public Class UserCreateViewModel
Public UserPerson As UserPersonModel
End Class
And these actions:
Function Create() As ActionResult
Dim userVM As New UserCreateViewModel()
Return View("Create", userVM)
End Function
'
' POST: /Admin/User/Create
<HttpPost()>
<ValidateAntiForgeryToken()>
Function Create(userVM As UserCreateViewModel) As ActionResult
End Function
userVM.UserPerson does not bind to the form values the same way seasonVM.Season does. In fact, it is Nothing (aka. null)
Does anyone have any ideas?
If you're curious about the views, they are structured identically, as in:
#Using Html.BeginForm()
#Html.AntiForgeryToken()
#Html.ValidationSummary(True)
<div class="editor-label">
#Html.LabelFor(Function(model) model.UserPerson.NewUsername)
</div>
<div class="editor-field">
#Html.EditorFor(Function(model) model.UserPerson.NewUsername)
#Html.ValidationMessageFor(Function(model) model.UserPerson.NewUsername)
</div>
<p>
<input type="submit" value="Create" />
</p>
End Using
AND
#Using Html.BeginForm()
#Html.AntiForgeryToken()
#Html.ValidationSummary(True)
<div class="editor-label">
#Html.LabelFor(Function(model) model.Season.SeasonDescription)
</div>
<div class="editor-field">
#Html.EditorFor(Function(model) model.Season.SeasonDescription)
#Html.ValidationMessageFor(Function(model) model.Season.SeasonDescription)
</div>
<p>
<input type="submit" value="Create" />
</p>
End Using
Just a note: I've omitted irrelevant code, mostly just additional properties on the view pages. I will say there is no property named "userVM" on my UserPersonModel as was the case here: Model is null when form submitted
UPDATE
OK. I think I'm about ready to give up on figuring out why Season is binding properly, but UserPerson is not.
I thought I had figured out the answer, but it didn't seem to actually make a difference:
I have
Public Class SeasonCreateViewModel
Public Property Season As Season
End Class
and I have
Public Class UserCreateViewModel
Public UserPerson As UserPersonModel
End Class
When lined up like this, the difference seems obvious. In SeasonCreateViewModel, I have a property Season identically named to the class it is an instance of (Season). In UserCreateViewModel, I have a property UserPerson, which is named slightly differently from its class UserPersonModel. Because of this, I thought the model binder does not automatically match userVM.UserPerson to its corresponding class.
So I changed the class UserPersonModel to UserPerson so the Form values would match up in the same way they do for Season (ie, to the classname), but it STILL did not fix it.
What does fix it, however, is if I change this:
Function Create(userVM As UserCreateViewModel) As ActionResult
to this
Function Create(userPerson As UserPerson) As ActionResult
Why this suddenly binds properly, where it didn't before? I have no idea. Does this help anyone answer this question, though?
As I understand it, you are not creating a new instance of UserPersonModel. I could be very wrong though :)
Public Class UserCreateViewModel
Public UserPerson As UserPersonModel
End Class
I think in your action, you should do something like:
Function Create() As ActionResult
Dim userVM As New UserCreateViewModel()
//userVM.UserPerson = new UserPersonModel() -- in c#
//userVM.UserPerson As New UserPersonModel() -- in VB?
Return View("Create", userVM)
End Function
Or just ignore this answer :P
I've ran into this before and the answer is not at all obvious, but makes sense after you know. If you bind an entire model (rather than a static type), then every single member of that model must be posted in the form, otherwise, the modelbinder doesn't recognize the posted values as an instance of the model.
If you don't want to post every member, but rather just a subset, then you need to create a view model with just the fields you want to post, and use something like AutoMapper or just manually map the fields on the actual model in your POST action.
Related
This is a very basic model/view/controller and the model is not coming from the view with any data to the controller. It is probably something basic that I am missing!
Two Models:
Namespace Models
Public Class Search
Public Property Search() As String = String.Empty
Public Property Member() As Member = Nothing
End Class
End Namespace
Namespace Models
Public Class Member
Public Property ContactRefID() As String = String.Empty
End Class
End Namespace
Controller:
Imports Test.Models
Imports Test.Services
Namespace Test
Public Class HomeController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Dim search As New Search
Return View("Index", search)
End Function
<HttpPost()>
Function Index(ByVal search As Search) As ActionResult
Dim member As New Member
Dim homeRepository As New HomeRepository
member = homeRepository.GetMemberData(search)
search.Member = member
Return View("Index", search)
End Function
End Class
End Namespace
View:
#ModelType Test.Models.Search
#Code
ViewData("Title") = "Search Page"
End Code
#Using Html.BeginForm("Index", "Home", FormMethod.Post)
#<div class="row">
<div class="col-md-4">
<label>Search Member/Account Number:</label>
#Html.TextBoxFor(Function(model) model.Search)
<br /><br />
<button id="btnSubmit" type="submit" class="btn btn-primary btn-large clrButton">
Search
</button>
</div>
</div>
<div class="row">
#*...To be filled with Search.Member data*#
</div>
End Using
When I enter a member/account number in the textbox to initiate the search, the search value does not get sent to the 2nd Index function in the controller. It definitely gets called because my breakpoint inside of it gets hit, but the "ByVal search As Search" comes back as Nothing.
I know in 2013 that the get:set items are not needed for the Models, but I tried to add them and it still didn't work.
I also set a value for search.Search in the top index and the textbox gets populated with the value. But when I click the Search button, it doesn't get sent to the 2nd index function either.
Any suggestions?
DefaultModelBinder is unable to bind/recognize a property if the type has the same name - in this case Search. Just change the property to something else and you're good to go...
Generally it's a good practice to think through the objects you're going to create and avoid such tautology. It will be easier for you to maintain these objects if they sounds reasonable. E.g. in this case, you use the name Search for your class. There is no sense to name the sought value Search too. Just name it as it should - SoughtValue
I have recently started incorporating Kendo UI into my project. I have a strongly typed view and wish to bind the Kendo grid to the appropriate View Model on the view:
#ModelType IEnumerable(Of IMS_2.Models.expenseclaims)
#Code
ViewData("Title") = "Index"
End Code
<h2>Index</h2>
<div>
#code
Html.Kendo().Grid(Model).Name("ExpenseClaims") _
.Columns(Sub(c)
c.Bound(Function(x) x.ClaimDate).Width(140)
c.Bound(Function(x) x.Title).Width(190)
c.Bound(Function(x) x.Company)
End Sub)
end code
</div>
The code executes without exceptions on the server and with no javascript errors at the client. Examination of the rendered source shows no mention of the grid:
<h2>Index</h2>
<div>
</div>
<hr />
<footer>
...etc
My code is (I think) a direct translation of other examples I've seen in c# (see http://telerikhelper.net/2012/10/26/using-kendo-grid-in-asp-net-mvc-4-0/)
Expenseclaims is generated by the EF template and is defined as:
Partial Public Class expenseclaims
Public Property id As Long
Public Property Title As String
Public Property ClaimDate As Nullable(Of Date)
Public Property Creator As Nullable(Of Long)
Public Property Company As Long
Public Property AdvanceOffset As Nullable(Of Decimal)
Public Overridable Property expenselines As ICollection(Of expenselines) = New HashSet(Of expenselines)
Public Overridable Property companies As companies
End Class
The controller code is:
Public Class ExpenseController
Inherits System.Web.Mvc.Controller
Private db As New IMSEntities
' GET: /Expense/
Function Index() As ActionResult
Return View(db.expenseclaims.ToList())
End Function
Which is where I am stumped...Any help gratefully appreciated.
Edit: You do need the .tolist()
A really good vb start is at Telerik, at least that is where i started
http://docs.telerik.com/kendo-ui/getting-started/using-kendo-with/aspnet-mvc/vb
I did mine a bit different but this should get same result for you. I think possibly the issue is you are wrapping this is a code block which isn't really right from what I read. Here is an example of what I do for a tracking grid.
Where I have the datasource(since I use an ajax source and you want to use a model) you can just put the following:
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(20)
.ServerOperation(false)
)
The bottom line is that you seem to be missing the datasource and I am unsure about the code block to be honest, but I do not do it that way.
#(Html.Kendo().Grid(Of TrackingGroupModel)().Name("grid") _
.Columns(Function(col)
col.Bound(Function(e) e.TrackingNumber).ClientTemplate("<a href='#=ShippingCompanyUrl##=TrackingNumber#' target='_blank'>#=TrackingNumber#</a>")
col.Bound(Function(e) e.DateAdded).Format("{0:D}")
col.Bound(Function(e) e.ProductCount).Title("Total Shipped")
col.Command(Function(command) command.[Custom]("View Details").Click("showDetails"))
End Function)
.DataSource(Function(ds) ds.Ajax().Read(Function(read) read.Action("getTracking", "OrderManagement", New With {.id = Model.Id})))
)
#model MyMVC.Models.MyMVC.MyModel
#{
ViewBag.Title = "Index";
}
The reason why I ask this question is in MVC we can have more than 1 form, so I would like to have a model for each form. But when I was trying to add another model at the top of the view, it displays an error.
I know we can use ViewModel which has model1 and model2 inside, but it's not the question I am asking.
I just simply want to know is there any way that we can put in 2 models into 1 view.
Update:
For example, I want to have 2 forms in my view, so for each form I'd like to have a separated model.
Update 2
Thank you all for your replies. I now know that MVC only supports one single model on one view. Do you guys consider this a disadvantage of MVC? Why or Why not?(No one has answered it yet.... why?)
Your answers are similar, so I don't even know which one should be set as an answer.
You should use some prtialviews for other models. You have your main view with main model. Inside of that view, you could have some partialviews with their models and their validations.
--------------------------------------Edit:
As others said it is better to use View Models and combine the models with each others. But as you wanted I created a sample project for you on my Github account:
https://github.com/bahman616/ASPNET_MVC_multiple_models_in_a_view_with_partialview.git
Here is the simplified code of that project:
Here are two models:
public partial class Person
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
}
public partial class Company
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
I want to have both create views in one view, so this is the parent view:
#model ASP_NET_MVC_Multiple_Models_In_A_View.Models.Person
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("Create","Person")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#{Html.RenderAction("_CompanyCreate", "Company");}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And this is the partial view:
#model ASP_NET_MVC_Multiple_Models_In_A_View.Models.Company
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm("_CompanyCreate","Company")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Company</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Simple answer:
No, you can't have two models.
Details
There is only one way to declare a Model to be used by a view:
#model [namespace].[path].[ViewModelName]
There is no way to specify multiple of these above.
In addition to that, you can only send one object to a view from the Controller, which would be the model:
public ActionResult WhateverAction()
{
var model = new MyViewModel();
return View(model);
}
Theoretically you cant do it because it binds one model to one view. But you can create somethink like a model helper which combines two other models to one
public class model1
{
string var1 {get; set;}
string var2 {get; set;}
}
public class model2
{
string var3 {get; set;}
string var4 {get; set;}
}
public class modelofBoth
{
model1 mod1 {get; set;}
model2 mod2 {get; set;}
}
If you cannot/don't want to modify the model passed to the view then you could pass the additional object in via the ViewBag object. It feels like a hack to me but it works:
Controller:
ViewBag["Model2"] = yourObject
View:
#{var Model2 = ViewBag["Model2"] as yourObjectType;}
Technically, you could write your own view engine (derived from the RazorViewEngine?) using a custom WebViewPage and have it do whatever you want. The view derives from WebViewPage<T> where T is the type of the model for the page. To support multiple models, the view base class would need to have multiple generic types. Using the baked in RazorViewEngine, however, you're limited to a single model type per view and much of the controller framework presumes a single model per view so you'd have to rewrite that, too. There doesn't seem to be much point in that, however, as you could simply have your models be properties of the (single) view model, then use partial views for each of the forms providing the property as the model for the partial.
For more information see http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view.aspx
Answer is NO. Thats why ViewModel pattern Exists. because the razor engine is totally relied on the model you passed, the model is used for ModelBinding for the basic CRUD Operations.
You can easily have 1 model per form. For each form, you just have to do something like this:
var model = new MyModel();
#Html.TextBoxFor(m => model.Title)
#Html.ValidationMessageFor(m => model.Title)
The validation works like a charm.
This way, you can keep your model to display information as usual and have 1 model per form to submit data (for example newsletter subscription)
if your models are similar,
Make a new model contains references to all models you want, and use it.
if models are not similar, you can fill new model with similar fields you need from all your models.
but if you wants use totally different models, you should use Partial views.
I am sure this is quite straightforward but I am a bit stuck here. The routing defined for my app is just the default. I have the following controller defined.
namespace Baynes.Wedding.Web.Controllers
{
public class AdminController : Controller
{
private readonly IAuthProvider _authProvider;
private readonly IDocumentRepository _documentRepository;
public AdminController(IAuthProvider authProvider, IDocumentRepository documentRepository)
{
_authProvider = authProvider;
_documentRepository = documentRepository;
}
public ViewResult EditDocument(int id)
{
var document = _documentRepository.Select(id);
return View(new DocumentEditViewModel(document));
}
[HttpPost]
public ActionResult EditDocument(DocumentEditViewModel model)
{
if (ModelState.IsValid)
{
_documentRepository.Update(model.ToDocument());
return RedirectToAction("ListDocuments");
}
return View();
}
}
}
When I navigate to /Admin/EditDocument/1/ then the first action executes exactly as expected, rendering the following view:-
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm("EditDocument", "Admin", FormMethod.Post)) {
#Html.ValidationSummary(true)
#Html.HiddenFor(m => Model.Id)
<div>
#Html.LabelFor(m => Model.Title)
</div>
<div>
#Html.TextBoxFor(m => Model.Title)
</div>
<div>
#Html.LabelFor(m => Model.Body)
</div>
<div>
#Html.TextAreaFor(m => Model.Body)
</div>
<div>
#Html.LabelFor(m => Model.Url)
</div>
<div>
#Html.TextBoxFor(m => Model.Url)
</div>
<input type="submit" value="Edit" />
}
On submitting this I get an error:-
No parameterless constructor defined for this object. Other questions seemingly related questions MVC: No parameterless constructor defined for this object suggest that it is a to do with the IoC container not being set up properly, but surely the fact that the first action executes without a problem means that isn't the problem here?
Any help would be greatly appreciated.
Regards.
Simon
add to class DocumentEditViewModel default constructor
public DocumentEditViewModel (){}
The MVC framework is trying to create an instance of the DocumentViewModel class but it can't find a publicly accessible default constructor (that does not take any arguments). You can either define such a default constructor like #simplyDenis suggested or define a cusotm ModelBinder that can create the instance using your custom constructor.
Does DocumentEditViewModel have a parameterless constructor? I believe this is needed for modelbinding on your post view.
Most likely you have dependency injections mechanism.
But MVC requires "special" way to register container.
Look at this link Adding Unity to Your Web Applications, patterns and practices
I had a slightly different problem. One of my Model classes was abstract.
If DocumentEditViewModel constructor's accessibility level is protected you will get also the same error.
I have a multi-step file import process. I have a hidden form input in my view that I am trying to populate with the "CurrentStep" from the view model.
<% = Html.HiddenFor(model => model.CurrentStep) %>
CurrentStep is an Enum and I always get the default value rather than the one I provided to the view model. on the other hand this gets me the correct value:
<p><% = Model.CurrentStep %></p>
I realise I could just hand code the hidden input but I want to know: what am I doing wrong? Is there a better way to keep track of the current step between POSTs?
What you are doing wrong is that you are trying to modify the value of a POSTed variable in your controller action. So I suppose you are trying to do this:
[HttpPost]
public ActionResult Foo(SomeModel model)
{
model.CurrentStep = Steps.SomeNewValue;
return View(model);
}
and html helpers such as HiddenFor will always first use the POSTed value and after that the value in the model.
So you have a couple of possibilities:
Remove the value from the modelstate:
[HttpPost]
public ActionResult Foo(SomeModel model)
{
ModelState.Remove("CurrentStep");
model.CurrentStep = Steps.SomeNewValue;
return View(model);
}
Manually generate the hidden field
<input type="hidden" name="NextStep" value="<%= Model.CurrentStep %>" />
Write a custom helper which will use the value of your model and not the one that's being POSTed
My solution was to use Darin's second option, because option 1 (clearing from the model state) means hard coding a string (and the naming convention can be tricky with complex models), and wanted to avoid option 3 because I already have so many custom helpers.
<input type="hidden" name="#Html.NameFor(x => Model.SomeId)" value="#Model.SomeId" />
Just a reminder that you can use Html.NameFor to keep things clean.
Make sure you model property has a "set" operator.
This won't get updated on post-back:
#Html.HiddenFor( m => m.NoSeq)
public Class MyModel
{
int _NoSeq;
public NoSeq
{
get { return _NoSeq };
}
}