So I have this application in ASP MVC 3.
My database has two tables: Comenzi and DetaliiComenzi with one-to-many relationship (and Link-to-Sql) - in my application I want my users to buy some products by making a oder(stored in table comenzi) and for that order a list of products he wants to buy (will be stored in DetaliiComenzi with Order.id as foreign key).
Basically, after I create a new entry for Comenzi, I want to be able to make a list of products for that order (something like a shop chart but the user will choose his products in a view, adding how many products as he likes).
I have used Steve Sanderson’s method of editing (and creating) a variable length list.
-- Here is the model for which I create the list.
When I'm choosing a single product to oder I must first select the Group (Grupa) which he belongs to from a dropdownlist (using ListaGrupe) and then from a second dropdownlist (ListaProduse) a product from that particular group of products I selected in the first dropdownlist.
public class Comd
{
public string Grupa { get; set; }
public string Produs { get; set; }
public string Cantitate { get; set; }
public string Pret { get; set;}
public string TVA { get; set; }
public string Total { get; set; }
public List<SelectListItem> ListaGrupe
{
get;
set;
}
public List<SelectListItem> ListaProduse
{
get;
set;
}
}
--The Controller:
public ActionResult ComandaDetaliu(string id)
{
Comd model = new Comd();
IProduseRepository _prod = new ProduseRepository();
model.ListaGrupe = _listecomanda.GetGrupe();
string first = model.ListaGrupe[0].Value;
model.ListaProduse = _listecomanda.GetProduse(first);
string pret = _prod.GetByDenumire(model.ListaProduse[0].Text).pret.ToString();
model.Pret = pret;
double fr = 0.24;
model.TVA = fr.ToString();
var data = new[] { model };
return View(data);
}
-- The View
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<InMVC3.Models.Comd>>" %>
<%# Import Namespace="InMVC3.Helpers"%>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Comanda numarul: <%: RouteData.Values["id"].ToString()%></h2>
<% using(Html.BeginForm()) { %>
<div id="editorRows">
<% foreach (var item in Model)
Html.RenderPartial("ProduseEditor", item);
%>
</div>
<%= Html.ActionLink("Adauga alt produs", "Add", null, new { id = "addItem" }) %>
<input type="submit" value="Finished" />
<% } %>
-- The Partial View "Produse Editor"
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<InMVC3.Models.Comd>" %>
<%# Import Namespace="InMVC3.Helpers" %>
<div class="editorRow">
<script type="text/javascript">
$(document).ready(function () {
$("#Grupa").change(function () {
var url = '<%= Url.Content("~/") %>' + "Comenzi/ForProduse";
var ddlsource = "#Grupa";
var ddltarget = "#Produs";
$.getJSON(url, { id: $(ddlsource).val() }, function (data) {
$(ddltarget).empty();
$.each(data, function (index, optionData) {
$(ddltarget).append("<option value='" + optionData.Value + "'>" + optionData.Text + "</option>");
});
});
});
});
</script>
<% using(Html.BeginCollectionItem("comds")) { %>
Grupa: <%= Html.DropDownListFor(x => x.Grupa, Model.ListaGrupe) %>
Produsul: <%= Html.DropDownListFor(x => x.Produs, Model.ListaProduse) %>
Cantitate: <%=Html.TextBoxFor(x=>x.Cantitate) %>
Pret: <%=Html.DisplayFor(x => x.Pret, new { size = 4})%>
TVA: <%= Html.DisplayFor(x=>x.TVA) %>
Total: <%=Html.DisplayFor(x=>x.Total) %>
Sterge
<% } %>
-- And the JsonResult method
public JsonResult ForProduse(string id)
{
throw new NotSupportedException();
JsonResult result = new JsonResult();
var produsele = _listecomanda.GetProduse(id);
result.Data = produsele;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
All I need to know is how to make the call to the JsonResult action because this is what doesn't works so that when I change the selected value in the first dropdownlist to dynamically change the second too.
Of course, I also need to change the other properties too but that after I get how to make getJson to work.
If you need more details please tell me.
UPDATE 1:
--The Helper
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null) {
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
UPDATE
I now have another issue. When I Post that list to the actiont, I get the following error at the foreach statement inside the controller action: Object reference not set to an instance of an object.
-- The Controller Action
[HttpPost]
public ActionResult ComandaDetaliu(IEnumerable<Comd> comenzi)
{
if (ModelState.IsValid)
{
foreach (var item in comenzi)
{
detalii_comenzi det = new detalii_comenzi();
det.id_comanda = Convert.ToInt32(RouteData.Values["id"].ToString());
det.id_produs = Convert.ToInt32(item.Produs);
det.cantitate_comandata = Convert.ToDecimal(item.Cantitate);
det.cantitate_livrata = 0;
det.pret =Convert.ToDecimal(item.Pret);
det.tvap = Convert.ToDecimal(item.TVA);
}
return RedirectToAction("Index");
}
return View(comenzi);
}
Your problem is the duplicate IDs - Every row has a dropdown with ID "Grupa" so your jquery selector will match the dropdowns in every row.
You need to add a prefix to the controls - there are several ways to achieve that - a search for "mvc3 field prefix" brings up several other questions:
How to define form field prefix in ASP.NET MVC
ASP.MVC 3 Razor Add Model Prefix in the Html.PartialView extension
ASP.NET MVC partial views: input name prefixes
Most of those are focused on mapping when the form is posted, but the same issue applies with your javascript.
You could just update the ids in your script to something like "##(ViewBag.Prefix)Grupa", but a better approach would be to use classes instead of ids in your selector and make the script reusable - something like:
ddlSource = this;
ddlDest = this.Parent().find(".produs");
Related
MVC newbie question:
I'm picking up a URL of the form go/{mainnav}/{subnav}, which I've successfully routed to the GoController class, method:
public ActionResult Index(string mainnav, string subnav) {
return View();
}
So far, so good. But now I want the view to return different HTML, depending on the values of mainnav or subnav. Specifically, inside a javascript block, I want to include the line:
myobj.mainnav = [value of mainnav parameter];
and, only if subnav is not null or empty:
myobj.subnav = [value of subnav parameter];
How do you pass those parameters to an aspx page that doesn't have a codebehind?
You use a ViewModel class to transfer the data:
public class IndexViewModel
{
public string MainNav { get; set; }
public string SubNav { get; set; }
public IndexViewModel(string mainnav, string subnav)
{
this.MainNav = mainnav;
this.SubNav = subnav;
}
}
Your action method then comes out
public ActionResult Index(string mainnav, string subnav)
{
return View(new IndexViewModel(mainnav, subnav));
}
This means your view has to be strongly typed:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<YourNameSpace.IndexViewModel>" %>
In your view, you output the data like so:
myobj.mainnav = <%: Model.MainNav %>;
An alternative solution if you use MVC3, would be to use the dynamic ViewBag:
public ActionResult Index(string mainnav, string subnav)
{
ViewBag.MainNav = mainnav;
ViewBag.SubNav = subnav;
return View();
}
which would be accessed in the page as so:
myobj.mainnav = <%: ViewBag.MainNav %>;
However, I would recommend that you read up on unobtrusive javascript and see if you can improve your design to avoid this specific problem.
If you are using MVC3 I would suggest passing the values into the ViewBag.
ViewBag.MainNav = "xxxx";
ViewBag.SubNav = null;
then on your view page, where you define the JavaScript and add the value.
if you dont have MVC 3 if you use ViewData["MainNav"]), to store your value has the same effect.
Did you try accessing parameters from Request in your view?
i.e.
Request.Params["mainnav"]
or
Request.Params["subnav"]
Works in MVC
i'm using following approach:
ViewModel.class:
public class TitleBodyModel
{
public string Title { get; set; }
public string Body { get; set; }
public TitleBodyModel() { Title = Body = ""; }
public TitleBodyModel(string t, string b) { this.Title = t; this.Body = b; }
}
In the main View:
#Html.Partial("_TitleBody" , new XXX.Models.TitleBodyModel("title", "body" ))
Then in a partial view:
#model XXX.Models.TitleBodyModel
<div class="bl_item">
<div class="bl_title">#Model.Title</div>
<div class="bl_body">#Model.Body</div>
</div>
If i understand this can be a solution:
public ActionResult Index(string mainnav, string subnav)
{
return View(mainnav | subnav);
}
In the Html View you can use View
and after
<%=Model %>
I am new to ASP.NET and MVC 2. I'm trying to iterate over a list of lists of strings within my view page. However I cannot get any data to be displayed.
Controller Code.
public class RCADSChildController : Controller
{
//
// GET: /RCADSChild/
public ActionResult Index()
{
var model = new RCADSChildViewModel();
ViewData["DataList"] = model.Data;
ViewData["Count"] = model.Amount;
Debug.WriteLine(model.Amount);
return View(model);
}
}
Model
public class RCADSChildViewModel
{
public int Amount { get; set;}
public List<List<String>> Data { get; set; }
public RCADSChildViewModel()
{
Queries connect = new Queries();
Data = connect.getQuestionData();
Amount = Data.Count;
}
}
View
<div class="col-md-6">
<%
foreach (List<String> list in ViewData["DataList"] as List<List<String>>)
{
list[0].ToString();
list[1].ToString();
list[2].ToString();
list[3].ToString();
}
%>
The lists are definitely populated with data as I have checked using Debug.WriteLine().
Any help would be much appreciated.
You are simply converting them to string and not actually writing them out.
I believe you will want something like:
<%
foreach (List<String> list in ViewData["DataList"] as List<List<String>>)
{ %>
<%= list[0].ToString(); %>
<%= list[1].ToString(); %>
<%= list[2].ToString(); %>
<%= list[3].ToString(); %>
<%
}
%>
I have a project to make an online shop between users (post a product, buy, etc.) using a database. In this project I have a view called "ShoppingCart":
#model IEnumerable<MyFirstProject.Models.Product>
#{
ViewBag.Title = "ShoppingCart";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Your Shopping Cart</h2>
#if (Model == null)
{
<div style="float:left">Your cart is empty.</div>
<div>
Total payment: 0
</div>
}
else
{
decimal tPrice = 0;
<div>
<table style="float:left">
#foreach (var product in Model)
{
tPrice = tPrice + product.Price;
{ Html.RenderPartial("ProductLine", product);}
}
</table>
</div>
<div>
Total payment: #tPrice
</div>
}
It receives a list of products which the user decided to buy and displays them (not the important part). I need to add a button which will send the list to an action result in the "ShoppingController":
[HttpPost]
public ActionResult ShoppingCart(List<Product> bought)
{
if (ModelState.IsValid)
{
foreach (var listP in bought.ToList())
{
foreach (var databaseP in db.Products.ToList())
{
if (listP.ProductID == databaseP.ProductID)
{
databaseP.State = 1;
db.SaveChanges();
break;
}
}
}
return RedirectToAction("Index");
}
else
{
return View(bought);
}
}
"State" indicates if the product was bought or not (0=not bought, 1=bought), db is the database
If you wan't to post any data from a view to an action method, you should keep that data in form elements and keep that in a form. Since you want to post a collection of items, You may use Editor Templates.
Let's start by creating a view model.
public class ShoppingCartViewModel
{
public decimal TotalPrice { set; get; }
public List<Product> CartItems { set; get; }
}
public class Product
{
public int Id { set; get; }
public string Name { set; get; }
}
Now in your GET action, you will create an object of the ShoppingCartViewModel, load the CartItems property and send to the view.
public ActionResult Index()
{
var cart = new ShoppingCartViewModel
{
CartItems = new List<Product>
{
new Product { Id = 1, Name = "Iphone" },
new Product { Id = 3, Name = "MacBookPro" }
},
TotalPrice = 3234.95
};
return View(cart);
}
Now i will create an EditorTemplate. To do that, Go to your ~/Views/YourControllerName folder, and Create a directory called EditorTemplates and add a view with name Product.cshtml
The name of the file should match with the name of the type.
Open this new view and add the below code.
#model YourNamespace.Product
<div>
<h4>#Model.Name</h4>
#Html.HiddenFor(s=>s.Id)
</div>
You can keep the display however you want. But the important thing is, We need to keep a form field for the productId. We are keeping that in a hidden field here.
Now let's go back to our main view. We need to make this view strongly typed to our ShoppingCartViewModel. We will use the EditorFor html helper method in this view to call our editor template
#model ReplaceYourNamespaceHere.ShoppingCartViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.CartItems)
<p>Total : #Model.TotalPrice</p>
<input type="submit" />
}
And in your HttpPost action method, We will have a paramer of type ShoppingCartViewModel. When the form is submitted, MVC Model binder will map the posted form values to an object of ShoppingCartViewModel.
[HttpPost]
public ActionResult Index(ShoppingCartViewModel model)
{
foreach (var item in model.CartItems)
{
var productId = item.Id;
// to do : Use productId and do something
}
return RedirectToAction("OrderSucessful");
}
You can iterate through the CartItems collection and get the Id of the Products and do whatever you want.
If you wan't to allow the user to edit the items (using a check box) in this page, Take a look at this answer. It is basically same, but you add a boolean property to Product class and use that for rendering a checkbox.
Therefore I faced an issue with MVC ActionLink and bootstrap dropdown. I succeeded with simple menu extension where I passed such a parameters like strings and one bool. But now I am trying to make my own extension which could generate Bootstrap Dropdown and add selected css class to parent of the dropdown - "ONEofTHEdropdownITEMSselected" - when one of those items in dropdown is selected (when selecting dropdown item it routes to different controller there fore can be few or more controllers):
Dropdown <b class="caret"></b>
and
<li class="dropdown">
Dropdown <b class="caret"></b>
<ul class="dropdown-menu">
<li>Action1</li>
<li>Action2</li>
</ul>
</li>
Below is my UI/MenuExtensions.cs what I am trying to achieve - to pass two parameters which could generate the bootstrap dropdown and I can manually insert new menu items in that dropdown.
public static class MenuExtensions
{
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string text,
string action,
string controller,
string cssClass = "item",
bool isController = false
)
{
var li = new TagBuilder("li");
var routeData = htmlHelper.ViewContext.RouteData;
var currentAction = routeData.GetRequiredString("action");
var currentController = routeData.GetRequiredString("controller");
if ((string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) || isController) &&
string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
li.AddCssClass("am-selected");
li.InnerHtml = htmlHelper.ActionLink(text, action, controller, new { Area = "" }, new { #class = cssClass }).ToHtmlString();
return MvcHtmlString.Create(li.ToString());
}
public static MvcHtmlString SelectMenu(
this HtmlHelper htmlHelper,
string cssClass,
SelectMenuItem[] menuItems
)
{
TagBuilder list = new TagBuilder("li")
{
InnerHtml = ""
};
string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
foreach (SelectMenuItem menuItem in menuItems)
{
TagBuilder li = new TagBuilder("li")
{
InnerHtml = htmlHelper.ActionLink(menuItem.Text, menuItem.Action, menuItem.Controller, null, new { }).ToHtmlString()
};
ul.InnerHtml += li.ToString();
}
return MvcHtmlString.Create(list.ToString());
}
}
Here is the external class
public class SelectMenuItem
{
public string Text { get; set; }
public string Action { get; set; }
public string Controller { get; set; }
public bool IsVisible { get; set; }
public SelectMenuItem()
{
IsVisible = true;
}
}
After that my html looks like this.
#Html.SelectMenu("dropdown", new []{
new SelectMenuItem{ Text = "ViewOne", Controller = "Controller1", Action = "index", IsVisible = SystemUser.Current().IsAdmin},
new SelectMenuItem{ Text = "ViewTwo", Controller = "Controller2", Action = "index"}
});
The problem is SelectMenu renders only this:
<li></li>
No need to reinvent the wheel. With TwitterBootstrapMVC desired output is achieved with the following syntax:
#using (var dd = Html.Bootstrap().Begin(new DropDown("Dropdown").SetLinksActiveByControllerAndAction()))
{
#dd.ActionLink("Action1", "index", "controller1")
#dd.ActionLink("Action2", "index", "controller2")
}
Notice the extension method SetLinksActiveByControllerAndAction(). That's what makes links active based on current controller/action.
Disclaimer: I'm the author of TwitterBootstrapMVC.
You need to purchase a license if working with Bootstrap 3. For Bootstrap 2 it's free.
Controller
public ActionResult Create()
{
return View(new PageViewModel());
}
[HttpPost]
public ActionResult Create(Page page)
{
try
{
repPage.Add(page);
repPage.Save();
return RedirectToAction("Edit");
}
catch
{
return View(new PageViewModel());
}
}
PageViewModel
public class PageViewModel
{
public Page Page { get; set; }
public List<Template> Templates { get; set; }
private TemplateRepository repTemplates = new TemplateRepository();
public PageViewModel()
{
Page = new Page();
Templates = repTemplates.GetAllTemplates().ToList();
}
}
Parts of my View
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Website.Models.PageViewModel>" %>
<%= Html.TextBoxFor(model => model.Page.Name, new { #style = "width:300px;" })%>
<%= Html.DropDownListFor(model => model.Page.Template, new SelectList(Model.Templates, "ID", "Name"), new { #style = "width:306px;" })%>
Template:
ID
Name
Page:
ID
Name
TemplateID
My dropdownlist is populated correctly in the view, no problems there. My problem is that I dont get the selected value from the dropdownlist.
In my controller I i put a breakpoint in Edit and see that the Name textbox is populated with the value I type into it. But the selected from the dropdownlist is set to null.
alt text http://www.mgmweb.no/images/debug.jpg
Am I missing something, I thought it should set the correct Template value into the Page object. What am I doing wrong?
Try something like this maybe?
The key in the collection is the name of the dropdownlist control...
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
try
{
string selectedvalue = collection["Template"];
return RedirectToAction("Edit");
}
catch
{
return View(new PageViewModel());
}
}
The only thing that you get back from the web page is the id of the selected template. The default model binder doesn't know how to take this id and retrieve the correct template object from your repository. To do this you would need to implement a custom model binder that is able to retrieve the values from the database, construct a Template object, and assign it to the Page. Or... you could do this manually in the action given that, according to your comments elsewhere, you know how to get the template's id from the posted values.
Ok, I did like this:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
Page page = new Page();
page.Name = collection["Page.Name"];
page.TemplateID = int.Parse(collection["Page.Template"]);
page.Created = DateTime.Now;
repPage.Add(page);
repPage.Save();
return RedirectToAction("Edit");
}
catch
{
return View(new PageViewModel());
}
}
And it works good, I just wanted to know if there is a better way to to this without manually getting the values from the collection.
But I guess it is not possible without making your own model binder as tvanfosson said.
Thank you everyone.
for model binding, use in the ActionResult:
partial ActionResult(FormCollection form)
{
Page page = new Page();
UpdateModel(page);
return view(page);
}
your class:
public class Page
{
public string Name {get; set;}
public int Template {get; set;}
public DateTime Created {get; set;}
public Page()
{
this.Created = DateTime.Now;
}
}
attribute names in your class should be equal to the name of the fields of view