I have a simple ASP.NET MVC 3 dummy app (just learning MVC coming from WebForms).
And I'm pretty confused about how to update a form without actually having some DB stuff in between. I just have a form with a textbox and after I press the button I want to see the string in uppercase. But my nothing happens.
The controller:
public ActionResult Index()
{
ToUppercaseModel model = new ToUppercaseModel { TheString = "testing" };
return View(model);
}
[HttpPost]
public ActionResult Index(ToUppercaseModel model)
{
model.TheString = model.TheString.ToUpper();
return View(model);
}
The model:
public class ToUppercaseModel
{
[Display(Name = "My String")]
public string TheString { get; set; }
}
And the view:
#using (Html.BeginForm()) {
<div>
<div class="editor-label">
#Html.LabelFor(m => m.TheString)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.TheString)
</div>
<p>
<input type="submit" value="Convert" />
</p>
</div>
}
This is a simple as it gets I think. Now obviously the return View(model); in the 2nd Index method is not working. I saw some stuff about RedirectToAction() and storing the data in TempData. Most example just submit some id, but since I don't have db that does not work.
If I do this:
return RedirectToAction("Index", model);
I get a
No parameterless constructor defined for this object.
Error Message. This should be simple, no? I think I understand the Post/Redirect/Get concept, but don't see how to apply it for something simple as this.
Thanks for some clarification.
When MVC renders a view it will use the attempted value of a field rather than the model's value if it exists (eg in a datefield I put "Tuesday", this won't model bind but you'll want to show the user the field with their input and highlighted as invalid), you're changing the model's value but not the attempted value.
The attempted value is held in the modelstate dictionary:
ModelState["KeyToMyValue"].Value.Value.AttemptedValue
Accessing and changing these values can be tricky unless you want a load of magic strings in your code, and as validation happens on modelbinding your changed value won't be validated.
My recommendation in these circumstances is to call ModelState.Clear(), this will remove all validation and attempted values, then change your model directly. Finally you want to get your validation on the model by using TryValidateModel(yourModel).
Be aware that this method is probably the easiest non-hacky method of doing this but will remove attempted values that could not bind from the returned view.
You have to call 1 method from the 2 method, but you have to change it
public ActionResult Index(ToUppercaseModel model)
and send to 1 method your model.
public ActionResult Index(ToUppercaseModel? model)
{
if (model == null)
ToUppercaseModel model = new ToUppercaseModel { TheString = "testing" };
return View(model);
}
I think I got a solution, it works, but not sure if this is the way it should be?
Basically I just put my model into the TempData and call the normal Index method again.
public ActionResult Index()
{
ToUppercaseModel model = null;
if (TempData["FeaturedProduct"] == null)
{
model = new ToUppercaseModel { TheString = "testing" };
}
else
{
model = (ToUppercaseModel)TempData["FeaturedProduct"];
}
return View(model);
}
[HttpPost]
public ActionResult Index(ToUppercaseModel model)
{
model.TheString = model.TheString.ToUpper();
TempData["FeaturedProduct"] = model;
//return View(model);
return RedirectToAction("Index");
}
Related
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 trying this code:-
If no query string supplied to the Index Method then render a Branch Locator View. When a Branch Id is selected in that View, post back to a Redirect To Route Result OR Action Result method and then redirect back to Index with a query string of the selected Branch Id.
I can run through the code successfully without and then with the query string.
I even run through the Index View and can see the Model working however, the Index View does not render, the Branch Selector View remains. Network developer tools shows the correct URL with query string correctly in place when doing the Redirect.
(NOTE: Both methods are on the same controller).
If I add the same query string directly in the Browser address bar it works fine!
I have this code:
[HttpGet]
public ActionResult Index()
{
var querystringbranchId = Request.QueryString["branchId"];
if(!string.IsNullOrEmpty(querystringId))
{
....do stuff like build a model using the branchId...
return View(Model);
}
return View("BranchSelector")
}
[HttpPost]
public RedirectToRouteResult BranchDetails(FormCollection formCollection)
{
var querystringBranchId = formCollection["BranchList"];
var branchId = int.Parse(querystringBranchId);
return RedirectToAction("Index", new { branchId });
}
Try using strongly typed model on the post, and specifying the param as an actual param - Using View models is going to be much better for you.
I have tested the below - It seemed to work as expected for me:
[HttpGet]
public ActionResult Index(int? branchId)
{
if (branchId.HasValue)
{
return View(branchId);
}
return View("BranchSelector");
}
[HttpPost]
public RedirectToRouteResult BranchDetails(MyModel myModel)
{
return RedirectToAction("Index", new { myModel.BranchId });
}
public class MyModel
{
public int BranchId { get; set; }
}
The View:
<div>
#using (Html.BeginForm("BranchDetails", "Home", FormMethod.Post))
{
#Html.TextBox("BranchId","123")
<input type="submit" value="Go"/>
}
</div>
#MichaelLake Thanks to your post I found the problem. I tried your code and sure enough it works as expected. I didn't mention I was using a Kendo Combobox control (!) loaded with the branches. I didn't mention that as the actual data I needed was available in the post method so, thought the issue was with the Controller methods. I had the Kendo control name as BranchList, I changed it to BranchId and it now works with the original code as expected! The Kendo name becomes the element Id and has to match to work.
Many Thanks!
This will work for you. Cheers :D
return RedirectToAction("Index", "ControllerName", new { branchId = branchId});
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 have recently taken over support of an ASP.NET MVC project, and am trying to work through some of the errors, one in particular has me stumped though.
We have a 'New' page to add new items, with the following code running when the page is posted:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return View("Index");
}
return View("Index");
}
However, when it tries to load the Index view, I get the following error: "Object reference not set to an instance of an object." and it points to the following block of code at the top of a file called RecordsView.cshtml:
#for (var i = 0; i < Model.Records.Count; i++)
The record does add correctly though, it just doesn't load the listings page correctly, and since this is just a "nice to have" I thought I'd simplify things by changing it so that it either returns some text which generates an error as it's expecting a boolean returned.
Any ideas on how to fix this please? I'm stumped.
Thanks!
The flow of your code here doesn't look right:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return View("Index");
}
return View("Index");
}
From your description above, it sounds as though you're POSTing from your New view, to this New action, which should then redirect, when successful, to your Index action. Currently, the above code is not doing that, and it also fails to redisplay the form if the model isn't valid. It should probably look more like this:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
// Redisplay the `New` view, passing the model
// back to it in order to repopulate the form
return View(i);
}
The important distinction between return View(...) and return RedirectToAction(...) is that the latter will run the action and return the associated view. The former will simply return the view. That has implications in that if the Index action builds a model, and passes it to the Index view, none of that will happen if you simply return the view.
You could of course do something like:
return View("Index", new YourModelType());
but that isn't going to work if, as discussed above, your Index action performs some other data construction for your model, such as building drop down lists, which new YourModelType() wouldn't do.
Also, when data that is sent to a POST action is valid, you should be redirecting to another action (as I've done above), rather than simply returning a view, in order to conform with the Post-Redirect-Get pattern, which prevents some types of duplicate form submissions.
You display Index view, and seems that it requires some Model - Model.Records. And you don't pass it in this HttpPost action.
If you have action for that Index page, then you can just do
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
return View(i);
}
It will just redirect a user to Index view, after creation of new RecordView item
Basically you may are trying to achieve PRG(Post/Redirect/Get) modal.
I guess, the problem is you are not sending the model for your GET request.
Post--> Save --> Redirect --> Load Data -->Assign to View in Index -->Access in view
//POST & REDIRECT
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
}
//GET
public ActionResult Index()
{
var model=new MyViewModel();
model.Records=repository.GetRecords(i.DogIncident);
return View(model); //Assign to View in Index
}
Index.cshtml
#model MyViewModel
#for (var i = 0; i < Model.Records.Count; i++)
If Records is a list, make sure your ViewModel has a constructor:
public class RecordView
{
public List<Record> Records { get; set; }
public RecordView()
{
Records = new List<Record>();
}
}
You mentioned that the record adds correctly, so you must be passing a valid record model into your view from some other action than the one provided.
If #for (var i = 0; i < Model.Records.Count; i++) is the cause of this error, my guess is that the model exists, but the Records property has not been set. One immediate work around would be checking the existence of this property before accessing it. For example:
if (Model.Records != null) {
for (var i = 0; i < Model.Records.Count; i++) .....
}
i think you have collections are not instantiated, the error may be in models not in view models. this because when ever you have a collection you need to instantiate inside of constructor of that entity.
May be this is your answer...!Just look
[HttpPost]
public ActionResult New(RecordView)
{
if(ModelState.IsValid)
{
repositry.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
}
After form submit Html editor helpers (TextBox, Editor, TextArea) display old value not a current value of model.text
Display helpers (Display, DisplayText) display proper value.
Is there any way editor helpers to display current model.text value?
Model
namespace TestProject.Models
{
public class FormField
{
public string text { get;set; }
}
}
Controller
using System.Web.Mvc;
namespace TestProject.Controllers
{
public class FormFieldController : Controller
{
public ActionResult Index (Models.FormField model=null)
{
model.text += "_changed";
return View(model);
}
}
}
View
#model TestProject.Models.FormField
#using (Html.BeginForm()){
<div>
#Html.DisplayFor(m => m.text)
</div>
<div>
#Html.TextBoxFor(m => m.text)
</div>
<input type="submit" />
}
When you submit the form to an MVC action the values of the input fields are recovered from the POSTEd values available in the form and not from the model. That makes sense right? We don't want the user to show a different value in a textbox than they have just entered and submitted to the server.
If you want to show the updated model to the user then you should have another action and from the post action you have to redirect to that action.
Basically you should have two actions one action that renders the view to edit the model and another one saves the model to database or whatever and redirect the request to the former action.
An example:
public class FormFieldController : Controller
{
// action returns a view to edit the model
public ActionResult Edit(int id)
{
var model = .. get from db based on id
return View(model);
}
// action saves the updated model and redirects to the above action
// again for editing the model
[HttpPost]
public ActionResult Edit(SomeModel model)
{
// save to db
return RedirectToAction("Edit");
}
}
When using HTML editors such as HTML.EditorFor() or HTML.DisplayFor(), if you attempt to modify or change the model values in the controller action you won't see any change unless you remove the ModelState for the model property you want to change.
While #Mark is correct, you don't have to have a separate controller action (but you usually would want to) and you don't need to redirect to the original action.
e.g. - call ModelState.Remove(modelPropertyName)...
public ActionResult Index (Models.FormField model=null)
{
ModelState.Remove("text");
model.text += "_changed";
return View(model);
}
And if you want to have separate actions for GET and POST (recommended) you can do...
public ActionResult Index ()
{
Models.FormField model = new Models.FormField(); // or get from database etc.
// set up your model defaults, etc. here if needed
return View(model);
}
[HttpPost] // Attribute means this action method will be used when the form is posted
public ActionResult Index (Models.FormField model)
{
// Validate your model etc. here if needed ...
ModelState.Remove("text"); // Remove the ModelState so that Html Editors etc. will update
model.text += "_changed"; // Make any changes we want
return View(model);
}
I had some similar problem, I hope I can help others have similar problem:
ActionExecutingContext has Controller.ViewData.
as you can see:
new ActionExecutingContext().Controller.ViewData
This ViewData contains ModelState and Model. The ModelState shows the state of model has passed to controller for example. When you have an error on ModelState the unacceptable Model and its state passed to View. So you will see the old value, yet. Then you have to change the Model value of ModelState manually.
for example for clearing a data:
ModelState.SetModelValue("MyDateTime", new ValueProviderResult("", "", CultureInfo.CurrentCulture));
Also you can manipulate the ViewData, as here.
The EditorFor, DisplayFor() and etc, use this ViewData contents.