I am trying to create a cascading drop down list, and I am getting an error.
This contains what I am try to do with in my controller class:
public ActionResult Create()
{
ViewBag.Status = new SelectList(db.pjt_Statuses, "pjt_Status_ID", "StatusName");
ViewBag.CategoryID = new SelectList(db.pjt_Categories, "pjt_Category_ID", "CatName");
return View();
}
public ActionResult SubCategory(int id)
{
var SubCategory = from s in db.pjt_SubCategories
where s.CategoryID == id
select s;
return Json(SubCategory.ToList());
}
// POST: /Project/Create
[HttpPost]
public ActionResult Create(pjt_Projects pjt_projects)
{
if (ModelState.IsValid)
{
pjt_projects.CreationDate = DateTime.Now;
db.pjt_Projects.Add(pjt_projects);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Status = new SelectList(db.pjt_Statuses, "pjt_Status_ID", "StatusName", pjt_projects.Status);
ViewBag.CategoryID = new SelectList(db.pjt_Categories, "pjt_Category_ID", "CatName", pjt_projects.CategoryID);
return View(pjt_projects);
}
View
This shows what I am try to do on the front end. I am getting a error with the bold line in my view code below. The error I am getting is:
There is no ViewData item of type IEnumerable<SelectListItem> that has the key pjt_SubCat_ID.
Code:
<div class="span3 offset1">
#Html.LabelFor(model => model.CategoryID, "Category")
#Html.DropDownList("CategoryID", String.Empty)#*, null, new { #onchange = "FetchSubCategories();" })*#
<br />
#Html.ValidationMessageFor(model => model.CategoryID)
</div>
<div class="span3 offset1">
<label>Sub Category</label>
#Html.DropDownList("pjt_SubCat_ID", String.Empty)
<br />
#Html.ValidationMessageFor(model => model.SubCategoryID)
</div>
Can someone advise me what is going wrong here?
The problem is that the engine is trying to get the SelectList from the viewbag and it cannot find it. I am guessing that you want the Dropdown to display the items you have stored in the viewbag under "pjt_SubCat_ID" but you are not adding that item to the viewbag anywhere in the code that you have listed in your question.
If that is supposed to be the data returned from the SubCategory() method, you will not find that data in the viewbag and you should have a javascript that calls this method on the click event of the original SelectList (category id one) and binds the returned data to the subcategory one.
Related
today I have the problem, that after i inserted all data to a formular to create a new Product, the programm say that ModelState.IsValid==false.
When i look into the modelState during debugging there is a Error on field 0. The error: "The CuId field is required".
To prevent that i set CuId right in the Creat POST action like so in the ProductController.cs:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product)
{
int lastcu = db.Customers.Max(l => l.Id);
product.CuId = last;
if (ModelState.IsValid)
{
db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Create", "NewIssue");
}
return View(product);
}
But again it sets the same error.
My view looks like that. Actually the model.CuId should already set there:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Product</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CuId, "Customer")
#ViewBag.Cuname
#Html.HiddenFor(model => model.CuId, new { id = "lastcu" })
</div>
My GET Controller looks like this:
public ActionResult Create()
{
int lastcu = db.Cu.Max(l => l.Id);
//gives the id a Name
var lastcuname = db.Customers.Find(lastcu).Name;
//show to User by creating the product
ViewBag.Cuname = lastcuname;
ViewBag.CuId = lastcu;
return View();
}
When I look in debug mode into the values of the model product, all fields are filled (also CuId) except of the the foreign key what is bind to the product.CuId and the Id of the product what is set automatically from the database.
Hopefully you can help me. Thanks in advance.
As for the first part of your question, ModelState is populated by the DefaultModelBinder when the method is first called. If property CuId has the [Required] attribute and its value is not posted back, then an error is added to ModelState and therefore ModelState.IsValid is false. Just setting a property of your model does not remove ModelState values.
As for the second part of your question, your not passing you model to the view in the GET method so #Html.HiddenFor(m => m.CuId) generates a hidden input with no value (because the value of model.CuId is null or its default). All you currently doing is passing some values to the view using ViewBag (not good practice) which you never even use. Instead, pass the model to the view as follows.
public ActionResult Create()
{
int lastcu = db.Cu.Max(l => l.Id);
var lastcuname = db.Customers.Find(lastcu).Name;
// Initialize a new instance of the model and set properties
Product model = new Product()
{
CuId = lastcu,
Cuname = lastcuname // assume this is a property of your model?
};
return View(model); // return the model
}
Side note: #Html.LabelFor(model => model.CuId, "Customer") generates a html <label> which is an accessibility element. Clicking on it sets focus to its associated form control. But you don't have an associated form control (just a hidden input which cannot receive focus)
So, I am sending a dictionary object to my view from the controller.
// GET: QuestionResponses/Create
public ActionResult Create(int questionnaireUID)
{
var questions = from q in db.QUESTIONS
where q.QuestionnaireUID == questionnaireUID
select q;
ViewBag.NextQuestion = from q in db.QUESTIONS
where q.QuestionnaireUID == questionnaireUID
select new SelectListItem
{
Selected = (q.QuestionnaireUID == questionnaireUID),
Text = q.Question1,
Value = q.QuestionUID.ToString()
};
Dictionary<QUESTION, QUESTION_RESPONSES> dict = new Dictionary<QUESTION, QUESTION_RESPONSES>();
foreach (var question in questions)
{
dict.Add(question, new QUESTION_RESPONSES { QuestionUID = question.QuestionUID, Response = "", NextQuestion = "" });
}
return View(dict);
}
The reasoning behind this is that I need to view data from one model and need to add/edit data from another model. I tried using Tuples and was not able to get it to work (if you could tell me how to do this with Tuples, that would be helpful too).
This is what the view does with this Dictionary object.
<div class="form-group">
<h2> Reponses </h2>
<p> For each question, enter in the appropriate response(s). All questions must have at least one response. <p>
<div id="editorRows">
<div class="rows_no_scroll">
#foreach (var item in Model.ToArray())
{
<!-- The question that responses are being added to. -->
Html.RenderPartial("QuestionRow", item.Key);
<!-- All questions pertaining to this questionnaire. -->
// <p>Please select the question which should be asked as a response to this question.</p>
#Html.DropDownList("NextQuestion", null, htmlAttributes: new { #class = "form-control", id = "ddl_questions_" + count})
#Html.ValidationMessageFor(model => item.Value.NextQuestion, "", new { #class = "text-danger" })
<!-- The next question link and responses being inputted by user. -->
Html.RenderPartial("ResponseEditorRow", item.Value);
// <p> Question #count </p>
count += 1;
}
</div> <!--/rows_no_scroll-->
</div> <!-- /editorRows -->
</div> <!-- /form-group -->
For completeness, here are what the partial views are doing.
QuestionRow:
<div class="questionRow">
<!-- Hide attribute(s) not being viewed/edited. -->
#Html.HiddenFor(model => model.QuestionUID)
#Html.HiddenFor(model => model.QuestionnaireUID)
<!-- Show attribute(s) being viewed. -->
#Html.DisplayFor(model => model.Question1)
<div class="addQuestion">Add Response</div>
</div>
ResponseEditorRow:
<div class="editorRow">
#using (Html.BeginCollectionItem("questions"))
{
<!-- Hide attribute(s) not being viewed/edited. -->
#Html.HiddenFor(model => model.ResponseUID)
#Html.HiddenFor(model => model.QuestionUID)
<br>
<!-- Display attribute(s) being edited. -->
#Html.EditorFor(model => model.Response, new { htmlAttributes = new { #type = "text", #name = "question", #class = "question_input" } })
#Html.ValidationMessageFor(model => model.Response, "", new { #class = "text-danger" })
<input type="button" name="addRow[]" class="deleteRow" value="Delete">
}
</div>
The problem that I am having is that when I get back to my controller to POST the data inserted by the user, my Dictionary is empty. I'm not sure if I am inserting the information correctly. I am changing the dictionary object toArray(), not sure if this is affecting anything...
Here is the HTTP POST create method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ResponseUID, QuestionUID, Response, NextQuestion")] Dictionary<QUESTION, QUESTION_RESPONSES> question_question_response)
{
if (ModelState.IsValid)
{
foreach (var item in question_question_response.ToArray())
{
db.QUESTION_RESPONSES.Add(item.Value);
db.SaveChanges();
return RedirectToAction("Index"); // Update to take user to Actions/Create page.
}
}
ViewBag.NextQuestion = new SelectList(db.QUESTIONS, "QuestionUID", "Question1");
return View(question_question_response);
}
Open to any suggestions of different ways I could do this or on what may be wrong with what I am currently doing.
Create 2 ViewModels:
public class QuestionAireViewModel {
public int QuestionAireId {get;set;}
public List<QuestionViewModel> Quesitons {get;set;}
}
public class QuestionViewModel{
public int QuestionId {get;set;}
public string Question {get;set;}
public string QuestionResponse {get;set;}
}
In your view pass this QuestionAireViewModel
Generate QuestionAireViewModel in your controller like this:
public ActionResult GetQuestions(int id)
{
var questionAire = db.QuesitonAire.First(s => s.QuestionAireId == id)
var questions = new List<QuestionViewModel>();
foreach(var question in questionAire.Questions){
questions.Add(new QuestionViewModel(){
Quesiton = question.Question,
});
}
var model = new QuestionAireViewModel(){
QuestionAireId = questionAire.Id,
Quesitons = questions
};
return View(model);
}
Then on POST Form method will be:
[HttpPost]
public ActionResult SaveQuestions(QuestionAireViewModel model)
{
}
I am trying to use knockout with MVC strongly typed view. Since my model will have over 20 properties, I prefer to use strongly-typed view model to post back data by using ko.mapping.toJS and ko.Util.postJson. The Eligible field was passed back correctly, however the following code does not post back the selected option from drop down list, it just showed value as 0 when I looked that selectOptionModel on the controller. Can someone point out what I did wrong?
the view model from server side is as follows:
public class SelectOptionModel
{
public bool Eligible { get; set; }
public int selectedOption { get; set; }
public IEnumerable<SelectListItem> AvailableOptions
{
get
{
return Enum.GetValues(typeof(OptionEnum)).Cast<OptionEnum>()
.Select(x => new SelectListItem
{
Text = x.ToString(),
Value = x.ToString()
});
}
}
}
public enum OptionEnum
{
[Description("First")]
FirstOption = 1,
[Description("Second")]
SecondOption = 2,
[Description("Third")]
ThirdOption = 3
}
The razor view is like following:
#model TestKo.Models.SelectOptionModel
...
subViewModel = ko.mapping.fromJS(#Html.Raw(Json.Encode(Model)));
...
}
#using (Html.BeginForm()){
<button type="submit" class="button" id="SaveBtn">Save</button>
<div data-bind="with:vm">
<div>
#Html.LabelFor(model => model.Eligible)
#Html.CheckBoxFor(model => model.Eligible, new { data_bind = "checked: selectOptionVM.Eligible" })
</div>
<div>
#Html.LabelFor(model => model.selectedOption)
#Html.DropDownListFor(model => model.selectedOption, Model.AvailableOptions,
new
{ data_bind = "options: selectOptionVM.AvailableOptions, optionsText: 'Text', optionsValue: 'Value', value: selectOptionVM.selectedOption"
})
</div>
</div>
}
The javascript for the knockout view model is:
sectionVM = function (data) {
var self = this;
var selectOptionVM = data;
return {
selectOptionVM: selectOptionVM
}
}
$(document).ready(function () {
var viewModel = {
vm: new sectionVM(subViewModel)
};
ko.applyBindings(viewModel);
$("#SaveBtn").click(function () {
var optionModel = ko.toJS(viewModel.vm.selectOptionVM);
ko.utils.postJson($("form")[0], optionModel)
});
});
The controller part:
[HttpPost]
public ActionResult Create(SelectOptionModel selectOptionModel)
{
try
{
// TODO: Add insert logic here
var modelSaved = selectOptionModel;
return RedirectToAction("Index");
}
catch
{
return View();
}
}
I'm venturing a bit of a guess here, but this could be the problem: the id-bit of your selected option will always be a string (because it will go in the <option value="" attribute). Your endpoint expects an int. As far as I can see, you don't convert the selectedOption before sending it to the server. try parseInt(selectedOption, 10) before sending it to the server. Also, use the network tool in your browser to debug the info that is being sent to the controller. That might help you to zone in on the problem.
Actually it works. Somehow it was not working previously, but after I cleared cache, cookies etc, it just worked. Thanks everyone!
I have been using ASP.NET MVC 4 for a while but I have not yet come across a situation where i need to insert a value into the database from a scaffolded Create view which is based on a value passed from another view. I have tried to infer from the Edit view to try and modify my code to work but I have run into a snag. I got an error similar to this post. Here is my code from the view passing the value
#Html.ActionLink("Allocate", "Create", "Allocation", new { id=item.requestID}, null)
this is from the list of requests already in the database from the Index view
here is my code on the controller that is trying to force the Create method to use the ID passed from the link above
public ActionResult Create(int id = 0)
{
Request request = db.Requests.Find(id);
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", request.requestID);
return View(request);
}
then here is the posting code to the db
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Allocation allocation)
{
if (ModelState.IsValid)
{
db.Allocations.Add(allocation);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", allocation.requestID);
return View(allocation);
}
Basically what I am trying to do is allocate funds to a request made where by the allocation is entered into the db based on the request id. I am trying to prevent the user from choosing the request id from a drop down list. When I run this i get an error
The model item passed into the dictionary is of type 'System.Data.Entity.DynamicProxies.Request_A52006F7570E0448EE323CB36858E4D13EED0BAD958340B32FF166708545DA8C', but this dictionary requires a model item of type 'BudgetAllocation.Models.Allocation'.
If theres anyone out there who can help me out with this please do as soon as you can. I appreciate all the effort offred!!!!!
//EDIT
Here is my Create view
#model BudgetAllocation.Models.Allocation
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Allocation</legend>
#Html.HiddenFor(model => model.requestID)
<div class="editor-label">
#Html.LabelFor(model => model.allocAmount, "Amount")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.allocAmount)
#Html.ValidationMessageFor(model => model.allocAmount)
</div>
<p>
<input type="submit" value="Allocate" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The problem is your view id strongly typed with BudgetAllocation.Models.Allocation while in get action of Create you are passing object of type BudgetAllocation.Models.Request thats why you are getting the exception.
You have to pass object of type BudgetAllocation.Models.Allocation in Create get action as well because you view is strongly typed to it.
public ActionResult Create(int id = 0)
{
Request request = db.Requests.Find(id);
return View(request) // <-------------- here is the mistake
}
it should return allocation object, something like this, it is just an example may be you need to do some other thing instead of selecting:
public ActionResult Create(int id = 0)
{
Allocation allocation = db.Allocations.Find(x=>x.requestID == id);
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", request.requestID);
return View(allocation);
}
UPDATE:
you simply need to do like this not return allocaiton object return simply View:
public ActionResult Create(int id = 0)
{
ViewBag.requestID = new SelectList(db.Requests, "requestID", "requestID", request.requestID);
return View();
}
I am very new to MVC
I need some help to over come the issue of passing parameter to a controller on form submit
what i have got is the following controller and the view
public ActionResult Index(string method ="None")
{
if (Request.HttpMethod == "POST")
{
switch (method)
{
case "Add10":
_bag.GetBag = Get100Products().Take(10).ToList<Product>();
break;
case "Clear":
_bag = null;
_bag.GetBag = null;
_bag = new Models.Bag();
break;
case "Add":
if ((Request.Form["Id"] != null) && (Request.Form["Id"] != ""))
{
if (_bag.GetBag.Count < 100)
{
var p = GetProduct(Request.Form["Id"]);
int qnt = Convert.ToInt16(Request.Form["qnt"]);
if (p.ItemNumber != null)
{
p.Quantity = qnt;
p.Index++;
_bag.Item = p;
}
}
}
break;
}
}
return View(_bag.GetBag);
}
and the view part of the view
<div style="vertical-align:middle">
#using (Html.BeginForm("", "Home", new { method = "Add10" }, FormMethod.Post))
{
<!-- form goes here -->
<input type="submit" value="Add 10 Items to bag" />
}
#using (Html.BeginForm("GetDiscount", "Home", FormMethod.Post))
{
<div>
<!-- form goes here -->
<input type="submit" value="Get Discount" />
With MAX time in seconds <input type="text" name="time" maxlength="2" value="2" />
</div>
}
#using (Html.BeginForm("", "Home", new { method = "Clear" }, FormMethod.Post))
{
<input type="submit" value="Empty the bag" />
}
</div>
so i am expecting when the use clicked button Add 10 Items to bag to pass the method value "Add10" to the index controller and when clicked Empty the bag to pass "Clear" the method value in index controller
but it always shows as "None"
what have I done wrong?
</form>
First of all, you have to add [HttpPost] to your controller in order to accept POST requests:
[HttpPost]
public ActionResult Index(string method ="None")
{
You should differentiate GET and POST actions.
You can do like this:
// [HttpGet] by default
public ActionResult Index(Bag bag = null)
{
// "bag" is by default null, it only has a value when called from IndexPOST action.
return View(bag);
}
[HttpPost]
public ActionResult Index(string method)
{
// Your logic as specified in your question
return Index(_bag.GetBag);
}
EDIT:
Your code is wrong, for example you will get a NullReferenceException because your try to call a property on a null object (_bag):
_bag = null;
_bag.GetBag = null; // NullReferenceException: _bag is null!
Also your code would be cleaner and more easier to maintain if we split this Action into several actions and follow the technology philosophy.
Do you consider refactoring this piece of code into smaller and more understandable chunks?