How to pass a parameter to controller through view? - c#

I am trying to pass an ICollection argument through a view to the TeamMemberController.
I use SQL database with ASP.NET Core
The database is stored locally. When clicking the red marked button there should appear a new page containing a list of the team members. The TeamMembers are currently displayed to the left of the marked button. The view button should send the parameter and direct us to the teamMemberpage
But as you can see, the list appears to be empty
I have tried looking at the network in my browser and it gives me this:
Query String Parameters(1) :teamMembers: System.Collections.Generic.List`1[BugTracking.Data.Entities.TeamMember]
Video demonstrating issue
https://youtu.be/dJbloxDCeok
Code:
Project Index View
#foreach (var item in Model) {
<a asp-action="ShowTeamMembers"
asp-controller="TeamMember"
asp-route-teamMembers="#item.TeamMembers" class="btn btn-
secondary">view</a>
}
TeamMemberController
public class TeamMemberController : Controller
{
private readonly DatabaseContext _context;
public TeamMemberController(DatabaseContext context)
{
_context = context;
}
// GET: TeamMembers
public async Task<IActionResult> Index()
{
return View(await _context.TeamMembers.ToListAsync());
}
public IActionResult ShowTeamMembers(ICollection<TeamMember> teamMembers)
{
return View(nameof(Index), teamMembers);
}
}

The anchor tag will generate a url link to the given Controller/Action. Thus, it can only contain parameters that can be contained in the url of the link. So you cannot pass an object through on the url.
Try passing the Id of the team member
Project Index View
#foreach (var item in Model) {
<a asp-action="ShowTeamMembers"
asp-controller="TeamMember"
asp-route-Id="#item.TeamMembers.ID" class="btn btn-
secondary">view</a>
}
TeamMemberController
public IActionResult ShowTeamMembers(int Id)
{
var team = _context.TeamMembers.Where(team => team.ID == Id).FirstOrDefault()
return View(nameof(Index), team);
}

I solved the problem by sending the id of the project itself, instead of trying to send the List.
I could then from the TeamMemberController handle that object and take bind the list to a variable.
Code:
Project Index View
<a asp-action="ShowTeamMembers"
asp-controller="TeamMember"
asp-route-Id="#item.Id" class="btn btn-secondary">view</a>
TeamMemberController
public IActionResult ShowTeamMembers(Guid Id)
{
Project? project = _context.Projects.Include(p => p.TeamMembers).Where(p => p.Id == Id).FirstOrDefault();
List<TeamMember> team = project?.TeamMembers ?? new();
return View(nameof(Index), team);
}

Related

How can we get the Id of an item inside the controller?

So basically as the title said, currently working on a MVC5, how can I get the Id of the list of Items that I have in view of the controller? By a button click, I'm calling an action which doesn't have a view but requires the Id of the item, which is resulting in a NULL.
I read this article but they don't seem to work for me:
How to get the current user in ASP.NET MVC
public ActionResult GetItem(int Id)
{
Item item = new Repository().GetId(Id);
..
}
Okay, so after a discussion on the requirement, the following changes were required to be made on the View:
<div class="col-lg-4">
<a href="#Url.Action("GetItem", "Upload", new { id = Model.id}"">
</a>
<P>#p.Path</P>
</div>
And the Controller:
public ActionResult GetItem(int id)
The URL for each item would be generated uniquely based on the Model.id

How to add and show data from a list of objects in MVC?

I'm working on a Lexical Analyzer and I want to show all data on a table in MVC. But to simplify code I'll add an example to show what I want to achieve. I have a logic.cs class where the Lexical Analyzer will be receiving the string coming into, and I want to Add items to the List accordingly to the Lexical Analyzer method.
This is my code:
Controller
Repository repo = new Repository();
logic logica = new logic();
public ActionResult Index()
{
var getrepo = repo.GetData();
return View(getrepo.ToList());
}
[HttpPost]
public ActionResult Index(string str) {
logica.Logic_t(str); //I send str parameter to the logic class
var getrepo = repo.GetData();
return View(getrepo.ToList());
Model
Repository.cs
public class Repository
{
public List<data_table> data = new List<data_table>() { };
public List<data_table> GetData() {
return data;
}
}
data_table.cs
public int Line { get; set; }
public string Token { get; set; }
logic.cs
Repository repo = new Repository();
public void Logic_t(string s)
{
int Line = 1;
repo.data.Add(new data_table { Line =Line , Token = " NUMBER" });
}
View
#model IEnumerable<pruebaarray.Models.data_table>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
<textarea rows="10" cols="50" class="textarea" name="str">
</textarea>
<input type="submit" value="send-to-logic" class="btn btn-primary"/>
}
<table class="table-bordered">
<tr>
<th>Line</th>
<th>Token</th>
</tr>
#foreach (var item in Model) {
<tr>
<th>#item.Line</th>
<th>#item.Token</th>
</tr>
}
</table>
And this is my final view:
My code has no Errors, but when I click the submit button, nothing shows in the table. What am I missing? or what could be wrong?
PD: My Lexical Analyzer logic has recursive methods so It will be adding data constantly to the List.
UPDATE: I got this just by setting List to static
Currently, your form does not know which controller or action to target.
Html.BeginForm() has several overloads.
For example:
BeginForm(HtmlHelper, String, String, Object, FormMethod, Object)
Writes an opening tag to the response and sets the action tag
to the specified controller, action, and route values. The form uses
the specified HTTP method and includes the HTML attributes.
Check the overloads here

ASP.NET form submit part of a model

This is just something that has been puzzling me, I'm wondering if there's a built in way for this.
Say you have a Package class
public class Package
{
public A AObject { get; set; }
public B BObject { get; set; }
}
And you have a view that uses this Package.
public ActionResult Action()
{
return View(new Package());
}
Now the view will accept this model and have 2 forms.
#model Path.To.Package
#Html.BeginForm("SubmitA", "MyController")
{
#Html.TextBoxFor(m => m.AObject.SomeProperty);
<input type="submit" />
}
#Html.BeginForm("SubmitB", "MyController")
{
#Html.TextBoxFor(m => m.BObject.AnotherProperty);
<input type="submit" />
}
If one would create two actions needed above that take Package as argument, this would work without question...
public JsonResult SubmitA(Package items) { ... }
public JsonResult SubmitB(Package items) { ... }
But at SubmitA the BObject would be null and in SubmitB AObject would be null.
My question here is whether you can submit only a part of the model? So the first form would only submit AObject and the second BObject so you could actually reach these via the following actions:
public JsonResult SubmitA (A a) { ... }
public JsonResult SubmitB (B b) { ... }
You can use the Prefix property of BindAttribute to bind to complex properties of a model. The attribute effectively removes the prefix from the submitted name/value pairs when binding to model.
Your controller methods would be
public JsonResult SubmitA([Bind(Prefix = "AObject")]A model) { ... }
public JsonResult SubmitB([Bind(Prefix = "BObject")]B model) { ... }
You should really use separate view model for each form. You can of course, use bind attribute or use specific property names in the controller action. But, that doesn't solve your real problem. You can only get either of the values and the other object will be unassigned or NULL. This is why you should have separate view model for each view / form. You can build your Package object once you have values for both objects.

Managing routes and controller variables in ASP.NET MVC 5

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.

How does my view know what type of data is being returned using MVC?

So I created a project to test DI in MVC 4. Yeah, that was the easy part. I have a controller that looks like this.
public class CompanyManagementController : Controller
{
private ICompanyService companyService;
public CompanyManagementController(ICompanyService companyService)
{
this.companyService = companyService;
}
public ActionResult Index()
{
return View(companyService.GetCurrentCompanies());
}
}
What I can't figure out is how to tell the view Index.cshtml that I have this List that I am returning to it. When I have just a POCO it's easy. I did this
[HttpGet]
public ActionResult Create()
{
var company = new Company();
return View(company);
}
[HttpPost]
public ActionResult Create(Company company)
{
using(var context = new InventoryContext())
{
context.Companies.Add(company);
context.SaveChanges();
}
return View("Create");
}
That was super easy because I just said
#model MvcInventoryManager.Models.Company
in the Create.cshtml file.
Any hints? What I want to do is be able to loop through the list of companies that get returned.
Thanks!
What is the return type of companyService.GetCurrentCompanies()? That would be the type you'd want to use for binding the Model in the view. For example, if the type is IEnumerable<Company> then in your view you would declare this:
#model IEnumerable<MvcInventoryManager.Models.Company>
That way the type of the Model property would match what's being passed to the view and you could loop through the Model:
foreach (var company in Model)
after declaring your model in the view just loop through it like so:
#foreach (var company in Model)
{
Html.DisplayFor(modelItem => company.companyID)
Html.DisplayFor(modelItem => company.CompanyName)
}

Categories

Resources