Thanks Aaron, I'm not the best with unit of work. I add code as follows:
_unitOfWorkManager.Current.SaveChanges();
return;
and get 'System.NullReferenceException: 'Object reference not set to an instance of an object.' unitOfWorkManager has been successfuly injected but there is no Current property.
But I'm sure you're right and I'll persevere with unit of work and post back when and if I can get a solution. But before I go, how do I set the userId in the unit of work?
public void MakeNcEntityDisplayNameUnique(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(tenantId))
{
var adminUsers = _userManager.GetUsersInRoleAsync("Admin").GetAwaiter().GetResult();
var userId = adminUsers.Select(m => m.Id).FirstOrDefault();
using (_abpSession.Use(tenantId, userId))
{
Thanks again for the pointer.
I have an hourly job invoked from Hangfire which loops through each tenant and makes DisplayName unique for each entity in a given OrganizationUnit
public void MakeNcEntityDisplayNameUnique(int tenantId)
{
var adminUsers = _userManager.GetUsersInRoleAsync("Admin").GetAwaiter().GetResult();
var userId = adminUsers.Select(m => m.Id).FirstOrDefault();
using (_abpSession.Use(tenantId, userId))
{
var organizationUnits = _organizationUnitRepository.GetAllList();
foreach (var organizationUnit in organizationUnits)
{
var entities = _ncEntityRepository.GetAllList(e => e.OrganizationUnitId == organizationUnit.Id);
var duplicates = entities.GroupBy(x => x.DisplayName)
.Where(g => g.Count() > 1)
.Select(y => y.Key)
.ToList();
foreach (var duplicate in duplicates)
{
foreach (var entity in _ncEntityRepository.GetAllList(e => e.DisplayName == duplicate && e.OrganizationUnitId == organizationUnit.Id))
{
var surname = entity.GetData<AttributeDataDto>("Surname").Value;
var newDisplayName = entity.DisplayName + " " + surname.Substring(0, 1);
var entityExists = _ncEntityRepository.FirstOrDefault(e => e.DisplayName == newDisplayName && e.OrganizationUnitId == organizationUnit.Id);
if (entityExists != null)
{
entity.DisplayName = entity.DisplayName + " " + surname;
}
else
{
entity.DisplayName = newDisplayName;
}
_ncEntityRepository.Update(entity);
}
}
}
return;
}
}
When the update is persisted for each entity the value of LastModifierUserId is 1 - the admin of the host. I have attempted to set the session UserId in the first two lines of the code block. How do I get the value AbpSession.UserId to persist as the LastModifierUserId?
@maliming, Excellent, thank you.
@ryancyq,
I'm not sure what you mean. Where is StaticRoleDefintion
?
I have this in StaticRoleNames.cs
:
public static class StaticRoleNames
{
public static class Host
{
public const string Admin = "Admin";
}
public static class Tenants
{
public const string Admin = "Admin";
public const string Carer = "Carer";
public const string CareSenior = "Care Senior";
public const string CareManager = "Care Manager";
}
}
and this in AppRoleConfig.cs
:
public static class AppRoleConfig
{
public static void Configure(IRoleManagementConfig roleManagementConfig)
{
//Static host roles
roleManagementConfig.StaticRoles.Add(
new StaticRoleDefinition(
StaticRoleNames.Host.Admin,
MultiTenancySides.Host,
grantAllPermissionsByDefault: true)
);
//Static tenant roles
roleManagementConfig.StaticRoles.Add(
new StaticRoleDefinition(
StaticRoleNames.Tenants.Admin,
MultiTenancySides.Tenant,
grantAllPermissionsByDefault: true)
);
roleManagementConfig.StaticRoles.Add(
new StaticRoleDefinition(
StaticRoleNames.Tenants.Carer,
MultiTenancySides.Tenant)
);
roleManagementConfig.StaticRoles.Add(
new StaticRoleDefinition(
StaticRoleNames.Tenants.CareSenior,
MultiTenancySides.Tenant)
);
roleManagementConfig.StaticRoles.Add(
new StaticRoleDefinition(
StaticRoleNames.Tenants.CareManager,
MultiTenancySides.Tenant)
);
}
}
To call RoleManager.SetGrantedPermissionsAsync(staticRole.GrantedPermissions)
as you have suggested where do I put GrantedPermissions
? Could you give me an example?
Furthermore, in the interests of DRY how do I call the same permissions from thye seed process in EntityFrameworkCore?
When creating a new tenant with TenantManager.CreateWithAdminUserAsync
what is the best practise for creating permissions following the creation of static roles? The method creates my static roles thus:
CheckErrors(await _roleManager.CreateStaticRoles(tenant.Id));
await _unitOfWorkManager.Current.SaveChangesAsync(); //To get static role ids
I have a static role called "Carer" and in my TenantRoleAndUserBuilder.CreateRolesAndUsers
method I have the following code block:
var carerRole = _context.Roles.IgnoreQueryFilters().FirstOrDefault(r => r.TenantId == _tenantId && r.Name == StaticRoleNames.Tenants.Carer);
if (carerRole == null)
{
carerRole = _context.Roles.Add(new Role(_tenantId, StaticRoleNames.Tenants.Carer, StaticRoleNames.Tenants.Carer) { IsStatic = true, IsDefault = true }).Entity;
_context.SaveChanges();
//Grant selected permissions to carer role
var permissions = PermissionFinder
.GetAllPermissions(new AppAuthorizationProvider(false))
.Where(p => p.MultiTenancySides.HasFlag(MultiTenancySides.Tenant))
.ToList();
foreach (var permission in permissions.OrderBy(m => m.Name))
{
if (
permission.Name == "Pages.Tenant.Dashboard" ||
permission.Name == "Pages.Tenant.Dashboard.FormSubmissionActivityChart" ||
permission.Name == "Pages.NcEntities" ||
permission.Name == "Pages.NcEntity.Display" ||
permission.Name == "Pages.NcEntity.Display.Dashboard" ||
permission.Name == "Pages.NcEntity.Display.Dashboard.FluidPieChart" ||
permission.Name == "Pages.NcEntity.Display.Dashboard.FoodChart" ||
permission.Name == "Pages.NcEntity.Display.Dashboard.SSkinCareBundle" ||
permission.Name == "Pages.NcEntity.Display.Dashboard.FormSubmissionActivityChart" ||
permission.Name == "Pages.NcEntity.Display.Dashboard.FormSubmissionActivityByStaffChart" ||
permission.Name == "Pages.NcEntity.Display.Dashboard.WarningsBarChart" ||
permission.Name == "Pages.NcEntity.Display.Profile" ||
permission.Name == "Pages.NcEntity.Display.Profile.Metrics" ||
permission.Name == "Pages.NcEntity.NcCarePlans" ||
permission.Name == "Pages.NcEntity.NcCarePlan.Display" ||
permission.Name == "Pages.NcEntity.NcWarnings" ||
permission.Name == "Pages.NcEntity.NcWarning.Display" ||
permission.Name == "Pages.NcEntity.Display.FormSubmissions" ||
permission.Name == "Pages.NcEntity.Display.Media")
{
_context.Permissions.Add(
new RolePermissionSetting
{
TenantId = _tenantId,
Name = permission.Name,
IsGranted = true,
RoleId = carerRole.Id
});
}
}
_context.SaveChanges();
}
Using DRY principles, what is the best way to refactor this code?
Thanks @ryancyq. So, if I have four tenants all with the same project manager I can link accounts in all tenants to make it easier for the project manager to traverse between tenants without having to login and out again. I think I get the drift now. I wasn't aware that accounts could be linked between tenants, I thought it was host to tenant only. This is the system which just keeps on giving!
GSuite? Is that one of the X-Men? So it's a simple way of collection users you might want to impersonate as opposed to having to go to the tenant and "Login as this user"?
Methinks perhaps one for @ismcagdas;
What is the business case for linked users? Under what circumstances do I need to link to a user when I can simply login as a user? I am sure this functionality would be useful to me but I cannot see the circumstances under which I would need to use it.
Cheers, Bob
For anyone interested this was the solution though I still can't see why I can't set the session on the server....
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
tenant: Tenant;
constructor(private storage: Storage) {
this.storage.get(TENANT_KEY)
.then((data) => {
this.tenant = JSON.parse(data);
});
}
intercept(req: HttpRequest<any>, next: HttpHandler) {
//Get the auth token from the service.
return Observable
.fromPromise(this.storage.get(TOKEN_KEY))
.flatMap((token) => {
// Clone the request and replace the original headers with
// cloned headers, updated with the authorization.
const authReq = req.clone({
headers: req.headers
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${token}`)
.set('Abp.TenantId', this.tenant.tenantId.toString())
});
// send cloned request with header to the next handler.
return next.handle(authReq);
})
}
@ryancyq, thanks. here is my authorization interceptor for the angular app:
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private storage: Storage) { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
// Get the auth token from the service.
return Observable
.fromPromise(this.storage.get(TOKEN_KEY))
.flatMap((token) => {
// Clone the request and replace the original headers with
// cloned headers, updated with the authorization.
const authReq = req.clone({
headers: req.headers
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${token}`)
});
// send cloned request with header to the next handler.
return next.handle(authReq);
})
}
Everyone keeps telling me to set Abp.TenantId in the HTTP header and then directing me to a section in the documentation which tells me how the system then gets this value. My tenant id is held in app storage, the question is how do I SET it in the above? And, on the server why doesn't this work?
using (_unitOfWorkManager.Current.SetTenantId(model.TenantId))
And, this?
AbpSession.Use(model.TenantId, null);
It seems like a lot of work, having to add to the http header, why is not possible to set it on the server?
Cheers, Bob