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.
Related
I have a Controller that has two action methods used by a page:
The standard OnGet, that is called, when the page is visited in browser and another method SortData that is called using an anchor tag.
Both of these methods interact with the same property of the controller, but there seem to be like two different object references to the property for each of the methods, because the data inside SortData is inconsistent with the date in OnGet.
Also, if I won't initialize that property inside the Controller's constructor but inside the OnGet method, it will be null inside SortData, even though OnGet has been called first.
My question is how do I get SortData to use the same Property that OnGet uses?
The code looks like this:
public class MyController : Controller
{
private MyClass Property {get; set;}
public SearchController()
{
Property = new MyClass()
}
[HttpGet("sortdata")]
public IActionResult SortData(string sortAttribute)
{
Property.SortData(sortAttribute);
return View("Index", Property);
}
public IActionResult OnGet([FromQuery]string requeststring)
{
ViewData["Message"] = requeststring;
Property.Datas = requeststring == null ?
searchService.GetAll() :
searchService.searchByRequestString(requeststring);
return View("Index", Property);
}
}
Of course, the service used in OnGet is being initialized in the Controller as well, but I removed it from the example to keep it rather simple.
So you can see that OnGet modifies Property and returns the Index page. At that page, there is an anchor that calls SortData which also modifies Property. But it is not the same Property as in OnGet, it is still at it's state of initialization. But I want the actual Property to be modified and then return the Index page with sorted data.
Yes, this is correct behavior. Each web request lives in it’s own context. you have two web requests, it instantiates a new controller for each request, so of course you do not have the same instance of the property.
Now that you know that this is the correct behavior, you will want ask a new question, which will be how do you share a property across multiple requests. That, however is the wrong question. The answer to that is “you don’t”. web requests should be stateless. instead, you should add your sort parameters to the query parameters.
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..
I'm working on a Web Application project using C# and MVC that will take in two URLs in a form and use them to create an instance of a class I have created called "ImageSwap." This model has some data (a username of the person performing the swap; two variables which hold the URLs of two images to be swapped; two variables which save the actual names of these files without all of the rest of the URL information; and two arrays which represent the file locations to check for these files). Right now, I have it so that the initial index view creates the instance of the class and passes it to the same view with the information put in through the form, submitted via POST, like so:
public ActionResult Index()
{
Ops.Operations.Models.ImageSwapModel newImageSwap = new Models.ImageSwapModel();
return View(newImageSwap);
}
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
//Perform data manipulation and set needed values
}
}
It then performs some functions on the data, such as parsing out the filename at the end of the URL, and a directory number (which is the first part of this filename, i.e. directory#_fileName.jpg). All of this works fine.
My problem is that I would like to pass this model to another view once it has data populated in all of its fields by this initial ActionResult so that I can have a verification view, which would allow the user to preview the two files side by side so that they can ensure they are swapping the appropriate images. They should then be able to hit another submit button which will initiate the actual moving/replacing of the images and be taken to a page confirming.
Is there a way to pass data from this controller to a different view? My confusion arises because I cannot create another version of an ActionResult of Index with the same input, but I do not want to have the actual swapping of the images occur without a preview and a prompt. Should I re-write my Index view so that it utilizes partial views in order to accomplish this? What is the easiest way to have data persist through multiple steps and views?
What is the easiest way to have data persist through multiple steps
and views?
Your question sounds like you're trying to achieve what you can easily do with sessions. The session object allows you to persist data between requests simply by adding it to the Session object on the HttpContext that exists within the base class that your controller extends, like so:
(Note the Serializable attribute. This allows your object to be serialized into the session object).
[Serializable]
public class ImageSwapModel {
// Your class's properties
}
Then in your controller you can do the following:
[HttpPost]
public ActionResult Index(ImageSwapModel imageSwap)
{
var oldFileFound = false;
var newFileFound = false;
if (ModelState.IsValid)
{
this.HttpContext.Session["ImageSwap"] = imageSwap;
}
}
When you want to retrieve the model you can grab it from the session like so:
var imageSwap = (ImageSwapModel)this.HttpContext.Session["ImageSwap"];
Taking it one step further:
Whilst the above will work fine, generally it's not a good practice to reference the HttpContext object directly in your code as it creates unnecessary coupling to the HttpContext object that can easily be avoided. Instead you should opt to inject an instance of the session object via Dependency Injection. Here is a similar answer that provides a basic idea as to how you can do this.
You can return different views with Models being passed to them in your one Index action like
if(some condition)
{
Return View("ViewVersion1", MyModelVersion1);
}
else
{
Return View("ViewVersion2", MyModelVersion2);
}
I am making a form with MVC and am using the [ControlType]For([expression]) helper methods (EG Html.TextBoxFor(...))
To bind the data to the controls I am using the following ViewModel:
public class UserViewModel
{
//The Model that will be used to bind data to the elements
public UserModel User { get; set; }
//Used to bind selectable options to DropDownLists
public SelectList DescentTypes { get; set; }
public SelectList GenderTypes { get; set; }
}
When using this the name of the controls get set to name="Property.SubProperty" (EG name="User.Id") but I would rather it show as name="Id" on my html form.
Is it possible to do this without having to write a lot of custom code so the framework can translate it back to the ViewModel (UserViewModel) or just the Model (User) itself?
I'd advise leaving the default naming in place unless you have a very good reason to alter it. IDs (which it appears your question is leaning towards) are more flexible.
Changing IDs
IDs aren't submitted with a form, so you can set them as desired without breaking model binding. By default, they are hierarchical, but you can override them inline:
#Html.TextBoxFor( o => o.UserName, new { id = "foo" } )
Of course, this is manual work.
If the big concern is external JS/CSS, I'd suggest using class names and data-* attributes in your (CSS/jQuery/whatever) selectors rather than IDs.
#Html.TextBoxFor( o => o.User.UserName, new { data_role="grand-total" } )
It's still manual, but it's descriptive and independent of an ID.
Sometimes I use a snippet of script in my views to initialize a larger JS class with data that is most easily available directly within the view. This lets the bulk of the script reside in an external file while allowing dynamic values to be used to initialize it. This is useful for more than just IDs.
Altering Generated Markup and Binding
For reference, let's say you wanted to change ID and name.
Write your own HtmlHelper extension methods to create the markup you want. You could probably wrap the existing methods that do not take an expression and pass explicit values to them to indicate the name that you want.
Write your own ModelBinder to map the raw form collection.
Determine a strategy for dealing with hierarchical objects (which is the main reason the naming convention exists in the first place).
Item #3 could be addressed by decorating properties to indicate how the naming should be performed and how model binding should map. This could become complicated quickly.
public class UserViewModel
{
// use this metadata to determine how to handle properties on this member
[Flatten]
public UserModel User { get; set; }
public SelectList DescentTypes { get; set; }
public SelectList GenderTypes { get; set; }
}
Alternatives
Flatten your view model by adding User's properties directly to it. It looks like your are composing your view model from domain model(s). This isn't usually a good idea. I'd suggest reading the pros/cons of binding directly to domain models.
Leave the naming alone. It really isn't hurting anything and it makes life easy. You can avoid ever directly working with names/IDs in your client code by using helper methods.
For example:
// JavaScript + Razor
var name = "#Html.NameFor( o => o.User.Id )";
alert(name);
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.