WAASUL
WAASUL
CC#
Created by WAASUL on 12/17/2024 in #help
Include directory only in DEBUG.
Is there a way to only include a directory in debug. It's an asp net core web app?
12 replies
CC#
Created by WAASUL on 12/10/2024 in #help
Audit Trails
How would I go about implementing an audit trails system in my ef core web api. I need to track, when something is changed, by whom, when etc. Does anyone have an example?
4 replies
CC#
Created by WAASUL on 12/4/2024 in #help
Data retrieval EF-core
private static readonly Expression<Func<Shift, ShiftDTO>> MapShiftToDto = shift => new ShiftDTO
{
Id = shift.Id,
Date = shift.Date,
Start = shift.Start,
End = shift.End,
Break = shift.Break,
Overtime = shift.Overtime,
DepartmentId = shift.DepartmentId,
Department = new DepartmentDTO
{
Id = shift.DepartmentId,
Name = shift.Department.Name,
Color = shift.Department.Color,
}
};
private static readonly Expression<Func<Shift, ShiftDTO>> MapShiftToDto = shift => new ShiftDTO
{
Id = shift.Id,
Date = shift.Date,
Start = shift.Start,
End = shift.End,
Break = shift.Break,
Overtime = shift.Overtime,
DepartmentId = shift.DepartmentId,
Department = new DepartmentDTO
{
Id = shift.DepartmentId,
Name = shift.Department.Name,
Color = shift.Department.Color,
}
};
I have a question regarding this. The department info is quite important on the client side. I do have a query that fetches the departments. I could for instance just get the department on the client side. The problem is that it makes the process very complicated. Also what if the departments on the client side are dirty meaning outdated or another user just created another department on some other device. All of a sudden the department will not be available. Should i also return the department when returning the shifts?
4 replies
CC#
Created by WAASUL on 12/3/2024 in #help
Azure Managed Identity
if (!builder.Environment.IsProduction()) return;

// Retrieve configuration settings
var configuration = builder.Configuration;

try
{
// Add Azure Key Vault
var keyVaultEndpoint = new Uri(configuration[Configs.KeyVaultUrl]!);
var azureCredential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = Configs.ManagedIdentityClientId,
});
var secretClient = new SecretClient(keyVaultEndpoint, azureCredential);
configuration.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
}
catch (Exception e)
{
Log.Error(e.Message);
}
if (!builder.Environment.IsProduction()) return;

// Retrieve configuration settings
var configuration = builder.Configuration;

try
{
// Add Azure Key Vault
var keyVaultEndpoint = new Uri(configuration[Configs.KeyVaultUrl]!);
var azureCredential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = Configs.ManagedIdentityClientId,
});
var secretClient = new SecretClient(keyVaultEndpoint, azureCredential);
configuration.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
}
catch (Exception e)
{
Log.Error(e.Message);
}
I have spent almost 8 hours trying to find the issue. For some reason managed identity is not working in production mode. It seems to be unable to load the secrets. Even though I have successfully defined the resource access polices. The code was working fine before I went to sleep. All of a sudden when I woke up its not working? Can anyone please help?
25 replies
CC#
Created by WAASUL on 12/2/2024 in #help
✅ Ef-core query efficiency?
I have a question regarding querying data. Which one is more efficient since I only need the department ids. The first one
private async Task<OrganizationMemberDTO?> RetrieveMemberAsync(CancellationToken ct)
{
return await db.OrganizationMembers
.AsNoTracking()
.Where(e => e.Id == MemberId && e.OrganizationId == OrganizationId)
.Select(e => new OrganizationMemberDTO
{
Id = e.Id,
Position = e.Position,
Departments = e.Departments.Select(d => d.DepartmentId)
}).FirstOrDefaultAsync(ct);
}
private async Task<OrganizationMemberDTO?> RetrieveMemberAsync(CancellationToken ct)
{
return await db.OrganizationMembers
.AsNoTracking()
.Where(e => e.Id == MemberId && e.OrganizationId == OrganizationId)
.Select(e => new OrganizationMemberDTO
{
Id = e.Id,
Position = e.Position,
Departments = e.Departments.Select(d => d.DepartmentId)
}).FirstOrDefaultAsync(ct);
}
or second one
private async Task<OrganizationMember?> RetrieveMemberAsync(CancellationToken ct)
{
return await db.OrganizationMembers
.AsNoTracking()
.Include(e => e.Departments)
.FirstOrDefaultAsync(e => e.Id == MemberId && e.OrganizationId == OrganizationId, ct);
}
private async Task<OrganizationMember?> RetrieveMemberAsync(CancellationToken ct)
{
return await db.OrganizationMembers
.AsNoTracking()
.Include(e => e.Departments)
.FirstOrDefaultAsync(e => e.Id == MemberId && e.OrganizationId == OrganizationId, ct);
}
27 replies
CC#
Created by WAASUL on 11/26/2024 in #help
Time conversion issue?
/// <summary>
/// Validates if the start and end times are in the future.
/// </summary>
/// <param name="date">The date of the shift (in local time).</param>
/// <param name="start">The start time of the shift (in local time).</param>
/// <param name="end">The end time of the shift (in local time).</param>
/// <returns>True if the shift is in the future; otherwise, false.</returns>
public static bool IsScheduledInFuture(DateTime date, TimeOnly start, TimeOnly end)
{
// Combine the provided date with the start and end times to create local DateTime values
var localStart = date.Date.Add(start.ToTimeSpan());
var localEnd = end > start
? date.Date.Add(end.ToTimeSpan()) // Same day
: date.Date.AddDays(1).Add(end.ToTimeSpan()); // Next day for overnight shifts

// Convert local times to UTC explicitly
var utcStart = localStart.ToUniversalTime();
var utcEnd = localEnd.ToUniversalTime();

// Get current UTC time
var now = DateTime.UtcNow;

// Ensure the shift starts and ends in the future
return utcStart > now && utcEnd > now;
}
/// <summary>
/// Validates if the start and end times are in the future.
/// </summary>
/// <param name="date">The date of the shift (in local time).</param>
/// <param name="start">The start time of the shift (in local time).</param>
/// <param name="end">The end time of the shift (in local time).</param>
/// <returns>True if the shift is in the future; otherwise, false.</returns>
public static bool IsScheduledInFuture(DateTime date, TimeOnly start, TimeOnly end)
{
// Combine the provided date with the start and end times to create local DateTime values
var localStart = date.Date.Add(start.ToTimeSpan());
var localEnd = end > start
? date.Date.Add(end.ToTimeSpan()) // Same day
: date.Date.AddDays(1).Add(end.ToTimeSpan()); // Next day for overnight shifts

// Convert local times to UTC explicitly
var utcStart = localStart.ToUniversalTime();
var utcEnd = localEnd.ToUniversalTime();

// Get current UTC time
var now = DateTime.UtcNow;

// Ensure the shift starts and ends in the future
return utcStart > now && utcEnd > now;
}
What am I missing here? The date, start and end are in local. For some reason this is returning true even though if the current start and end are in the past? I have written some tests, all of them are passing locally. When testing from the client, the behavior is not the same. The client is calling the server.
11 replies
CC#
Created by WAASUL on 10/7/2024 in #help
✅ Ef Core unexpected query behaviour
var memberships = await db.OrganizationMembers.AsTracking()
.Include(om => om.Organization)
.Where(om => om.UserId == user.Id && om.InvitationStatus == InvitationStatus.Approved &&
om.Role > MemberRole.None)
.ToListAsync(cancellationToken);
var memberships = await db.OrganizationMembers.AsTracking()
.Include(om => om.Organization)
.Where(om => om.UserId == user.Id && om.InvitationStatus == InvitationStatus.Approved &&
om.Role > MemberRole.None)
.ToListAsync(cancellationToken);
/// <summary>
/// Represents an enumeration of member roles.
/// </summary>
public enum MemberRole
{
/// <summary>
/// Represents the None role.
/// </summary>
None,

/// <summary>
/// Represents the Read role.
/// </summary>
Read,

/// <summary>
/// Represents the Write role.
/// </summary>
Write,

/// <summary>
/// Represents the Administrator role.
/// </summary>
Admin
}
/// <summary>
/// Represents an enumeration of member roles.
/// </summary>
public enum MemberRole
{
/// <summary>
/// Represents the None role.
/// </summary>
None,

/// <summary>
/// Represents the Read role.
/// </summary>
Read,

/// <summary>
/// Represents the Write role.
/// </summary>
Write,

/// <summary>
/// Represents the Administrator role.
/// </summary>
Admin
}
For some reason it's not including MemberRole.admin. It's including MemberRole.Write?
68 replies
CC#
Created by WAASUL on 9/8/2024 in #help
✅ Navigation Property
Any reason as to why I'm getting this message/warning?
The relationship defined by this property contributes to a dependency loop.
The relationship defined by this property contributes to a dependency loop.
20 replies
CC#
Created by WAASUL on 9/3/2024 in #help
Caching?
I have been meaning to ask a question about caching. How do you determine what data to cache and when?
6 replies
CC#
Created by WAASUL on 6/23/2024 in #help
Dublicate Properties in Tables?
I have a table named Users and one named Organizations. The relation between them is many to many. The joining table is called OrganizationMember. I'm wondering whether it would be a good idea, to store certain properties from the User table inside the OrganizationMember. Such as Email, & Full Name and AvatarUrl. The reason being is that OrganizationMember will be used very much. I don't want to modify my queries to include the User every time. Any suggestions would be appreciated.
6 replies
CC#
Created by WAASUL on 5/19/2024 in #help
Query improvement suggestions
/// <summary>
/// Retrieves roles assigned to the user within organizations.
/// </summary>
/// <param name="userId">The ID of the user.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The roles assigned to the user within organizations.</returns>
private async Task<Dictionary<Guid, MemberRole>> GetOrganizationRolesAsync(Guid userId,
CancellationToken cancellationToken)
{
// Get all organizations the user is a member of
var userOrganizations = await db.OrganizationMembers
.Where(om => om.UserId == userId)
.Select(om => om.OrganizationId)
.ToListAsync(cancellationToken);

// Get roles in organizations where the user has a role
var organizationRoles = await db.OrganizationMemberRoles
.Where(omr => omr.OrganizationMember.UserId == userId)
.Select(omr => new { omr.OrganizationId, omr.OrganizationRole.Name })
.ToListAsync(cancellationToken);

// Parse roles and add to dictionary
var parsedRoles = organizationRoles.ToDictionary(
omr => omr.OrganizationId,
omr =>
{
if (Enum.TryParse<MemberRole>(omr.Name, true, out var role))
return role;

throw new InvalidCastException();
});

// For organizations where the user doesn't have a role, add with MemberRole.None
foreach (var orgId in userOrganizations) parsedRoles.TryAdd(orgId, MemberRole.None);

return parsedRoles;
}
/// <summary>
/// Retrieves roles assigned to the user within organizations.
/// </summary>
/// <param name="userId">The ID of the user.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The roles assigned to the user within organizations.</returns>
private async Task<Dictionary<Guid, MemberRole>> GetOrganizationRolesAsync(Guid userId,
CancellationToken cancellationToken)
{
// Get all organizations the user is a member of
var userOrganizations = await db.OrganizationMembers
.Where(om => om.UserId == userId)
.Select(om => om.OrganizationId)
.ToListAsync(cancellationToken);

// Get roles in organizations where the user has a role
var organizationRoles = await db.OrganizationMemberRoles
.Where(omr => omr.OrganizationMember.UserId == userId)
.Select(omr => new { omr.OrganizationId, omr.OrganizationRole.Name })
.ToListAsync(cancellationToken);

// Parse roles and add to dictionary
var parsedRoles = organizationRoles.ToDictionary(
omr => omr.OrganizationId,
omr =>
{
if (Enum.TryParse<MemberRole>(omr.Name, true, out var role))
return role;

throw new InvalidCastException();
});

// For organizations where the user doesn't have a role, add with MemberRole.None
foreach (var orgId in userOrganizations) parsedRoles.TryAdd(orgId, MemberRole.None);

return parsedRoles;
}
Is there a way I can improve this query?
3 replies
CC#
Created by WAASUL on 5/18/2024 in #help
Access Token Expiry date suggestions
What does you guys think, should be the expiry of an access token? I set the expiry of the refresh token to 30 days and the access token to 1 day. Any suggestions would be appreciated. Sincerely.
1 replies
CC#
Created by WAASUL on 3/31/2024 in #help
DeleteBehavior?
I'm always confused which DeleteBehavior to assign when configuring my Entities. Can someone please explain to me?
3 replies
CC#
Created by WAASUL on 3/29/2024 in #help
Project structure
No description
17 replies
CC#
Created by WAASUL on 3/14/2024 in #help
✅ How should I name my ef core migrations?
I'm wondering if there is a good way to name your migrations. I have noticed that, you quickly end-up with a lot of migrations. I'm just wondering if there is a good naming convention.
21 replies
CC#
Created by WAASUL on 2/24/2024 in #help
✅ Npgsql.PostgresException
public class CreatePostHandler(
IHttpContextAccessor contextAccessor,
DatabaseContext db,
ISender mediator,
IValidator<CreatePostCommand> validator)
: RequestHandler(contextAccessor), IRequestHandler<CreatePostCommand, Result<Post>>
{
private const string StorageContainer = "images";

public async Task<Result<Post>> Handle(CreatePostCommand request, CancellationToken cancellationToken)
{
try
{
var validationResult = await ValidateInputAsync(request, cancellationToken);
if (!validationResult.IsValid)
return Result<Post>.Failure("Invalid input received.",
validationResult.ErrorMessages(), 400);

var newBlobName = $"{Guid.NewGuid()}{request.File.GetExtensionType()}";
var uploadResult = await UploadFileAsync(newBlobName, request.File, cancellationToken);

if (!uploadResult.IsSuccessful)
return uploadResult.ToResult(_ => new Post());

var uri = uploadResult.Data;
var nsfwResult = await ClassifyFileAsync(uri, cancellationToken);
if (!nsfwResult.IsSuccessful)
{
await DeleteNewlyUploadedFileAsync(newBlobName, cancellationToken);
return nsfwResult.ToResult<Post>(default!);
}

var post = new Post
{
Caption = request.Caption,
Url = uri.ToString(),
AuthorId = UserId
};

return await CreatePostAsync(post, cancellationToken);
}
catch (Exception e)
{
return Result<Post>.Failure("Something unexpected occurred.", [e.ToString()], 500);
}
}

private async Task<ValidationResult> ValidateInputAsync(CreatePostCommand request,
CancellationToken cancellationToken)
{
return await validator.ValidateAsync(request, cancellationToken);
}

private async Task DeleteNewlyUploadedFileAsync(string blobName, CancellationToken cancellationToken)
{
await mediator.Send(
new DeleteFileFromStorageCommand(blobName, StorageContainer),
cancellationToken);
}

private async Task<Result<Uri>> UploadFileAsync(string blobName, IFormFile file,
CancellationToken cancellationToken)
{
return await mediator.Send(new UploadFileToStorageCommand
{
ContainerName = StorageContainer,
BlobName = blobName,
ContentType = file.ContentType,
Stream = file.OpenReadStream()
}, cancellationToken);
}

private async Task<Result> ClassifyFileAsync(Uri uri, CancellationToken cancellationToken)
{
return await mediator.Send(new ClassifyImageCommand(uri), cancellationToken);
}

private async Task<Result<Post>> CreatePostAsync(Post post, CancellationToken cancellationToken)
{
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
foreach (var hashtag in hashtags)
post.Hashtags.Add(hashtag);

db.Posts.Add(post);
await db.SaveChangesAsync(cancellationToken);
return Result<Post>.Success(post);
}
}
public class CreatePostHandler(
IHttpContextAccessor contextAccessor,
DatabaseContext db,
ISender mediator,
IValidator<CreatePostCommand> validator)
: RequestHandler(contextAccessor), IRequestHandler<CreatePostCommand, Result<Post>>
{
private const string StorageContainer = "images";

public async Task<Result<Post>> Handle(CreatePostCommand request, CancellationToken cancellationToken)
{
try
{
var validationResult = await ValidateInputAsync(request, cancellationToken);
if (!validationResult.IsValid)
return Result<Post>.Failure("Invalid input received.",
validationResult.ErrorMessages(), 400);

var newBlobName = $"{Guid.NewGuid()}{request.File.GetExtensionType()}";
var uploadResult = await UploadFileAsync(newBlobName, request.File, cancellationToken);

if (!uploadResult.IsSuccessful)
return uploadResult.ToResult(_ => new Post());

var uri = uploadResult.Data;
var nsfwResult = await ClassifyFileAsync(uri, cancellationToken);
if (!nsfwResult.IsSuccessful)
{
await DeleteNewlyUploadedFileAsync(newBlobName, cancellationToken);
return nsfwResult.ToResult<Post>(default!);
}

var post = new Post
{
Caption = request.Caption,
Url = uri.ToString(),
AuthorId = UserId
};

return await CreatePostAsync(post, cancellationToken);
}
catch (Exception e)
{
return Result<Post>.Failure("Something unexpected occurred.", [e.ToString()], 500);
}
}

private async Task<ValidationResult> ValidateInputAsync(CreatePostCommand request,
CancellationToken cancellationToken)
{
return await validator.ValidateAsync(request, cancellationToken);
}

private async Task DeleteNewlyUploadedFileAsync(string blobName, CancellationToken cancellationToken)
{
await mediator.Send(
new DeleteFileFromStorageCommand(blobName, StorageContainer),
cancellationToken);
}

private async Task<Result<Uri>> UploadFileAsync(string blobName, IFormFile file,
CancellationToken cancellationToken)
{
return await mediator.Send(new UploadFileToStorageCommand
{
ContainerName = StorageContainer,
BlobName = blobName,
ContentType = file.ContentType,
Stream = file.OpenReadStream()
}, cancellationToken);
}

private async Task<Result> ClassifyFileAsync(Uri uri, CancellationToken cancellationToken)
{
return await mediator.Send(new ClassifyImageCommand(uri), cancellationToken);
}

private async Task<Result<Post>> CreatePostAsync(Post post, CancellationToken cancellationToken)
{
var hashtags = await mediator.Send(new ExtractHashtagsCommand(post.Caption), cancellationToken);
foreach (var hashtag in hashtags)
post.Hashtags.Add(hashtag);

db.Posts.Add(post);
await db.SaveChangesAsync(cancellationToken);
return Result<Post>.Success(post);
}
}
Any reasons as to why I'm getting this error?
27 replies
CC#
Created by WAASUL on 2/23/2024 in #help
Azure video analyzer
:blue_siren: :blue_siren: Is there any azure service that I can use to analyze a video's content? I want to check whether or not a video contains, nudity or other inappropriate stuff?
5 replies
CC#
Created by WAASUL on 2/10/2024 in #help
✅ Suggestion on file upload?
When uploading file to azure storage. Should I upload the files through the server (api)? or is there another way. The thing that scares me is that if multiple people upload at the same time than we will have a memory issue. The clients in this case are mobile apps? Any insights would be appreciated.
45 replies
CC#
Created by WAASUL on 2/1/2024 in #help
Verify Google Id Token: FormatException
I'm using google sign in on my client. Before authenticating the user I want to Verify the ID Token which I get from the client. On the server side. I'm trying to validate the ID Token (jwt) by using Google.Apis.Auth nuget package. But I'm getting a format exception. Here is the code:
/// <summary>
/// Handler for verifying Google ID tokens.
/// </summary>
public class VerifyGoogleIdTokenHandler(IConfiguration configuration)
: IRequestHandler<VerifyGoogleIdTokenCommand, Result<GoogleJsonWebSignature.Payload>>
{
private readonly GoogleJsonWebSignature.ValidationSettings _settings = new()
{
Audience = new[]
{
configuration[ConfigurationKeys.GoogleWebClientId], configuration[ConfigurationKeys.GoogleAndroidClientId]
}
};

public async Task<Result<GoogleJsonWebSignature.Payload>> Handle(VerifyGoogleIdTokenCommand request,
CancellationToken cancellationToken)
{
try
{
var payload = await GoogleJsonWebSignature.ValidateAsync(request.IdToken, _settings);
if (payload == null)
throw new InvalidJwtException("Invalid ID token");

return Result<GoogleJsonWebSignature.Payload>.Success(payload);
}
catch (Exception ex)
{
return Result<GoogleJsonWebSignature.Payload>.Failure("Invalid ID token", new[] { ex.ToString() }, 400);
}
}
}
/// <summary>
/// Handler for verifying Google ID tokens.
/// </summary>
public class VerifyGoogleIdTokenHandler(IConfiguration configuration)
: IRequestHandler<VerifyGoogleIdTokenCommand, Result<GoogleJsonWebSignature.Payload>>
{
private readonly GoogleJsonWebSignature.ValidationSettings _settings = new()
{
Audience = new[]
{
configuration[ConfigurationKeys.GoogleWebClientId], configuration[ConfigurationKeys.GoogleAndroidClientId]
}
};

public async Task<Result<GoogleJsonWebSignature.Payload>> Handle(VerifyGoogleIdTokenCommand request,
CancellationToken cancellationToken)
{
try
{
var payload = await GoogleJsonWebSignature.ValidateAsync(request.IdToken, _settings);
if (payload == null)
throw new InvalidJwtException("Invalid ID token");

return Result<GoogleJsonWebSignature.Payload>.Success(payload);
}
catch (Exception ex)
{
return Result<GoogleJsonWebSignature.Payload>.Failure("Invalid ID token", new[] { ex.ToString() }, 400);
}
}
}
2 replies
CC#
Created by WAASUL on 1/31/2024 in #help
ASP.NET Core Identity?
Is there a way here I can check if the token has expired or if it already has been used?
/// <summary>
/// Handles the logic for resetting a user's password based on the provided command.
/// </summary>
public class ResetPasswordHandler(UserManager<User> userManager)
: IRequestHandler<ResetPasswordCommand, Result<bool>>, IDisposable
{
public void Dispose()
{
userManager.Dispose();
GC.SuppressFinalize(this);
}

public async Task<Result<bool>> Handle(ResetPasswordCommand request, CancellationToken cancellationToken)
{
// Validate the input using the ResetPasswordValidator.
var validator = new ResetPasswordValidator();
var validationResult = await validator.ValidateAsync(request, cancellationToken);

if (!validationResult.IsValid)
return Result<bool>.Failure("Invalid input received.", 400);

// Find the user by email address.
var user = await userManager.FindByEmailAsync(request.Email);
if (user == null)
return Result<bool>.Failure("User not found.", 404);

// Reset the user's password.
var result = await userManager.ResetPasswordAsync(user, request.Token, request.Password);

// Return the appropriate result based on the reset operation success.
return !result.Succeeded
? Result<bool>.Failure("Unable to reset password.", 500)
: Result<bool>.Success(true);
}
}
/// <summary>
/// Handles the logic for resetting a user's password based on the provided command.
/// </summary>
public class ResetPasswordHandler(UserManager<User> userManager)
: IRequestHandler<ResetPasswordCommand, Result<bool>>, IDisposable
{
public void Dispose()
{
userManager.Dispose();
GC.SuppressFinalize(this);
}

public async Task<Result<bool>> Handle(ResetPasswordCommand request, CancellationToken cancellationToken)
{
// Validate the input using the ResetPasswordValidator.
var validator = new ResetPasswordValidator();
var validationResult = await validator.ValidateAsync(request, cancellationToken);

if (!validationResult.IsValid)
return Result<bool>.Failure("Invalid input received.", 400);

// Find the user by email address.
var user = await userManager.FindByEmailAsync(request.Email);
if (user == null)
return Result<bool>.Failure("User not found.", 404);

// Reset the user's password.
var result = await userManager.ResetPasswordAsync(user, request.Token, request.Password);

// Return the appropriate result based on the reset operation success.
return !result.Succeeded
? Result<bool>.Failure("Unable to reset password.", 500)
: Result<bool>.Success(true);
}
}
If so how do I set the expiration time?
1 replies