I have a MVC view that has a table that contains a list of all users within a specific OU within Active Directory. I am trying to add a column to the table that takes you to a Details view that shows the details of the user (shows additional details) but I am having a hard time figuring out how to do this without using Entity Framework and passing the id of the object to the details view. Any thoughts on how I could accomplish this? Right now when I click on the Details actionlink, I am taken to the Details view but there is no data being passed. Any help would be greatly appreciated!
public ActionResult NonComputerUsers(User model)
{
List<User> user = new List<User>();
using (var context = new PrincipalContext(ContextType.Domain, "XXX", "OU=XXX,DC=XXX,DC=com"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry entry = result.GetUnderlyingObject() as DirectoryEntry;
user.Add(new User()
{
FirstName = (String)entry.Properties["givenName"].Value,
LastName = (String)entry.Properties["sn"].Value,
});
}
}
}
return View(user.ToList());
}
public ActionResult Details(User model)
{
?????????
return View(model);
}
**List View**
#model ADUserManagement.Models.User
<table class="table">
<tr>
<th>
First Name
</th>
<th>
Last Name
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.ActionLink("Details", "Details", "Users", new { item = item.FirstName })
</td>
</tr>
}
**Details View**
#model ADUserManagement.Models.User
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<table class="table table-bordered table-striped">
<tr>
<td>First Name</td>
<td>#Model.FirstName</td>
</tr>
<tr>
<td>Last Name</td>
<td>#Model.LastName</td>
</tr>
<tr>
<td>SAM Account Name</td>
<td>#Model.SamAccountName</td>
</tr>
There are a couple ways to do this. One of which is the way you are already doing it. You can add all the properties you want to display on your details page to your User model. Use that list to build your table, then pass the applicable User object back to your Details view to display the extra details.
That method is good because it saves you having to do a second lookup to AD for your Details view (essentially, your ????????? can be removed - you don't need any code there).
I was going to say that the down side is that, in your search, you're asking for more details for most of the accounts than you'd actually use, but the UserPrincipal objects that PrincipalSearcher returns already pulls in all the AD attributes for each user account anyway. If you used DirectorySearcher directly, you'd have more control of that. But that's probably not a big deal, unless you have performance problems with that search.
The other way to do it, which I think is unnecessary in your current example, is to store some unique identifier from AD (e.g. distinguishedName, sAMAccountName, userPrincipalName, objectGuid, objectSid) and pass just that back to your Details action where you can look up just that account again and get all the details you need for your Details view.
Related
I try to display a string from Action in a View. This sounds like a very simple and easy task, but I don't get it how to do this.
My async Action returns Content Result:
public async Task<IActionResult> DisplayName(string key)
{
// Retrieves the requested culture
var rqf = Request.HttpContext.Features.Get<IRequestCultureFeature>();
// Culture contains the information of the requested culture
var culture = rqf.RequestCulture.Culture;
return Content(await Task.FromResult(_displayProvider.GetDisplayName(key, culture.Name)));
}
My View is very simple html:
#model List<MyModel>
<table class="table table-bordered table-striped datatable">
<thead class="table-head-dark">
<tr>
<th>
#Html.DisplayNameFor(model => model.First().Id)
</th>
<th>
Actions
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Id)
</td>
<td>
<partial name="_EditDetailButton" model="#item.Id" />
<partial name="_DeleteButton" model="#item" />
</td>
</tr>
}
</tbody>
</table>
Instead of #Html.DisplayNameFor(model => model.First().Id) I want to Display a user-defined value. The user-defined value is returned from the Action.
I tried #Url.Action("DisplayName", "DisplayName", new { key = "MyModel.Id" }) but this is rendering an Url.
I tried Html.RenderAction("DisplayName", "DisplayName", new { key = "LastName" }) but RenderAction does not exist in ASP.NET Core 5.
I could call a static Class e. g. DisplayNameProvider.GetDisplayName("MyModel.Id", ???) but i dont know how to get the choosen culture to pass it to the method.
How do I get this working? I am also not familiar with components in ASP.NET Core.
Or is there a completely different solution for displaying strings, which the user has defined and save to the database? The DisplayNameProvider is retrieving the strings from database and handles caching.
UPDATE:
The Goal is to store and change displaynames for properties in the database. The user can change the displaynames. In my example the user wants a different displayname for the column header. I cannot use resource files as these are static and cannot be changed during runtime.
This is my DisplayNameProvider.GetDisplayName Method:
public string GetDisplayName(string ressourceKey, string language)
{
var ressources = GetCached(language);
var item = ressources.FirstOrDefault(r => r.ResourceKey == ressourceKey);
return item.Text;
}
Thanks in advance
I ended up using a string localizer, which returns the desired value for my properties.
Now i can use displaynamefor and it shows the value of the language. E.g.:
#Html.DisplayNameFor(model => model.MyProperty)
I have a table that has one empty column into which user can enter a comment:
Table
-----
TbMapId | UniqueAdp | Dealership | Line
--------------------------------------------------------------------
1 | [Insert comment here] | Derby | abc123
2 | [Insert comment here] | Keighley | cda345
3 | [Insert comment here] | Manchester | 876ghj
There is a lot of data to comment on, I can't expect a user to open an 'Edit' page and type in a comment one by one. Instead I need user to be able to input a bunch of comments (say 20 at a time against 20 rows) and save them all at one click of submit button.
If you want to jump straight to working solution go to EDIT #2 & look at Rudi's accepted answer
View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.TBMapBalancesList.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(Model => Model.TBMapBalancesList[i].TbMapId)
#Html.HiddenFor(Model => Model.TBMapBalancesList[i].TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.TBMapBalancesList[i].UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].Line)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Model
I've learned today that I need to use List to be able to iterate through the lines in table by the use of #for loop (as shown above). before I was trying to use IEnumerable. So I added a definition to the model for public List<TBMapBalances> TBMapBalancesList { get; set; }
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapBalances> TBMapBalances { get; set; }
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalancesList { get; set; }
[...]
}
Controller:
Now this is where I need the help with, my code doesn't throw any errors at all. When I press Submit nothing happens:
[Authorize]
[HttpPost]
public async Task<IActionResult> TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
try
{
_context.Update(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
}
return RedirectToAction("TbMapView");
}
EDIT #1
Changes to View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.TBMapBalances.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(Model => Model.TBMapBalances[i].TbMapId)
#Html.HiddenFor(Model => Model.TBMapBalances[i].TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.TBMapBalances[i].UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].Line)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Changes to model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNET_Core_1_0.Models.TbMapViewModels
{
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; }
[...]
}
}
Changes to Controller:
Now this is where I need the help with, my code doesn't throw any errors at all when at the current state - when I press Submit nothing happens and no data gets saved to the database.
however, when you uncomment line _context.Update(tbMapViewModel.TBMapBalances); I get an error that List is not part of any Model and is not found.
Also, below code is something I wrote trying to follow this SO post: update-multiple-records-at-once-in-asp-net-mvc - Initially I was trying to make it Async but I was getting even more errors and couldn't continue. I thought I am going to follow it as closely as possible in hope that it will get me a working starting point.
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
// _context.Update(tbMapViewModel.TBMapBalances);
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
EDIT #2 - A hero to the rescue - big thanks to #RudiVisser for help
First of all if any of you guys are - like me - stuck using .net core 1.0.0
make sure you upgrade to the latest version first (1.1.7 lts). Part of my grief was that I was an USER 1.0 and did not upgrade my installation as fixes and additions kept coming out. Don't be that guy, like I was...
All below is thanks to Rudi's help:
View
#using (Html.BeginForm("TbMapViewEdit", "TbMap"))
{
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#Html.EditorFor(m => m.TBMapBalances);
</tbody>
</table>
</div>
</div>
</div>
}
Put your "Method", "Controller" in (Html.BeginForm("TbMapViewEdit", "TbMap")) otherwise the form POST action will be set to the current location.
Model
Truncated for brevity. I have view model with List that I will be saving the data to and one other table just for displaying some info.
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; } = new List<TBMapBalances>();
[...]
}
Controller
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.UniqueAdp = TbListId.UniqueAdp;
}
}
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
Error that I was making here is that I was trying to replace the key with essentially the copy of itself (Find ID of 1 and set it to ID of 1) instead of picking up on the actual one field that I needed to edit which in my case was UniqueAdp.
Then came the new thing to me, which was Editor Template:
Editor Template
Create a folder called EditorTemplates in 'Shared' Folder under your 'Views' folder with the exact name of the model that you intend to edit. In my case the model was called TBMapBalances so I created a TBMapBalances.cshtml file inside the newly created folder, then pasted this (this was originally in my main view file):
#model ASPNET_Core_1_0.Models.TBMapBalances
<tr>
<td>
#Html.DisplayFor(Model => Model.TbMapId)
#Html.HiddenFor(Model => Model.TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.Line)</td>
<td>#Html.DisplayFor(Model => Model.MapResult)</td>
</tr>
By the way the new { #class = "control-label_DI" } is there to supposedly add class to each created input field. This doesn't seem to work in .net core and is left there just as a reminder to myself that I need to do this somehow.
Research:
Update multiple records at once in asp.net mvc
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms
http://www.binaryintellect.net/articles/b1e0b153-47f4-4b29-8583-958aa22d9284.aspx
How to bind an Array in MVC Core
https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/
ASP.NET Core 1.0 POST IEnumerable<T> to controller
This problem has 2 parts to it, the first is that there needed to be a way to edit collections of data. This can be solved with EditorTemplates, which involves creating a single editor model and then calling #Html.EditorFor(..) on the collection of items you wish to edit.
A minimal sample (Full Fx, not Core) is available on Github.
The second problem was with the way the entities were being updated, the property being changed was wrong and hence not saving (the PK was being updated to the PK) and the entity was being attached when it's already tracked.
foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
try
{
_context.Update(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
It's important to remember what Entity Framework does for you when you retrieve a model from the database. It is automatically tracked by the context, and so it's already attached and ready to update, anything you change will be automatically tracked and subsequently saved.
The call to _context.Update(..) tries to attach the non-tracked models (from your POSTed data) to the context based on ID, but because you've already pulled that ID out (in your .Where(..).FirstOrDefault(..)) it's already tracked, and so blows up.
Also given that this is EFC 1.0 and you have no .Find(..) method, using .SingleOrDefault(..) is probably a better method to use on a primary key field.
Your rewritten code could be as so:
foreach (var postedModel in tbMapViewModel.TBMapBalancesList)
{
var dbModel = _context.TBMapBalances.SingleOrDefault(p => p.TbMapId == postedModel.TbMapId);
if (dbModel != null)
{
dbModel.UniqueAdp = postedModel.UniqueAdp;
}
}
await _context.SaveChangesAsync();
For posterity though I wouldn't recommend it for security reasons, if you wanted to attach your whole posted model to the context (based on ID) and update it, you can do so with code similar to your original, removing the foreach loop:
_context.UpdateRange(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
(I don't recommend it because everything that was posted will then be set in the database, from experience it's advisable to only set the fields you're expecting to update as per the first code set. It should, however, be quicker than the foreach loop given that you're not loading from the database and saving back in, only the latter)
Do you have the inputs for the comments already built into the razor page? I do not see them. What you would want to do is create a form with the input types that you want for each item in the loop inside the loop. Each form would then reference the iterator as a hidden value to pass when posted. If the loop is foreach(var item in Model.items){} you would have a form containing the inputs in that block with a hidden input that looks like <input type="hidden" name="index" value="#item.index"/> This will allow you to post with whatever identifier you need to attach that specific form data to the correct model.
See this answer for the structure of the form inside the loop Multiple forms on one MVC form, created with a loop, only the first submits data
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I am trying to get my ‘manage’ link to redirect to the relevant account.
I have a BankAccsController which works for fine for viewing details for a logged in user and to see both accounts held in the database, but what I want to do is be able to click the corresponding button to the right of the account and for it to take me to the details for that specific account. I know I will have to create a new method to replace the current two ‘ViewCurrent and ViewSavings’ and use parameters but I’m unsure how to do it and can’t find the correct words to type it into Google.
Controller
//View all accounts of logged in user
[Authorize]
public ActionResult Index()
{
var userId = User.Identity.GetUserId();
var bankAccs = db.BankAccs.Where(a => a.AccUserId == userId).ToList();
return View(bankAccs.ToList());
}
////View current account of logged in user
public ActionResult ViewCurrent()
{
var userId = User.Identity.GetUserId();
ViewModels.CurrentAccVm bankVm = new ViewModels.CurrentAccVm();
bankVm.BankAccList = db.BankAccs.Where(a => a.AccUserId == userId && a.AccTypeId == 1).ToList();
bankVm.UserName = User.Identity.GetUserName();
return View(bankVm);
}
////View Savings account of logged in user
public ActionResult ViewSavings()
{
var userId = User.Identity.GetUserId();
ViewModels.CurrentAccVm bankVm = new ViewModels.CurrentAccVm();
bankVm.BankAccList = db.BankAccs.Where(a => a.AccUserId == userId && a.AccTypeId == 2).ToList();
bankVm.UserName = User.Identity.GetUserName();
return View(bankVm);
}
View
#model IEnumerable<Atm11.Models.BankAcc>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Balance)
</th>
<th>
#Html.DisplayNameFor(model => model.AccType.AccountType)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Balance)
</td>
<td>
#Html.DisplayFor(modelItem => item.AccType.AccountType)
</td>
<td>
#Html.ActionLink("Manage", "ViewAccounts", new { id = item.Id })
</td>
</tr>
}
</table>
You are so close! Add the below method to your controller. Your view is constructed correctly so no changes are needed to that file.
Controller
public ActionResult ViewAccounts(int id)
{
var myAccount = DB.Accounts.GetByID(id);
return View(myAccount);
}
Next, you will need to create a ViewAccounts view.
I currently have a for-loop in my .cshtml file which iterates through items inside a ViewBag. I want to sort these objects based on a DateTime property called orderDate in the Order.cs file. The for loop in the .cshtml file looks like the following:
<table id="order-history-table" class="table table-hover">
<thead>
<tr>
<th>
Order Number
</th>
<th>
Order Date
</th>
<th>
Order Total
</th>
</tr>
</thead>
<tbody>
#foreach (var item in ViewBag.list)
{
<tr>
<td>
<a class="orderNumber" data-toggle="modal" data-target="#modal-container" href="#Url.Action("orderDetails", "Orders", new { orderNumber = item.order.OrderNumber })">#item.order.OrderNumber</a>
<br />
#Html.ActionLink("Re-Order", "ReOrder", new { orderNumber = #item.order.OrderNumber }, new { onclick = "return confirm('Are you sure you wish to re-order?');" })
</td>
<td>#item.order.OrderDate.ToString("dd/MM/yyyy")</td>
<td style="text-align: center">$#(Math.Round(item.order.OrderTotal, 2))</td>
</tr>
}
</tbody>
I want this table to be sorted so that the most recent orders are shown at the top of the table. Since the data is being pulled from a database, and the tables values are generated dynamically, where do I perform this sorting? is it done through jQuery? How do I do this?
Thank you in advance
You can use LINQ OrderBy to do that.
So in your action method where you set the ViewBag.list, You can use OrderByDescending method on the collection.
var yourOrderList=new List<Order>();
// to do : Load Order items to yourOrderList
yourOrderList=yourOrderList.OrderByDescending(f=>f.OrderDate).ToList();
ViewBAg.list=yourOrderList;
I also recommend using a view model to pass data from your action method to view.
var yourOrderList=new List<Order>();
// to do : Add orders to yourOrderList
yourOrderList=yourOrderList.OrderByDescending(f=>f.OrderDate).ToList();
return View(yourOrderList);
And in your view
#model List<Order>
<h3>Orders</h3>
#foreach(var o in Model)
{
<p>#o.OrderDate.ToString()</p>
}
Since your view is strongly typed, you can do the same ordering in your razor view also as needed.
#model List<Order>
<h3>Orders</h3>
#foreach(var o in Model.OrderByDescending(g=>g.OrderDate))
{
<p>#o.OrderDate.ToString()</p>
}
I sent a viewdata to a view page like this.
public ActionResult SelectRes(int id)
{
var ResList = resrepository.GetAll();
ViewData["ResList"] = new SelectList(ResList, "ResId", "Res_KORNM");
return View(svcresrelationRepository.GetAll());
}
#foreach (var item in ViewData["ResList"] as List<ITSDapper.Dapper.Resource>)
{
<tr>
<td style="text-align:center;">
#Html.DisplayFor(a=> item.Res_KORNM)
</td>
</tr>
}
And I tried to display the viewdata in a view page but it's not worked.
(Object reference not set to an instance of an object)
How display the viewdata in foreach statement?
You would typically use a SelectList for data that is to be selected by a user.
If this is the intention you can just use an Html.DropDownList:
#Html.DropDownList("SelectedId",
(IEnumerable<SelectListItem>)ViewData["ResList"])
If you are simply needing to view the data, I would change your server-side code to use something other than a SelectList.
This could be done as follows:
Controller
public ActionResult SelectRes(int id)
{
var ResList = resrepository.GetAll();
ViewData["ResList"] = ResList;
return View(svcresrelationRepository.GetAll());
}
View
#foreach (var item in ViewData["ResList"] as List<ITSDapper.Dapper.Resource>)
{
<tr>
<td style="text-align:center;">
#Html.DisplayFor(a=> item.Res_KORNM)
</td>
</tr>
}
You are converting reference to wrong type, you are creating SelectList instance while in View you are convering it to IList<T>, you should convert it to SelectList:
#foreach (var item in ViewData["ResList"] as SelectList)
{
<tr>
<td style="text-align:center;">
#Html.DisplayFor(a=> item.Text) // note this as well
</td>
</tr>
}