I have a view and am passing a view model to it in order to populate the strongly typed inputs. The view model is created just fine. I have debugged the controller to ensure that model is created properly and the properties of the view model are set with the correct values from the database (SQL Management Server 2008), which it is doing with no problem. When I pass the view model into the view, only some of the strongly typed inputs are populated and the others aren't. I've checked to make sure that I have all of the inputs are pointing to the correct model parameter. The form consists of 6 pages that I'm using Kendo UI TabStrip to keep the pages separate. The view model is fairly large so I'm wondering if that is a factor. The basic functionality is that a user can start filling out a form, save it to the db, retrieve what they have filled out, and then complete it at a later date. Here is some sample code:
Here is a code snippet of the view:
#model myWilmer.Models.QuoteViewModel
<section id="quoteEntry">
#Html.ValidationSummary(true)
#using (Html.BeginForm("CreateQuote", "Quotes", FormMethod.Post, new { id = "quotesForm" }))
{
#Html.TextBoxFor(m => m.QuoteNum, new { #class = "k-textbox", Value = "", id = "quoteNumber"})
}
Here is a snippet of the view model:
public class QuoteViewModel
{
public QuoteViewModel()
{
}
[Display(Name = "Quote Number:")]
[Required]
[RegularExpression(#"^[0-9]+$")]
public int QuoteNum { get; set; }
}
Here is a snippet of the controller:
[HttpGet]
public ActionResult Edit(int id)
{
QuoteViewModel qvm;
//...Creating ViewModel
return View(qvm);
}
You are overriding the Value by having Value = "". Remove that and it should work.
#Html.TextBoxFor(m => m.QuoteNum, new { #class = "k-textbox", id = "quoteNumber"})
Related
In a single ASP.NET Razor view, I want to display 10 textboxes (the user will enter various search criteria into these), and then when the user submits the form and the server processes the action, I want to display the results in the same view (let's assume that there can be around a 1,000 rows that need to be displayed). Along with the results, I also want criteria textboxes populated with the values that the user had chosen previously (so that the user can modify the criteria if required and then click Submit again).
How do I pass the various data back and forth?
Option 1 - create a single view model class, that has 10 properties (one for each criteria), and a 11th property (a List) that holds the results.
As far as I understand, the values in the model will be passed back to the server when the user clicks Submit (that's how the server gets the criteria values). Does this mean that all the 1,000 rows are also passed back?
Option 2 - create a view model class that holds the 10 properties. Use ViewBag for passing the List that holds the results. Thus, only the criteria data is sent back when the user clicks Submit a 2nd item, while the displayed rows aren't sent back to the server (since they weren't stored in the model).
Are there other approaches? Which approach should I use?
Go with Option 1: I don't see any reason to use ViewBag in this scenario. Just pass the model to the view in your controller action method.
return View(model);
You'd have two action methods, both returning same view. One method for when you first visit the page:
[HttpGet]
public ActionResult Query()
{
return View(new FormQueryModel())
}
...and another one for when user submits search criteria. This runs the query and pass a model populated with results for the view to display them.
[HttpPost]
public ActionResult Query(FormQueryModel model)
{
var queryManager = new QueryManager(model);
model.QueryResults = queryManager.GetResults();
return View(model);
}
And no, you don't have to post back the results from a previous search to the controller again. In my case I just leave results outside the form tag so it's not posted back. But as long you don't bind the result, you'd be okay.
#model FormQueryModel
#using (Html.BeginForm("Query", "Home"))
{
#Html.LabelFor(m => m.Age)
#Html.TextBoxFor(m => m.Age)
#Html.LabelFor(m => m.Country)
#Html.TextBoxFor(m => m.Country)
}
#if (Model.QueryResults.Count > 0)
{
#foreach (var result in Model.QueryResults)
{
//display results here
}
}
Also you need to add some pagination as the user is not going to read 1000 rows. If the search is returning too many rows user would add more filter conditions instead.
public class FormQueryModel
{
public int PageSize { get; set; }
[Display(Name = "Enter your age")]
public int Age { get; set; }
[Display(Name = "Enter your country")]
public string Country { get; set; }
public List<QueryResult> QueryResults { get; set; }
public FormQueryModel()
{
this.QueryResults = new List<QueryResult>();
}
}
This question and community wiki answer has been added to assist in closing out numerous unanswered questions as discussed in this meta post.
I have some code and when it executes, it throws an exception saying:
The model item passed into the dictionary is of type Bar but this dictionary requires a model item of type Foo
What does this mean, and how do I fix it?
The error means that you're navigating to a view whose model is declared as typeof Foo (by using #model Foo), but you actually passed it a model which is typeof Bar (note the term dictionary is used because a model is passed to the view via a ViewDataDictionary).
The error can be caused by
Passing the wrong model from a controller method to a view (or partial view)
Common examples include using a query that creates an anonymous object (or collection of anonymous objects) and passing it to the view
var model = db.Foos.Select(x => new
{
ID = x.ID,
Name = x.Name
};
return View(model); // passes an anonymous object to a view declared with #model Foo
or passing a collection of objects to a view that expect a single object
var model = db.Foos.Where(x => x.ID == id);
return View(model); // passes IEnumerable<Foo> to a view declared with #model Foo
The error can be easily identified at compile time by explicitly declaring the model type in the controller to match the model in the view rather than using var.
Passing the wrong model from a view to a partial view
Given the following model
public class Foo
{
public Bar MyBar { get; set; }
}
and a main view declared with #model Foo and a partial view declared with #model Bar, then
Foo model = db.Foos.Where(x => x.ID == id).Include(x => x.Bar).FirstOrDefault();
return View(model);
will return the correct model to the main view. However the exception will be thrown if the view includes
#Html.Partial("_Bar") // or #{ Html.RenderPartial("_Bar"); }
By default, the model passed to the partial view is the model declared in the main view and you need to use
#Html.Partial("_Bar", Model.MyBar) // or #{ Html.RenderPartial("_Bar", Model.MyBar); }
to pass the instance of Bar to the partial view. Note also that if the value of MyBar is null (has not been initialized), then by default Foo will be passed to the partial, in which case, it needs to be
#Html.Partial("_Bar", new Bar())
Declaring a model in a layout
If a layout file includes a model declaration, then all views that use that layout must declare the same model, or a model that derives from that model.
If you want to include the html for a separate model in a Layout, then in the Layout, use #Html.Action(...) to call a [ChildActionOnly] method initializes that model and returns a partial view for it.
This question already has a great answer, but I ran into the same error, in a different scenario: displaying a List in an EditorTemplate.
I have a model like this:
public class Foo
{
public string FooName { get; set; }
public List<Bar> Bars { get; set; }
}
public class Bar
{
public string BarName { get; set; }
}
And this is my main view:
#model Foo
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
#Html.EditorFor(m => m.Bars)
And this is my Bar EditorTemplate (Bar.cshtml)
#model List<Bar>
<div class="some-style">
#foreach (var item in Model)
{
<label>#item.BarName</label>
}
</div>
And I got this error:
The model item passed into the dictionary is of type 'Bar', but this
dictionary requires a model item of type
'System.Collections.Generic.List`1[Bar]
The reason for this error is that EditorFor already iterates the List for you, so if you pass a collection to it, it would display the editor template once for each item in the collection.
This is how I fixed this problem:
Brought the styles outside of the editor template, and into the main view:
#model Foo
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
<div class="some-style">
#Html.EditorFor(m => m.Bars)
</div>
And changed the EditorTemplate (Bar.cshtml) to this:
#model Bar
<label>#Model.BarName</label>
Observe if the view has the model required:
View
#model IEnumerable<WFAccess.Models.ViewModels.SiteViewModel>
<div class="row">
<table class="table table-striped table-hover table-width-custom">
<thead>
<tr>
....
Controller
[HttpGet]
public ActionResult ListItems()
{
SiteStore site = new SiteStore();
site.GetSites();
IEnumerable<SiteViewModel> sites =
site.SitesList.Select(s => new SiteViewModel
{
Id = s.Id,
Type = s.Type
});
return PartialView("_ListItems", sites);
}
In my case I Use a partial view but runs in normal views
Consider the partial map.cshtml at Partials/Map.cshtml. This can be called from the Page where the partial is to be rendered, simply by using the <partial> tag:
<partial name="Partials/Map" model="new Pages.Partials.MapModel()" />
This is one of the easiest methods I encountered (although I am using razor pages, I am sure same is for MVC too)
First you need to return an IEnumerable version of your model to the list view.
#model IEnumerable<IdentityManager.Models.MerchantDetail>
Second, you need to return a list from the database. I am doing it via SQL Server, so this is code I got working.
public IActionResult Merchant_Boarding_List()
List<MerchantDetail> merchList = new List<MerchantDetail>();
var model = new MerchantDetail();
try
{
using (var con = new SqlConnection(Common.DB_CONNECTION_STRING_BOARDING))
{
con.Open();
using (var command = new SqlCommand("select * from MerchantDetail md where md.UserGUID = '" + UserGUID + "'", con))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
model.biz_dbaBusinessName = reader["biz_dbaBusinessName"].ToString();
merchList.Add(model);
}
}
}
}
}
catch (Exception ex)
{
}
return View(merchList);
Passing the model value that is populated from a controller method to a view
public async Task<IActionResult> Index()
{
//Getting Data from Database
var model= await _context.GetData();
//Selecting Populated Data from the Model and passing to view
return View(model.Value);
}
one more thing.
if your view is a partial/sub page and the model for that partial view is null for some reason (e.g no data) you will get this error. Just need to handle the null partial view model
I'm developing a registration flow where user comes and fills 5 pages to complete a process. I decided to have multiple views and one controller and a ProcessNext action method to go step by step. Each time Process Next gets called it gets the origin view and next view. Since each view associated with there own view model i have created a base view model which all view specific view model derived from. Now the issue is, casting is throwing an exception.. here is the sample code
Base View Model
public class BaseViewModel
{
public string viewName;
}
Personal View Model
public class PersonalViewModel : BaseViewModel
{
public string FirstName;
// rest properties comes here
}
Index.cshtml
#Model PersonalViewModel
#using (Html.BeginForm("ProcessNext", "Wizard", FormMethod.Post, new { class = "form-horizontal", role = "form" }))
#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control" })
<input type="submit" class="btn btn-default" value="Register" />
Basically, I'm binding the view with PersonalViewModel here
Now in Controller ProcessNext Action method looks like this.
public ActionResult ProcessNext(BaseViewModel viewModelData)
{
PersonalViewModel per = (PersonalViewModel) viewModelData;
}
This is failing and throwing a type case exception, why?..
My idea is to use only one action method to transform all these derived view model and send to a common class to validate and process. Please help me to get through this issue.. Thanks!
The reason that you see this exception is that your model type is BaseViewModel and not PersonalViewModel. Model binder is the one that creates a model and since your action's model is BaseViewModel it creates a BaseViewModel object.
I would recommend you to create separate actions for each one of your steps. Each action should have its corresponding model. I also think that you should prefer with composition instead of inheritance in this case.
public class FullModel
{
public FirstStepModel FirstStep {get;set;}
public SecondStepModel SecondStep {get;set;}
}
Then once you start your flow (on a first step for example) you can create a FullModel object and store it somewhere (session/cookie/serialize into a text and send to client - it is really up to you).
Then in controller you will have
[HttpGet]
public ActionResult ProcessFirst()
{
HttpContext.Session["FullModel"] = new FullModel(); //at the beginning store full model in session
var firstStepModel = new FirstsStepModel();
return View(firstStepModel) //return a view for first step
}
[HttpPost]
public ActionResult ProcessFirst(FirstStepModel model)
{
if(this.ModelState.IsValid)
{
var fullModel = HttpContext.Session["FullModel"] as FullModel; //assuming that you stored it in session variable with name "FullModel"
if(fullModel == null)
{
//something went wrong and your full model is not in session..
//return some error page
}
fullModel.FirstStep = model;
HttpContext.Session["FullModel"] = fullModel; // update your session with latest model
var secondStepModel = new SecondStepModel();
return View("SecondStepView", secondStepModel) //return a view for second step
}
// model is invalid ...
return View("FirstStepView", model);
}
[HttpPost]
public ActionResult ProcessSecond(SecondStepModel model)
{
var fullModel = HttpContext.Session["FullModel"] as FullModel; //assuming that you stored it in session variable with name "FullModel"
if(fullModel == null)
{
//something went wrong and your full model is not in session..
//return some error page
}
fullModel.SecondStep = model;
HttpContext.Session["FullModel"] = fullModel; // update your session with latest model
var thirdStepModel = new ThirdStepModel();
return View("ThirdStepModel", thirdStepModel); //return a view for a third step
}
Of course you should extract all the shared code to some reusable method.
And it is entirely up to you what persistence technique to use for passing FullModel between the request.
If you still prefer to go with one Action solution you need to create a custom model binder that is going create derived instances based on some data that is passed from the client. Take a look at this thread
I figured it out a generic way to handle this situation using Model Binders. Here it is..
You might need to have a extended model binder from DefaultBinder to implement to return your model type.
public class WizardModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var viewIdContext = bindingContext.ValueProvider.GetValue("ViewId");
int StepId = 0;
if (!int.TryParse(viewIdContext, out StepId))
throw new InvalidOperationException("Incorrect view identity");
//This is my factory who gave me child view based on the next view id you can play around with this logic to identify which view should be rendered next
var model = WizardFactory.GetViewModel(StepId);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, model.GetType());
bindingContext.ModelMetadata.Model = model;
return model;
}
}
You would register this binder from your gloab asx like
ModelBinders.Binders.Add(typeof(BaseViewModel), new WizardModelBinder());
Thanks to all who responsed to my query..!! Let me know if you guys have any questions.
Happy coding..!!
I am working on a multi-step form which must commit the first step to database before the other steps add up and commit finally after last step. Looking at the options I decided to use sessions(however I will be happy to use any better alternative). I managed to get this to work and later decided to use ajax for the form submission hence the requirement of partialviews. The problem is the dropdowns are returning null viewdata values - ie they are not binding. Can anyone suggest the best way to compose in the controller to make this work. It works fine if I revert to return views instead of return partialviews.
Controller
[AllowAnonymous]
public ActionResult ContactViewDetails()
{
ViewBag.CountryList = new SelectList(db.Countries, "CountryId", "CountryName");
return PartialView("ContactViewDetails");
}
model
public int CountryId{get;set;}
public virtual Country Country { get; set; }
.......and others
Default Page View
<script src="~/Scripts/jquery-2.1.3.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<div id="divContainer">
#Html.Partial("RegViewDetails")
</div>
PartialView: ContactViewDetails.cs
#{
AjaxOptions options = new AjaxOptions();
options.HttpMethod = "POST";
options.InsertionMode = InsertionMode.Replace;
options.UpdateTargetId = "divContainer";
}
#using (Ajax.BeginForm("ContactViewDetails", "OnlineApplication", options, new { #class = "form-horizontal" }))
{
#Html.DropDownListFor(x => x.CountryId, (IEnumerable<SelectListItem>)ViewBag.CountryList, new { #class = "chooseOption" })
#Html.......others
}
The ContactViewDetails page is the second step in the form succession The first step is RegViewDetails page as you can see in the Default page view. After validation RegViewDetails returns ContactViewDetails Partial
......
return PartialView("ContactViewDetails");
I am using ASP.NET MVC3 with the razor view engine. I am also using a the Yahoo User Interface 2 (YUI2) simple editor.
My view has a view model called ProductEditViewModel. In this view model I have a property defined as:
public string LongDescription { get; set; }
In my view I would create the YUI2 simple editor from this input field. The field is defined in the view like:
<td>#Html.TextAreaFor(x => x.LongDescription, new { cols = "75", rows = "10" })<br>
#Html.ValidationMessageFor(x => x.LongDescription)
</td>
Here is a partial view of my Edit action method:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ProductEditViewModel viewModel)
{
if (!ModelState.IsValid)
{
// Check if valid
}
// I added this as a test to see what is returned
string longDescription = viewModel.LongDescription;
// Mapping
Product product = new Product();
product.InjectFrom(viewModel);
// Update product in database
productService.Update(product);
return RedirectToRoute(Url.AdministrationProductIndex());
}
When I view the contents of the longDescription variable then it should contain the values from the editor. If I edit the contents in the editor then longDescription still only contains the original contents, not the updated contents. Why is this?
I suspect that somewhere in your POST action you have written something like this:
[Authorize]
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(ProductEditViewModel viewModel)
{
...
viewModel.LongDescription = "some new contents";
return View(viewModel);
}
If this is the case then you should make sure that you have cleared the value from the ModelState before modifying it because HTML helpers will always first use the value from model state and then from the model.
So everytime you intend to manually modify some property of your view model inside a POST action make sure you remove it from modelstate:
ModelState.Remove("LongDescription");
viewModel.LongDescription = "some new contents";
return View(viewModel);
Now when the view is displayed, HTML helpers that depend on the LongDescription property will pick the new value instead of using the one that was initially submitted by the user.