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 });
Related
Im a bit new at mvc, and i dont find out what am i miss. When i launch the login, in the view at foreach (var item in Model) <- the Model gets null, and stops with a System.NullReferenceException. A dont really have a clue why, and i hope somebody can give some advice what's wrong with the following code or where to start looking for the error.
The model:
public class LoginModels
{
public string UserLogin { get; set; }
public string Address { get; set; }
public string Password { get; set; }
public List<string> emailSubject { get; set; }
}
The controller:
public ActionResult Login(string address, string password, LoginModels model)
{
using (Imap imap = new Imap())
{
try
{
imap.ConnectSSL("imap.gmail.com");
imap.Login(address, password);
imap.SelectInbox();
List<long> uids = imap.Search(Flag.All);
model.emailSubject = new List<string>();
foreach (long uid in uids)
{
var eml = imap.GetMessageByUID(uid);
IMail email = new MailBuilder().CreateFromEml(eml);
model.emailSubject.Add(email.Subject);
}
Session["user"] = new LoginModels() { UserLogin = address, Address = address };
return RedirectToAction("Index", "Home", model.emailSubject);
}
catch (Exception e)
{
ViewBag.exceptionMessage = e;
return View("LoginFailed");
}
}
The view:
#using TheOnlineArchivator.Models;
#model List<TheOnlineArchivator.Models.LoginModels>
#{
ViewBag.Title = "Home";
}
#{
var user = Session["user"] as LoginModels;
if (user != null)
{
<h2>You are logged on as #user.Address</h2>
<table>
#foreach (var item in Model)
{
foreach (var elem in item.emailSubject)
{
<tr>
<td>#elem</td>
</tr>
}
}
</table>
}
}
It looks like you forgot to pass an instance of List<TheOnlineArchivator.Models.LoginModels> to the view when you rendered this view inside your controller action. What you have shown so far is your Login controller action but you didn't show us your Home/Index action. Inside this action you should make sure that you are passing a non-null model to the view:
public class HomeController : Controller
{
public ActionResult Index()
{
List<LoginModels> model = ... go get your model from somewhere and make sure it is not null
return View(model);
}
}
I'm reading a book Pro ASP.NET MVC3 Framework - Freeman Sandersan where it's shown how to make simple administration im web shop.
The problem is when I try to save changes of one product it does not save.
Edit view:
#using (Html.BeginForm()) {
#Html.EditorForModel()
<input type="submit" value="Save" />}
Edit method:
[HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
repository.SaveProduct(product);
TempData["message"] = string.Format("{0} has been saved", product.Name);
return RedirectToAction("Index");
}
// there is something wrong with the data values
return View(product);
}
Save product method:
private EFDbContext context=new EFDbContext();
public void SaveProduct(Product product)
{
if (product.ProductID == 0)
{
context.Products.Add(product);
}
context.SaveChanges();
}
Results of editing: I change the name of the product to "Kayakkkkkkkkkk". Temp message says that saving completed but the name of product is still "Kayak".
Solved.
public void SaveProduct(Product product)
{
if (product.ProductID == 0)
{
context.Products.Add(product);
}
else
{
context.Products.Attach(product);
context.Entry(product).State = EntityState.Modified;
}
context.SaveChanges();
}
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))
{
...
}
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 ")"