I'm working on a search criteria building page. In addition to several string and numerical type fields, there are several "multiple choice" options.
I'm using the [Get] signature without parameters(pass the CriteriaModel to the view) >> [Post] signature with CriteriaModel parameter (redirect to searching controller)
I've built lightweight option classes (just value, name pairs) and am populating several List<> with the primitive options.
Using Html.DropDownListFor, I'm able to get them to display.
...but...
When I enter the [Post] version, the List<>s are all set to null and empty. Further, the other criteria fields supposed to be populated afterwards are also default and empty.
Technically, I don't need a whole list of values back - if I could even just have the index of the selected value - but I'm up against a wall here.
Pertinent model data:
public class CriteriaModel
{
[DisplayName("Owner Name")]
public string OwnerName { get; set; }
[DisplayName("Subdivision")]
public List<Subdivision> Subdivision { get; set; }
[DisplayName("PIN")]
public string PIN { get; set; }
}
public class Subdivision
{
public int ID { get; set; }
public string Name { get; set; }
}
Pertinent controller code:
[HttpGet]
public ActionResult Index()
{
CriteriaModel criteria = new CriteriaModel();
...fill in the Subdivisions...
View(criteria);
}
[HttpPost]
public ActionResult Index(CriteriaModel search_criteria)
{
return View("Search obtained" + search_criteria.Subdivision.First().Name);
}
And pertinent View markup:
#model REOModern.Models.CriteriaModel
...bunch of HTML...
#Html.LabelFor(model => model.Subdivision)
#Html.DropDownListFor(x => x.Subdivision, new SelectList(Model.Subdivision, "ID", "Name", Model.Subdivision.First().ID))
...other HTML...
<button type="submit" class="btn btn-primary" value="Index">Search</button>
I should clarify: I know that my 'return View("Search obtained" + ...' will fail, but it should show the piece of data that I need. The problem is it's a null reference exception. Until I can fix that, there's no point in building a user-friendly View for submitted search criteria.
MVC does not repopulate the List<> elements.
You would split the selected value out into another property of the model.
So in your model, include something like this
public int SelectedValue { get; set; }
Then for your Html.DropDownListFor helper you would use
Html.DropDownListFor(model => model.SelectedValue, Model.DropDownList, new { /* htmlAttributes */ });
Of course they're empty. The only data that exists in your post action is that which was posted via the form. Since the entire dropdown list, itself, was not posted, merely a selected item(s), the lists are empty. For anything like this, you need to rerun the same logic in your post action to populate them as you did in your get action. It's usually better to factor out this logic into a private method on your controller that both actions can use:
private void PopulateSomeDropDownList(SomeModel model)
{
// logic here to construct dropdown list
model.SomeDropDownList = dropdownlist;
}
Then in your actions:
PopulateSomeDropDownList(model);
return View(model);
Related
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).
In a C# MVC 5 Internet application, I have a HTTP Get Edit action result, that gets an object, and places this object in a ViewModel and this is then displayed in a View.
One of the fields in the ViewModel is a value that is not edited in the view. In the HTTP Post Edit action, the value that is not edited in the view has been reset.
How can I keep this value so that it is the same value in the HTTP Post method as the HTTP Get method?
Thanks in advance
EDIT
Here is the ViewModel code:
public class MapLocationViewModel
{
[Editable(false)]
public int mapCompanyForeignKeyId { get; set; }
public MapLocation mapLocation { get; set; }
}
Here is the code at the bottom of the HTTP Get Edit Action result, where the mapCompanyForeignKeyId is set:
MapLocationViewModel mapLocationViewModel = new MapLocationViewModel();
mapLocationViewModel.mapLocation = maplocation;
mapLocationViewModel.mapCompanyForeignKeyId = maplocation.mapCompanyForeignKeyId;
return View(mapLocationViewModel);
Here is the HTTP Post Edit Action result code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(MapLocationViewModel mapLocationViewModel)
In the above HTTP Edit Action result code, the mapLocationViewModel.mapCompanyForeignKeyId is reset to 0, after this value has been set to a number in the HTTP Get Edit Action result.
You should try make hidden input. With Razor syntax it would be:
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.YourProperty)
}
YourProperty will not be visible but it's value will be in the view model sent to the POST method.
You can also use HiddenInputAttribute for this:
[HiddenInput(DisplayValue=false)]
public int YourProperty {get; set;}
If you are using the #Html.TextboxFor(m => m.MyField) or similar helpers within a form, by default, it should automatically spit out all the existing values for each field and thus you should see all values whether modified or not. When it is posted, each included field will be serialized. If you use the helpers, you won't have to worry about naming convention as Razor and the model binder will do the work for you.
Check the request coming into your POST action to see if it is a model binding issue or a client issue. If you don't see the desired members in the body (or query string, if a GET) then you must not be sending them from the client, which can be due to improper serialization/naming of fields, not including the field in the page, not sending the value of the field to the page, or including the field outside of the form, among other reasons...
Example:
public class MyViewModel
{
[Required]
public string Field1 { get; set; }
[Required]
public string Field2 { get; set; }
}
...
#model MyViewModel
#using (Html.BeginForm("MyAction", ...)
{
#Html.LabelFor(m => m.Field1)
#Html.TextboxFor(m => m.Field1)
<br />
#Html.LabelFor(m => m.Field2)
#Html.TextAreaFor(m => m.Field2)
<button type="submit">Submit</button>
}
...
[HttpPost]
public ActionResult MyAction(MyViewModel model)
{
if (!ModelState.IsValid)
return MyGetAction(model);
...
}
This question already has answers here:
Populating a razor dropdownlist from a List<object> in MVC
(9 answers)
Closed 8 years ago.
I'm stuck creating a proper create/edit view in ASP.NET MVC5. I've got two models Dog and Human. A dog belongs to one Human. I'm trying to create a dropdown in the create and edit views for Dog that'll allow me to select a Human by name for that particular Dog. Here are my models:
Human:
public class Human
{
public int ID { get; set; }
public string Name { get; set; }
}
Dog:
public class Dog
{
public int ID { get; set; }
public string Name { get; set; }
public Human Human { get; set; }
}
My create action:
// GET: /Dog/Create
public ActionResult Create()
{
ViewBag.HumanSelection = db.Humen.Select(h => new SelectListItem
{
Value = h.ID.ToString(),
Text = h.Name
});
return View();
}
And here is the relevant part of my view:
<div class="form-group">
#Html.LabelFor(model => model.Human.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Human, ViewBag.HumanSelection);
</div>
</div>
I get the following error when I run this:
Compiler Error Message: CS1973: 'System.Web.Mvc.HtmlHelper<Test.Models.Dog>' has no applicable method named 'DropDownListFor' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
I'm new to C# & the Entity framework. What am I doing wrong? Is there a way of doing this without manually querying the database? Something like the collection form helpers in Rails?
I've followed a bunch of tutorials that are either old or too complicated for me to follow.
Important to note is that if you use DropDownListFor(x => x.Human), the returned value of the dropdownlist should be a Human object.
It isn't. In your own code snippet, you set the value of the SelectListItem to the ID of the Human. Therefore, when you submit your form, you will receive the ID that you selected.
Add the following to your model:
public int HumanId { get; set; }
Bind your dropdownlist to that int:
#Html.DropDownListFor(model => model.HumanId, (SelectList)ViewBag.HumanSelection);
Now, when you get back to the controller, use that ID to look up the actual Human you want:
[HttpPost]
public ActionResult Create (CreateModel model)
{
if(model.HumanId > 0)
{
model.Human = GetHumanByID(model.HumanId);
//or however you want to get the Human entoty from your database
}
}
It's a simplified solution, but I suspect your main confusion stems from the fact that you're expecting to receive a Human from the DropDownList, while it will actually only return an int (the ID).
Edit
I don't have much information on your data model, but if you're using entity framework, odds are that your Dog class will have a foreign key property called HumanId. If that is the case, you don't even need to get the Human entity like I showed you before. If you put the selected ID in the HumanId property, Entity Framework should be able to use that to create the relation between Human/Dog you want.
If this is the case, it would seems best to elaborate on this in your question, as this would otherwise be more guesswork than actual confirmation.
Edit 2 going offtopic here
Your code:
db.Humen
The plural form of man is men, woman is women; but for human, it's humans :) Humen does sounds like an awesome suggestion though ;)
The problem is that you are attempting to bind a Human type to a dropdown in the UI, a dropdown whose values are strings (the IDs of Human instances) and text are also strings (the names of Human instances).
What you should be binding to the dropdown instead is the ID of the Human, to match the fact that the ID is being used as the value. So with a view model such as
public class CreateDogModel
{
public string Name { get; set; }
[Range(0, int.MaxValue)]
public int Human { get; set; }
public IEnumerable<Human> Humans { get; set; }
}
And the GET controller action
[HttpGet]
public ActionResult Create()
{
var model = new CreateDogModel
{
Humans = db.Human.ToList()
};
return View(model);
}
The view then becomes
#Html.DropDownListFor(
model => model.Human,
Model.Humans.Select(h => new SelectListItem
{
Text = h.Name,
Value = h.ID.ToString()
}),
"Please select a Human");
In your POST controller action, you now look up the chosen human by the Human property value from the View model
[HttpPost]
public ActionResult Create(CreateDogModel model)
{
if (!ModelState.IsValid)
{
// fetch the humans again to populate the dropdown
model.Humans = db.Human.ToList();
return View(model);
}
// create and persist a new dog
return RedirectToAction("Index");
}
My Problem is like this ,Im trying to get a model object from a view after seinding it with a form,the model Looks like this:
public class PackageModel
{
public PackageDTO Package { get; set; }
public IEnumerable<SelectListItem> Allcategories { get; set; }
}
while PackageDTO is just an DTO object conatining many attributes.
Now the view for this model,ist just showing the attributes and this model will be sent within a httppost request to the index page as normal(there it will be processed ans saved ),
the index method in the Controller Looks like this:
[HttpPost]
public ActionResult Index(PackagemODEL packageModel, FormCollection form)
{
}
Now i dont know what im doing wrong,but the Object packageModel is not totally null,just the list Allcategories and another string Attribute in the PackageDTO object,the rest seems to be working.
The view contains this code
<fieldset>
<legend>#Resources.AppvManagementService_EditPackage_Title</legend>
#using (Html.BeginForm("Index","WantedController",FormMethod.Post,new {enctype="multipart/form-data"}))
{
#Html.ValidationSummary()
<labelName </label>#Html.TextBoxFor(model=>model.Package.Name) <br/>
<label>Sid </label>#Html.TextBoxFor(model=>model.Package.Sid,new {#disabled="disabled"}) <br/>
<label>Category </label>#Html.DropDownList("CategoryName",Model.Allcategories,Model.Package.Category)<br/>
<label>Description: </label>#Html.TextBoxFor(model=>model.Package.Description) <br/>
<label>Type: </label>#Html.TextBoxFor(model=>model.Package.Type) <br/>
<button type="submit">submit</button>
}
Doest anyone have any idea why ist like this?? am i doing something wrong(im sure i am :))
thx for every one
How do you expect Allcategories to be populated? Your view contains a field, which posts a value under the name "CategoryName" - there's nothing in your view that populates a list of categories. More importantly; to you really need it to be populated? It seems to me that Allcategories is only really needed for populating the dropdown in the view. On the post, you shouldn't need it. If you DO still need it, you're going to have to either:
Repopulate it in the controller on the HttpPost method:
[HttpPost]
public ActionResult Index(PackagemODEL packageModel, FormCollection form)
{
packageModel.Allcategories = new IEnumerable<SelectListItem>();
}
Clutter up your view with pointless hidden fields to pass the values back in (I wouldn't recommend this for a list of items unless you really need to):
#for (int i = 0; i < Model.Allcategories.Count; i++)
{
#Html.HiddenFor( m => m.Allcategories[i])
}
Populate it in the model constructor:
public class PackageModel
{
public IEnumerable<SelectListItem> Allcategories { get; set; }
public PackageModel()
{
Allcategories = new IEnumerable<SelectListItem>();
/* Add values to Allcategories here */
}
}
If the values of Allcategories doesn't change, you could also consider making it a static readonly property of your model and hardcoding the values (or pulling them from a config file or similar).
As for getting back the selected CategoryName, you need a field in your model in which to store it, otherwise the only way to access it at the moment is via Request.Form:
public class PackageModel
{
public IEnumerable<SelectListItem> Allcategories { get; set; }
public string CategoryName { get; set; }
}
#Html.DropDownListFor(m => m.CategoryName, Model.Allcategories, Model.Package.Category)
An aside: Please, please, please take your DTO out of your model and set appropriate properties in your model itself. Your DTO does not belong in your view model, which is a model for your view and nothing more.
Sorry if this is a repeated question, I scanned the related questions and didn't see anything obvious.
I'm using an EditModel with an Entity object, along with two SelectLists in it. The problem is, once I reach my POST action, the SelectedValues for both drop downs are still the same default values I set in the constructor for the model, no matter what I actually select on the browser.
My constructor sets some default values for the SelectedValues, but they are just 0 and "" (which aren't valid values in the dropdowns). I have a feeling the problem revolves around that somehow, but I'll give more details.
Here is a stripped down version of the model:
public class UserAccountModel
{
public UserAccount UserAccountData { get; set; } // Entity from EF
public SelectList Organizations { get; set; }
public SelectList Roles { get; set; }
public UserAccountModel() : this(null)
{
}
public UserAccountModel(UserAccount obj)
{
UserAccountData = (obj == null) ? new UserAccount() : obj;
// default selected value
int orgChildId = (UserAccountData.Organization == null) ? 0 : UserAccountData.Organization.ID;
string roleChildId = (UserAccountData.Role == null) ? "" : UserAccountData.Role.Name;
// go get the drop down options and set up the property binding
using (UserAccountRepository rep = new UserAccountRepository())
{
Organizations = new SelectList(rep.GetOrganizationOptions(), "ID", "ID", orgChildId);
Roles = new SelectList(rep.GetRoleOptions(), "ID", "Name", roleChildId);
}
}
public void UpdateModel()
{
UserAccountData.Organization = Organizations.SelectedValue as Organization;
UserAccountData.Role = Roles.SelectedValue as Role;
}
}
This is the Dropdowns portion of the view:
<div class="field">
<label for="ID">Organization:</label>
<%= Html.DropDownList("ID", Model.Organizations) %>
</div>
<div class="field">
<label for="Name">Role:</label>
<%= Html.DropDownList("Name", Model.Roles) %>
</div>
I might have done something stupid and obvious here. The examples are much more straight forward when using the ViewData dictionary, but I couldn't find too many examples trying to use straight model binding for SelectLists.
Any help is greatly appreciated!
Chris
Select elements only post back the actual value of the selected it. In this case, the parameters will be received back at the server/controller as ID (organization) and Name (role). The model you use for the update action should either contain these as properties or your controller action should accept them directly as parameters. The lists on the model won't be repopulated -- and the names don't match in any event.
Modify your model by adding:
public int ID { get; set; }
public string Name { get; set; }
with controller action as:
public ActionResult Update( UserAccountModel userAccount )
{
...
}
Note that if there is a validation error, you'll need to repopulate the SelectList properties (reconstituting the menus).
You simply forgot the navigational path to the ID property :)
<%= Html.DropDownList("Organization.ID", Model.Organizations) %>