I'm extremely frustrated trying to switch to MVC after a couple years of webforms development.
Here's my extremely simple problem that I can't manage to solve:
I have a list of States in a table called StateProvince.
I have a DropDownList.
I want the DropDownList to display all of the States.
Keep it simple, I know nothing about MVC.
Here's what I have. All this gives me is a DropDownList filled with "System.Web.Mvc.SelectListItem".
Action:
public ActionResult Create()
{
var dbTimecard = new TimecardDbDataContext();
IEnumerable<SelectListItem> stateProvinces = dbTimecard.StateProvinces.Select(p => new SelectListItem
{
Value = p.StateProvinceId.ToString(),
Text = p.Name
});
SelectList theList = new SelectList(stateProvinces);
ViewData["StateProvince"] = theList;
return View();
}
View:
<div class="editor-label">
<%: Html.LabelFor(model => model.StateProvinceId) %>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(model => model.StateProvinceId, (SelectList)ViewData["StateProvince"])%>
<%: Html.ValidationMessageFor(model => model.StateProvinceId) %>
</div>
Here is what I was looking for:
Action:
public ActionResult Create()
{
var dbTimecard = new TimecardDbDataContext();
IEnumerable<SelectListItem> stateProvinces = dbTimecard.StateProvinces.Select(p => new SelectListItem
{
Value = p.StateProvinceId.ToString(),
Text = p.Name
});
ViewData["StateProvince"] = stateProvinces;
return View();
}
View:
<div class="editor-field">
<%: Html.DropDownListFor(model => model.StateProvinceId, (IEnumerable<SelectListItem>)ViewData["StateProvince"])%>
<%: Html.ValidationMessageFor(model => model.StateProvinceId) %>
</div>
Try replacing this
SelectList theList = new SelectList(stateProvinces);
with this...
SelectList theList = new SelectList(stateProvinces, "Value", "Text");
Common browsers don't support PUT verb within HTML forms. You might need to handle this using ajax:
$(function() {
// when some button is clicked:
$('#someButton').click(function() {
$.ajax({
url: '/controller/action',
data: { selectedState: $('#StateProvinceId').val() },
type: 'PUT',
success: function(data) {
alert('state successfully submitted');
}
});
return false;
});
});
When the data is posted, the items listed in the DropDown are not posted back to the model so I am assuming you are not fetching them again and re-adding them to your model before returning your model back to the view.
You need to make sure on your post that you are filling Model.StateProvinces and then passing it to your View. Only the values are persisted unlike WebForms which would maintain the DropDownList items in the ViewState and rebuild them for you.
So assuming your controller looks something like:
// POST: /YourController/YourAction/{ID}
[HttpPost]
public ActionResult YourAction(YourModel model, int id)
{
// model.StateProvinceId has the selected value
// you need to fill StateProvinces since it is empty as the list is not posted
// back so it is not autofilled into your model.
model.StateProvinces = LoadYourStateProvincesList();
// This would blow up if StateProvinces is null because your View is assuming that
// it has a valid list of StateProvinces in it to build the DropDown
return View(model);
}
I actually asked a question a while back that might be of some use to help explain how to handle DropDown lists:
Best way of implementing DropDownList in ASP.NET MVC 2?
EDIT: Based on your comment it you are not even getting the first list up... well it looks like you might not have your model setup correctly. Do you even have a model? Your create is just returning View() with no model data in it. You need to create a Model and return it.
Eg.
public class YourModel
{
public int StateProvinceId { get; set; }
/// ... whatever else you need
}
// Then in your view your controller you need to set it and create it
View(new YourModel() { StateProvinceId = 123 };);
You need the model so that when the form IS posted back you can retrieve the value because MVC will stuff the value into your model like th example I posted above regarding the POST part.
EDIT: Ok now that question is clearer and a lot simpler than the original question was making the problem out to be. The problem is your SelectList needs tell it which fields to put where:
SelectList theList = new SelectList(stateProvinces, "Value", "Text");
In your original question you had:
<%: Html.DropDownListFor(model => model.StateProvinceId, new SelectList(Model.StateProvinces, "StateProvinceId", "Name") )%>
Which was totally correct but it was how you were building up Model.StateProvinces that was wrong so it threw me off thinking it had to be something else.
Related
I have two models called channels and programs.. in showing the dropdown of channels and all I want is that when I select any channel, the view will show the programs of the specific channel. Problem is when I select any channel, it takes the value of channel and return it to the controller and apply filtering logic and pass the data to the new View..Although the view is executing all values properly on the browser it is not visible.. any help would be grateful.
View Code:
#model SitefinityWebApp.Mvc.Models.Channel
#Html.DropDownListFor(m => m.Title, Model.Items, "Select Channel-", new { #id = "ddl" })
<div id="myddl">
</div>
<script>
$('#ddl').change(function () {
var selectedValue = $('#ddl').val();
$.post('#Url.Action("GetProgramByChannel", "Channels")', { selection : selectedValue }, function (data) {
});
});
</script>
GetProgramByChannel Method Taking the selected value
public ActionResult GetProgramByChannel(string selection)
{
var model = new ProgramsModel();
return View("ProgramsByChannel", model.GetChildItemsOfChannel(selection));
}
ProgramsByChannelView
#model SitefinityWebApp.Mvc.Models.Channel
<div id="ddl">
#foreach (var item in Model.Programs)
{
#Html.DisplayFor(m => item.Title) <br />
#Html.DisplayFor(m => item.ShortDescription) <br />
#Html.DisplayFor(m => item.LongDescription) <br />
}
</div>
Because,with your current code, when user make a selection on the dropdown, you are making an ajax call and the result (the new view result with channels will come as the response for this ajax call) is not being used anywhere.
Instead of doing the ajax call, you can make a new GET request to the action method which will return the view.
$('#ddl').change(function () {
var v = $(this).val();
var url ='#Url.Action("GetProgramByChannel", "Channels")?selection='+v;
window.location.href=url;
});
If you do not want to issue a new GET request,but want to show the result in the same view, then all you have to do is update the DOM with the results coming back from the ajax call.
$('#ddl').change(function () {
var v = $(this).val();
var url ='#Url.Action("GetProgramByChannel", "Channels")';
$.post(url, { selection : v} ,function(result){
$("#myddl").html(result); //update the myddl div
});
});
Now you have to make sure that your action method return a view without layout as you are looking for the partial page result. You can use the PartialView method to do so.
public ActionResult GetProgramByChannel(string selection)
{
var model = new ProgramsModel();
return PartialView("ProgramsByChannel", model.GetChildItemsOfChannel(selection));
}
Also, I see you are overwriting the Id value of dropdown to ddl. Why would you do that ? What is wrong with Title being the Id value of the SELECT element ? If you do not override, the helper will generate the SELECT element with Title as the value of Id and name attributes
I am working on my third year project and I'm struggling with this one section so badly, I have looked around and found some answers but I really done know how to implement in my code because it always just doesn't work. So I don't know what I'm doing wrong.
What i would like is for the partial view to change when the drop down selected item has changed.
This is what was generated in the view for this section.
<div class="form-group">
#Html.LabelFor(model => model.TypeID, "TypeID", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("TypeID", String.Empty)
#Html.ValidationMessageFor(model => model.TypeID)
</div>
</div>
Most solutions I have seen use a #html.dropdownlistfor().
Any help would be appreciated even if you could just point me to the right place.
The drop down list is filled from the database relationships.
I have got this to work if i use labels in an "a" tag with an href but they are hard coded onto the page. I want to use the drop down list so that if i update the database it will have the updated list instead of me going to change it in the code, its also more user friendly in my eyes.
Thanx in advance
You could retrieve the data from the server and construct/change the DOM via JQuery, or you could use a partial view that is appropriate for each question type, attaching an event handler to the change event of the drop-down via JQuery.
One approach, loading partial views:
yourNamespace.yourScript.js file (include the file in your main view via the <script> tag with the src attribute):
(function ($) {
if (!window.yourNamespace) {
window.yourNamespace = {};
}
if (!window.yourNamespace.yourPageScript) {
window.yourNamespace.yourPageScript = function () {
var populateView = function (dropDown) {
if (dropDown && dropDown.length) {
var value = dropdown.val();
$.ajax({
method: "GET",
cache: false,
url: "some.url/PopulateType",
dataType: "HTML"
data: { selectedValue: value }
})
.done(function (response) { // jQuery 1.8 deprecates success() callback
var div = $('#partialViewDiv');
div.html('');
div.html(response);
});
}
};
return {
populateView: populateView
};
};
}
}(jQuery));
Your main view could have something like this:
<script type="text/javascript">
// put the script section somewhere appropriate in the page
$(document).ready(function () {
var dropDown = $('#TypeId'); // assuming the ID of this element is 'TypeId'
dropDown.change(function () {
yourNamespace.yourPageScript.populateView(dropDown);
});
});
</script>
<div id="partialViewDiv">
#Html.RenderPartial("path to initial partial view", model);
</div>
partial view example (adjust to be proper for any particular dropdown selection):
#model namespaceName.QuestionTypeModel
<div class="form-group>
#* put controls appropriate to the particular partial views here, such as radio buttons for the multiple choice question type, etc. *#
<div>
<div class="form-group">
#Html.LabelFor(model => model.TypeID, "TypeID", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("TypeID", Model.QuestionTypeValues)
#Html.ValidationMessageFor(model => model.TypeID)
</div>
</div>
Part of the controller:
[HttpGet]
public ActionResult Index()
{
var model = new MainViewModel();
// populate the model with data here for the initial display, including the initial drop-down values,
// presumably the same way you do now
// into model.QuestionTypeValues
return View(model); // the main view
}
[HttpGet]
public ActionResult PopulateType(int selectedValue) // could use an enum for the selectable values
{
var model = new QuestionViewModel();
string partialViewName = null;
// populate with data appropriate to the partial views
switch (selectedValue)
{
case 0:
partialViewName = "partial view name for item 0";
// populate "model" with the appropriate data
break;
case 1:
partialViewName = "partial view name for item 1";
// populate "model" with the appropriate data
break;
// and so on...
default:
throw new ArgumentException("unknown selected value", "selectedValue");
break;
}
return PartialView(partialViewName, model);
}
The approach to use jQuery to build the DOM elements instead of using partial views is left as an exercise.
So I am working on a MVC which is basically three steps.
Create a view for each step i.e.
StepOne
StepTwo
StepThree
On step one and two I ask the users to enter some details.
All the values for the multiple step I store in one Model.
And getting from StepOne to StepTwo is fine. Certain values in my model are being set and maintained.
But on StepTwo when I do my second httppost and pass the model, it seems to just create a new instance of the model and values from stepone are not maintained.
<% using (Html.BeginForm("StepTwo", "Home", FormMethod.Post, new { id = "restrictionForm" })) { %>
<%: Html.AntiForgeryToken() %>
<div id="wrapping" class="clearfix">
<h3>Postcode Restriction Type : </h3>
<%= Html.DropDownListFor(x => x.SelectedRestriction, Model.RestrictionTypes,"Select Restriction...", new { #class = "selmenu required" }) %>
<h3>Restriction Description : </h3>
<%= Html.TextBoxFor(m => m.RestrictionDescription, new { #class = "txtblock required" }) %>
</div>
<section id="buttons">
<input type="submit" value="Submit" id="submitBtn" />
</section>
And in my controller
On Page Load my Model is still intact and still maintains values from previous step.
[Authorize]
public ActionResult StepTwo(PostcodesModel model)
{
var summaryMessage = "";
model.SummaryMessage = summaryMessage;
model.RestrictionTypes = _Provider.GetRestrictionTypes();
return View(model);
}
But at the Httppost, the model has lost values and seems to have created new instance of model.
[Authorize]
[HttpPost]
[ActionName("StepTwo")]
[ValidateAntiForgeryToken]
public ActionResult StepTwoPost(PostcodesModel model)
{
return View(model);
}
Any idea how I can maintain model between Http Posts ?
It seems from your question that you believe models persist across requests. This is not true.
You either pass information to the view via your model from the controller, or submit values from your view to your controller and MVC handles this by binding html form inputs to your View Model.
If you want to persist your View Model across each step you need to take the values accepted and copy them into a new model (or directly inject it) when calling your new view.
Something like this (I just typed this up off my head so its not clean but should give you an idea):
[HttpGet]
public ActionResult StepOne()
{
var model = new MyNewModel();
return View(model);
}
/* NOTE THE MODEL PASSED BACK HERE IS NOT THE EXACT SAME OBJECT
AS THE ONE CREATED IN THE GET ACTION ABOVE, MODEL BINDING HAS OCCURRED
TO READ YOUR FORM INPUTS AND MATCH THEM TO A NEW MODEL WHICH IS EXPECTED */
[HttpPost]
public ActionResult StepOne(MyNewModel model)
{
if (ModelState.IsValid)
{
// Do something here
// pass model to new view
TempData["model"] = model;
return RedirectToAction("StepTwo");
}
return View(model);
}
[HttpGet]
public ActionResult StepTwo()
{
MyNewModel model;
if (TempData["model"] != null)
{
model = (MyNewModel) TempData["model"];
// make some changes if necessary
model.MyProperty = 2;
return View(model);
}
return RedirectToAction("StepOne");
}
I think you can also keep your model in Session ( per application ) or in a ViewState ( per page ).
Every time you make a post you upgrade the session. It's also optimal because on the client side you receive only a session identifier.
Some differences between Session and Viewstate:
Session is per application, while ViewState is per page
Session sends to the client side only a session identifier, while ViewState sends an ecrypted text
I have created a dropdownlist on the view and showing a list.
#Html.DropDownListFor(m => m.SelectedId, new SelectList(Model.List, "ID", "Name"))
I want to refresh the page when the user selects the value from the drop down.
I don't know how to map the selection event from dropdown to a controller function without clicking any button.
On the view load there is a controller function which is populating the view with the list.
public ActionResult Populate()
{
List<string> list = get it from sql server
ViewModel viewModel = new ViewModel();
viewModel.list = list;
return view();
}
But how do you call a controller function which will take the selected value as an Id and retrieves the data and refreshes the page with the result.
You can't do it without javascript help. Just bind on select event and send the form or make an ajax request.
using jQuery:
$('#yourDropDownId').change(function(){
$('#yourFormId').submit();
});
or if you need ajax call insted of submit use $.post or $.get.
Add this to your layout in the head:
<script type="text/javascript">
$(document).ready(function () {
$('select:[autopostback=true],input[type=checkbox]:[autopostback=true],input[type=radio]:[autopostback=true]').live('change',function () {
$(this).closest('form').submit();
});
});
</script>
in your view:
#using (Html.BeginForm())
{
#Html.DropDownListFor(m => m.SelectedId, new SelectList(Model.List, "ID", "Name"), new { autopostback = "true" })
}
The form that your dropdownlist is in will get submitted when you change selection of your dropdownlist. If the result of the action of that form is the same page, it will be reloaded with whatever stuff being updated
$(document).ready(function() {
$("#ddl").change(function() {
var strSelected = "";
$("#ddl option:selected").each(function() {
strSelected += $(this)[0].value;
});
var url = "/Home/MyAction/" + strSelected;
$.post(url, function(data) {
// do something if necessary
});
});
});
or
<%=Html.DropDownListFor(m => m.SelectedId, new SelectList(Model.List, "ID", "Name"), new { onchange="this.form.submit();" })%>
It's simple. In your javascript you have:
$(document).ready(function () {
$('#SelectedId').change(function () {
var id = $(this).val();
$.getJSON("/YourController/YourAction", { id: id},
function (data) {
$("#SomeDivSelector").html(data);
});
});
});
Your controller should look like:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult YourAction(int id)
{
//do something
return Json(ControlToString("~/Views/YourController/YourView.cshtml", yourModelWithData), JsonRequestBehavior.AllowGet);
}
And ControlToString is defined:
private string ControlToString(string controlPath, object model)
{
//CshtmlView control = new CshtmlView(controlPath);
RazorView control = new RazorView(this.ControllerContext, controlPath, null, false, null);
this.ViewData.Model = model;
using (System.Web.UI.HtmlTextWriter writer = new System.Web.UI.HtmlTextWriter(new System.IO.StringWriter()))
{
control.Render(new ViewContext(this.ControllerContext, control, this.ViewData, this.TempData, writer), writer);
string value = ((System.IO.StringWriter)writer.InnerWriter).ToString();
return value;
}
}
Regards.
What you want to apply, against the concept of the technology you're using.
MVC based on ASP.NET technology, but another way executing. MVC not use better of life cycle of ASP.NET, so, does not "Postback". MVC - in the root based on architectural pattern, that allows to separate the different layers of any system, therefore approach to the development on current technology is completely different. learn more about MVC: http://www.asp.net/mvc
if you want still implement your problem you can use ASP.NET concept and use AutoPostback property of DropDownList control.
I have this:
<div id="miniShoppingCartContainer">
#Html.Action("MiniShoppingCart", "ShoppingCart")
</div>
where MiniShoppingCart action returns MiniShoppingCart.cshtml partial view with all the content.
In this partial view I added an ajax call for increasing the quantity of product cart:
#using (Ajax.BeginForm("IncreaseProductQuantity", "ShoppingCart", new { shoppingCartItemId = item.Id }, new AjaxOptions { UpdateTargetId = "miniShoppingCartContainer", InsertionMode = InsertionMode.Replace }))
{
<li>
<input type="submit" class="btn-up" />
</li>
}
which calls a method:
public ActionResult IncreaseProductQuantity(int shoppingCartItemId)
{
//get shopping cart item
var cart = _workContext.CurrentCustomer.ShoppingCartItems
.Where(x => x.ShoppingCartType == ShoppingCartType.ShoppingCart).ToList();
var sci = cart.Where(x => x.Id == shoppingCartItemId).FirstOrDefault();
if (sci == null)
{
return RedirectToRoute("ShoppingCart");
}
//update the cart item
_shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer,
sci.Id, sci.Quantity + 1, true);
return MiniShoppingCart();
}
Please note that at the end of the method I call the MiniShoppingCart ActionResult which prepares the cart and return the PartialView (as you see at the beginning of the post).
Well, the update of a product is happening fine but the content is not refreshed (or replaced)...
Can you please indicate where I am wrong?
UPDATE:
Doing an inspection with Chrome Dev. Tools I see an error when doing post:
POST http://localhost/ShoppingCart/IncreaseProductQuantity?shoppingCartItemId=11 500 (Internal Server Error)
f.support.ajax.f.ajaxTransport.sendjquery-1.7.1.min.js:4
f.extend.ajaxjquery-1.7.1.min.js:4
ejquery.unobtrusive-ajax.min.js:5
(anonymous function)jquery.unobtrusive-ajax.min.js:5
f.event.dispatchjquery-1.7.1.min.js:3
f.event.add.h.handle.ijquery-1.7.1.min.js:3
2
It's strange to guess what is the problem from this log...Basically, If I make debug I can see that it does all the operations until return PartialView(model); of MiniShoppingCart() method...
Issue found:
>The partial view 'IncreaseProductQuantity' was not found or no view engine supports the searched locations.
So basically, doing return MiniShoppingCart(); from IncreaseProductQuantity method doesn't automatically return the MiniShoppingCart partial view but will still try to return IncreaseProductQuantity partial view which of course does not exists.
Consequently, I have done it like:
var model = PrepareMiniShoppingCartModel();
return PartialView("MiniShoppingCart", model);