User Information on Entities

Thanks Atanas, my basic implementation of what I've learnt today seems to fail to expose the username.

I have implemented the partial class for overriding the Saving function and can access the properties to update the modified and creation dates but I can't seem to figure how to expose the username for the CreatedBy and ModifiedBy properties.

I look forward to your demo. Thanks.

This tutorial show how to get the current user name.

1 Like

Yeah I'd seen that but in the SaveChanges function I can't seem to figure out how to access the ```
HttpContext. I can see how that would work from the controller but I was trying to handle all this from that override function.

You are right. There is no easy way to get that in the EF context class at the moment. Will have to inject it during code generation. Right now you can get it only from the Controller classes. This would allow you to set the CreatedBy/UpdatedBy fields but you will have to create a partial class for every controller which is less than ideal. We just have to release a new Radzen version that takes care of this.

@mumfie, how did you manage to pass in the username?

We just released Radzen 2.12.1 which has the needed changes. Also we released the AuditTrail sample application.

There are two custom files that you need to replicate in your application - server\Startup.Audit.cs and server\data\AuditTrailDbContext.Audit.cs (you should create a partial class for the DB context that Radzen has generated for your database).

The actual logging is done in the Audit method. You can rename the properties that the sample is using to match your existing DB schema, perform additional logging etc:

public static void Audit(EntityEntry entityEntry, string userName)
{
     var type = entityEntry.Entity.GetType();
     if (entityEntry.State == EntityState.Added)
     {
          SetProperty(type, entityEntry, "CreatedBy", userName);
          SetProperty(type, entityEntry, "CreatedAt", DateTime.UtcNow);
     }
     else if (entityEntry.State == EntityState.Modified)
     {
          SetProperty(type, entityEntry, "UpdatedBy", userName);
          SetProperty(type, entityEntry, "UpdatedAt", DateTime.UtcNow);
     }
}
4 Likes

Now that's what I call support. Thank you so much @korchev!

1 Like

I've got time to try and implement this, this morning and I'm running into an issue in the SaveChanges override.

It's failing on this line : var userName = httpAccessor.HttpContext.User.FindFirst(ClaimTypes.Name).Value;

I can see that it's failing to get the logged in userName. Diving into the identity I can see it looks like the logged in user isn't authenticated(?), I think. I've logged the user out and logged it back in and still can't get it to find the username.

Thanks.

This will happen if the first step is missing - the Startup.Audit.cs file.

Typo alert! :sob:

Thanks @korchev

I have been looking at implementing this today in my Blazor sample application and I seem to be running into a problem because the httpAccessor isn't injected into the DbContext when the project is built. I have implemented security I can see by looking at the DbContext that the httpAccessor doesn't exist.

Would it be an issue to implement this in the Blazor projects too?

Thank you.

The current user in Blazor is obtained by other means: https://www.radzen.com/documentation/blazor/get-current-user/

Ok, thanks for the link.

I've added:

private readonly SecurityService security;

public PaccorDbContext(DbContextOptions<PaccorDbContext> options, SecurityService security) : this(options)
{
    this.security = security;
}

To my MyContext.Audit.cs class file. and in the SaveChanges() method changed:

var userName = httpAccessor.HttpContext.User.FindFirst(ClaimTypes.Name).Value;

to:

var userName = security.User.UserName;

The application builds and starts but I get this error in chrome just as the pages were about to load:

InvalidOperationException: No authentication handler is registered for the scheme 'Bearer'. The registered schemes are: Identity.Application, Identity.External, Identity.TwoFactorRememberMe, Identity.TwoFactorUserId. Did you forget to call AddAuthentication().Add[SomeAuthHandler]("Bearer",...)?

In Blazor you don't need the Startup.Audit.cs file.

Of course.

Thanks a million! :slight_smile:

Is there a way also to track the changes made? Like for example , an end user edited a Price from $100 to $150 , Admins should be able to see who changes , what time which I believe is already solved, and in addition to that admin should be able to see what records where changed per field and what are the values.

I have done many of these audit log services in the past and a simple way to record change data is to have an XML or JSON based post of pre and post data,
i.e. the audit table could be something like below;

Guid Id {get;set;}
DateTime datetime {get;set;}
String Action {get;set;} <-- i.e. this could be the API reference PUT / POST / etc.
String Table {get;set;}
String ReferenceId {get set}
String OriginalUserId {get;set;}
String originalRecord {get;set;} <-- this is a JSON/XML snapshot of the original record
String UpdatingUserId {get;set;}
String UpdatedRecord {get;set;} <-- this is a JSON / XML snapshot of the updated record

what this does is provide a date time of the change, what the change was and who did it.

from there it's up to the system administrators to pull the records apart as required which is typically not a business function.

ideally this would be a simple configurable option to enable / disable in a radzen project, i.e. Enable Audit log? Y / N

For angular, there is a sample project but the changes need to be implemented in the app. radzen-examples/AuditTrail at master · radzenhq/radzen-examples · GitHub
image

Hi,

I have added the AuditTrail segment as per this post. I am doing this in a Blazor WASM 7.0 project

public IDFContext(DbContextOptions options, SecurityService security) : base(options)
{
this.security = security;
}

but I am getting an error in this section of the SecurityServices.cs
""HttpNavigationManager has not been initialized"

public SecurityService(NavigationManager navigationManager, IHttpClientFactory factory)
{
this.baseUri = new Uri($"{navigationManager.BaseUri}odata/Identity/");
this.httpClient = factory.CreateClient("Idf.Server");
this.navigationManager = navigationManager;
}

Hi @Indesign_Services,

Indeed Blazor WASM requires a different approach as the SecurityService won't work in a controller. You can check this article: Customize CRUD pages | Create a Blazor CRM application with Radzen Blazor Studio | Radzen Blazor Studio