Open squashleague opened 2 years ago
All this information is in the database table dbo.AuditTrails (assuming your class inherits AuditableEntity
Craig
@MomboMan I think the main issue is that currently the CreatedBy stores the user id, the dbo.AuditTrails have the info, the problem is that if you want to link that to the users name, first name, last name how would you go about it?
I don't think you should be looking up dbo.AuditTrails in order to get the username in the product listing page
How would you populate the username here?
Product Listing | Name | Price | User |
---|---|---|---|
Product1 | $100 | User1 | |
Product2 | $60 | User2 | |
Product3 | $70 | User1 |
When you have a user id, you can simply fetch the user details using the IUserService. There's a GetAsync method there which returns a UserResponse which has all the data you need.
I don't think you should be looking up dbo.AuditTrails in order to get the username in the product listing page
If your trying to find out who Created the brand then you're going to be digging in the AuditTrails table anyway, yes?
This is my code in AuditTrails.razor.cs. I'm not just showing what changed, I'm wanting to show who changed what*:
private async Task GetDataAsync()
{
var users = await UserManager.GetAllAsync();
if (users.Succeeded) {
var response = await AuditManager.GetCurrentUserTrailsAsync();
if (response.Succeeded) {
var query = from evnt in response.Data
join user in users.Data on evnt.UserId equals user.Id
select ( new RelatedAuditTrail {
AffectedColumns = evnt.AffectedColumns,
DateTime = evnt.DateTime,
Id = evnt.Id,
NewValues = evnt.NewValues,
OldValues = evnt.OldValues,
PrimaryKey = evnt.PrimaryKey,
TableName = evnt.TableName,
Type = evnt.Type,
UserName = user.UserName,
LocalTime = DateTime.SpecifyKind(evnt.DateTime, DateTimeKind.Utc).ToLocalTime()
});
Trails = query.ToList();
}
else {
foreach (var message in response.Messages) {
_snackBar.Add(message, Severity.Error);
}
}
}
}
Then the code from AuditTrails.razor:
<RowTemplate>
<MudTd DataLabel="Id">@context.Id</MudTd>
<MudTd DataLabel="Name">
<MudHighlighter Text="@context.TableName" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Date">
<MudItem>
<MudChip Icon="@Icons.Material.Filled.Watch" IconColor="Color.Secondary" Label="true" Color="Color.Surface">@_localizer["Local"] : @context.LocalTime.ToString("G", CultureInfo.CurrentCulture)</MudChip>
</MudItem>
<MudItem>
<MudChip Icon="@Icons.Material.Filled.Watch" IconColor="Color.Secondary" Label="true" Color="Color.Surface">@_localizer["User"] : @context.UserName</MudChip>**
</MudItem>
</MudTd>
<MudTd DataLabel="Tax">@context.Type</MudTd>
<MudTd Style="text-align:right">
<MudButton Variant="Variant.Filled" DisableElevation="true" EndIcon="@Icons.Filled.KeyboardArrowDown" IconColor="Color.Secondary" OnClick="@(() => ShowBtnPress(context.Id))">@((context.ShowDetails == true)? _localizer["Hide"] : _localizer["Show"]) @_localizer["Trail Details"]</MudButton>
</MudTd>
</RowTemplate>
Lastly this is the inherited class, RelatedAuditTrail, mentioned above in AuditTrails.razor.cs:
public class RelatedAuditTrail : AuditResponse
{
public bool ShowDetails { get; set; } = false;
public DateTime LocalTime { get; set; }
public string UserName { get; set; }
}
*I still have to change the AuditService to get all trails and not just the current user.
Craig
When you have a user id, you can simply fetch the user details using the IUserService. There's a GetAsync method there which returns a UserResponse which has all the data you need.
@fretje cheers for the repsonse, the problem I see is that if you are are showing a listing page you might have 50 records so for each userid you might be doing 50 requests GetAsync just to get the username
If your trying to find out who Created the brand then you're going to be digging in the AuditTrails table anyway, yes?
@MomboMan I do understand that the information is available in the AuditTrails and I can get the user information.
I think to simplify things I'll explain it this way. What I want to achieve is for the Products Page.
Can you show me how you would modify the Pages/Catalog/Products.razor file so that you can show 1 extra column username, see the following example. How can the username be populated?
<MudTd DataLabel="Name">
<MudHighlighter Text="@context.Name" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Brand">
<MudHighlighter Text="@context.Brand" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Description">
<MudHighlighter Text="@context.Description" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Barcode">
<MudHighlighter Text="@context.Barcode" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Username">
@context.Username <----------------------------------------------------
</MudTd>
Possible Solution One way I was thinking was to introduce my own Users table that links to the Identity user table via the Id. Then when a user registers you populate both tables. Throughout the project I just care about my Users table for additional info. Not sure if it's the right way to go about things?
Appreciate your feedback
@fretje cheers for the repsonse, the problem I see is that if you are are showing a listing page you might have 50 records so for each userid you might be doing 50 requests GetAsync just to get the username
This is a case for caching I would think. Or otherwise fetch the whole list of users (GetAllAsync) and then map the userid's to the names... in that case it's only 2 requests: one to get the listing page and one to get all the users. You then kind of "join" those 2 together in code in stead of in sql.
@fretje cheers for the repsonse, the problem I see is that if you are are showing a listing page you might have 50 records so for each userid you might be doing 50 requests GetAsync just to get the username
This is a case for caching I would think. Or otherwise fetch the whole list of users (GetAllAsync) and then map the userid's to the names... in that case it's only 2 requests: one to get the listing page and one to get all the users. You then kind of "join" those 2 together in code in stead of in sql.
Yeah that's how I currently ended up doing it for now, but just thought there would be a better way to do it
@dotnetshadow
Reread the GetDataAsync() method above. It performs a join on the two tables and inserts the UserName into the new class RelatedAuditTrail.
You want something similar. So you would add this to Products.razor.cs:
// Top of the file
public List<ExtendedProductsPage> Extended= new();
// bottom of file
public class ExtendedProductsPage : GetAllPagedProductsResponse
{
public string UserName { get; set; }
}
And also in Products.razor.cs change
private MudTable<GetAllPagedProductsResponse> _table;
To
private MudTable<ExtendedProductsPage> _table;
then in Products.razor
<RowTemplate>
<MudTd DataLabel="Id">@context.Id</MudTd>
<MudTd DataLabel="Name">
<MudHighlighter Text="@context.Name" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Brand">
<MudHighlighter Text="@context.Brand" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Description">
<MudHighlighter Text="@context.Description" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="Barcode">
<MudHighlighter Text="@context.Barcode" HighlightedText="@_searchString" />
</MudTd>
<MudTd DataLabel="User Name">
<MudHighlighter Text="@context.UserName" HighlightedText="@_searchString" />
</MudTd>
...
And then change the GetProductsAsync() into something close to the GetAllAsync() above. Replacing the Audit stuff with Products stuff. I'd write it but I would need to change too much of my code. You will need to get rid of the pagination stuff and write a GetAllAsync() method under the Application branch. (p.s. I've never got the pagination stuff to work properly.)
Or at least that should be close.
Craig
@MomboMan Awesome thanks, much appreciated. I can see that you are pretty much grabbing the list of users upfront then joining to it. A similar approach to @fretje. That was my original implementation even before adding to this discussion. Maybe @iammukeshm might have an alternative approach
@fretje cheers for the repsonse, the problem I see is that if you are are showing a listing page you might have 50 records so for each userid you might be doing 50 requests GetAsync just to get the username
This is a case for caching I would think. Or otherwise fetch the whole list of users (GetAllAsync) and then map the userid's to the names... in that case it's only 2 requests: one to get the listing page and one to get all the users. You then kind of "join" those 2 together in code in stead of in sql.
Yeah that's how I currently ended up doing it for now, but just thought there would be a better way to do it
Testing an implementation myself, and need that to work, and loading all users will not work at scale (we have over 100k users so far). ONe thought I had was to make the API call a ViewModel, with the with Viewmodel a complex view-model(s) call (joined entities) return the viewModel, with the link (name, email, etc of what you want returned for the user link...
Describe your documentation need There should be some documentation on how to connect entities to the BlazorHeroUser
Requirement
Expected behavior An example of how the product listing page could potentially show the user (username, full name etc) who created the product
It seems like a common task that perhaps should have some guidence? In my particular case I have a an entity with an owned entity so I'm not sure how to follow the ChatHistory example totally
Similar questions have been asked before
239
248
261