Repository: project-studying-dotnet/Streetcode-Server-May-2025
Pull request #61: Refactor null checks and mapping logic for reliability
Author: login: ErikSavchynAI
Created: 2025-05-18T23:22:35Z | Merged: 2025-05-20T13:40:01Z


## Summary of issue

Current mapping logic and null checks are inconsistent, potentially leading to null reference exceptions and reduced code clarity.

## Summary of change

- Implemented proper null checks across mappings and handler logic to enhance reliability.
- Consolidated conditional operations for improved readability.
- Enhanced validation for mapped objects to ensure robustness.


Diff:
diff --git a/Streetcode/Streetcode.BLL/Mapping/AdditionalContent/TagProfile.cs b/Streetcode/Streetcode.BLL/Mapping/AdditionalContent/TagProfile.cs
index 9c68db42..7628d9a5 100644
--- a/Streetcode/Streetcode.BLL/Mapping/AdditionalContent/TagProfile.cs
+++ b/Streetcode/Streetcode.BLL/Mapping/AdditionalContent/TagProfile.cs
@@ -13,6 +13,6 @@ public TagProfile()
         CreateMap<Tag, StreetcodeTagDTO>().ReverseMap();
         CreateMap<StreetcodeTagIndex, StreetcodeTagDTO>()
             .ForMember(x => x.Id, conf => conf.MapFrom(ti => ti.TagId))
-            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag.Title ?? ""));
+            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag != null ? ti.Tag.Title : ""));
     }
 }
diff --git a/Streetcode/Streetcode.BLL/Mapping/Partners/PartnerProfile.cs b/Streetcode/Streetcode.BLL/Mapping/Partners/PartnerProfile.cs
index 6c7cd93e..3ece7d5c 100644
--- a/Streetcode/Streetcode.BLL/Mapping/Partners/PartnerProfile.cs
+++ b/Streetcode/Streetcode.BLL/Mapping/Partners/PartnerProfile.cs
@@ -1,4 +1,5 @@
 using AutoMapper;
+using Streetcode.BLL.DTO.AdditionalContent;
 using Streetcode.BLL.DTO.Partners;
 using Streetcode.DAL.Entities.Partners;
 
@@ -9,8 +10,15 @@ public class PartnerProfile : Profile
     public PartnerProfile()
     {
         CreateMap<Partner, PartnerDTO>()
-            .ForPath(dto => dto.TargetUrl.Title, conf => conf.MapFrom(ol => ol.UrlTitle))
-            .ForPath(dto => dto.TargetUrl.Href, conf => conf.MapFrom(ol => ol.TargetUrl));
+            .ForMember(dto => dto.TargetUrl, opt =>
+                opt.MapFrom(src => (string.IsNullOrEmpty(src.TargetUrl) && string.IsNullOrEmpty(src.UrlTitle)) ? null : new UrlDTO
+                {
+                    Title = src.UrlTitle, Href = src.TargetUrl ?? string.Empty
+                }))
+            .ReverseMap()
+            .ForMember(dest => dest.UrlTitle, opt => opt.MapFrom(src => src.TargetUrl!.Title))
+            .ForMember(dest => dest.TargetUrl, opt => opt.MapFrom(src => src.TargetUrl!.Href));
+
         CreateMap<Partner, CreatePartnerDTO>().ReverseMap();
         CreateMap<Partner, PartnerShortDTO>().ReverseMap();
     }
diff --git a/Streetcode/Streetcode.BLL/Mapping/Sources/SourceLinkCategoryProfile.cs b/Streetcode/Streetcode.BLL/Mapping/Sources/SourceLinkCategoryProfile.cs
index 6dde7bdb..94df01f4 100644
--- a/Streetcode/Streetcode.BLL/Mapping/Sources/SourceLinkCategoryProfile.cs
+++ b/Streetcode/Streetcode.BLL/Mapping/Sources/SourceLinkCategoryProfile.cs
@@ -14,8 +14,12 @@ public SourceLinkCategoryProfile()
             .ReverseMap();
         CreateMap<SourceLinkCategory, CategoryWithNameDTO>().ReverseMap();
         CreateMap<SourceLinkCategory, ImageDTO>()
-            .ForMember(dest => dest.MimeType, opt => opt.MapFrom(src => src.Image.MimeType))
-            .ForMember(dest => dest.BlobName, opt => opt.MapFrom(src => src.Image.BlobName));
+            .ForMember(
+                dest => dest.MimeType,
+                opt => opt.MapFrom(src => src.Image != null ? src.Image.MimeType : null))
+            .ForMember(
+                dest => dest.BlobName,
+                opt => opt.MapFrom(src => src.Image != null ? src.Image.BlobName : null));
         CreateMap<SourceLinkCategoryDTO, SourceLinkCategory>()
             .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
             .ForMember(dest => dest.Image, opt => opt.MapFrom(src => src.Image))
diff --git a/Streetcode/Streetcode.BLL/Mapping/Streetcode/StreetcodeProfile.cs b/Streetcode/Streetcode.BLL/Mapping/Streetcode/StreetcodeProfile.cs
index 735051fe..e990de7a 100644
--- a/Streetcode/Streetcode.BLL/Mapping/Streetcode/StreetcodeProfile.cs
+++ b/Streetcode/Streetcode.BLL/Mapping/Streetcode/StreetcodeProfile.cs
@@ -16,7 +16,7 @@ public StreetcodeProfile()
         CreateMap<StreetcodeContent, StreetcodeShortDTO>().ReverseMap();
         CreateMap<StreetcodeContent, StreetcodeMainPageDTO>()
              .ForPath(dto => dto.Text, conf => conf
-                .MapFrom(e => e.Text.Title))
+                .MapFrom(e => e.Text != null ? e.Text.Title : null))
             .ForPath(dto => dto.ImageId, conf => conf
                 .MapFrom(e => e.Images.Select(i => i.Id).LastOrDefault()));
     }
diff --git a/Streetcode/Streetcode.BLL/Mapping/Timeline/TimelineItemProfile.cs b/Streetcode/Streetcode.BLL/Mapping/Timeline/TimelineItemProfile.cs
index c72d3bf0..eb7d1cd2 100644
--- a/Streetcode/Streetcode.BLL/Mapping/Timeline/TimelineItemProfile.cs
+++ b/Streetcode/Streetcode.BLL/Mapping/Timeline/TimelineItemProfile.cs
@@ -13,9 +13,11 @@ public TimelineItemProfile()
         CreateMap<TimelineItem, TimelineItemDTO>()
             .ForMember(dest => dest.HistoricalContexts, opt => opt.MapFrom(x => x.HistoricalContextTimelines
                 .Select(x => new HistoricalContextDTO
-                {
-                    Id = x.HistoricalContextId,
-                    Title = x.HistoricalContext.Title
-                }).ToList()));
+                        {
+                            Id = x.HistoricalContextId,
+                            Title = x.HistoricalContext != null
+                                ? (x.HistoricalContext.Title ?? string.Empty)
+                                : string.Empty
+                        }).ToList()));
     }
 }
diff --git a/Streetcode/Streetcode.BLL/MediatR/Media/StreetcodeArt/GetByStreetcodeId/GetStreetcodeArtByStreetcodeIdHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Media/StreetcodeArt/GetByStreetcodeId/GetStreetcodeArtByStreetcodeIdHandler.cs
index cd0bf07a..ff1b332a 100644
--- a/Streetcode/Streetcode.BLL/MediatR/Media/StreetcodeArt/GetByStreetcodeId/GetStreetcodeArtByStreetcodeIdHandler.cs
+++ b/Streetcode/Streetcode.BLL/MediatR/Media/StreetcodeArt/GetByStreetcodeId/GetStreetcodeArtByStreetcodeIdHandler.cs
@@ -44,10 +44,10 @@ public async Task<Result<IEnumerable<StreetcodeArtDTO>>> Handle(GetStreetcodeArt
             .GetAllAsync(
                 predicate: s => s.StreetcodeId == request.StreetcodeId,
                 include: art => art
-                    .Include(a => a.Art)
-                    .Include(i => i.Art.Image) !);
+                    .Include(a => a.Art!)
+                    .ThenInclude(a => a!.Image!));
 
-            if (art is null)
+            if (!art.Any())
             {
                 string errorMsg = $"Cannot find an art with corresponding streetcode id: {request.StreetcodeId}";
                 _logger.LogError(request, errorMsg);
@@ -55,10 +55,19 @@ public async Task<Result<IEnumerable<StreetcodeArtDTO>>> Handle(GetStreetcodeArt
             }
 
             var artsDto = _mapper.Map<IEnumerable<StreetcodeArtDTO>>(art);
+            if (artsDto is null)
+            {
+                string errorMsg = $"Unable to map art data for streetcode id: {request.StreetcodeId}";
+                _logger.LogError(request, errorMsg);
+                return Result.Fail(new Error(errorMsg));
+            }
 
             foreach (var artDto in artsDto)
             {
-                artDto.Art.Image.Base64 = _blobService.FindFileInStorageAsBase64(artDto.Art.Image.BlobName);
+                if (artDto.Art?.Image != null && !string.IsNullOrEmpty(artDto.Art.Image.BlobName))
+                {
+                    artDto.Art.Image.Base64 = _blobService.FindFileInStorageAsBase64(artDto.Art.Image.BlobName);
+                }
             }
 
             return Result.Ok(artsDto);
diff --git a/Streetcode/Streetcode.BLL/MediatR/News/Update/UpdateNewsHandler.cs b/Streetcode/Streetcode.BLL/MediatR/News/Update/UpdateNewsHandler.cs
index 261c9500..2f90663e 100644
--- a/Streetcode/Streetcode.BLL/MediatR/News/Update/UpdateNewsHandler.cs
+++ b/Streetcode/Streetcode.BLL/MediatR/News/Update/UpdateNewsHandler.cs
@@ -35,11 +35,11 @@ public async Task<Result<NewsDTO>> Handle(UpdateNewsCommand request, Cancellatio
 
             var response = _mapper.Map<NewsDTO>(news);
 
-            if (news.Image is not null)
+            if (news.Image is not null && response.Image?.BlobName != null)
             {
                 response.Image.Base64 = _blobSevice.FindFileInStorageAsBase64(response.Image.BlobName);
             }
-            else
+            else if (response.ImageId != null)
             {
                 var img = await _repositoryWrapper.ImageRepository.GetFirstOrDefaultAsync(x => x.Id == response.ImageId);
                 if (img != null)
diff --git a/Streetcode/Streetcode.BLL/MediatR/Sources/SourceLinkCategory/GetCategoriesByStreetcodeId/GetCategoriesByStreetcodeIdHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Sources/SourceLinkCategory/GetCategoriesByStreetcodeId/GetCategoriesByStreetcodeIdHandler.cs
index 039453f4..115779fc 100644
--- a/Streetcode/Streetcode.BLL/MediatR/Sources/SourceLinkCategory/GetCategoriesByStreetcodeId/GetCategoriesByStreetcodeIdHandler.cs
+++ b/Streetcode/Streetcode.BLL/MediatR/Sources/SourceLinkCategory/GetCategoriesByStreetcodeId/GetCategoriesByStreetcodeIdHandler.cs
@@ -44,7 +44,10 @@ public async Task<Result<IEnumerable<SourceLinkCategoryDTO>>> Handle(GetCategori
 
         foreach (var srcCategory in mappedSrcCategories)
         {
-            srcCategory.Image.Base64 = _blobService.FindFileInStorageAsBase64(srcCategory.Image.BlobName);
+            if (srcCategory.Image is { BlobName: not null })
+            {
+                srcCategory.Image.Base64 = _blobService.FindFileInStorageAsBase64(srcCategory.Image.BlobName);
+            }
         }
 
         return Result.Ok(mappedSrcCategories);


Reviews:
user: login: DenysSaiuk
body: I would change a little some writting style. On the other hand everything is ok.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T05:03:52Z
user: login: ErikSavchynAI
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T06:22:45Z
user: login: DenysSaiuk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T06:30:48Z
user: login: Ilbko
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T09:21:57Z
user: login: Ilbko
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T09:26:04Z
user: login: Ilbko
body: Small changes are in need of addressing before proceeding
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T09:31:56Z
user: login: Yazheviika
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T04:19:44Z
user: login: DenysSaiuk
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T04:40:04Z
user: login: Ilbko
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T13:13:55Z

Review comments:
diff_hunk: @@ -13,6 +13,6 @@ public TagProfile()
         CreateMap<Tag, StreetcodeTagDTO>().ReverseMap();
         CreateMap<StreetcodeTagIndex, StreetcodeTagDTO>()
             .ForMember(x => x.Id, conf => conf.MapFrom(ti => ti.TagId))
-            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag.Title ?? ""));
+            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag != null ? ti.Tag.Title : ""));
path: Streetcode/Streetcode.BLL/Mapping/AdditionalContent/TagProfile.cs
user: login: DenysSaiuk
body: I don't think that it is a better approach than past.
created_at: 2025-05-19T05:00:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,6 +13,6 @@ public TagProfile()
         CreateMap<Tag, StreetcodeTagDTO>().ReverseMap();
         CreateMap<StreetcodeTagIndex, StreetcodeTagDTO>()
             .ForMember(x => x.Id, conf => conf.MapFrom(ti => ti.TagId))
-            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag.Title ?? ""));
+            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag != null ? ti.Tag.Title : ""));
path: Streetcode/Streetcode.BLL/Mapping/AdditionalContent/TagProfile.cs
user: login: ErikSavchynAI
body: the previous implementation leads to a possible null reference exception because there we do not check Tag for null
created_at: 2025-05-19T06:22:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,6 +13,6 @@ public TagProfile()
         CreateMap<Tag, StreetcodeTagDTO>().ReverseMap();
         CreateMap<StreetcodeTagIndex, StreetcodeTagDTO>()
             .ForMember(x => x.Id, conf => conf.MapFrom(ti => ti.TagId))
-            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag.Title ?? ""));
+            .ForMember(x => x.Title, conf => conf.MapFrom(ti => ti.Tag != null ? ti.Tag.Title : ""));
path: Streetcode/Streetcode.BLL/Mapping/AdditionalContent/TagProfile.cs
user: login: DenysSaiuk
body: Ok, got it.
created_at: 2025-05-19T06:30:48Z
author_association: CONTRIBUTOR
path: Streetcode/Streetcode.BLL/MediatR/Sources/SourceLinkCategory/GetCategoriesByStreetcodeId/GetCategoriesByStreetcodeIdHandler.cs
user: login: Ilbko
body: Could be changed to a plainer if (srcCategory.Image?.BlobName != null)
created_at: 2025-05-19T09:21:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,11 +35,11 @@ public async Task<Result<NewsDTO>> Handle(UpdateNewsCommand request, Cancellatio
 
             var response = _mapper.Map<NewsDTO>(news);
 
-            if (news.Image is not null)
+            if (news.Image is not null && response.Image != null && response.Image.BlobName != null)
path: Streetcode/Streetcode.BLL/MediatR/Newss/Update/UpdateNewsHandler.cs
user: login: Ilbko
body: Could be changed to a plainer if (news.Image is not null && response.Image?.BlobName != null)
created_at: 2025-05-19T09:26:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: DenysSaiuk
created_at: 2025-05-20T04:39:50Z
body: Everything is ok.
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-20T13:39:33Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=project-studying-dotnet_Streetcode-Server-May-2025&pullRequest=61) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [13.3% Coverage on New Code](https://sonarcloud.io/component_measures?id=project-studying-dotnet_Streetcode-Server-May-2025&pullRequest=61&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=project-studying-dotnet_Streetcode-Server-May-2025&pullRequest=61)


author_association: NONE