Skip to content

Commit c0c36a8

Browse files
committed
Merge branch 'dev'
2 parents d0c7914 + 3df0435 commit c0c36a8

28 files changed

+2219
-7443
lines changed
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Switching Between Organization Units
2+
3+
In most companies, a user belongs to more than one organization. Also, in some applications, we need to filter the data shown depending on the logged-in user's organization. For such scenarios, allowing users to select one of the organizations they belong to is a good practice.
4+
5+
For creating a custom data filter, you can check [https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-data-filter-ef-core](https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-data-filter-ef-core).
6+
7+
In order to allow users to change their active organization unit, we can design a UI like the one below;
8+
9+
![image](/Images/Blog/switch-ou-sample-screenshot.jpg)
10+
11+
By using this dropdown, a user can switch between organization units.
12+
13+
## Implementing Organization Unit Switching
14+
15+
In this section, we'll dive into the implementation details of enabling users to switch between different organizational units within an ASP.NET Zero project.
16+
17+
### Creating Claims Principal for Organization Unit
18+
19+
In this section, we create this functionality by overriding the **CreateAsync** function within the `UserClaimsPrincipalFactory` class. This modification ensures that we can access the organizational unit information for logged-in users.
20+
21+
```csharp
22+
public override async Task<ClaimsPrincipal> CreateAsync(User user)
23+
{
24+
var userWithOrgUnits = await GetUserWithOrganizationUnitsAsync(user.Id);
25+
26+
var claim = await base.CreateAsync(userWithOrgUnits);
27+
28+
string organizationUnitsJson = JsonSerializer.Serialize(userWithOrgUnits.OrganizationUnits.First());
29+
30+
claim.Identities.First().AddClaim(new Claim("Organization_Unit", organizationUnitsJson));
31+
32+
return claim;
33+
}
34+
```
35+
36+
We are overwriting the **CreateAsync** method of `UserClaimsPrincipalFactory` to store user's selected organization unit in claims principal.
37+
38+
Within this context, we add the first element of the Organization Unit as a claim during user principal creation.
39+
40+
```csharp
41+
private async Task<User> GetUserWithOrganizationUnitsAsync(long userId)
42+
{
43+
return await UserManager.Users
44+
.Include(u => u.OrganizationUnits)
45+
.Where(x => x.Id == userId)
46+
.FirstOrDefaultAsync();
47+
}
48+
```
49+
50+
Private `GetUserWithOrganizationUnitsAsync` method retrieves a user with user's associated organization units from the database.
51+
52+
Now, we can use `Organization_Unit` claim as the currently selected organization unit for logged-in user and filter any data using its value.
53+
54+
### Modifying the Organization Unit in Session
55+
56+
Now, we must allow logged-in user to switch selected organization unit. To do that, we first need to add a new field to `AbpSession` by following this [document](https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-session-field-aspnet-core).
57+
58+
```csharp
59+
public class MyAppSession : ClaimsAbpSession, ITransientDependency
60+
{
61+
private const string OrganizationUnitClaimTypeName = "Organization_Unit";
62+
private readonly UserManager _userManager;
63+
public MyAppSession(
64+
IPrincipalAccessor principalAccessor,
65+
IMultiTenancyConfig multiTenancy,
66+
ITenantResolver tenantResolver,
67+
IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider,
68+
UserManager userManager) :
69+
base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
70+
{
71+
_userManager = userManager;
72+
}
73+
74+
public string OrganizationUnit => PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == OrganizationUnitClaimTypeName)?.Value;
75+
76+
public async Task SwitchOrganizationUnitAsync(UserOrganizationUnit newUserOrganizationUnit)
77+
{
78+
var oldOrganizationUnit = JsonSerializer.Deserialize<UserOrganizationUnit>(OrganizationUnit);
79+
80+
SetOrganizationUnit(newUserOrganizationUnit);
81+
82+
await _userManager.RemoveFromOrganizationUnitAsync(oldOrganizationUnit.UserId, oldOrganizationUnit.OrganizationUnitId);
83+
}
84+
85+
private void SetOrganizationUnit(string newOrganizationUnit)
86+
{
87+
var identity = PrincipalAccessor.Principal?.Identity as ClaimsIdentity;
88+
if (identity != null)
89+
{
90+
var oldOrganizationUnitClaim = identity.FindFirst(OrganizationUnitClaimTypeName);
91+
if (oldOrganizationUnitClaim != null)
92+
{
93+
identity.RemoveClaim(oldOrganizationUnitClaim);
94+
}
95+
96+
identity.AddClaim(new Claim(OrganizationUnitClaimTypeName, newOrganizationUnit));
97+
}
98+
}
99+
}
100+
```
101+
102+
With these additions, the `MyAppSession` class now includes methods to both retrieve and set the organization unit information in the session. This enables management of organization unit data throughout the user's session.
103+
104+
### Usage Example: Switching Organization Units
105+
106+
In this section, we will demonstrate how to use a method for switching a user's organization unit within a new service class. The following example shows the `OrganizationUnitManager` class, which uses `MyAppSession` to allow a user to change currently selected organization unit.
107+
108+
```csharp
109+
public class OrganizationUnitManager
110+
{
111+
private readonly MyAppSession _myAppSession;
112+
private readonly UserManager _userManager;
113+
114+
public OrganizationUnitManager(MyAppSession myAppSession, UserManager userManager)
115+
{
116+
_myAppSession = myAppSession;
117+
_userManager = userManager;
118+
}
119+
120+
public async Task SwitchOrganizationUnitAsync(UserOrganizationUnit newUserOrganizationUnit)
121+
{
122+
var oldOrganizationUnit = JsonSerializer.Deserialize<UserOrganizationUnit>(_myAppSession.OrganizationUnit);
123+
124+
_myAppSession.SetOrganizationUnit(newUserOrganizationUnit);
125+
126+
if (oldOrganizationUnit != null)
127+
{
128+
await _userManager.RemoveFromOrganizationUnitAsync(oldOrganizationUnit.UserId, oldOrganizationUnit.OrganizationUnitId);
129+
}
130+
131+
await _userManager.AddToOrganizationUnitAsync(newUserOrganizationUnit.UserId, newUserOrganizationUnit.OrganizationUnitId);
132+
}
133+
}
134+
```
135+
136+
#### Usage Steps
137+
138+
So, we completed the backend side to switch user's current organization unit in claims principal. So, let's see how we can use it from the client side.
139+
140+
1. **Fill the user's organization units**: As shown in the image above in the introcution section, we need to fill the user's organization units to a dropdown. To do this, you can use `_userManager.GetOrganizationUnitsAsync(user)` and map its result to a DTO class and return it to client and fill the dropdown using returned list.
141+
2. **Retrieve the Current Organization Unit:** The oldOrganizationUnit is deserialized from the current session's OrganizationUnit claim. So, you can use this value to set selected value of the organization unit dropdown.
142+
3. **Set the New Organization Unit:** On the change event of the dropdown, call an API endpoint (This is not explained above but you can use ProfileAppService and create a new method in this application service class) and this endpoint should call;
143+
144+
```csharp
145+
public async Task ChangeOrganizationUnit(UserOrganizationUnit newUserOrganizationUnit){
146+
await _organizationUnitManager.SwitchOrganizationUnitAsync(newUserOrganizationUnit);
147+
}
148+
```
149+
150+
So, that's all and our app allows users to change their active organization unit on the UI.

0 commit comments

Comments
 (0)