Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions src/Application/Common/Models/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,22 +120,30 @@ protected Result(bool succeeded, IEnumerable<string>? errors, T? data)
public static new Task<Result<T>> FailureAsync(params string[] errors) => Task.FromResult(Failure(errors));

/// <summary>
/// Executes the appropriate function based on whether the operation succeeded.
/// Executes the appropriate action based on whether the operation succeeded.
/// </summary>
/// <typeparam name="TResult">The return type.</typeparam>
/// <param name="onSuccess">Function to execute if the operation succeeded, with the data.</param>
/// <param name="onFailure">Function to execute if the operation failed, with an error message.</param>
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<string, TResult> onFailure)
=> Succeeded ? onSuccess(Data!) : onFailure(ErrorMessage);
/// <param name="onSuccess">Action to execute if the operation succeeded, with the data.</param>
/// <param name="onFailure">Action to execute if the operation failed, with an error message.</param>
public void Match(Action<T> onSuccess, Action<string> onFailure)
{
if (Succeeded)
onSuccess(Data!);
else
onFailure(ErrorMessage);
}

/// <summary>
/// Asynchronously executes the appropriate function based on whether the operation succeeded.
/// Asynchronously executes the appropriate action based on whether the operation succeeded.
/// </summary>
/// <typeparam name="TResult">The return type.</typeparam>
/// <param name="onSuccess">Asynchronous function to execute if the operation succeeded, with the data.</param>
/// <param name="onFailure">Asynchronous function to execute if the operation failed, with an error message.</param>
public Task<TResult> MatchAsync<TResult>(Func<T, Task<TResult>> onSuccess, Func<string, Task<TResult>> onFailure)
=> Succeeded ? onSuccess(Data!) : onFailure(ErrorMessage);
/// <param name="onSuccess">Asynchronous action to execute if the operation succeeded, with the data.</param>
/// <param name="onFailure">Asynchronous action to execute if the operation failed, with an error message.</param>
public async Task MatchAsync(Func<T, Task> onSuccess, Func<string, Task> onFailure)
{
if (Succeeded)
await onSuccess(Data!);
else
await onFailure(ErrorMessage);
}

/// <summary>
/// Maps the data contained in the result to a new type.
Expand Down
4 changes: 1 addition & 3 deletions src/Application/Common/Models/UploadRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@ namespace CleanArchitecture.Blazor.Application.Common.Models;

public class UploadRequest
{
public UploadRequest(string fileName, UploadType uploadType, byte[] data, bool overwrite = false,string? folder=null, ResizeOptions? resizeOptions=null)
public UploadRequest(string fileName, UploadType uploadType, byte[] data, bool overwrite = false,string? folder=null)
{
FileName = fileName;
UploadType = uploadType;
Data = data;
Overwrite = overwrite;
Folder = folder;
ResizeOptions = resizeOptions;
}
public string FileName { get; set; }
public string? Extension { get; set; }
public UploadType UploadType { get; set; }
public bool Overwrite { get; set; }
public byte[] Data { get; set; }
public string? Folder { get; set; }
public ResizeOptions? ResizeOptions { get; set; }
}
62 changes: 48 additions & 14 deletions src/Application/Common/Security/UserProfileStateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,56 @@

namespace CleanArchitecture.Blazor.Application.Common.Security;

public class UserProfileStateService
public class UserProfileStateService : IDisposable
{
// Cache refresh interval of 60 seconds
private TimeSpan RefreshInterval => TimeSpan.FromSeconds(60);
private UserProfile _userProfile = new UserProfile() { Email="", UserId="", UserName="" };

// Internal user profile state
private UserProfile _userProfile = new UserProfile { Email = "", UserId = "", UserName = "" };

// Dependencies
private readonly UserManager<ApplicationUser> _userManager;
private readonly IMapper _mapper;
private readonly IFusionCache _fusionCache;
private readonly IServiceScope _scope;

public UserProfileStateService(
IMapper mapper,
IServiceScopeFactory scopeFactory,
IFusionCache fusionCache)
{
var scope = scopeFactory.CreateScope();
_userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
_scope = scopeFactory.CreateScope();
_userManager = _scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
_mapper = mapper;
_fusionCache = fusionCache;

}

/// <summary>
/// Loads and initializes the user profile from the database.
/// </summary>
public async Task InitializeAsync(string userName)
{
var key = GetApplicationUserCacheKey(userName);
var result = await _fusionCache.GetOrSetAsync(key,
_ => _userManager.Users.Where(x => x.UserName == userName).Include(x => x.UserRoles)
.ThenInclude(x => x.Role).ProjectTo<ApplicationUserDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync(), RefreshInterval);
if(result is not null)
var result = await _fusionCache.GetOrSetAsync(
key,
_ => _userManager.Users
.Where(x => x.UserName == userName)
.Include(x => x.UserRoles).ThenInclude(x => x.Role)
.ProjectTo<ApplicationUserDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync(),
RefreshInterval);

if (result is not null)
{
_userProfile = result.ToUserProfile();
NotifyStateChanged();
}
}

/// <summary>
/// Gets or sets the current user profile.
/// </summary>
public UserProfile UserProfile
{
get => _userProfile;
Expand All @@ -49,11 +67,19 @@ public UserProfile UserProfile
}
}

public event Func<Task>? OnChange;

private void NotifyStateChanged() => OnChange?.Invoke();
/// <summary>
/// Refreshes the user profile by removing the cached value and reloading data from the database.
/// </summary>
public async Task RefreshAsync(string userName)
{
RemoveApplicationUserCache(userName);
await InitializeAsync(userName);
}

public void UpdateUserProfile(string userName,string? profilePictureDataUrl, string? fullName,string? phoneNumber,string? timeZoneId,string? languageCode)
/// <summary>
/// Updates the user profile and clears the cache.
/// </summary>
public void UpdateUserProfile(string userName, string? profilePictureDataUrl, string? fullName, string? phoneNumber, string? timeZoneId, string? languageCode)
{
_userProfile.ProfilePictureDataUrl = profilePictureDataUrl;
_userProfile.DisplayName = fullName;
Expand All @@ -63,13 +89,21 @@ public void UpdateUserProfile(string userName,string? profilePictureDataUrl, str
RemoveApplicationUserCache(userName);
NotifyStateChanged();
}

public event Func<Task>? OnChange;

private void NotifyStateChanged() => OnChange?.Invoke();

private string GetApplicationUserCacheKey(string userName)
{
return $"GetApplicationUserDto:{userName}";
}

public void RemoveApplicationUserCache(string userName)
{
_fusionCache.Remove(GetApplicationUserCacheKey(userName));
}

public void Dispose() => _scope.Dispose();
}

4 changes: 2 additions & 2 deletions src/Application/Features/Contacts/Caching/ContactCacheKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// See the LICENSE file in the project root for more information.
//
// Author: neozhu
// Created Date: 2025-03-13
// Last Modified: 2025-03-13
// Created Date: 2025-03-19
// Last Modified: 2025-03-19
// Description:
// Defines static methods and properties for managing cache keys and expiration
// settings for Contact-related data. This includes creating unique cache keys for
Expand Down Expand Up @@ -35,7 +35,7 @@
public static string GetByIdCacheKey(string parameters) {
return $"ContactCacheKey:GetByIdCacheKey,{parameters}";
}
public static IEnumerable<string>? Tags => new string[] { "contact" };

Check warning on line 38 in src/Application/Features/Contacts/Caching/ContactCacheKey.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

Check warning on line 38 in src/Application/Features/Contacts/Caching/ContactCacheKey.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
public static void Refresh()
{
FusionCacheFactory.RemoveByTags(Tags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Command for adding/editing a contact entity with validation, mapping,
// domain events, and cache invalidation.
// Documentation: https://docs.cleanarchitectureblazor.com/features/contact
Expand All @@ -23,17 +23,17 @@
[Description("Name")]
public string Name {get;set;}
[Description("Description")]
public string? Description {get;set;}

Check warning on line 26 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

Check warning on line 26 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Email")]
public string? Email {get;set;}

Check warning on line 28 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

Check warning on line 28 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Phone number")]
public string? PhoneNumber {get;set;}

Check warning on line 30 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

Check warning on line 30 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Country")]
public string? Country {get;set;}

Check warning on line 32 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

Check warning on line 32 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.


public string CacheKey => ContactCacheKey.GetAllCacheKey;
public IEnumerable<string>? Tags => ContactCacheKey.Tags;

Check warning on line 36 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

Check warning on line 36 in src/Application/Features/Contacts/Commands/AddEdit/AddEditContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
private class Mapping : Profile
{
public Mapping()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Validator for AddEditContactCommand: enforces field length and required property rules for Contact entities.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
// </auto-generated>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Command and handler for creating a new Contact.
// Uses caching invalidation and domain events for data consistency.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
Expand All @@ -24,13 +24,13 @@
[Description("Name")]
public string Name {get;set;}
[Description("Description")]
public string? Description {get;set;}

Check warning on line 27 in src/Application/Features/Contacts/Commands/Create/CreateContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Email")]
public string? Email {get;set;}

Check warning on line 29 in src/Application/Features/Contacts/Commands/Create/CreateContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Phone number")]
public string? PhoneNumber {get;set;}

Check warning on line 31 in src/Application/Features/Contacts/Commands/Create/CreateContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Country")]
public string? Country {get;set;}

Check warning on line 33 in src/Application/Features/Contacts/Commands/Create/CreateContactCommand.cs

View workflow job for this annotation

GitHub Actions / Analyze

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.

public string CacheKey => ContactCacheKey.GetAllCacheKey;
public IEnumerable<string>? Tags => ContactCacheKey.Tags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Validator for CreateContactCommand: enforces max lengths and required fields for Contact entities.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
// </auto-generated>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Command and handler for deleting Contact entities.
// Implements cache invalidation and triggers domain events.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Validator for DeleteContactCommand: ensures the ID list for contact is not null and contains only positive IDs.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
// </auto-generated>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Import command & template for contacts.
// Validates Excel data, prevents duplicates, and provides a template for bulk entry.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
Expand Down Expand Up @@ -62,11 +62,11 @@ public async Task<Result<int>> Handle(ImportContactsCommand request, Cancellatio

var result = await _excelService.ImportAsync(request.Data, mappers: new Dictionary<string, Func<DataRow, ContactDto, object?>>
{
{ _localizer[_dto.GetMemberDescription(x=>x.Name)], (row, item) => item.Name = row[_localizer[_dto.GetMemberDescription(x=>x.Name)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.Description)], (row, item) => item.Description = row[_localizer[_dto.GetMemberDescription(x=>x.Description)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.Email)], (row, item) => item.Email = row[_localizer[_dto.GetMemberDescription(x=>x.Email)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.PhoneNumber)], (row, item) => item.PhoneNumber = row[_localizer[_dto.GetMemberDescription(x=>x.PhoneNumber)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.Country)], (row, item) => item.Country = row[_localizer[_dto.GetMemberDescription(x=>x.Country)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.Name)], (row, item) => item.Name = row[_localizer[_dto.GetMemberDescription(x=>x.Name)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.Description)], (row, item) => item.Description = row[_localizer[_dto.GetMemberDescription(x=>x.Description)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.Email)], (row, item) => item.Email = row[_localizer[_dto.GetMemberDescription(x=>x.Email)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.PhoneNumber)], (row, item) => item.PhoneNumber = row[_localizer[_dto.GetMemberDescription(x=>x.PhoneNumber)]].ToString() },
{ _localizer[_dto.GetMemberDescription(x=>x.Country)], (row, item) => item.Country = row[_localizer[_dto.GetMemberDescription(x=>x.Country)]].ToString() },

}, _localizer[_dto.GetClassDescription()]);
if (result.Succeeded && result.Data is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Validator for ImportContactsCommand: ensures the Data property is non-null and non-empty for contact import.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
// </auto-generated>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// UpdateContactCommand & handler: updates an existing Contact with cache invalidation and raises ContactUpdatedEvent.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
// </auto-generated>
Expand All @@ -11,17 +11,16 @@
// Usage:
// Use UpdateContactCommand to update an existing contact. If found, changes are applied, cache is invalidated, and ContactUpdatedEvent is raised.


using CleanArchitecture.Blazor.Application.Features.Contacts.Caching;
using CleanArchitecture.Blazor.Application.Features.Contacts.DTOs;
using CleanArchitecture.Blazor.Application.Features.Contacts.Caching;

namespace CleanArchitecture.Blazor.Application.Features.Contacts.Commands.Update;

public class UpdateContactCommand: ICacheInvalidatorRequest<Result<int>>
{
[Description("Id")]
public int Id { get; set; }
[Description("Name")]
[Description("Name")]
public string Name {get;set;}
[Description("Description")]
public string? Description {get;set;}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Validator for UpdateContactCommand: ensures required fields (e.g., Id, non-empty Name, max length for properties) are valid for updating a contact.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
// </auto-generated>
Expand Down
2 changes: 1 addition & 1 deletion src/Application/Features/Contacts/DTOs/ContactDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// ContactDto: transfers contact data between layers.
// Docs: https://docs.cleanarchitectureblazor.com/features/contact
// </auto-generated>
Expand All @@ -22,13 +22,13 @@
[Description("Name")]
public string Name {get;set;}
[Description("Description")]
public string? Description {get;set;}

Check warning on line 25 in src/Application/Features/Contacts/DTOs/ContactDto.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Email")]
public string? Email {get;set;}

Check warning on line 27 in src/Application/Features/Contacts/DTOs/ContactDto.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Phone number")]
public string? PhoneNumber {get;set;}

Check warning on line 29 in src/Application/Features/Contacts/DTOs/ContactDto.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.
[Description("Country")]
public string? Country {get;set;}

Check warning on line 31 in src/Application/Features/Contacts/DTOs/ContactDto.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. Auto-generated code requires an explicit '#nullable' directive in source.


private class Mapping : Profile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Handles ContactCreatedEvent: triggered when a new contact is created.
// Extendable for additional actions (e.g., notifications, system updates).
// </auto-generated>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Handles ContactDeletedEvent: triggered when a contact is deleted.
// Extendable for additional actions (e.g., notifications, system updates).
// </auto-generated>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// <auto-generated>
// CleanArchitecture.Blazor - MIT Licensed.
// Author: neozhu
// Created/Modified: 2025-03-13
// Created/Modified: 2025-03-19
// Handles ContactUpdatedEvent: triggered when a contact is updated.
// Extendable for additional actions (e.g., notifications, system updates).
// </auto-generated>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// See the LICENSE file in the project root for more information.
//
// Author: neozhu
// Created Date: 2025-03-13
// Last Modified: 2025-03-13
// Created Date: 2025-03-19
// Last Modified: 2025-03-19
// Description:
// Defines a query to export contact data to an Excel file. This query
// applies advanced filtering options and generates an Excel file with
Expand All @@ -26,7 +26,7 @@ public class ExportContactsQuery : ContactAdvancedFilter, ICacheableRequest<Resu
public IEnumerable<string>? Tags => ContactCacheKey.Tags;
public override string ToString()
{
return $"Listview:{ListView}:{CurrentUser?.UserId}-{LocalTimezoneOffset.TotalHours}, Search:{Keyword}, {OrderBy}, {SortDirection}";
return $"Listview:{ListView}:{CurrentUser?.UserId}, Search:{Keyword}, {OrderBy}, {SortDirection}";
}
public string CacheKey => ContactCacheKey.GetExportCacheKey($"{this}");
}
Expand Down Expand Up @@ -63,11 +63,11 @@ public async Task<Result<byte[]>> Handle(ExportContactsQuery request, Cancellati
new Dictionary<string, Func<ContactDto, object?>>()
{
// TODO: Define the fields that should be exported, for example:
{_localizer[_dto.GetMemberDescription(x=>x.Name)],item => item.Name},
{_localizer[_dto.GetMemberDescription(x=>x.Description)],item => item.Description},
{_localizer[_dto.GetMemberDescription(x=>x.Email)],item => item.Email},
{_localizer[_dto.GetMemberDescription(x=>x.PhoneNumber)],item => item.PhoneNumber},
{_localizer[_dto.GetMemberDescription(x=>x.Country)],item => item.Country},
{_localizer[_dto.GetMemberDescription(x=>x.Name)],item => item.Name},
{_localizer[_dto.GetMemberDescription(x=>x.Description)],item => item.Description},
{_localizer[_dto.GetMemberDescription(x=>x.Email)],item => item.Email},
{_localizer[_dto.GetMemberDescription(x=>x.PhoneNumber)],item => item.PhoneNumber},
{_localizer[_dto.GetMemberDescription(x=>x.Country)],item => item.Country},

}
, _localizer[_dto.GetClassDescription()]);
Expand Down
Loading
Loading