I am teaching myself asp .net mvc3. I have a "add property" form which allows user to upload property details to the website. I have been struggling with this error for a long time now.
For simplification, lets consider that I have two tables in my database.
CustomerTypes: The database has 1 Owner, 2 Broker, 3 Commercial etc
Property: This is the table that gets populated by the form.
I use CustomerTypes (and other such tables) to create radio buttons. The user fills the form and selects a choice for "customer type". However, I get an "object reference not set to an instance of an object" error on submit. This is is because "null" is
set for Model.CustomerTypes. However, Model.CustomerTypes is only used to create radio buttons. I am not sure what is wrong. The code is below:
View:
#model Website.ViewModels.AddPropertyViewModel
<fieldset>
<legend>Property</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Property.CustomerType)
#foreach (var item in Model.CustomerTypes)
{
#Html.RadioButtonFor(model => model.Property.CustomerType, Convert.ToInt32(item.Value)) #item.Text
}
</div>
...
AddPropertyViewModel:
namespace Website.ViewModels
{
public class AddPropertyViewModel
{
public Property Property { get; set; }
...
public IEnumerable<SelectListItem> CustomerTypes { get; set; }
...
}
Controller:
public ActionResult AddProperty()
{
AddPropertyViewModel viewModel = new AddPropertyViewModel
{
...
CustomerTypes = websiterepository.GetCustomerTypeSelectList(),
...
};
return View(viewModel);
GetCustomerTypeSelectList functions is:
public IEnumerable<SelectListItem> GetCustomerTypeSelectList()
{
var customerTypes = from p in db.CustomerType
orderby p.CustomerTypeDescription
select new SelectListItem
{
Text = p.CustomerTypeDescription,
Value = SqlFunctions.StringConvert((double)p.CustomerTypeId)
};
return customerTypes;
}
The value in POST is set for Property_CustomerType correctly based on the selection
--- Added further info ---
I start the form as:
#using (Html.BeginForm("AddProperty", "Property", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
...
}
The controller is:
[HttpPost]
public ActionResult AddProperty(AddPropertyViewModel viewModel)
{
if (ModelState.IsValid)
{
//
if (viewModel.File1.ContentLength > 0)
{
var fileName = Path.GetFileName(viewModel.File1.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data"), fileName);
viewModel.File1.SaveAs(path);
}
var property = viewModel.Property;
websiterepository.Add(property);
return RedirectToAction("Index", "Home");
}
return View(viewModel);
}
Here is a screenshot of error:
I have tried submitting the form commenting these radio buttons and it works.
The issue is that CustomerTypes isn't populated when your render the view after posting to the server.
If we look at the flow of actions being performed we see that
You populate the CustomerTypes collection before rendering the
inital page
You post your data back to the server but do not
preserve the CustomerTypes collection (Because there's no need to)
You render the view again but this time without populating
CustomerTypes.
Kaboom!
Populating the CustomerTypes property before you return the view for the second time should fix your problem:
[HttpPost]
public ActionResult AddProperty(AddPropertyViewModel viewModel)
{
[...]
viewModel.CustomerTypes = websiterepository.GetCustomerTypeSelectList();
return View(viewModel);
}
Related
I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.
I have a budgeting application, I have 3 models I am pulling into 1 view.
Budget - get the users budgeting information details (i.e, name of budget, date, etc.)
BillBudgetTotal - Allows the user to add a cumulative total for that budget (i.d., budgetid, total amount)
BudgetTotalBreakdown - Allows the user to break their budget down into futher details (i.e., break the total amount down by bill name (water, gas, electric, misc, etc.)
The UI will give the user the option to select a budget (via dropdown) they want to work in and then allow them to enter in their dollar amounts based on which bill they selected.
Problem: I am trying to figure out a way to allow the partial view (the area under the dropdown) to refresh based on the dropdown selection. I can't seem to get the partial to refresh with the updated model (it needs to be reset based on the value of the dropdownlist selection). I have exhausted multiple options on stack overflow.
Something like this:
Model:
public class MyBudgets : Financials
{
public Budgets Budget{ get; set; }
public BillBudgetTotal BudgetTotals { get; set; }
public BillBudgetTotalBreakdown BudgetTotalBreakdown { get; set; }
}
Html:
<div class="col-md-3"></div>
<div class="row col-md-6">
#Html.DropDownListFor(model => model.Budget.SelectedBills, Model.Budget.SelectedBills.Select(b => new SelectListItem() { Value = b.Bill_Id.ToString(), Text = b.Bill}), "Select A Bill...", new { #class = "form-control"})
</div>
<div class="col-md-3"></div>
<br /><br />
<hr />
<div id="billBudgetPartial">
#Html.Partial("Budgeting/_BillTotalAmount", Model);
</div>
Controller:
[HttpGet]
public ActionResult Budgets(int budgetId)
{
MyBudgets model = new MyBudgets
{
Budgets = _executionRepository.RetrieveBudgets(budgetId)
};
model.Budget.SelectedBills = _executionRepository.SetSelectedBudgets(budgetId);
return View(model);
}
[HttpPost]
public ActionResult Budgets()
{
return Json(new { success = "false" });
}
public ActionResult BillTotalAmount(int id)
{
var model = new MyBudgets
{
Budgets = _executionRepository.RetrieveBudgetsByBillBudget(id),
BillBudgetTotal = _executionRepository.RetrieveBillBudgetByBillId(id),
BillBudgetTotalBreakdown = _executionRepository.RetrieveBillBudgetTotalBreakdown (id)
};
return PartialView("Execution/_BillTotalAmount", model);
}
You can use ajax to do partial update to your page. when razor render your page, it will generate a SELECT element with the id "Budget_SelectedBills". So listen to the change event on this dropdown, get the selected value and send that to your server(an action method) and let it return the partial view for the markup you want below. You may use jQuery load method to update the DOM with the new markup coming from server.
#section Scripts
{
<script>
$(function(){
$("#Budget_SelectedBills").change(function(e){
var val=$(this).val();
$("#billBudgetPartial").load("/Budgeting/BillDetails/"+val);
});
});
</script>
}
Assuming you have BillDetails action method in BudgetingController which accpets the billId an return the partial view for the bottom portion of screen.
public ActionResult BillDetails(int id)
{
var model = ReplaceYourModelForBillTotalAmountViewHere();
return PartialView("Budgeting/_BillTotalAmount", model);
}
EDIT: As per the comment
How can I pass 2 parameters in this? like not just the id from the
drop but something else the list the #Model.BudgetId
If your javascript code is in the same razor view, you can simply use Model.BudgetId as the second querystring param value.
Assuming BudgetId is an int type
#secion Scripts
{
<script>
$(function(){
$("#Budget_SelectedBills").change(function(e){
var val=$(this).val();
$("#billBudgetPartial").load("/Budgeting/BillDetails/"+val
+"?budgetId="+#Model.BudgetId);
});
});
</script>
}
Now make sure that your action method has this second parameter
public ActionResult BillDetails(int id,int budgetId)
{
var model = ReplaceYourModelForBillTotalAmountViewHere();
return PartialView("Budgeting/_BillTotalAmount", model);
}
If your javascript code is in an external js file, you may keep Model.BudgetId to somewhere in the DOM and read that. Either a hidden field or keep it in html 5 data attributes of the select element.
I have a view that loads a record with a certain record number. Once the page is loaded, it gives the user an opportunity to login for additional information. Once the login logic is performed, I need to return to that same view with the same record number intact. I am passing the record number to the action using a hidden input in the form. What I can't seem to figure out is how to return to that same view and provide it with that record #. The code I am trying below is not working. I know this is MVC 101 stuff but a hint in the right direction would be appreciated, or feel free to scrap my method and suggest something better!
Form in view:
<form action="/MyView/Authenticate/#item.ID" method="post" enctype="multipart/form-data">
<input name="form_id" type="hidden" value="#item.ID">
.....
Form action:
[HttpPost]
public ActionResult Authenticate()
{
int myid = Convert.ToInt16(Request["form_id"]);
.....
return View("Index", new { id = myid } );
}
EDIT:
It turns out that the correct view is being returned, but it is expecting a model item type of "JobSummaryModel" per the Index action result below. So the question I actually need answered is, how do I pass both the record id and this view model to it?
public ActionResult Index(int id = 0)
{
List<JobSummaryModel> jdata;
ViewBag.IsResults = false;
if (id != 0)
{
ViewBag.IsResults = true;
}
jdata = db.Jobs.Where(c => c.ID == id).Select(c => new JobSummaryModel() { ID = c.ID, Name = c.Name, City = c.City, PostalCode = c.PostalCode, JobDescription = c.PositionDescription }).ToList();
return View(jdata);
}
EDIT:
Thanks Reddy, your suggestions worked! My only remaining issue is that when I return to my Index view from the Authenticate action, I do not seem to have my "jdata". Is my Index action result not being rerun when I return the Index view via my Authenticate action? I am coming from a web forms background where, in an instance like this, the Load/Init events would automatically run when a form is loaded. Do I need to bind my "jdata" in the Authenticate action and include it in the viewmodel?
EDIT: Resolved. Changed my "return View" to a "return RedirectToAction" to resolve my final issue. Thanks everyone!
Answer For your after Edit:
All you want to pass to view is a int Id and your List<JobSummaryModel> jdata right?
So create a ViewModel JObSummaryModelHelper
Public class JObSummaryModelHelper
{
public int Id {get;set;}
public List<JobSummaryModel> jdata {get;set;}
}
Now in your controller
public ActionResult Index(int id = 0)
{
JObSummaryModelHelper jobDetails = new JObSummaryModelHelper();
jobDetails.Id = id;
ViewBag.IsResults = false;
if (id != 0)
{
ViewBag.IsResults = true;
}
jobDetails .jdata = db.Jobs.Where(c => c.ID == id).Select(c => new JobSummaryModel() { ID = c.ID, Name = c.Name, City = c.City, PostalCode = c.PostalCode, JobDescription = c.PositionDescription }).ToList();
return View(jobDetails );
}
Now make sure your view is set to expect this new viewmodel
#model JObSummaryModelHelper
carry on with your manipulation......
You are better off creating a ViewModel for this like so:
Create a View Model class i.e.
public class AuthViewModel
{
public int MyId { get; set; }
}
In your View put the following directive at the top:
#model AuthViewModel
In your initial [HttpGet] method return the view model:
[HttpGet]
public ActionResult Authenticate()
{
var model = new AuthViewModel { MyId = 123 };
return View("Index", model );
}
It's best to use Html helpers in your view, so you can change it to this:
#using(Html.BeginForm()
{
#Html.HiddenFor(m => m.MyId)
...
}
The above uses naming conventions to post back to the action that you are on.
Then return it to your view like this:
[HttpPost]
public ActionResult Authenticate(AuthViewModel model)
{
int myid = model.MyId;
return View("Index", model );
}
Then you can output using this razor syntax #Model.MyId
It's really worth doing some tutorials to learn the conventions, a small amount of time invested in this will save you a lot of time in the future.
Instead of
return View("Index", new { id = myid } );
could you do
return Index(myid);
My Controller populates my Model with a list with strings that appear in a DropDownList in my View. When the view is posted back to my Controller, that list is suddenly null. Why is it null, and what happened to the list of strings I created?
The list was properly populated and shows up in the View. The remainder of the form elements DO properly post back. For example, selectedName has whatever name the user clicked on. The only thing that is not posting back is nameList.
Here is the relevant part of my model,
public class MyModel
{
[Display(Name = "Selected")]
public string selectedName{ get; set; }
[Display(Name = "Names")]
public List<string> nameList{ get; set; }
}
the relevant Get and Post parts of my Controller,
public class MyController: Controller
{
[HttpGet]
public ActionResult Index()
{
List<string> nameList= getNames();
MyModel model = new MyModel()
model.nameList= nameList;
// Now, model.nameList has a bunch of stuff in it
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
if(model.nameList== null)
{
cry();
postOnStackOverflow();
}
return View(model);
}
}
and the relevant part of my View (which is encapsulated inside of a form).
<p>
#Html.LabelFor(c => c.nameList):
#Html.DropDownListFor(c => c.selectedName, new SelectList(Model.nameList), new { onchange = "this.form.submit();" })
</p>
Only the value of the drop down list is posted when you post the form. I assume that your control in question is on a form.
I am not sure why you want to always return to the view you posted from, but you need to repopulate the list:
[HttpPost]
public ActionResult Index(MyModel model)
{
List<string> names = getNames();
model.nameList = names;
return View(model);
}
That is the expected behaviour considering what you have in your view. You need to reload the namelist collection property incase you are returning model to the same view again.
[HttpPost]
public ActionResult Index(MyModel model)
{
if(ModelState.IsValid)
{
// Save and redirect
}
//reload the collection again and return the model to the view
model.nameList=getNames();
return View(model);
}
The customer can view their cust details page where they can change their pre-recorded delivery run (if they wish too) I have a drop down list containing towns for delivery runs:
<div class="editor-label">#Html.DropDownListFor(model => model.DeliveryRunList, Model.DeliveryRunList)</div>
When the customer profile loads it displays the correct town in the drop down(reading from the DB, which they previously selected when registering).
However if they change the town and save it, the user is returned to the home page and the newly selected tow is saved to the DB. But, if the user returns to the customer profile page the drop down displays the previously selected town, as opposed to the new one just previously selected and saved to the DB. Is it being stored in the cache somewhere.
Why is it not updating to what is actually in the DB??
Codebehind:
CustomerPart custPart = _custService.Get(custId);
if (DeliveryRunList.HasValue)
{
custPart.DeliveryRun_Id = DeliveryRunList.Value;
}
_custService.Update(custPart);
Thanks
I suppose model is a CustomerPart instance, and you have defined it more or less in this way.
public class CustomerPart
{
public int DeliveryRun_Id {get; set;}
public SelectList(or some IEnumerable) DeliveryRun_Id
}
I feel your code isn't updating the DB since you use the wrong attributes. The first lambda expression should be model => model.TheAttributeYouWantToUpdate, in this case DeliveryRun_Id.
So it should be:
#Html.DropDownListFor(model => model.DeliveryRun_Id, Model.DeliveryRunList)
rather than
#Html.DropDownListFor(model => model.DeliveryRunList, Model.DeliveryRunList)
It's not even clear where is this code inside the controller:
CustomerPart custPart = _custService.Get(custId);
if (DeliveryRunList.HasValue)
{
custPart.DeliveryRun_Id = DeliveryRunList.Value;
}
_custService.Update(custPart);
A common way of doing it is to have two methods of the same name for editing, one for HttpGet and one for HttpPost, and use a #Html.BeginForm() in the razor view for updating, rather than updating the info in controller.
Example:
public ActionResult Edit(int id = 0) {
InvestmentFund Fund = InvestmentFundData.GetFund(id);
return Fund == null ? (ActionResult)HttpNotFound() : View(Fund);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(InvestmentFund Fund)
{
if (ModelState.IsValid)
{
InvestmentFundData.Update(Fund);
return RedirectToAction("List");
}
return View(Fund);
}
In View
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#* For the attributes of your model *#
#Html.LabelFor ...
#Html.EditorFor ...
#Html.ValidationMessageFor ...
<input type="Submit"m value="Save">
}