I am new to Asp.net MVC and am having problems with the following code.
#model SportsStore.Domain.Entities.ShippingDetails
#{
ViewBag.Title = "SportsStore: Checkout";
}
<h2>Check out now</h2>
Please enter your details and we'll send your goods right away!
#using (Html.BeginForm("Checkout", "Cart"))
{
#Html.ValidationSummary()
<h3>Ship to</h3>
<div>Name: #Html.EditorFor(x => x.Name)</div>
<h3>Address</h3>
<div>Line 1: #Html.EditorFor(x => x.Line1)</div>
<div>Line 2: #Html.EditorFor(x => x.Line2)</div>
<div>Line 3: #Html.EditorFor(x => x.Line3)</div>
<div>City: #Html.EditorFor(x => x.City)</div>
<div>State: #Html.EditorFor(x => x.State)</div>
<div>Zip: #Html.EditorFor(x => x.Zip)</div>
<div>Country: #Html.EditorFor(x => x.Country)</div>
<h3>Options</h3>
<label>
#Html.EditorFor(x => x.GiftWrap)
Gift wrap these items
</label>
<p align="center">
<input class="actionButtons" type="submit" value="Complete order"/>
</p>
}
I expect the input of type submit to call the Post version Checkout action from my controller, shown below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;
namespace SportsStore.WebUI.Controllers
{
public class CartController : Controller
{
private IProductsRepository repository;
private IOrderProcessor orderProcessor;
public CartController(IProductsRepository repo, IOrderProcessor proc)
{
repository = repo;
orderProcessor = proc;
}
[HttpPost]
public ViewResult Checkout(ShippingDetails shippingDetails, Cart cart)
{
var test = Request.Form["Line1"];
if (cart.Lines.Count() == 0)
{
ModelState.AddModelError("", "Sorry, your cart is empty!");
}
if (ModelState.IsValid)
{
orderProcessor.ProcessOrder(cart, shippingDetails);
cart.Clear();
return View("Completed");
}
else
{
return View(shippingDetails);
}
}
public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)
{
Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
if (product != null)
{
cart.AddItem(product, 1);
}
return RedirectToAction("Index", new { returnUrl });
}
public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl)
{
Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
if (product != null)
{
cart.RemoveLine(product);
}
return RedirectToAction("Index", new { returnUrl });
}
public ViewResult Index(Cart cart, string returnUrl)
{
return View(new CartIndexViewModel { Cart = cart, ReturnUrl = returnUrl });
}
public ViewResult Summary(Cart cart)
{
return View(cart);
}
[HttpGet]
public ViewResult Checkout()
{
return View(new ShippingDetails());
}
private Cart GetCart()
{
Cart cart = (Cart)Session["Cart"];
if (cart == null)
{
cart = new Cart();
Session["Cart"] = cart;
}
return cart;
}
}
}
However, whenever I press this input button, nothing happens.
Could anybody please tell me what is wrong? I thought an input button of type submit would call the post version of the action, but apparently this is not working.
Edit:
I tried disabling all javascript in the browser, but this does not solve the problem.
Here is my routing info:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null,"", // Only matches the empty URL (i.e. /)
new
{
controller = "Product",
action = "List",
category = (string)null,
page = 1
}
);
routes.MapRoute(null, "Page{page}", new { Controller = "Product", action = "List" });
routes.MapRoute(null,"{category}", // Matches /Football or /AnythingWithNoSlash
new { controller = "Product", action = "List", page = 1 });
routes.MapRoute(null,
"{category}/Page{page}", // Matches /Football/Page567
new { controller = "Product", action = "List" }, // Defaults
new { page = #"\d+" } // Constraints: page must be numerical
);
routes.MapRoute(null, "{controller}/{action}");
}
And here is my ApplicationStart method in Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());
}
I don't really know what else to do. If anybody has any ideas, please let me know.
I expect the input of type submit to call the Post version Checkout action from my controller
Why do you expect something like that? You don't seem to have indicated it on your form:
#using (Html.BeginForm("Checkout", "Cart"))
{
...
}
If you do not explicitly specify the action and controller name to be called when rendering the form, then the same action as the one that rendered this view will be called with HttpPost. So for example if this view was rendered from the Index action, then if you use Html.BeginForm, ASP.NET MVC will look for an Index action with HttpPost on the same controller.
For example:
public ActionResult Index()
{
... render the form
}
[HttpPost]
public ActionResult Index(ShippingDetails shippingDetails, Cart cart)
{
... process the form submission
}
That's the convention. If you don't want to follow the convention you need to use the overload of Html.BeginForm which allows you to specify the action and controller that you want to be invoked.
You do have to explicitly specify that you want to POST the form. By default, it does a GET.
#using(Html.BeginForm("Checkout", "Cart", FormMethod.Post))
{
...
}
Related
I am very new to this and am doing a little project to get to know how it all works.
So I'm looking to create a header image area on each page by placing the code in the "_Layout.cshtml" file and attempting to control what image displays according to "ViewBag.Title".
I have broken the code up into the pages, followed by a pic. of the error. I just can't work out what the problem is.
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace WebSite_Project_1.Controllers
{
public partial class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
[ActionName("Funny-Bones")]
public ActionResult FunnyBones()
{
ViewBag.Message = "This is funny bones.";
return View();
}
public class Headers
{
public string HeaderName { get; set; }
public string PageName { get; set; }
public int HeaderWidth { get; set; }
public int HeaderHeight { get; set; }
public string HeaderType { get; set; }
}
public ActionResult HeaderImages()
{
var model = new List<Headers>();
model.Add(new Headers { HeaderName = "home", PageName = "Home Page", HeaderWidth = 2200, HeaderHeight = 1172, HeaderType = ".png" });
model.Add(new Headers { HeaderName = "about", PageName = "About", HeaderWidth = 2200, HeaderHeight = 1172, HeaderType = ".png" });
model.Add(new Headers { HeaderName = "contact", PageName = "Contact", HeaderWidth = 2200, HeaderHeight = 1172, HeaderType = ".png" });
model.Add(new Headers { HeaderName = "funnybones", PageName = "Funny Bones", HeaderWidth = 2200, HeaderHeight = 1172, HeaderType = ".png" });
return View(model);
}
}
}
_Layout.cshtml
#model IEnumerable<WebSite_Project_1.Controllers.HomeController.Headers>
<div class="headersImage">
#foreach (var item in Model)
{
if (#item.PageName == #ViewBag.Title)
{
<img src="~/Content/Images/#item.HeaderName+#item.HeaderType" title="#item.HeaderName" />
}
}
</div>
#RenderBody()
The problem starts when I try and publish it and then i get this error pointing to Model in the "foreach" loop.
I'm not a 100% sure the loop is right, but haven't been able to get that far yet.
Link to MVC error
You should never specify a model for a layout. The model passed in will always be the one for the view, which almost invariably, will not be the same one the layout is wanting.
For things like this, you should use child actions. Essentially, just take your existing HeaderImages action, add the [ChildActionOnly] attribute (which prevents it from being routed to directly), and change the return to:
return PartialView("_HeaderImages", model);
You can call the view whatever you want, but essentially it would just have the following:
#model IEnumerable<WebSite_Project_1.Controllers.HomeController.Headers>
<div class="headersImage">
#foreach (var item in Model)
{
if (#item.PageName == #ViewBag.Title)
{
<img src="~/Content/Images/#item.HeaderName+#item.HeaderType" title="#item.HeaderName" />
}
}
</div>
Finally, in your layout, remove the model definition line and replace the header image code currently there with:
#Html.Action("HeaderImages", "Home")
EDIT
Sorry, I missed one thing. The child action will render in a separate context from the main view/layout (that's sort of the point). However, that means it has its own ViewBag, so you can't access the ViewBag from the main action directly. Instead, you'll need to pass it in as a route value:
#Html.Action("HeaderImages", "Home", new { title = ViewBag.Title })
Then, modify your child action to accept this param, and set its ViewBag:
[ChildActionOnly]
public ActionResult HeaderImages(string title)
{
...
ViewBag.Title = title;
return PartialView("_HeaderImages", model);
}
I've faced some problem recently and couldn't find the solution. I'm working on SportsStore from Adam Freeman's book Pro MVC 4. Look at this please:
I have a View called Index:
#model WebUI.Models.CartIndexViewModel
.
.
.
<p align="center" class="actionButtons">
Kontynuuj zakupy
</p>
CartController:
{
public class CartController : Controller
{
private IProductRepository repository;
public CartController(IProductRepository repo)
{
repository = repo;
}
public ViewResult Index(string returnUrl)
{
return View(new CartIndexViewModel
{
Cart = GetCart(),
ReturnUrl = returnUrl
});
}
public RedirectToRouteResult AddToCart(int productID, string returnUrl)
{
Product product = repository.Products.FirstOrDefault(p => p.ProductID == productID);
if (product != null)
{
GetCart().AddItem(product, 1);
}
return RedirectToAction("Index", new { url = returnUrl });
}
public RedirectToRouteResult RemoveFromCart(int productId, string returnUrl)
{
Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
if (product != null)
{
GetCart().RemoveLine(product);
}
return RedirectToAction("Index", new { url = returnUrl });
}
private Cart GetCart()
{
Cart cart = (Cart)Session["Cart"];
if (cart == null)
{
cart = new Cart();
Session["Cart"] = cart;
}
return cart;
}
}
}
ProductSummary View:
#model Domain.Entities.Product
<div class="item">
<h3>#Model.Name</h3>
#Model.Description
#using (Html.BeginForm("AddToCart", "Cart"))
{
#Html.HiddenFor(x => x.ProductID)
#Html.Hidden("returnUrl", Request.Url.PathAndQuery)
<input type ="submit" value="+ Dodaj do koszyka"/>
}
<h4>#Model.Price.ToString("c")</h4>
</div>
and CartIndexModelView:
public class CartIndexViewModel
{
public Cart Cart { get; set; }
public string ReturnUrl { get; set; }
}
And my problem is actually that my Kontynuuj zakupy
returns empty <a>KontunuujZakupy</a> Html, which I guess means that #Model.ReturnUrl doesn't get any value at all. I couldn't figure out why because i am begginer, would You mind to give me a clue about that? Thanks.
//edit
"Kontynuuj zakupy" means Continue Shopping :)
Your index action looks like this:
public ViewResult Index(string returnUrl) { ... }
It takes the parameter of returnUrl and insert that into the model which you return. If you browse to your website without specifying a return URL, it will be blank, for example:
http://localhost:1234
http://localhost:1234/Home/Index
Try passing a parameter like this:
http://localhost:1234?returnUrl=xxxx
http://localhost:1234/Home/Index?returnUrl=xxxx
Notice that the parameter name matches the index action. So in your AddToCart and RemoveFromCart actions, you need to change the name of the parameter from url to returnUrl.
return RedirectToAction("Index", new { returnUrl = returnUrl });
you can simply change the last line of your AddToCart action to:
return RedirectToAction("Index", new { returnUrl = returnUrl });
I have a controller
public class JobSearchResultController : Controller {
public ActionResult Index() {
return View();
}
}
the View ~/Views/JobSearchResult/Index
#Html.Raw("<b> TEST VIEW </b>")
And then I register the route
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[] {
new RouteDescriptor {
Priority = 5,
Route = new Route(
"JobSearchResult",
new RouteValueDictionary {
{"area", "Module.Test"},
{"controller", "JobSearchResult"},
{"action", "Index"}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "Module.Test"}
},
new MvcRouteHandler())
}
};
}
And I'm calling it in one of the other views
#Html.ActionLink("Click Me",
"Index",
new { controller = "JobSearchResult", area = "Module.Test"" })
Works good, it takes me to the Actual View/Index displaying the plain page with
TEST VIEW
in localhost/Orchard.Web/JobSearchResult URL
Now, my problem is I'm expecting this page to use the current theme / current layout (masterpage) just like a normal content type
How can I do this?
I know theres an [Admin] attribute where you can be able to display in Admin view using the Admin theme but how about if I want to display it in the Client. Is there something like
[Client] //display in Client
public class JobSearchResultController : Controller {
public ActionResult Index() {
return View();
}
}
Thanks in advance!
Yes. You can apply the Orchard.Themes.ThemedAttribute:
[Themed] //display in Client
public class JobSearchResultController : Controller {
public ActionResult Index() {
return View();
}
}
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
}
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 ")"