Model Binder null on List<object> sometimes - c#

I have a view model,and one of the properties of the view model is a an object called Profile. One of the properties of Profile is a list of another object, called CD. From the view, I set the POST body values to the following
Profile.CD[0].Prop1=TEST&Profile.CD[0].Prop2=TEST&Profile.CD[1].Prop1=TEST2&Profile.CD[1].Prop2=TEST2
If I were to add a third object to the list in the view, which would get posted as Profile.CD[2].Prop1=TEST3 , in the controller all of the sudden Profile.CD is null. 2 items and below Profile.CD gets the values I would expect. As soon as I add in that third item the model binder stops working. I'm at my wits end, and I've tried everything I can think of.
Things I've tried
Removing an item from the view, and adding a new -- WORKS
Removing both from the view, and adding 2 new items -- WORKS
Adding a third item in the view -- FAILS Profile.CD is null in the view model
I'm not using any model state validation rules. When debugging, I've tried something like the following in the immediate window ?Request.Form.Where(x => x.Keys.Contain("Profile.CD")).ToList()) and sure enough, all of my values are present in the Request object even though the list is null in the view model.
The values of the objects in Profile.CD do not have to be unique.. I've tried setting every single value to "TEST" just to verify it's not the input causing this problem.
I'm truly lost..
View Model
public class PortalViewModel {
public ProfileModel Profile { get; set; }
}
Profile Model
public class ProfileModel {
//bunch of other static properties that are bound just fine.. like strings and decimals...
public List<CDModel> CD { get; set; }
}
Controller
public async Task<IActionResult> Edit (PortalViewModel Model)
{
Repo.UpdateProfile(Model.Profile); // in this method it foreachs Profile.CD , but sometimes this is null and other times it get's it's values....
return Ok();
}

I have not been able to find a coding solution to this problem, and started exploring other avenues. I have fixed the problem, but it makes very little sense.
I upgraded the project from .net core 1.0.1 to .net core 1.0.5 , and everything is working 100% of the time. In this upgrade, I made no code changes what so ever. Just upgraded .net core. Very very odd..

Related

Updating Controller and Views after adding column to Model

I am new to Entity framework, so this is probably a stupid question.
I have added a new field to a Model class. I have created the migration and updated the database and this all works as expected. The new column has appeared in the SQL table.
I have manually modified the Create/Details Views (I'm guessing this isn't automatic) to include the new column. The new column is called "Level".
However, my Controller class doesn't seem to have picked up the changes. It doesn't insert the value when creating a new row. There are references to column names in the Controller class, but the new column name isn't there. E.g.
public async Task<IActionResult> Create([Bind("Id,Name")] Course course)
Is there any way to "refresh" the Controller to pick up the changes? Or will I have to manually edit the lines where the columns are named and add the new column?
If your view has a control bound to this new Level field and you want to pass it to the controller to write, then you will need to add it to the Bind attribute:
public async Task<IActionResult> Create([Bind("Id,Name,Level")] Course course)
While it might look like when you pass a Model to a view, and have a Form in the view that calls an action on the Controller passing that Model back, this isn't actually what happens. ASP.Net MVC is merely formatting the code in that way. Honestly this is a bit of a bad practice when using EF Entities as it trips up quite a few developers expecting that Entity references are being passed between server and view. It leads to confusing problems and is also an open door to data tampering when used incorrectly.
When the controller passes the Model to the View(), it is passing that Model to the View Renderer to compose the HTML view that will be sent to the browser. So if you're passing a Course entity, that entity does not travel to the client, it is consumed by the Server to build the HTML.
When your Form goes to call the controller, it will automatically append any bound fields that it knows about. This will include any bound entry controls, and if there are any details that don't have edit controls bound to them, you need to use bound Hidden controls to ensure that the MVC Javascript knows to pick those up too.
If you use a Bind attribute, this tells MVC which properties coming back from a Form submit or Ajax call that you actually want to accept. If "Level" isn't in there, the value will be ignored even if passed. The View does not pass an actual entity, it will be a set of named parameters that MVC has some magic smarts to convert into a Model object based on convention.
The Action could be written as either:
public async Task<IActionResult> Create([Bind("Id,Name,Level")] Course course)
or
public async Task<IActionResult> Create(int id, string name, string level)
where in the second case you'd need to handle the creation and population of your Course entity using the values passed in.

Keep data in ViewModel when validating in MVC5

In my asp.NET MVC5 app I have a controller that supplies a view which is strongly typed vs a viewmodel. This viewmodel has a SelectList property (among others), and the controller supplies the data on creation from the database:
public ActionResult Simulation() {
var SimVM = new SimulationVM(
StrategyRepository.GetStrategies().Select(n => n.Name),
);
return View(SimVM);
}
The SelectList is then used as data source for a DropDown choice in a form. The HttpPost method does some datavalidation, i.e.,
[HttpPost]
public ActionResult Simulation(SimulationVM _simVM) {
if (ModelState.IsValid) {
// ...
}
else return View(_simVM);
}
So with the code above, the DropDown data is empty, since on posting, the SimulationVM object is created new. The usual trick of using Html.HiddenFor does not work on collections.
Of course, I could go back and fetch the data again from the database, but that seems to be bad, a database fetch for such a simple thing as a validation where I know the data hasn't changed.
What is the best (or for the sake of not being subjective: any) way to keep some data in the ViewModel (or repopulate it efficiently)?
If it is a requrement that you not go back to the database and you're 100% confident that the data will not change (i.e. this is a list of states as opposed to a list of orders or something) then you can add the collection to a session variable. Here's a link to a decent article:
https://code.msdn.microsoft.com/How-to-create-and-access-447ada98
That being said, I usually just go to the database and get the data again. If doing so for a second time is causing huge performance issues, it is most likely causing performance issues the first time and you should treat the problem rather than the symptom.

Need to access a variable from all Views

On the site I'm working on, there are users with different permissions on the site.
Given the schedule ID and employee ID that we're currently looking at, we can get their role-specific permissions.
Right now, our BaseModel has a property that properly accesses the DB and grabs this info.
For all views that pass a model to the view, everything runs fine.
The problem lies in Controller Methods where no model is passed. In a few views, all they're supplied is a few ViewBag entries, and work fine.
However, I /need/ the CurrentPermissions property in those pages nonetheless, for the layout. Whether or not the permissions have one boolean value set true/false, something may/may not be displayed/populated.
So, my option seem to be:
Somehow throw my CurrentPermissions into a ViewBag entry for all views, and access them through that instead of the base model.
I'm not sure how to do this. I've seen people using OnActionExecuting, but that fails since my connection to TransactionManager is not yet set up at that point.
Somehow throw just the BaseModel into those views that don't currently pass a model. I'm refraining from this as much as possible. I'm not sure how I would go about doing such, but it seems like that would over-complicate the situation.
How can I go about pushing this CurrentPermissions object (generated from a call to my TransactionManager) to every view (specifically, the Layouts!)
Your approach is what we use in out projects... and we use this approach to systematically remove the use of ViewBag changing it to ViewModels.
Other approach we have used (for UserPreferences in my case) is adding an ActionFilter that ends including the preference in the ViewBag. You decorate the actions needing it with [IncludePreferences] in my case (that is the name of my filter attribute.
EDIT ActionFilter:
public class IncludePreferencesAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller as BaseController;
// IController is not necessarily a Controller
if (controller != null)
{
//I have my preferences in the BaseController
//and cached but here you can query the DB
controller.ViewBag.MyPreferences = controller.TenantPreferences;
}
}
}
In you action you decorate it using [IncludePreferences]
As a temporary solution, I'm doing the following at the top of my Layout:
#{ OurModel.SupervisorRestriction CurrentSupervisorRestrictions = ViewBag.CurrentSupervisorRestrictions ?? Model.CurrentSupervisorRestrictions; }
This way, if we're passing in an object then it works just fine. Otherwise, I'll directly pass in a ViewBag.CurrentSupervisorRestrictions from the controller. There are only a few cases, so it's not that bad.
Better suggestions would be great, though.

C# List nullreference exception ASP.net MVC

Inside a loop I'm trying to add an object with the type Location to a List<Location> property.
CONTROLLER:
[HttpPost]
public ActionResult Index([Bind(Include = "CitySearch")]HomeViewModel model)
{
List<CityIdentity> ids = null;
ids = service.GetNamesId(model.CitySearch);
foreach (var id in ids)
{
var loc = service.GetLocation(id.CityId);
var location = new Location
{
CityId = id.CityId,
Place = loc.Place,
};
model.Locations.Add(location);
}
}
VIEWMODEL:
public class HomeViewModel
{
public string CitySearch { get; set; }
public List<Location> Locations { get; set; }
}
model is of type HomeViewModel and property Locations is of type List<Location>. model is instance from form HttpPost action from submitting the form in the View.
I'm debugging and the error's occurs at model.Locations.Add(location) and I'm getting object reference not set to an instance of an object. and nullref exception.
Any idea?
Based on your reactions and the context, it seems your Locations property isn't initialized at the time, so you can't use a method Add on this collection. To overcome this issue, you need to initialize this property first and then you can manipulate it whatever way you need to.
One way of achieving this is to initialize your Locations property in the constructor of your model (or wherever you see fit).
So just add something like this and you're good to go:
Locations = new List<Location>();
This initializes your property enough to use its methods.
Any idea?
Yes, either the model variable is null or the Locations property is null. So make sure that they are not null before accessing model.Locations. Otherwise the exception you are getting is perfectly normal. You cannot call methods on a null instance. By the way you could have used standard debugging techniques (putting a breakpoint in your code, running your application in Debug mode and inspecting the values of your variables) would have lead you to the same conclusion.
Now why your model or model.Locations property is null is a hugely different question. I guess that the form you submitted didn't contain input elements that obey the standard naming conventions for binding to a List. I invite you to familiarize yourself with those conventions and respect them if you expect the default model binder to be able to rehydrate your models in the POST actions.
Maybe somehow you put the model as action argument and expected that ASP.NET MVC will automagically populate it from somewhre? No, that's not how ASP.NET MVC works. There's no magic. ASP.NET MVC uses model binders which have strict rules. Only what you send as arguments to the action is what you can expect to get back.

Preparing models for ASP.NET MVC views

When returning strongly typed models for views such as Create and Edit (when validation of the object we are editing fails) I usually prepare the models like this:
//
// GET: /Invoice/Create
public virtual ActionResult Create()
{
// prepare the empty model
Invoice model = new Invoice();
model.Client = new Client();
model.Client.PostCode = new PostCode();
return View(model);
}
//
// POST: /Invoice/Create
[HttpPost]
public virtual ActionResult Create(Invoice document,
FormCollection collection)
{
// check for errors
if (!ViewData.ModelState.IsValid)
{
document.Client = new Client();
document.Client.PostCode = new PostCode();
return View(document);
}
Now I know that this is how others do it too, in fact you can see this same approach in MVC Music Store sample and others. However, this is very error prone because one might accidentally left out a referenced entity which is required in the view. It also requires too much thinking about view/model interaction. What I would want is some sort of automatism. Value typed properties in models usually aren't the problem because they default either to zero or empty strings. Reference types however should be initialized with new..but sooner or later we end up with code blocks that are being repeated, reference type properties being left out, etc..And I don't think it's good coding practice either.
What are other options we could take?
UPDATE:
Because replies kinda missed the point (they do not relief us of thinking about models in any way and require additional code in model classes), I was thinking if this option would work:
Use custom Action filter,
override OnActionExecuted()
use Reflection inside this method to take out the object from the Model and enumerate its public properties and try to initialize them.
I have steps 1, 2 and 3 partially implemented but I cannot figure out how to do "... = new Client();" programatically with Reflection.
Make the properties of your model return a new instance if it is null
private Client client;
public Client Client
{
get
{
if (client == null)
client = new Client();
return client;
}
}
I suggest that you use a Strongly typed view bound to a ViewModel that is distinct from the Domain Model you're trying to create, and put whatever necessary logic into the constructor of the ViewModel
I'm not sure I fully understand your question. You want what automated? ViewModels and Views? Are you creating strongly typed views?
I have created a T4 template that I point to a database and it generates a ViewModel for every table. Foreign keys become drop down lists, long strings get a TextArea instead of TextBox, etc. I then delete the ones I don't need and modify the ones I want to keep. It's not a totally automated process, but it does 80 to 90 percent of the work, depending upon the project.
Then I generate strongly typed Views from those ViewModels.
It also sounds like you might be interested in AutoMapper.

Categories

Resources