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.
Related
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);
}
I am using MVC application and entityframework,i have two table country and city which has PK_FK relation ship.In my UI i am using two tabs, first tab for countries and second for cities and both are displayed in a grid which is in two partial views.Now what i have to achieve is if i click a country in first tab corresponding country cities should display in second tab rather than complete country cities.How to do that following is the existing code i have done
controller
public ActionResult Index()
{
return View();
}
public ActionResult _gridfirsttab()
{
return PartialView(db.Countries.ToList());
}
public ActionResult _gridsecondtab()
{
return PartialView(db.Citynews.ToList());
}
Index view
<ul>
<li>Tab Header 1</li>
<li>#Html.ActionLink("Tab Header 2", "_gridsecondtab")</li>
<li>>#Html.ActionLink("Tab Header 3", "_gridthirdtab")</li>
</ul>
<div id="tabs-1">
#Html.Action("_gridfirsttab")
</div>
UPDATE
I did like shown below but it is duplicating webgrid, may HTTPGET action also triggering how to prevent it
first partial view grid
#if (grid.HasSelection)
{
conuntry = (firstmvc4.Models.Country)grid.Rows[grid.SelectedIndex].Value;
#Html.Action("_gridsecondtab", new {id =conuntry.id })
}
and created one more HTTPPOST action of first grid
[HttpPost]
public ActionResult _gridsecondtab(int id)
{
// Find the customer by name
var countries = db.Countries.FirstOrDefault(c => c.id == id);
// Get the customers products
var cities = countries.Citynews;
// Send products to the View to be rendered
return PartialView(cities);
}
But it is not properly working rather duplicating first grid itself
The background for my C# knowledge is 0. I am trying to learn it as I go. I just am not sure how to use the right words to search. So I am crying for help !
I have a small ASP.Net Web application that uses MVC framework. I have a Database which hold three tables Company, Territory, the third is a relationship table TerritoryCompany. So the basic set up is one Company can have branches in several Territory, and one Territory can have several Company. The relationship is Many-Many.
What I have is a CS Code that will allow you to create a company, nothing fancy, just
public void Save(CompanyModel company)
{
using (var db = new SampleDbContext())
{
Company entity;
if (company.CompanyId > 0)
{
entity = db.Companies.First(x => x.Id == company.CompanyId);
}
else
{
entity = new Company();
db.Companies.Add(entity);
}
entity.Name = company.CompanyName;
entity.PhoneNo = company.PhoneNo;
//What should I do?
db.SaveChanges();
}
}
Now as you see, I am able to add the Company Name and Phone Number. Good. This is the code I use in the Webpage,
#model PEF.SampleTesting.Domain.CompanyModel
#{
ViewBag.Title = "Add";
}
#using (Html.BeginForm("Save", "Companies"))
{
#Html.EditorForModel()
<label>Enter the list of service area</label><br />
<input id="ServiceArea" class="form-control" name="testBox" value="eg. BS1, BA5" /><br/>
<button type="Submit" class="btn btn-primary">Save</button>
}
Here is the CompaniesController
[HttpPost]
public ActionResult Save(CompanyModel model)
{
if (!ModelState.IsValid) return View((model.CompanyId == 0)?"Add":"Edit", model);
_companyService.Save(model);
return RedirectToAction("Index");
}
What I would like to do is, before doing the save changes db.SaveChanges(); when the company is created, I would like to
Add these entries for the Territory - if the territory does not exist in the Territory table
or
Create an entry in the relationship table for that CompanyID and the corresponding TerritoryID) for that particular Company.
I created a TextBox that will get a comma separated entry of territories. Use this TextBox value in a For and create as many entities to the table Territory (using for loop and split function maybe).
My Questions;
How would I pass the form value (TextBox) back to the code?
How can I use the For Loop to add this 'n' number of entries to the TerritoryCompany table?
Any helps or steps would be greatly appreciated.
Typically instead of passing your data entities to your view you would create a viewmodel, that will provide you with more flexibility...
Something like this class "AddCompanyViewModel", it has a property for your Company and also a property for your comma separated string of territories...
You will now pass this viewmodel too and from your controller instead of the entity type... this is a common scenario as your database structure will rarely perfectly match your domain layer (business layer)... view models essentially model your view.
public class AddCompanyViewModel
{
public CompanyModel Company { get; set; }
public string Territories { get; set; }
}
[HttpPost]
public ActionResult Save(AddCompanyViewModel model)
{
if (!ModelState.IsValid)
return View((model.Company.CompanyId == 0)?"Add":"Edit", model);
_companyService.Save(model.Company);
// Do something with territories...
var territories = model.Territories.Split(',');
return RedirectToAction("Index");
}
You can use the FormCollection parameter for the action.
public ActionResult Save(CompanyModel model, FormCollection formCol)
Reference the textbox via the name property.
Not quite sure about what you're asking for. You can use the Foreach loop to loop through each territory after you've split the textbox value. Then you create your TerritoryCompany model and save accordingly.
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.
The customer can view their cust details page where they can change their pre-recorded delivery run (if they wish too) I have a drop down list containing towns for delivery runs:
<div class="editor-label">#Html.DropDownListFor(model => model.DeliveryRunList, Model.DeliveryRunList)</div>
When the customer profile loads it displays the correct town in the drop down(reading from the DB, which they previously selected when registering).
However if they change the town and save it, the user is returned to the home page and the newly selected tow is saved to the DB. But, if the user returns to the customer profile page the drop down displays the previously selected town, as opposed to the new one just previously selected and saved to the DB. Is it being stored in the cache somewhere.
Why is it not updating to what is actually in the DB??
Codebehind:
CustomerPart custPart = _custService.Get(custId);
if (DeliveryRunList.HasValue)
{
custPart.DeliveryRun_Id = DeliveryRunList.Value;
}
_custService.Update(custPart);
Thanks
I suppose model is a CustomerPart instance, and you have defined it more or less in this way.
public class CustomerPart
{
public int DeliveryRun_Id {get; set;}
public SelectList(or some IEnumerable) DeliveryRun_Id
}
I feel your code isn't updating the DB since you use the wrong attributes. The first lambda expression should be model => model.TheAttributeYouWantToUpdate, in this case DeliveryRun_Id.
So it should be:
#Html.DropDownListFor(model => model.DeliveryRun_Id, Model.DeliveryRunList)
rather than
#Html.DropDownListFor(model => model.DeliveryRunList, Model.DeliveryRunList)
It's not even clear where is this code inside the controller:
CustomerPart custPart = _custService.Get(custId);
if (DeliveryRunList.HasValue)
{
custPart.DeliveryRun_Id = DeliveryRunList.Value;
}
_custService.Update(custPart);
A common way of doing it is to have two methods of the same name for editing, one for HttpGet and one for HttpPost, and use a #Html.BeginForm() in the razor view for updating, rather than updating the info in controller.
Example:
public ActionResult Edit(int id = 0) {
InvestmentFund Fund = InvestmentFundData.GetFund(id);
return Fund == null ? (ActionResult)HttpNotFound() : View(Fund);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(InvestmentFund Fund)
{
if (ModelState.IsValid)
{
InvestmentFundData.Update(Fund);
return RedirectToAction("List");
}
return View(Fund);
}
In View
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#* For the attributes of your model *#
#Html.LabelFor ...
#Html.EditorFor ...
#Html.ValidationMessageFor ...
<input type="Submit"m value="Save">
}