So I created MVC 4 app with Database First and Entity Framework approach.I have created respective Models,Controllers and Views(By adding MVC controller with read/write actions and views, using Entity Framework template). So it comes with Create,Delete,Edit,Index and Details Views and respective action methods.
When I click on Details,Edit or Delete I see that location id which is a parameter in action method is not passed passed with value, so only default value 0 is used. and I learned that with HttpGet we need to pass it as query string in URL,but in my case how can I implicitly pass the Location ID for Edit/Details or Delete.
You do it like this:
VIEW:
foreach (var item in Model)
{
<tr>
<td>
#Html.ActionLink("Edit", "EditAction", new {id = item._account})
</td>
<td>
#Html.ActionLink("Details", "DetailsAction", new {id = item._account})
</td>
<td>
#Html.ActionLink("Delete", "DeleteAction", new {id = item._account})
</td>
</tr>
}
CONTROLLER:
//
// GET: /Controller/EditAction/id
public ActionResult EditAction(int id)
{
//Do Something
}
//
// GET: /Controller/DetailsAction/id
public ActionResult DetailsAction(int id)
{
//Do Something
}
//
// GET: /Controller/DeleteAction/id
public ActionResult DeleteAction(int id)
{
//Do Something
}
the list View template's links are created using #Html.ActionLink right ? If so, they have to respectively contain the item Id they intend to point to explicitely, so they should be something like
#Html.ActionLink("Edit", new {id=item.Id}))
Which is correct if you use the default Route.
By the way I am surprised these links do not contain the right Id setup by default in list templates.
Related
I have made a webapp where you search by client id, and then adds orders on that client. The index action method assigns the chosen client to the viewmodel(vm.AllClients). The order table of course has information about the client. In the Insert method i want to use the information about the chosen client, but now vm.AllClients is returning null.
During debugging vm.AllClients is filled with one client object, as it should, during the running of the first method. When the second method is running vm.AllClients is empty.
I have tried to save the search string as a variable and find it in the db(Not a good solution), but the variable is also empty during the running of the second method. Also tried to save the chosen client as a Client object in the viewmodel, still no dice.
AddController
using MainWeb.Models;
public class AddController : Controller
{
OrderEntities db = new OrderEntities();// New instance of db.
ViewModel vm = new ViewModel();//New instance of viewmodel
[HttpPost]
public ActionResult Index(string searchTerm)
{
if (string.IsNullOrEmpty(searchTerm))
{
vm.AllClients = new List<Client>();
}
else
{
vm.AllClients = db.Clients.Where(x =>
x.RefNo.ToString().Equals(searchTerm)).ToList();
foreach (Client client in vm.AllClients)
{
vm.ThisClient = client;//Attempt at a different solution
break;
}
}
return View(vm);
}
public ActionResult InsertOrder(FormCollection form)
{
Order order = new Order();
order.ClientID = vm.AllClients[0].ID;//Here is where the error gets thrown
return RedirectToAction("Index");
}
View
#model MainWeb.Models.ViewModel
<div class="card border-primary mb-3 card-client" style="max-width: 40rem;">
<div class="card-header">Legg til</div>
<div class="card-body">
<div class="editor-label">
<table>
#using (Html.BeginForm("Test", "Add", FormMethod.Post))
{
<tr>
<td>#Html.Label("Velg Prosjekt:")</td>
</tr>
<tr>
<td>
#Html.DropDownList("fromDBProjects", (IEnumerable<SelectListItem>)ViewData["DBProjects"], new { #class = "form-control" })
</td>
</tr>
<tr>
<td>#Html.Label("Velg Produkt:")</td>
</tr>
<tr>
<td>
#Html.DropDownList("fromDBProducts", (IEnumerable<SelectListItem>)ViewData["DBProducts"], new { #class = "form-control" })
</td>
</tr>
<tr>
<td>#Html.Label("Pris:")</td>
</tr>
<tr>
<td><input type="submit" value="Submit" class="btn btn-primary" id="btn-search" /></td>
</tr>
}
</table>
</div>
</div>
</div>
</div>
}
ViewModel
namespace MainWeb.Models
{
public class ViewModel
{
public List<Client> AllClients { get; set; }
public Client ThisClient { get; set; }
}
}
Error:
Object reference not set to an instance of an object
In general I asume you are trying to write an new asp.net web application. But you should consider using asp.net core. This framework is the followup of asp.net mvc and you shouldn't start coding asp.net core mvc instead of the full framework.
That is deprecated and will get replaced by asp.net core mvc
I guess you should do the mvc tutorial from MS first. To get a better understanding how everything works.
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/?view=aspnetcore-2.2
But now to your question:
There are a couple of issues in your example:
ViewModel shouldn't be a central class. Create it in your Controller Action, because every webrequest get's a new instance of your controller!
When you hit "InsertOrder" - the controller is created newly for the new request. Which means you get a new instance of the controller and your viewModel is empty again
Your cshtml will never hit your "InsertOrder"
InsertOrder can map your formcollection directly into a class.
Minor: you shouldn't layout your html with a table - See: Why not use tables for layout in HTML?
Your controller should look like this
public class AddController : Controller
{
OrderEntities db = new OrderEntities();// New instance of db.
[HttpPost]
public ActionResult Index(string searchTerm)
{
var vm = new ViewModel();
if (string.IsNullOrEmpty(searchTerm))
{
vm.AllClients = new List<Client>();
}
else
{
vm.AllClients = db.Clients.Where(x =>
x.RefNo.ToString().Equals(searchTerm)).ToList();
foreach (Client client in vm.AllClients)
{
vm.ThisClient = client;//Attempt at a different solution
break;
}
}
return View(vm);
}
[HttpPost]
public ActionResult InsertOrder(ViewModel vm)
{
Order order = new Order();
order.ClientID = vm.AllClients[0].ID;//Here is where the error gets thrown
return RedirectToAction("Index");
}
And your view should set the form to this:
#using (Html.BeginForm("InsertOrder", "Add", FormMethod.Post))
Each request gets a fresh controller instance, so you cannot use the global Viewmodel variable. If you want to communicate between controller actions use ViewData object or simply send the data to the client and get it via FormCollection or your ViewModel class.
i have retrive name of buttons from database those button when i click a
button i need to show the some report.now i have everything is done.i
need to know how keep unique id for those buttons and how they worked
when i click button i need to show report in a separate page...
[HttpGet]
public ActionResult Estate()
{
return View("Estate", new EstateFunc().Estates());
}
[HttpPost]
public ActionResult Estate(int id=0)
{
if(id>0)
{
}
return View();
}
With MVC
View:
#foreach (var item in Model)
{
<tr>
<td>#item.Something...</td>
<td>#item.AnotherThing</td>
<td>
</td>
</tr>
}
This Razor code will generate as many button as the count of the collection items in your model. Every item in the collection should have unique ID and as you see we put this ID as parameter. So when the user clicks on any of the buttons, the unique ID will tell us which button was clicked (and to which item we should refer to)
Controller:
public ActionResult MyAction(int id = 0)
{
var model = ReportViewModel(id);
return ReportView(model);
}
private ReportViewModel(int id)
{
// Get Report from Database Where Id = id
// Populate ViewModel from the database data
// Return reportViewModel;
}
EDIT (adding example based on your code)
#foreach (var item in Model)
{
<div class="floated">
<a href="#Url.Action("MyAction", "MyController", new { Id = item.Id })"
class="Estates_button">#item.EstateName</a>
</div>
}
This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 6 years ago.
Controller:
[HttpPost]
public ActionResult GetChecked(FormCollection formCollection)
{
var checked = formCollection["Checked"].Split(',');
var ids = formCollection["Original.ID"].Split(',');
}
View:
#model IEnumerable<Models.Entry> []
<table>
#using (Html.BeginForm("GetChecked", "ControllerName"))
{
#foreach (var item in Model[0])
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Original.ID)
#Html.HiddenFor(modelItem => item.Original.ID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Original.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.New.Name)
</td>
<td>
#Html.CheckBoxFor(modelItem => item.Checked)
</td>
</tr>
}
</table>
//Then there's another table with Model[1]
Model
public class Entry
{
public Entry()
{
Original = new SomeObject();
New = new SomeObject();
Checked = false;
}
public SomeObject Original { get; set; }
public SomeObject New { get; set; }
public bool Checked { get; set; }
}
This works but the ids-array in my controller gets both a true- and a false value for the checked rows. I read that it's because of the FormCollection.
Question: How can I let GetChecked take an IEnumerable<Models.Entry> as a parameter instead? When I tried it it resulted in a null value.
There are a couple of things that you should change:
When rendering controls from a list or array using CheckBoxFor, EditorFor, etc. you should never use foreach - instead, ALWAYS use a for-loop and apply indexing to your collection. The reason is that indexing creates numbered items in your <form> that then no longer conflict with each other, and those numbered items are precisely what you need to successfully process a list of submitted values. See this answer for a simple example: https://stackoverflow.com/a/15375949/1220550
Don't use FormCollection, use a ViewModel class instead. By using FormCollection you are giving up on Databinding / ModelState / ValidationSummary which together are a superb feature of ASP.NET MVC. It is too much to explain it all here, but here is a great link that does exactly that.
It is best to use a fully defined ViewModel class, not only for databinding (see before) but also for consistency and ease-of-use. Having an array of IEnumerable<X> as your #model is at best confusing, and at worst a cause for errors. And what if suddenly you also want to pass an int? Impossible with IEnumerable<X>[], yet a piece of cake with a ViewModel - just add it to the class.
The result is null because you should bind your IEnumerable interface with a model binder. I think you are looking to create a model binding provider, because a provider can look at the type of the property and then create your custom model binder just for that property.
Take a look on this link also http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx
I'm trying to figure out how to work with ASP.NET route system so I've decided to develop the application which would allow the administrator to manage students and groups they're in. I'm not going into developing the database because right now my goal is to decide how to configure routes.
Right now I'm using the following system:
I've added Groups controller to view and edit groups. I'll provide only the parts of code that work with groups and students omitting models since IMHO they're not important.
So, when you enter the Groups controller Index page, the Index method is executed:
public ActionResult Index()
{
var groups = groupManager.GetGroups();
return View(groups);
}
Groups are displayed as follows:
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id }) |
#Html.ActionLink("Manage students", "Index", "Students", new { id = item.Id }, null)
</td>
</tr>
}
Students controller has a static variable to hold the group you're working with:
public static int currentGroupId;
When you click the 'Manage students' link, the Students controller's Index method is executed and the current id of the group you've chosen is passed:
public ActionResult Index(int? id)
{
Group group = groupManager.FindById(id);
currentGroupId = id.Value;
return View(studentManager.GetStudentsByGroup(id));
}
When you edit/create/delete the student, this id is used to redirect you back to Student's controller Index or to attach the student to the group as follows:
public ActionResult Create([Bind(Include = "Id,Title,Group")] Student student)
{
if (ModelState.IsValid)
{
student.Group = groupManager.FindById(currentGroupId);
studentManager.Add(student);
return RedirectToAction("Index/" + currentGroupId);
}
return View(student);
}
The same approach is used inside the edit view if you want to return to the list of students:
#Html.ActionLink("Back to List", "Index/" + StudentsController.currentGroupId)
It works fine but I'm not really sure that it's okay. I strongly dislike the usage of static variable inside the controller and I also don't like the way it looks in the address bar; you have something like 'localhost/Students/Index/{groupId}' when you view the students but you have 'localhost/Students/Edit/{studId}' when you edit the student which doesn't seem nice for me since the routes don't seem logical and understandable.
I've tried to set up a route using attributes. I've created the following routes for Students contoller in another project (in this example there's no page to edit groups, you enter the students page just from the corresponding group page):
public class StudentsController : Controller
{
[Route("Groups/Group/{id}/Students")]
public ActionResult Index(int? id)
{
return View();
}
[Route("Students/{studId?}")]
public ActionResult Student(int? studId)
{
return View();
}
[Route("Students/{studId}/Edit")]
public string Edit(int studId)
{
return "Editing " + studId;
}
}
[Route("Groups/Group/{id}/Students")] - this route is used to display the list of students in the group.
[Route("Students/{studId?}")] - this route is used to display the information about the user.
[Route("Students/{studId}/Edit")] - this route is used to edit the chosen user.
The routes work fine when I want to jump from one link to another but I wasn't able to figure out how to get the Group id when I want to create the student. The route doesn't contain it and I don't have any variable to hold it.
Could anybody recommend some smooth way to implement this functionality without using a static variable? Perhaps I should use another route, say, 'Groups/Group/{groupId}/Students/{studId}/Edit' so that I will be able to get the groupId from route?
Update:
As I've already understood, static is a real fail but I don't know how to store the information about the group. It's used everywhere in controller. I think I can try to save it inside the Html.ActionLink as route parameter. However, it'll get a bit complicated since I'll have to pass the value again whenever I have to write another method.
Update 2
I've finally managed to solve the problem, albeit not in the best way. I've used the routing attributes to set up the path. I've decided to use the path like 'Groups/Group/{groupId}/{Students}' because I need the groupId to go back to Index from edit/create/delete student pages.
So here's how it looks now:
First, I've changed the link to the manage students page. The link is displayed on groups Index page near each group.
#Html.ActionLink("Manage students", "Index", "Students", new { groupId = item.Id }, null)
Then I've changed the student Index method:
[Route("Groups/Group/{groupId}/Students")]
public ActionResult Index(int groupId)
{
ViewBag.groupId = groupId; //To use it in the view
return View(studentManager.GetStudentsByGroup(groupId));
}
The Index view contains groupId to use it in the 'go back to Index' link inside edit/create/delete pages:
<td>
#Html.ActionLink("Edit", "Edit", new { groupId = ViewBag.groupId, studentId = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { groupId = ViewBag.groupId, studentId = item.Id })
</td>
The edit methods look as follows:
[Route("Groups/Group/{groupId}/Students/Edit/{Id}")]
public ActionResult Edit(int groupId, int? studentId)
{
Student student = studentManager.FindById(studentId);
return View(student);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route("Groups/Group/{groupId}/Students/Edit/{Id}")]
public ActionResult Edit([Bind(Include = "Id,Name")] Student student)
{
if (ModelState.IsValid)
{
var studentWithId = studentManager.FindById(student.Id);
var groupId = studentWithId.Group.Id; //Because we don't pass the groupId to the Edit; should fix it ASAP
var group = groupManager.FindById(groupId);
student.Group= group;
studentManager.Edit(student);
return RedirectToAction("Index", new { groupId = groupId }); //To display the list of the student for that group when you finish editing
}
return View(student);
}
And here's the usage of 'back to list' link. ViewBag isn't used because already have the id in model.
#Html.ActionLink("Back to List", "Index", new {groupId = Model.Group.Id })
I suppose this way is better than the static variable but I don't like passing the groupId from view to controller and back whenever I have to do something. I don't think ViewData or TempData will work here since I have to let a user edit different groups at the same time and if I call TempData["groupId"] = groupId when they click the manage students button, it'll be absolutely the same as with the static variable. I guess I'll leave it as it is for now, maybe I'll come up with something better later.
I have view with UserSample model with two properties (Username, Password), All I want to achieve is to take these values from the textboxes and to store them into the object model and then in the Action method to take this object and manipulate it.
But here in this code the object properties in the action method Index are always null after submitting the form with filled text box fields.
Controller:
public ActionResult Index(UserSample user) //this is the object which properties I want to be asigned.
{
if (user != null)
{
}
return View();
}
View:
#using (#Html.BeginForm("Index","Login"))
{
<table>
<tr>
<td>Username: </td>
<td>#Html.TextBox("Username")</td>
</tr>
<tr>
<td>Password: </td>
<td>#Html.TextBox("Password")</td>
</tr>
<tr>
<td></td>
<td><input id="btnSubmit" type="submit" value="Submit" /></td>
</tr>
</table>
}
Try adding this line to your view:
#model UserSample
You probable need to include the namespace there. MyApp.Models.UserSample for example.
Then you can use stronly typed html-helpers:
<td>#Html.TextBoxFor(model => model.UserName)</td>
and
<td>#Html.TextBoxFor(model => model.Password)</td>
You should decorate the Index(UserSample user) method with the HttpPost attribute:
public ActionResult Index()
{
// Get method.
UserSample model = new UserSample();
return View(model);
}
[HttpPost]
public ActionResult Index(UserSample user)
{
// POST method.
}
When you post the form now, the UserSample object should be populated with the values from the form.
If you don't understand model binding, I suggest you look into it. This tutorial might get you started.