< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Item.BaseItemMapper
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemMapper.cs
Line coverage
71%
Covered lines: 183
Uncovered lines: 72
Coverable lines: 255
Total lines: 491
Line coverage: 71.7%
Branch coverage
48%
Covered branches: 81
Total branches: 168
Branch coverage: 48.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/4/2026 - 12:15:16 AM Line coverage: 71.5% (181/253) Branch coverage: 54.7% (92/168) Total lines: 4895/8/2026 - 12:15:13 AM Line coverage: 71.7% (183/255) Branch coverage: 54.7% (92/168) Total lines: 4915/20/2026 - 12:15:44 AM Line coverage: 71.7% (183/255) Branch coverage: 48.2% (81/168) Total lines: 491 5/4/2026 - 12:15:16 AM Line coverage: 71.5% (181/253) Branch coverage: 54.7% (92/168) Total lines: 4895/8/2026 - 12:15:13 AM Line coverage: 71.7% (183/255) Branch coverage: 54.7% (92/168) Total lines: 4915/20/2026 - 12:15:44 AM Line coverage: 71.7% (183/255) Branch coverage: 48.2% (81/168) Total lines: 491

Coverage delta

Coverage delta 7 -7

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Map(...)45.12%2168272.91%
Map(...)51.61%956279.46%
MapImageFromEntity(...)0%7280%
MapImageToEntity(...)0%620%
GetType(...)100%11100%
TypeRequiresDeserialization(...)100%11100%
DeserializeBaseItem(...)83.33%161270.58%
GetPathToSave(...)50%2266.66%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemMapper.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2
 3using System;
 4using System.Collections.Concurrent;
 5using System.Linq;
 6using System.Reflection;
 7using System.Text;
 8using System.Text.Json;
 9using Jellyfin.Database.Implementations.Entities;
 10using Jellyfin.Extensions;
 11using Jellyfin.Extensions.Json;
 12using MediaBrowser.Common;
 13using MediaBrowser.Controller;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Entities.Audio;
 16using MediaBrowser.Controller.Entities.TV;
 17using MediaBrowser.Controller.LiveTv;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.LiveTv;
 20using Microsoft.Extensions.Logging;
 21using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 22using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 23
 24namespace Jellyfin.Server.Implementations.Item;
 25
 26/// <summary>
 27/// Handles mapping between BaseItemEntity (database) and BaseItemDto (domain) objects.
 28/// </summary>
 29public static class BaseItemMapper
 30{
 31    /// <summary>
 32    /// This holds all the types in the running assemblies
 33    /// so that we can de-serialize properly when we don't have strong types.
 34    /// </summary>
 235    private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
 36
 37    /// <summary>
 38    /// Maps a Entity to the DTO.
 39    /// </summary>
 40    /// <param name="entity">The entity.</param>
 41    /// <param name="dto">The dto base instance.</param>
 42    /// <param name="appHost">The Application server Host.</param>
 43    /// <returns>The dto to map.</returns>
 44    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost)
 45    {
 7346        dto.Id = entity.Id;
 7347        dto.ParentId = entity.ParentId.GetValueOrDefault();
 7348        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 7349        dto.EndDate = entity.EndDate;
 7350        dto.CommunityRating = entity.CommunityRating;
 7351        dto.CustomRating = entity.CustomRating;
 7352        dto.IndexNumber = entity.IndexNumber;
 7353        dto.IsLocked = entity.IsLocked;
 7354        dto.Name = entity.Name;
 7355        dto.OfficialRating = entity.OfficialRating;
 7356        dto.Overview = entity.Overview;
 7357        dto.ParentIndexNumber = entity.ParentIndexNumber;
 7358        dto.PremiereDate = entity.PremiereDate;
 7359        dto.ProductionYear = entity.ProductionYear;
 7360        dto.SortName = entity.SortName;
 7361        dto.ForcedSortName = entity.ForcedSortName;
 7362        dto.RunTimeTicks = entity.RunTimeTicks;
 7363        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 7364        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 7365        dto.IsInMixedFolder = entity.IsInMixedFolder;
 7366        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 7367        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 7368        dto.CriticRating = entity.CriticRating;
 7369        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 7370        dto.OriginalTitle = entity.OriginalTitle;
 7371        dto.OriginalLanguage = entity.OriginalLanguage;
 7372        dto.Album = entity.Album;
 7373        dto.LUFS = entity.LUFS;
 7374        dto.NormalizationGain = entity.NormalizationGain;
 7375        dto.IsVirtualItem = entity.IsVirtualItem;
 7376        dto.ExternalSeriesId = entity.ExternalSeriesId;
 7377        dto.Tagline = entity.Tagline;
 7378        dto.TotalBitrate = entity.TotalBitrate;
 7379        dto.ExternalId = entity.ExternalId;
 7380        dto.Size = entity.Size;
 7381        dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
 7382        dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7383        dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7384        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 7385        dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7386        dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7387        dto.OwnerId = entity.OwnerId ?? Guid.Empty;
 7388        dto.Width = entity.Width.GetValueOrDefault();
 7389        dto.Height = entity.Height.GetValueOrDefault();
 7390        dto.UserData = entity.UserData;
 91
 7392        if (entity.Provider is not null)
 93        {
 7294            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 95        }
 96
 7397        if (entity.ExtraType is not null)
 98        {
 099            dto.ExtraType = (ExtraType)entity.ExtraType;
 100        }
 101
 73102        if (entity.LockedFields is not null)
 103        {
 72104            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 105        }
 106
 73107        if (entity.Audio is not null)
 108        {
 0109            dto.Audio = (ProgramAudio)entity.Audio;
 110        }
 111
 73112        dto.ProductionLocations = entity.ProductionLocations?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 73113        dto.Studios = entity.Studios?.Split('|') ?? [];
 73114        dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
 115
 73116        if (dto is IHasProgramAttributes hasProgramAttributes)
 117        {
 0118            hasProgramAttributes.IsMovie = entity.IsMovie;
 0119            hasProgramAttributes.IsSeries = entity.IsSeries;
 0120            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0121            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 122        }
 123
 73124        if (dto is LiveTvChannel liveTvChannel)
 125        {
 0126            liveTvChannel.ServiceName = entity.ExternalServiceId;
 127        }
 128
 73129        if (dto is Trailer trailer)
 130        {
 0131            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 132        }
 133
 73134        if (dto is Video video)
 135        {
 1136            video.PrimaryVersionId = entity.PrimaryVersionId;
 137        }
 138
 73139        if (dto is IHasSeries hasSeriesName)
 140        {
 0141            hasSeriesName.SeriesName = entity.SeriesName;
 0142            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0143            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 144        }
 145
 73146        if (dto is Episode episode)
 147        {
 0148            episode.SeasonName = entity.SeasonName;
 0149            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 150        }
 151
 73152        if (dto is IHasArtist hasArtists)
 153        {
 0154            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 155        }
 156
 73157        if (dto is IHasAlbumArtist hasAlbumArtists)
 158        {
 0159            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 160        }
 161
 73162        if (dto is LiveTvProgram program)
 163        {
 0164            program.ShowId = entity.ShowId;
 165        }
 166
 73167        if (entity.Images is not null)
 168        {
 72169            dto.ImageInfos = entity.Images.Select(e => MapImageFromEntity(e, appHost)).ToArray();
 170        }
 171
 73172        if (dto is IHasStartDate hasStartDate)
 173        {
 0174            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 175        }
 176
 177        // Fields that are present in the DB but are never actually used
 178        // dto.UnratedType = entity.UnratedType;
 179        // dto.TopParentId = entity.TopParentId;
 180        // dto.CleanName = entity.CleanName;
 181        // dto.UserDataKey = entity.UserDataKey;
 182
 73183        if (dto is Folder folder)
 184        {
 72185            folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKin
 72186            if (entity.LinkedChildEntities is not null && entity.LinkedChildEntities.Count > 0)
 187            {
 0188                folder.LinkedChildren = entity.LinkedChildEntities
 0189                    .OrderBy(e => e.SortOrder)
 0190                    .Select(e => new LinkedChild
 0191                    {
 0192                        ItemId = e.ChildId,
 0193                        Type = (MediaBrowser.Controller.Entities.LinkedChildType)e.ChildType
 0194                    })
 0195                    .ToArray();
 196            }
 197        }
 198
 73199        return dto;
 200    }
 201
 202    /// <summary>
 203    /// Maps a DTO to a database entity.
 204    /// </summary>
 205    /// <param name="dto">The DTO.</param>
 206    /// <param name="appHost">The application host for path resolution.</param>
 207    /// <returns>The database entity.</returns>
 208    public static BaseItemEntity Map(BaseItemDto dto, IServerApplicationHost appHost)
 209    {
 121210        var dtoType = dto.GetType();
 121211        var entity = new BaseItemEntity()
 121212        {
 121213            Type = dtoType.ToString(),
 121214            Id = dto.Id
 121215        };
 216
 121217        if (TypeRequiresDeserialization(dtoType))
 218        {
 100219            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 220        }
 221
 121222        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 121223        entity.Path = GetPathToSave(dto.Path, appHost);
 121224        entity.EndDate = dto.EndDate;
 121225        entity.CommunityRating = dto.CommunityRating;
 121226        entity.CustomRating = dto.CustomRating;
 121227        entity.IndexNumber = dto.IndexNumber;
 121228        entity.IsLocked = dto.IsLocked;
 121229        entity.Name = dto.Name;
 121230        entity.CleanName = dto.Name.GetCleanValue();
 121231        entity.OfficialRating = dto.OfficialRating;
 121232        entity.Overview = dto.Overview;
 121233        entity.ParentIndexNumber = dto.ParentIndexNumber;
 121234        entity.PremiereDate = dto.PremiereDate;
 121235        entity.ProductionYear = dto.ProductionYear;
 121236        entity.SortName = dto.SortName;
 121237        entity.ForcedSortName = dto.ForcedSortName;
 121238        entity.RunTimeTicks = dto.RunTimeTicks;
 121239        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 121240        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 121241        entity.IsInMixedFolder = dto.IsInMixedFolder;
 121242        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 121243        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 121244        entity.CriticRating = dto.CriticRating;
 121245        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 121246        entity.OriginalTitle = dto.OriginalTitle;
 121247        entity.OriginalLanguage = dto.OriginalLanguage;
 121248        entity.Album = dto.Album;
 121249        entity.LUFS = dto.LUFS;
 121250        entity.NormalizationGain = dto.NormalizationGain;
 121251        entity.IsVirtualItem = dto.IsVirtualItem;
 121252        entity.ExternalSeriesId = dto.ExternalSeriesId;
 121253        entity.Tagline = dto.Tagline;
 121254        entity.TotalBitrate = dto.TotalBitrate;
 121255        entity.ExternalId = dto.ExternalId;
 121256        entity.Size = dto.Size;
 121257        entity.Genres = string.Join('|', dto.Genres.Distinct(StringComparer.OrdinalIgnoreCase));
 121258        entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
 121259        entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
 121260        entity.ChannelId = dto.ChannelId;
 121261        entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
 121262        entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
 121263        entity.OwnerId = dto.OwnerId == Guid.Empty ? null : dto.OwnerId;
 121264        entity.Width = dto.Width;
 121265        entity.Height = dto.Height;
 121266        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 121267        {
 121268            Item = entity,
 121269            ProviderId = e.Key,
 121270            ProviderValue = e.Value
 121271        }).ToList();
 272
 121273        if (dto.Audio.HasValue)
 274        {
 0275            entity.Audio = (ProgramAudioEntity)dto.Audio;
 276        }
 277
 121278        if (dto.ExtraType.HasValue)
 279        {
 0280            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 281        }
 282
 121283        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Wher
 121284        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios.Distinct(StringComparer.OrdinalIgnoreCas
 121285        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags.Distinct(StringComparer.OrdinalIgnoreCase)) : nul
 121286        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 121287            .Select(e => new BaseItemMetadataField()
 121288            {
 121289                Id = (int)e,
 121290                Item = entity,
 121291                ItemId = entity.Id
 121292            })
 121293            .ToArray() : null;
 294
 121295        if (dto is IHasProgramAttributes hasProgramAttributes)
 296        {
 0297            entity.IsMovie = hasProgramAttributes.IsMovie;
 0298            entity.IsSeries = hasProgramAttributes.IsSeries;
 0299            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 0300            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 301        }
 302
 121303        if (dto is LiveTvChannel liveTvChannel)
 304        {
 0305            entity.ExternalServiceId = liveTvChannel.ServiceName;
 306        }
 307
 121308        if (dto is Video video)
 309        {
 0310            entity.PrimaryVersionId = video.PrimaryVersionId;
 311        }
 312
 121313        if (dto is IHasSeries hasSeriesName)
 314        {
 0315            entity.SeriesName = hasSeriesName.SeriesName;
 0316            entity.SeriesId = hasSeriesName.SeriesId;
 0317            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 318        }
 319
 121320        if (dto is Episode episode)
 321        {
 0322            entity.SeasonName = episode.SeasonName;
 0323            entity.SeasonId = episode.SeasonId;
 324        }
 325
 121326        if (dto is IHasArtist hasArtists)
 327        {
 0328            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists.Distinct(StringCompare
 329        }
 330
 121331        if (dto is IHasAlbumArtist hasAlbumArtists)
 332        {
 0333            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 334        }
 335
 121336        if (dto is LiveTvProgram program)
 337        {
 0338            entity.ShowId = program.ShowId;
 339        }
 340
 121341        if (dto.ImageInfos is not null)
 342        {
 121343            entity.Images = dto.ImageInfos.Select(f => MapImageToEntity(dto.Id, f)).ToArray();
 344        }
 345
 121346        if (dto is Trailer trailer)
 347        {
 0348            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 0349            {
 0350                Id = (int)e,
 0351                Item = entity,
 0352                ItemId = entity.Id
 0353            }).ToArray() ?? [];
 354        }
 355
 121356        entity.MediaType = dto.MediaType.ToString();
 121357        if (dto is IHasStartDate hasStartDate)
 358        {
 0359            entity.StartDate = hasStartDate.StartDate;
 360        }
 361
 121362        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 363
 364        // Fields that are present in the DB but are never actually used
 365        // dto.UserDataKey = entity.UserDataKey;
 366
 121367        if (dto is Folder folder)
 368        {
 121369            entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdde
 121370            entity.IsFolder = folder.IsFolder;
 371        }
 372
 121373        return entity;
 374    }
 375
 376    /// <summary>
 377    /// Maps a database image entity to a domain image info.
 378    /// </summary>
 379    /// <param name="e">The database image entity.</param>
 380    /// <param name="appHost">The application host.</param>
 381    /// <returns>The mapped image info.</returns>
 382    public static ItemImageInfo MapImageFromEntity(BaseItemImageInfo e, IServerApplicationHost? appHost)
 383    {
 0384        return new ItemImageInfo()
 0385        {
 0386            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 0387            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 0388            DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc),
 0389            Height = e.Height,
 0390            Width = e.Width,
 0391            Type = (ImageType)e.ImageType
 0392        };
 393    }
 394
 395    /// <summary>
 396    /// Maps a domain image info to a database image entity.
 397    /// </summary>
 398    /// <param name="baseItemId">The parent item ID.</param>
 399    /// <param name="e">The image info to map.</param>
 400    /// <returns>The mapped database entity.</returns>
 401    public static BaseItemImageInfo MapImageToEntity(Guid baseItemId, ItemImageInfo e)
 402    {
 0403        return new BaseItemImageInfo()
 0404        {
 0405            ItemId = baseItemId,
 0406            Id = Guid.NewGuid(),
 0407            Path = e.Path,
 0408            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 0409            DateModified = e.DateModified,
 0410            Height = e.Height,
 0411            Width = e.Width,
 0412            ImageType = (ImageInfoImageType)e.Type,
 0413            Item = null!
 0414        };
 415    }
 416
 417    /// <summary>
 418    /// Gets the type from a type name string.
 419    /// </summary>
 420    /// <param name="typeName">The type name.</param>
 421    /// <returns>The resolved type, or null.</returns>
 422    public static Type? GetType(string typeName)
 423    {
 147424        ArgumentException.ThrowIfNullOrEmpty(typeName);
 425
 147426        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 147427            .Select(a => a.GetType(k))
 147428            .FirstOrDefault(t => t is not null));
 429    }
 430
 431    /// <summary>
 432    /// Checks whether a type requires JSON deserialization.
 433    /// </summary>
 434    /// <param name="type">The type to check.</param>
 435    /// <returns>True if the type requires deserialization.</returns>
 436    public static bool TypeRequiresDeserialization(Type type)
 437    {
 194438        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 439    }
 440
 441    /// <summary>
 442    /// Deserializes a BaseItemEntity and sets all properties.
 443    /// </summary>
 444    /// <param name="baseItemEntity">The DB entity.</param>
 445    /// <param name="logger">Logger.</param>
 446    /// <param name="appHost">The application server Host.</param>
 447    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 448    /// <returns>A mapped BaseItem, or null if the item type is unknown.</returns>
 449    public static BaseItemDto? DeserializeBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost
 450    {
 75451        var type = GetType(baseItemEntity.Type);
 75452        if (type is null)
 453        {
 2454            logger.LogWarning(
 2455                "Skipping item {ItemId} with unknown type '{ItemType}'. This may indicate a removed plugin or database c
 2456                baseItemEntity.Id,
 2457                baseItemEntity.Type);
 2458            return null;
 459        }
 460
 73461        BaseItemDto? dto = null;
 73462        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 463        {
 464            try
 465            {
 12466                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 12467            }
 0468            catch (JsonException ex)
 469            {
 0470                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 0471            }
 472        }
 473
 73474        if (dto is null)
 475        {
 61476            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 477        }
 478
 73479        return Map(baseItemEntity, dto, appHost);
 480    }
 481
 482    private static string? GetPathToSave(string path, IServerApplicationHost appHost)
 483    {
 121484        if (path is null)
 485        {
 10486            return null;
 487        }
 488
 111489        return appHost.ReverseVirtualPath(path);
 490    }
 491}