null on HTTPPost on ASP.NET - c#

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.

Related

MVC Pass display data between views

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).

Model dropping List<>s after DropDownList

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);

Keep a value in a ViewModel even if the value is not modified

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);
...
}

Is my view model too "complex" to allow binding on post?

Using my model displaying a page works fine but the post does not return the bound model.
My classes:
public class ContactManager
{
public Contact Contact { get; set; }
public SelectList SalutationList { get; set; }
}
public class Contact
{
public int Id{get;set;}
public string FirstName{get; set;}
public SalutationType SalutationType{get; set;}
}
public class SalutationType
{
public int Id { get; set; }
public string Name { get; set; }
}
My View:
#model ViewModels.ContactManager
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Contact.Id)
#Html.DropDownListFor(model => model.Contact.SalutationType.Id, Model.SalutationList)
#Html.EditorFor(model => model.Contact.FirstName)
<input type="submit" value="Save" />
}
The issue seems to be in the DropDownListFor. The dropdown list displays correctly with the proper value but when I post this page the complete model is blank. If I simplify the DropDownListFor like this the values are posted as expected.
#html.DroDownListFor(model=>model.MyPlaceHolderProp, Model.SalutationList)
Is my model too complex? Am I not doing something correctly?
The models are based off of several tables using EF that I have created in a separate project. I am trying to avoid creating more classes/models then I have to.
You should post your controller action as well, as your model coming back as blank really has nothing to do with this. Changing the DropDownListFor definition one way or another should not effect the posting of any other values.
That said, you will run into another issue eventually here, so you need to regroup, anyways. You can't just post back the id value of a related item. Entity Framework will either complain that there's already an object with that id, or worse, if the object attaches, it will update the row with that id with the new posted value for Name, which in this case, is nothing, so it'll just clear it out.
When you create a relationship with a single item (a foreign key basically), if you don't specify a property to hold that foreign key value, Entity Framework creates one for you behind the scenes to track the relationship. In your case here, that means your Contacts table has a column named SalutationType_Id. However, there's no way from your class to directly access this value. This is why I recommend that you always provide an explicit property to handle the relationship:
[ForeignKey("SalutationType")]
public int SalutationTypeId { get; set; }
public SalutationType SalutationType { get; set; }
If you do that, then you can directly stuff the posted id there and Entity Framework will create the relationship.
#Html.DropDownListFor(m => m.Contact.SalutationTypeId, Model.SalutationList);
If you insist on keeping the key implicit, then you must create the relationship yourself, by creating a field on your view model to hold the posted value, then using that value to look up the SalutationType instance from the database, and then finally adding that to the Contact instance.
Add to your view model
public int SalutationTypeId { get; set; }
In your view
#Html.DropDownListFor(m => m.SalutationTypeId, Model.SalutationList)
In your POST action
var salutationType = db.SalutationTypes.Find(model.SalutationTypeId);
contact.SalutationType = salutationType;
You could do it this way. This may be the more "MVC best practice" way to handle it. Everything stays neatly in their models, and no manual IDs are required. The views are intended to be representations of the underlying models they are built on. If you are creating a view that has a form, then create a model that represents the form and use it in the view.
Revise your models like:
public class PostModel
{
public int ContactID { get; set; }
public int SalutationID { get; set; }
public string FirstName { get; set; }
}
public class PostView
{
public ContactManager contact { get; set; }
public PostModel post { get; set; }
}
Then create the PostView in the controller:
public ActionResult Index()
{
//create the PostView model
var pv = new PostView();
pv.ContactManager = contactManager;
pv.post = new PostView()
{
ContactID = contactManager.Contact.Id,
SalutationID = contactManager.SalutationType.Id,
FirstName = contactManager.Contact.FirstName
};
return View(pv);
}
Then the view could be like:
#model ViewModels.PostView
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.post.ContactID)
#Html.DropDownListFor(model => model.post.SalutationID, model.contact.SalutationList)
#Html.EditorFor(model => model.post.FirstName)
<input type="submit" value="Save" />
}
Then the post action in the controller:
[HttpPost]
public ActionResult Index(PostView pv)
{
//post code
//the posted data will be in pv.post
}
Have you considered using a custom model binder? Custom model binding isn't all that complicated for models that are still relatively simple, and you can handle the serialization/deserialization however you need to.
http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
http://ivonna.biz/blog/2012/2/2/custom-aspnet-model-binders-series,-part-3-subclassing-your-models.aspx
http://forums.asp.net/t/1944696.aspx?what+is+custom+model+binding+in+mvc
I am not sure this will help you... I wsa having a similar issue but I was using ajax to post back... anyway, I had forgotten to mark my binding class with the [Serializable] attribute.
so you might try
[Serializable]
public class Contract {
...
}
Again, I am using Json to post back to my controller so may not be related or help you. But, I guess could be worth a try.

ViewModel loses data on post

I'm trying to create a complex ViewModel that has both an Index Page and Create Page of Company Notes all within the Details Page of a Company, and would like some guidance as to whether I'm doing this properly.
My problem at the moment is that when I create a new Company Note, it doesn't have any information in the object beyond the EditorFor fields I include in my cshtml - it loses all the data in the ViewModel.
I have a Company model and CompanyController, and in my Details action, I populate all the notes that are relevant to the company, and a form to allow users to add a new note.
My Company and CompanyNotes model are very simple:
public class Company
{
public int CompanyID { get; set; }
// bunch of fields related to the company
public virtual ICollection<CompanyNote> CompanyNotes { get; set; }
}
public class CompanyNote
{
public int CompanyNoteID { get; set; }
public DateTime Date { get; set; }
public string Note { get; set; }
public Company Company { get; set; }
}
I have a ViewModel that looks like this:
public class CompanyViewModel
{
public Company Company { get; set; }
// List of all notes associated with this company
public IEnumerable<CompanyNote> CompanyNotes { get; set; }
// A CompanyNote object to allow me to create a new note:
public CompanyNote CompanyNote { get; set; }
}
This is my Details action, which populates the company record, gets a list of related notes, and displays a create form with a new, empty object:
public ActionResult Details(int id = 0)
{
var viewModel = new CompanyViewModel();
viewModel.Company = db.Companies.Find(id);
if (viewModel.Company == null)
{
return HttpNotFound();
}
viewModel.CompanyNotes = (from a in db.CompanyNotes
where a.Company.CompanyID.Equals(id)
select a).OrderBy(x => x.Date);
viewModel.CompanyNote = new CompanyNote
{
Date = System.DateTime.Now,
Company = viewModel.Company
};
return View(viewModel);
}
This is my CreateNote action in my CompanyController. (Should I split this out into a separate partial view? What would be the benefit?)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateNote(CompanyViewModel companyViewModel)
{
CompanyNote companyNote = companyViewModel.CompanyNote;
if (ModelState.IsValid)
{
db.CompanyNotes.Add(companyNote);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(companyViewModel);
}
Finally, here's a simplified version of detail.cshtml:
#model Project.ViewModels.CompanyViewModel
// My company detail display is here, removed for sake of berevity
#using (Html.BeginForm("CreateNote", "Company"))
{
#Html.EditorFor(model => model.CompanyNote.Date)
#Html.TextAreaFor(model => model.CompanyNote.Note})
<input type="submit" value="Create" />
}
When I post, my CreateNote action has a companyViewModel that is basically empty, with the exception of companyViewModel.CompanyNote.Date and companyViewModel.CompanyNote.Note, which are the fields in my form - all the other data in the ViewModel is null, so I'm not sure how to even include a reference back to the parent company.
Am I even on the right path here?
Thanks,
Robbie
When I post, my CreateNote action has a companyViewModel that is
basically empty, with the exception of
companyViewModel.CompanyNote.Date and
companyViewModel.CompanyNote.Note, which are the fields in my form -
all the other data in the ViewModel is null, so I'm not sure how to
even include a reference back to the parent company.
That's perfectly normal behavior. Only information that is included in your form as input fields is sent to the server when you submit the form and this is the only information you could ever hope the model binder be able to retrieve.
If you need the CompanyNotes collection in your HttpPost action simply query your backend, the same way you did in your GET action. You could do this by passing the company ID as a hidden field:
#Html.HiddenFor(model => model.Company.CompanyID)
So the idea is to only include as input fields in your form information that the user is supposed to somehow modify. For all the other information, well, you've already have it in your backend so all you have to do is hit it to get it.
Contrary to classic WebForms, there's no longer any notion of ViewState in ASP.NET MVC. It is much closer to the stateless nature of the HTTP protocol.

Categories