I want to pass id and quantity to action, but I get this error: parameter dictionary contains a null entry for parameter id.
I've tried to do map routing, but I can't find how to do it properly.
My Action:
[HttpPost]
public ActionResult Index(int id, int quantity)
{
var user = unitOfWork.Users.FindByEmail(HttpContext.User.Identity.Name);
unitOfWork.Carts.AddProductToCartByEmail(user.Email, id, quantity);
unitOfWork.Complete();
return View();
}
From this page I'm trying to pass the parameters:
#using PagedList;
#using PagedList.Mvc;
#model IPagedList<MVC_Task.Models.AllProductsModel>
#{
ViewBag.Title = "Index";
}
<h2>Products</h2>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.First().Name)
</th>
<th>
#Html.DisplayNameFor(model => model.First().Price)
</th>
#if (User.Identity.IsAuthenticated)
{
<th>
Quantity
</th>
}
</tr>
#foreach (var item in Model)
{
using (Html.BeginForm("Index", "Product", FormMethod.Post))
{
#Html.AntiForgeryToken()
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
#if (User.Identity.IsAuthenticated)
{
<td class="form-inline">
#Html.TextBoxFor(modelItem => item.Quantity, new { #type = "number", #class = "form-control" })
<input type="submit" value="Add" class="btn btn-default"
onclick="window.location.href = '#Url.Action("Index", new { item.Id, item.Quantity})';" /> //on this line I send the parameters
</td>
}
</tr>
}
}
</table>
<center>#Html.PagedListPager(Model, page => Url.Action("Index", new { page }))</center>
In post actions there is only one parameter can be taken from body, all other parameters you may pass them as route parametes or query string parameters, so your signature
public ActionResult Index(int id, int quantity)
may become:
public ActionResult Index(int id, [FromBody] int quantity)
you can call the action using the url /index?id=...
and try to call the action using ajax post or submit the form not a hyperlink
for more information see https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api#using-frombody
You're not really posting correctly, using the client side window.location.href - it will result in a HttpGet.
You should add a hidden input for the item id, here's the relevant snippet:
<td class="form-inline">
<!-- add the hidden input -->
#Html.HiddenFor(modelItem => item.Id)
<!-- keep this as is -->
#Html.TextBoxFor(modelItem => item.Quantity, new { #type = "number", #class = "form-control" })
<!-- remove the onclick redirect,
this will post to the action defined in BeginForm -->
<input type="submit" value="Add" class="btn btn-default"/>
</td>
Next, a model is often passed as a complex type. It has many benefits over simple types, because you can extend it with, e.g.; validation logic.
So, lets define a model:
public class PostModel
{
public int Id {get;set;}
public int Quantity {get;set;}
}
Now adjust your Index to accept the model:
[HttpPost]
public ActionResult Index(PostModel model)
{
//your logic
}
By default the binders will bind to the appropriate properties in the model. If your TextBox's name is Id, or Quantity, it will be able to do so. You can validate that in the rendered html.
Related
I'm having a challenge updating from Razor View table and not sure if my approach is right. Instead of displaying the data (to be edited) in DIV, I'm displaying in a TABLE. I can understand how the hidden property is used to update the row. In my case I am displaying multiple rows on the Edit page, but unable to update the database for only rows that have updates.
My view page is like this
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<table class="table">
<thead>
<tr>
<th> #Html.DisplayNameFor(model => model.Fields[0].Name) </th>
<th> #Html.DisplayNameFor(model => model.Fields[0].Value) </th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Fields)
{
<tr>
<td> <input type="hidden" asp-for=#item.ID /> </td>
<td> #Html.DisplayFor(modelItem => item.Name) </td>
<td contenteditable='true'> #Html.DisplayFor(modelItem => item.Value) </td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
And the code behind is this. The Fields is showing 0 in the OnPostAsync method.
[BindProperty]
public IList<FieldInfo> Fields { get; set; }
public async Task<IActionResult> OnGetAsync(string id)
{
Fields = await _context.FieldInfo.Where(m => m.GUID == id).ToListAsync();
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
foreach (var p in Fields) // In this approach Fields shows as count=0
{
_context.Attach(p.Value).State = EntityState.Modified;
}
_context.Attach(Fields).State = EntityState.Modified; // In this approach Exception: The entity type 'List<FieldInfo>' was not found.
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
If you want to work with a List or collection, you must use an index to identify individual elements. https://www.learnrazorpages.com/razor-pages/model-binding#binding-complex-collections
In your case, I would use an explicit index:
#foreach (var item in Model.Fields)
{
<tr>
<td> <input type="hidden" asp-for="Fields[item.ID].ID" /> <input type="hidden" name="Fields.Index" value="Fields[item.ID].ID" /></td>
<td> #item.Name</td>
<td><textarea asp-for="Fields[item.ID].Value">#Fields[item.ID].Value">#Fields[item.ID].Value</textarea> </td>
</tr>
}
Note that the value assigned to a td designated as contenteditable is not posted as part of a form. You should use a suitable form control like an input or textarea instead.
I got it working after changing the type as below, from IList to List
[BindProperty]
public List<FieldInfo> Fields { get; set; }
I am trying to create a view in my application that performs basic CRUD commands in ASP.NET Core to teach myself some new skills. I am however stuck and would appreciate some assistance please.
I would like to have each "component" of the application sitting in a partial view for maintenance going forward. I initially had my Index view use a declaration of type IEnumerable (for the for each loop):
#model IEnumerable<Project.Web.Models.Sample.SampleModel>
Which worked perfect for returning the list and rendering the page but then when trying to have my Modal window partially loaded into the page and insert data using the "CreateSample" function on the controller it was not picking up the function and failed the insert (no form action found). If I then try to add:
#model Project.Web.Models.Sample.SampleModel
to the CreateModal view page it throws an error and wont even let me render the page, I presume because its being partial loaded the app is seen as having two SampleModel declarations. If I create this page completely separate and not partially loaded with the normal #model declaration it works.
I have the basic setup going so far and have included my code for each below.
Model - SampleModel
public class SampleModel
{
public int Id { get; set; }
public string SampleText { get; set; }
}
Controller - SampleController
public class SampleController : Controller
{
public const string ControllerName = "Sample";
//Open Database Connection
private _DBContext DBDatabase = new _DBContext ();
public ActionResult Index()
{
var Model = DBDatabase.Sample.Select(s => new SampleModel
{
Id = s.Id,
SampleText = s.SampleText
}).ToList();
return PartialView(Model);
}
[ActionName("_CreateModal")]
public ActionResult InsertNewRecord()
{
var Model = DBDatabase.Sample.Select(s => new SampleModel
{
Id = s.Id,
SampleText = s.SampleText
}).ToList();
return PartialView("_CreateModal", Model);
}
Views - Index, View, Create
Index - Calls Partial Views for View and Create
#using Project.Web.Controllers
#model Project.Web.Models.Sample.SampleModel
<!--html stuff here -->
#await Html.PartialAsync("_CreateModal")
<!--more html stuff here -->
#await Html.PartialAsync("_ViewData")
View - Foreach to Loop Records
#model Project.Web.Models.Sample.SampleModel
<table style="width: 100%;" id="example">
<thead>
<tr>
<th>#</th>
<th>Sample Text</th>
<th class="text-center">Status</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
#foreach (var sample in Model)
{
<tr>
<th scope="row">#sample.Id</th>
<td>#sample.SampleText</td>
<td class="text-center">
<div class="badge badge-success">Active</div>
</td>
<td class="text-center">
<div role="group" class="btn-group-sm btn-group">
<button class="btn-shadow btn btn-primary">Edit</button>
<button class="btn-shadow btn btn-primary">Delete</button>
</div>
</td>
</tr>
}
</tbody>
</table>
Create - Insert New Record
#model Project.Web.Models.Sample.SampleModel
<form method="post" asp-action="/SampleModel/CreateSample">
<div class="form-group">
<label for="CreationTime">SampleText</label>
<div>
<input type="text" class="form-control" id="SampleText" name="SampleText" placeholder="SampleText">
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Sign up</button>
</div>
</form>
As per Ammar's comment, you've just copy-pasted the Index Controller's data access. When building a form allowing the user to create a single new item, then the pattern is to typically pre-instantiate an empty model and pass it to the view:
[ActionName("_CreateModal")]
public ActionResult InsertNewRecord()
{
var model = new SampleModel(); // If Id is a GUID, then you could assign one here
return PartialView("_CreateModal", model);
}
I have a index in controller and view following.
When I enter student ID on textbox search, I want to display information of student and list all activities of them. How to do that.
Thank you so much!
Controller:
public ActionResult Index(string Sid)
{
var grad = db.Gradings.Include(g => g.Activity).Include(g => g.Student).Where(g => g.StudentID == Sid).ToList();
}
View:
<div class="col-xs-4">
<p>
#using (Html.BeginForm())
{
<p>
Student ID: #Html.TextBox("Sid",ViewBag.FilterValue as string)
<input type="submit" value="Search" />
</p>
}
<table class="table">
<tr>
<th>Activity Name</th>
....
</tr>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Activity.Name)</td>
<td>#Html.DisplayFor(modelItem => item.Responsibility)</td>
....
</tr>
}
</table>
</div>
<h3> Total score: #ViewBag.Total</h3>
Thanks for your help!
You need one action to serve your view initially when the user requests it:
public ActionResult Index()
{
return View();
}
Then you need another action to process the user's request when the request submits the form:
public ActionResult Index(string Sid)
{
var grad = db.Gradings.Include(g => g.Activity).Include(g => g.Student).Where(g => g.StudentID == Sid).ToList();
return View(grad);
}
Now grad is a List<Grading> and above we are passing it to the view as the model so make sure you use it in your view. You may need to include the namespace for List and Grading:
#model List<Grading>
Finally instead of using foreach in your view, use a for loop so your HTML tags have unique IDs. Right now (with foreach), all Grading records will have the same name and id attributes.
I have a view model which holds the available products and after i show them in a view i have to get the particular item which he clicks on Add to Cart
My Action
public ActionResult Test1()
{
DUPVM obj = new DUPVM();
MembersipEntities Entity = new MembersipEntities();
obj.test = Entity.Dups.ToList();
return View(obj);
}
[HttpPost]
public ActionResult Test1(Dup model)
{
return View(model);
}
and the viewmodel
public class DUPVM
{
public IEnumerable<Dup> test { get; set; }
}
And in my view i will display the all products available by looping through them
#model MVC4.Models.DUPVM
#using (Html.BeginForm("Test1", "PDF", FormMethod.Post))
{
foreach (var item in Model.test)
{
#Html.DisplayFor(modelitem => item.ID)
<br>
#Html.DisplayFor(modelitem => item.Rate)
#Html.DisplayFor(modelitem => item.AgentID)
#Html.TextBoxFor(modelitem => item.ID)
<br>
<input type="submit" value="Add to cart" />
<br>
}
}
Then If a user clicks on Add to cart then i want to pass all details of that products to controller using model. But i am not able to do this.
First change your view to create the form inside the loop. This way you are getting a discrete form with only the values you require.
#model MVC4.Models.DUPVM
foreach (var item in Model.test)
{
#using (Html.BeginForm("Test1", "PDF", FormMethod.Post))
{
#Html.DisplayFor(modelitem => item.ID)
<br>
#Html.DisplayFor(modelitem => item.Rate)
#Html.DisplayFor(modelitem => item.AgentID)
#Html.TextBoxFor(modelitem => item.ID)
<br>
<input type="submit" value="Add to cart" />
<br>
}
}
Your view is strongly-typed to the DUPVM class but your controller action is expecting a single Dup. The default behaviour of the form Razor helpers is to post the model back in the same format it was accepted.
To get this to work you will need to create a partial view for each item, strongly type that partial view to a Dup (not a DUPVM) and either write some javascript that posts your selected item to the controller, or have a seperate form for each item.
You partial view might look something like this
#model MVC4.Models.Dup
#using (Html.BeginForm("Test1", "PDF", FormMethod.Post))
{
#Html.DisplayFor(x => x.ID)
<br>
#Html.DisplayFor(x => x.Rate)
#Html.DisplayFor(x => x.AgentID)
#Html.TextBoxFor(x => x.ID)
<br>
<input type="submit" value="Add to cart" />
<br>
}
And your main view like this:
#model MVC4.Models.DUPVM
#foreach (var item in Model.test)
{
Html.RenderPartial("_YourPartialView", item);
}
I'm writing a view that displays a list of managers. The managers have checkboxes next to their name to select them to be removed from the manager list. I am having problems binding the form submission back to my view model. Here's what the page looks like:
Here's the ViewModel for the page.
public class AddListManagersViewModel
{
public List<DeleteableManagerViewModel> CurrentManagers;
}
And here's the sub-ViewModel for each of the DeleteableManagers:
public class DeleteableManagerViewModel
{
public string ExtId { get; set; }
public string DisplayName { get; set; }
public bool ToBeDeleted { get; set; }
}
This is the code for the main View:
#model MyApp.UI.ViewModels.Admin.AddListManagersViewModel
<div class="row">
<div class="span7">
#using (Html.BeginForm("RemoveManagers","Admin"))
{
#Html.AntiForgeryToken()
<fieldset>
<legend>System Managers</legend>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(model => model.CurrentManagers)
</tbody>
</table>
</fieldset>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Delete Selected</button>
</div>
}
</div>
</div>
And this is the EditorTemplate I've created for DeleteableManagerViewModel:
#model MyApp.UI.ViewModels.Admin.DeleteableManagerViewModel
<tr>
<td>#Html.DisplayFor(model => model.DisplayName)</td>
<td>
#Html.CheckBoxFor(model => model.ToBeDeleted)
#Html.HiddenFor(model => model.ExtId)
</td>
</tr>
But when I submit the form to the controller the model comes back null! this is what I want it to do:
[HttpPost]
public virtual RedirectToRouteResult RemoveManagers(AddListManagersViewModel model)
{
foreach (var man in model.CurrentManagers)
{
if (man.ToBeDeleted)
{
db.Delete(man.ExtId);
}
}
return RedirectToAction("AddListManagers");
}
I tried following along this post: CheckBoxList multiple selections: difficulty in model bind back but I must be missing something....
Thanks for your help!
Hmm. I think this is ultimately the problem; here's what you're posing:
CurrentManagers[0].ToBeDeleted=true&CurrentManagers[0].ToBeDeleted=false&CurrentManagers[0].ExtId=X00405982144
Your model is an AddListManagersViewModel that has a collection of CurrentManagers. So, you're posting an array of DeleteableManagerViewModel, which isn't getting bound to the "wrapper" model. You can try changing the model parameter to
params DeleteableManagerViewModel[] model
I don't ever use the EditorFor extensions, though, so I'm just guessing...