MVC Form DropDownListFor - asp.net-mvc

In my view I am building a form where the user select a country from a list of Countries.I get this error about Object reference not set to an instance of an object. Is there anything I am missing?
#Html.DropDownListFor(
x => x.selectedCountryId,
new SelectList(Model.ListOfCountries, "Value", "Text"),
"-- please select a Country--",
new { id = "ddlCountry", #class = "form-control" }
)
#Html.ValidationMessageFor(x => x.selectedCountryId)
Model
[Required]
public int? selectedCountryId{ get; set; }
Error
System.NullReferenceException: 'Object reference not set to an instance of an object.'
System.Web.Mvc.WebViewPage<TModel>.Model.get returned null.
Action Method
public ActionResult Create(RequestFormViewModel model)
{
if (!ModelState.IsValid)
{
}
return View(TemplateName, "");
public ActionResult Index()
{
RequestFormViewModel result = new RequestFormViewModel ();
try
{
var FormInfo = GetFormInfo();
if (FormInfo != null)
{
result = FormInfo;
}
}
catch (Exception e)
{
}
return View(TemplateName, result);
}

Your action methods should be like below
[HttpGet]
public IActionResult Create()
{
//Way 1
RequestFormViewModel model = new RequestFormViewModel();
model.ListOfCountries = //Select from DB or Whatever
return View(model);
//Way 2
ViewBag.ListOfCountries = //Select from DB or Whatever
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(RequestFormViewModel model)
{
if (ModelState.IsValid)
{
}
return View(model);
}
and for view if you will use way 1 so you need to add ListOfCountries as [NotMapped] to your model or create Dto for this view
for way2 you need to bind dropdown from ViewBag instead of using ListOfCountries as a property

Related

DropDownListFor value cannot be null

I am new to MVC. I am using a DropDownListFor to populate a number of Customer fields when a Company is selected. I am using the following code for the DropDownListFor:
#Html.DropDownListFor(model => model.CustomerId, new SelectList(ViewBag.Customers, "CustomerId", "Company"), "---Select one---", new { htmlAttributes = new { #class = "company" } });
#Html.HiddenFor(model => model.Company)
This code retrieves the Customer data:
[HttpPost]
public ActionResult GetCustomer(int custId)
{
var data = db.Customers.Find(custId);
return Json(data);
}
The relevant fields in the ViewModel (from the Customer table) are:
public int CustomerId { get; set; }
public string Company { get; set; }
The code in the Create method that creates the ViewBag:
public ActionResult Create()
{
QuoteViewModel qvm = new QuoteViewModel();
qvm.QuoteDetail = new List<QuoteDetail>();
var customers = db.Customers.ToList();
ViewBag.Customers = customers;
return View(qvm);
}
And here is the code for the POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(QuoteViewModel qvm)
{
if (ModelState.IsValid)
{
Quote quote1 = new Quote();
quote1.CustomerId = qvm.CustomerId;
...
db.Quotes.Add(quote1);
customer.CustomerId = qvm.CustomerId;
...
db.Entry(customer).State = EntityState.Modified;
bool saveFailed;
do
{
saveFailed = false;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
var objContext = ((IObjectContextAdapter)db).ObjectContext;
// Get failed entry
var entry = ex.Entries.Single();
// Now call refresh on ObjectContext
objContext.Refresh(RefreshMode.ClientWins, entry.Entity);
}
} while (saveFailed);
return RedirectToAction("Index");
}
return View(qvm);
}
The population of the fields works fine, but when I attempt to Create the view model I get an error "Value cannot be null" on the DropDownListFor. I have researched others having this issue but cannot find an answer that applies to this case. Any help would be much appreciated.
The error is because in the POST method you return the view (ModelState is invalid), but have not set the value of ViewBag.Customers as you did in the GET method, so it null and you cannot create a SelectList from a null collection.
Your need assign ViewBag.Customers as you did in the GET method before your return View(qvm); statement.
As a side note, since you using a view model, that view model should contain a property (say) public IEnumerable<SelectListItem> CustomerList { get; set; } and you set that in the controller methods, and in the view
#Html.DropDownListFor(model => model.CustomerId, Model.CustomerList, "---Select one---", new { #class = "company" });
Are you making a full page POST request when a Company is selected?
If you are, you need to fill ViewBag.Customers because of ViewBag's lifetime.
http://www.dotnettricks.com/learn/mvc/viewdata-vs-viewbag-vs-tempdata-vs-session

Can we pass model as a parameter in RedirectToAction?

I want to know, there is any technique so we can pass Model as a parameter in RedirectToAction
For Example:
public class Student{
public int Id{get;set;}
public string Name{get;set;}
}
Controller
public class StudentController : Controller
{
public ActionResult FillStudent()
{
return View();
}
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return RedirectToAction("GetStudent","Student",new{student=student1});
}
public ActionResult GetStudent(Student student)
{
return View();
}
}
My Question - Can I pass student model in RedirectToAction?
Using TempData
Represents a set of data that persists only from one request to the
next
[HttpPost]
public ActionResult FillStudent(Student student1)
{
TempData["student"]= new Student();
return RedirectToAction("GetStudent","Student");
}
[HttpGet]
public ActionResult GetStudent(Student passedStd)
{
Student std=(Student)TempData["student"];
return View();
}
Alternative way
Pass the data using Query string
return RedirectToAction("GetStudent","Student", new {Name="John", Class="clsz"});
This will generate a GET Request like Student/GetStudent?Name=John & Class=clsz
Ensure the method you want to redirect to is decorated with [HttpGet] as
the above RedirectToAction will issue GET Request with http status
code 302 Found (common way of performing url redirect)
Just call the action no need for redirect to action or the new keyword for model.
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return GetStudent(student1); //this will also work
}
public ActionResult GetStudent(Student student)
{
return View(student);
}
Yes you can pass the model that you have shown using
return RedirectToAction("GetStudent", "Student", student1 );
assuming student1 is an instance of Student
which will generate the following url (assuming your using the default routes and the value of student1 are ID=4 and Name="Amit")
.../Student/GetStudent/4?Name=Amit
Internally the RedirectToAction() method builds a RouteValueDictionary by using the .ToString() value of each property in the model. However, binding will only work if all the properties in the model are simple properties and it fails if any properties are complex objects or collections because the method does not use recursion. If for example, Student contained a property List<string> Subjects, then that property would result in a query string value of
....&Subjects=System.Collections.Generic.List'1[System.String]
and binding would fail and that property would be null
[HttpPost]
public async Task<ActionResult> Capture(string imageData)
{
if (imageData.Length > 0)
{
var imageBytes = Convert.FromBase64String(imageData);
using (var stream = new MemoryStream(imageBytes))
{
var result = (JsonResult)await IdentifyFace(stream);
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(serializer.Serialize(result.Data));
if (faceRecon.Success) return RedirectToAction("Index", "Auth", new { param = serializer.Serialize(result.Data) });
}
}
return Json(new { success = false, responseText = "Der opstod en fejl - Intet billede, manglede data." }, JsonRequestBehavior.AllowGet);
}
// GET: Auth
[HttpGet]
public ActionResult Index(string param)
{
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(param);
return View(faceRecon);
}
[NonAction]
private ActionResult CRUD(someModel entity)
{
try
{
//you business logic here
return View(entity);
}
catch (Exception exp)
{
ModelState.AddModelError("", exp.InnerException.Message);
Response.StatusCode = 350;
return someerrohandilingactionresult(entity, actionType);
}
//Retrun appropriate message or redirect to proper action
return RedirectToAction("Index");
}
i did find something like this, helps get rid of hardcoded tempdata tags
public class AccountController : Controller
{
[HttpGet]
public ActionResult Index(IndexPresentationModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Save(SaveUpdateModel model)
{
// save the information
var presentationModel = new IndexPresentationModel();
presentationModel.Message = model.Message;
return this.RedirectToAction(c => c.Index(presentationModel));
}
}

How can use MVC DropDownlist

I have a problem with DropDownlist in MVC
I use ModelView in my application and this is my code
namespace MedicallexiconProject.ViewModel
{
public class WordViewModel
{
private readonly ICategoryService _categoryService;
public WordViewModel(ICategoryService categoryService)
{
_categoryService = categoryService;
var selectList = _categoryService.GetAllCategorysSelectList().
Select(x => new SelectListItem
{
Text = x.Name,
Value = x.ID.ToString()
}).ToList();
Categories = selectList;
}
public WordViewModel()
{
}
public string Name { get; set; }
private IList<SelectListItem> _categories;
public IList<SelectListItem> Categories
{
get
{
if (_categories == null)
{
_categories = new List<SelectListItem>();
}
return (_categories);
}
set { _categories = value; }
}
}
}
and this is my controller
[HttpGet]
public ActionResult Create()
{
var wordViewModel = new WordViewModel(_categoryService);
ViewBag.CategoryID = wordViewModel.Categories;
return View();
}
[HttpPost]
public ActionResult Create(WordViewModel wordViewModel)
{
Mapper.CreateMap<WordViewModel, Word>();
var word = new Word();
Mapper.Map(wordViewModel, word);
if (ModelState.IsValid)
{
_wordService.AddNewWord(word);
_uow.SaveChanges();
return RedirectToAction("Index");
}
return View(wordViewModel);
}
Now how can I insert dropdownlist in my View?
As AlfalfaStrange mentioned, you should not add logic in your ViewModel. That makes it ugly ! Keep your ViewModel simple POCO.
Add one more property in your ViewModel called "SelectedCategoryID" like this
public class WordViewModel
{
public int SelectedCategoryID { set;get;}
public IList<SelectListItem> Categories { set;get;}
public string Name { set;get;}
}
Initialize your Items (Categories) of your ViewModel in your GET method. Here i am calling a method called GetCategories which returns a list of categories.I can simply call the method wherever i want.
public ActionResult Create()
{
var model=new WordViewModel();
model.Categories=YourService.GetCategories();
return View(model);
}
In your strongly typed Create view , use this
#model WordViewModel
using(#Html.BeginForm())
{
#Html.DropDownFor(x=>x.SelectedCategoryID,
new SelectList(Model.Categories,"Value","Text"),"Select Category")
<input type="submit" value="Save" />
}
In your HttpPost action method , you can check for wordViewModel.SelectedCategoryID for the selected value.
[HttpPost]
public ActionResult Create(WordViewModel wordViewModel)
{
if(ModelState.IsValid)
{
//Checck for wordViewModel.SelectedCategoryID here now
}
//some validation failed. Let's reload the category data again.
wordViewModel.Categories=YourService.GetCategories();
return View(wordViewModel);
}
It's absolutely fine to include code that loads a dropdown list in your view model. A select list and a drop down are both "view" items.... they are not related to business logic and your controller and model need not know anything about SelectLists or SelectListItems or DropDownList, etc.

Display the same page after saving

I'd like show a form with some field (one in the example), submit it, save and display the same page with a reset of all fields. The probelm when I submit, I go the "Save" action but when I display the view the form is still filled in.
The model :
public class TestingModel
{
public string FirstName { get; set; }
}
The controller :
public class ChildController : Controller
{
public ActionResult Index()
{
TestingModel model = new TestingModel();
return View(model);
}
public ActionResult Save(TestingModel model)
{
Console.WriteLine(model.FirstName); //OK
//Save data to DB here ...
TestingModel testingModel = new TestingModel() { FirstName = string.Empty };
return View("Index", testingModel);
}
}
The view :
#using (Html.BeginForm("Save", "Child",FormMethod.Post))
{
#Html.TextBoxFor( m => m.FirstName)
<input type="submit" id="btSave" />
}
When Id debug to the view, in "Immediat window" Model.FirstName = "" but when the page is show I still have the value posted. I tried a ReditrectionToAction("Index") at the end of the Save method but same result.
Do you have an idea ?
Thanks,
If you want to do this you need to clear everything that's in the ModelState. Otherwise HTML helpers will completely ignore your model and use data from ModelState when binding their values.
Like this:
[HttpPost]
public ActionResult Save(TestingModel model)
{
//Save data to DB here ...
ModelState.Clear();
TestingModel testingModel = new TestingModel() { FirstName = string.Empty };
return View("Index", testingModel);
}
or simply redirect to the Index GET action in case of success:
[HttpPost]
public ActionResult Save(TestingModel model)
{
//Save data to DB here ...
return RedirectToAction("Index");
}
Try to return Index view without any model
return View("Index");
You should be posting your form back to the same ActionResult
public ActionResult Index()
{
TestingModel model = new TestingModel();
return View(model);
}
[HttpPost]
public ActionResult Index(TestingModel model)
{
Console.WriteLine(model.FirstName); //OK
//Save data to DB here ...
return RedirectToAction("Index");
}
You would be able to use the parameterless overload for BeginForm too
#using(Html.BeginForm())
{
//form
}

How to add validation errors in the validation collection asp.net mvc?

Inside my controller's action I have the following code:
public ActionResult GridAction(string id)
{
if (String.IsNullOrEmpty(id))
{
// add errors to the errors collection and then return the view saying that you cannot select the dropdownlist value with the "Please Select" option
}
return View();
}
UPDATE:
if (String.IsNullOrEmpty(id))
{
// add error
ModelState.AddModelError("GridActionDropDownList", "Please select an option");
return RedirectToAction("Orders");
}
UPDATE 2:
Here is my updated code:
#Html.DropDownListFor(x => x.SelectedGridAction, Model.GridActions,"Please Select")
#Html.ValidationMessageFor(x => x.SelectedGridAction)
The Model looks like the following:
public class MyInvoicesViewModel
{
private List<SelectListItem> _gridActions;
public int CurrentGridAction { get; set; }
[Required(ErrorMessage = "Please select an option")]
public string SelectedGridAction { get; set; }
public List<SelectListItem> GridActions
{
get
{
_gridActions = new List<SelectListItem>();
_gridActions.Add(new SelectListItem() { Text = "Export to Excel", Value = "1" });
return _gridActions;
}
}
}
And here is my controller action:
public ActionResult GridAction(string id)
{
if (String.IsNullOrEmpty(id))
{
// add error
ModelState.AddModelError("SelectedGridAction", "Please select an option");
return RedirectToAction("Orders");
}
return View();
}
Nothing happens! I am totally lost on this one!
UPDATE 3:
I am now using the following code but still the validation is not firing:
public ActionResult GridAction(string id)
{
var myViewModel= new MyViewModel();
myViewModel.SelectedGridAction = id; // id is passed as null
if (!ModelState.IsValid)
{
return View("Orders");
}
UPDATE 4:
$("#linkGridAction").click(function () {
alert('link grid action clicked');
$.get('GridAction/', { SelectedGridAction: $("#SelectedGridAction").val() }, function (result) {
alert('success');
});
});
And the Controller looks like the following:
// OrderViewModel has a property called SelectedGridAction.
public ActionResult GridAction(OrderViewModel orderViewModel)
{
return View();
}
UPDATE 5: Validation is not firing:
public ActionResult GridAction(OrderViewModel orderViewModel)
{
if (!ModelState.IsValid)
{
return View("Orders", orderViewModel);
}
return View();
}
Use ModelState.AddModelError()
ModelState.AddModelError("MyDropDownListKey", "Please Select");
and output to the view like this:
<%= Html.ValidationMessage("MyDropDownListKey") %>
You could use a view model:
public class MyViewModel
{
[Required]
public string Id { get; set; }
}
and then:
public ActionResult GridAction(MyViewModel model)
{
if (ModelState.IsValid)
{
// the model is valid, the user has selected an id => use it
return RedirectToAction("Success");
}
return View();
}
UPDATE:
After the hundreds of comments on my answer I feel in the necessity to provide a full working example:
As usual start with a view model:
public class MyViewModel
{
[Required]
public string SelectedItemId { get; set; }
public IEnumerable<SelectListItem> Items
{
get
{
// Dummy data
return new SelectList(Enumerable.Range(1, 10)
.Select(i => new SelectListItem
{
Value = i.ToString(),
Text = "item " + i
}),
"Value", "Text");
}
}
}
Then a controller:
public class HomeController: Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
// The user didn't select any value => redisplay the form
return View(model);
}
// TODO: do something with model.SelectedItemId
return RedirectToAction("Success");
}
}
and finally the view:
<% using (Html.BeginForm()) { %>
<%= Html.DropDownListFor(
x => x.SelectedItemId,
Model.Items,
"-- Select Item --"
) %>
<%= Html.ValidationMessageFor(x => x.SelectedItemId) %>
<input type="submit" value="OK" />
<% } %>
Regarding your update #3, I suspect thats because you are actually assigning the value, its just an empty string (Required is checking for null).
You want to do have this:
[Required(AllowEmptyStrings = false)]
Your best bet though would be to perform custom validation (you will likely want to verify the key is in the list, etc)
Edit: fixed typo in the code - forgot closing ")"

Resources