< 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    {
 7646        dto.Id = entity.Id;
 7647        dto.ParentId = entity.ParentId.GetValueOrDefault();
 7648        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 7649        dto.EndDate = entity.EndDate;
 7650        dto.CommunityRating = entity.CommunityRating;
 7651        dto.CustomRating = entity.CustomRating;
 7652        dto.IndexNumber = entity.IndexNumber;
 7653        dto.IsLocked = entity.IsLocked;
 7654        dto.Name = entity.Name;
 7655        dto.OfficialRating = entity.OfficialRating;
 7656        dto.Overview = entity.Overview;
 7657        dto.ParentIndexNumber = entity.ParentIndexNumber;
 7658        dto.PremiereDate = entity.PremiereDate;
 7659        dto.ProductionYear = entity.ProductionYear;
 7660        dto.SortName = entity.SortName;
 7661        dto.ForcedSortName = entity.ForcedSortName;
 7662        dto.RunTimeTicks = entity.RunTimeTicks;
 7663        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 7664        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 7665        dto.IsInMixedFolder = entity.IsInMixedFolder;
 7666        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 7667        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 7668        dto.CriticRating = entity.CriticRating;
 7669        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 7670        dto.OriginalTitle = entity.OriginalTitle;
 7671        dto.OriginalLanguage = entity.OriginalLanguage;
 7672        dto.Album = entity.Album;
 7673        dto.LUFS = entity.LUFS;
 7674        dto.NormalizationGain = entity.NormalizationGain;
 7675        dto.IsVirtualItem = entity.IsVirtualItem;
 7676        dto.ExternalSeriesId = entity.ExternalSeriesId;
 7677        dto.Tagline = entity.Tagline;
 7678        dto.TotalBitrate = entity.TotalBitrate;
 7679        dto.ExternalId = entity.ExternalId;
 7680        dto.Size = entity.Size;
 7681        dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
 7682        dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7683        dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7684        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 7685        dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7686        dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 7687        dto.OwnerId = entity.OwnerId ?? Guid.Empty;
 7688        dto.Width = entity.Width.GetValueOrDefault();
 7689        dto.Height = entity.Height.GetValueOrDefault();
 7690        dto.UserData = entity.UserData;
 91
 7692        if (entity.Provider is not null)
 93        {
 7594            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 95        }
 96
 7697        if (entity.ExtraType is not null)
 98        {
 099            dto.ExtraType = (ExtraType)entity.ExtraType;
 100        }
 101
 76102        if (entity.LockedFields is not null)
 103        {
 75104            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 105        }
 106
 76107        if (entity.Audio is not null)
 108        {
 0109            dto.Audio = (ProgramAudio)entity.Audio;
 110        }
 111
 76112        dto.ProductionLocations = entity.ProductionLocations?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 76113        dto.Studios = entity.Studios?.Split('|') ?? [];
 76114        dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
 115
 76116        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
 76124        if (dto is LiveTvChannel liveTvChannel)
 125        {
 0126            liveTvChannel.ServiceName = entity.ExternalServiceId;
 127        }
 128
 76129        if (dto is Trailer trailer)
 130        {
 0131            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 132        }
 133
 76134        if (dto is Video video)
 135        {
 1136            video.PrimaryVersionId = entity.PrimaryVersionId;
 137        }
 138
 76139        if (dto is IHasSeries hasSeriesName)
 140        {
 0141            hasSeriesName.SeriesName = entity.SeriesName;
 0142            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0143            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 144        }
 145
 76146        if (dto is Episode episode)
 147        {
 0148            episode.SeasonName = entity.SeasonName;
 0149            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 150        }
 151
 76152        if (dto is IHasArtist hasArtists)
 153        {
 0154            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 155        }
 156
 76157        if (dto is IHasAlbumArtist hasAlbumArtists)
 158        {
 0159            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 160        }
 161
 76162        if (dto is LiveTvProgram program)
 163        {
 0164            program.ShowId = entity.ShowId;
 165        }
 166
 76167        if (entity.Images is not null)
 168        {
 75169            dto.ImageInfos = entity.Images.Select(e => MapImageFromEntity(e, appHost)).ToArray();
 170        }
 171
 76172        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
 76183        if (dto is Folder folder)
 184        {
 75185            folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKin
 75186            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
 76199        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    {
 120210        var dtoType = dto.GetType();
 120211        var entity = new BaseItemEntity()
 120212        {
 120213            Type = dtoType.ToString(),
 120214            Id = dto.Id
 120215        };
 216
 120217        if (TypeRequiresDeserialization(dtoType))
 218        {
 99219            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 220        }
 221
 120222        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 120223        entity.Path = GetPathToSave(dto.Path, appHost);
 120224        entity.EndDate = dto.EndDate;
 120225        entity.CommunityRating = dto.CommunityRating;
 120226        entity.CustomRating = dto.CustomRating;
 120227        entity.IndexNumber = dto.IndexNumber;
 120228        entity.IsLocked = dto.IsLocked;
 120229        entity.Name = dto.Name;
 120230        entity.CleanName = dto.Name.GetCleanValue();
 120231        entity.OfficialRating = dto.OfficialRating;
 120232        entity.Overview = dto.Overview;
 120233        entity.ParentIndexNumber = dto.ParentIndexNumber;
 120234        entity.PremiereDate = dto.PremiereDate;
 120235        entity.ProductionYear = dto.ProductionYear;
 120236        entity.SortName = dto.SortName;
 120237        entity.ForcedSortName = dto.ForcedSortName;
 120238        entity.RunTimeTicks = dto.RunTimeTicks;
 120239        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 120240        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 120241        entity.IsInMixedFolder = dto.IsInMixedFolder;
 120242        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 120243        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 120244        entity.CriticRating = dto.CriticRating;
 120245        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 120246        entity.OriginalTitle = dto.OriginalTitle;
 120247        entity.OriginalLanguage = dto.OriginalLanguage;
 120248        entity.Album = dto.Album;
 120249        entity.LUFS = dto.LUFS;
 120250        entity.NormalizationGain = dto.NormalizationGain;
 120251        entity.IsVirtualItem = dto.IsVirtualItem;
 120252        entity.ExternalSeriesId = dto.ExternalSeriesId;
 120253        entity.Tagline = dto.Tagline;
 120254        entity.TotalBitrate = dto.TotalBitrate;
 120255        entity.ExternalId = dto.ExternalId;
 120256        entity.Size = dto.Size;
 120257        entity.Genres = string.Join('|', dto.Genres.Distinct(StringComparer.OrdinalIgnoreCase));
 120258        entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
 120259        entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
 120260        entity.ChannelId = dto.ChannelId;
 120261        entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
 120262        entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
 120263        entity.OwnerId = dto.OwnerId == Guid.Empty ? null : dto.OwnerId;
 120264        entity.Width = dto.Width;
 120265        entity.Height = dto.Height;
 120266        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 120267        {
 120268            Item = entity,
 120269            ProviderId = e.Key,
 120270            ProviderValue = e.Value
 120271        }).ToList();
 272
 120273        if (dto.Audio.HasValue)
 274        {
 0275            entity.Audio = (ProgramAudioEntity)dto.Audio;
 276        }
 277
 120278        if (dto.ExtraType.HasValue)
 279        {
 0280            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 281        }
 282
 120283        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Wher
 120284        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios.Distinct(StringComparer.OrdinalIgnoreCas
 120285        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags.Distinct(StringComparer.OrdinalIgnoreCase)) : nul
 120286        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 120287            .Select(e => new BaseItemMetadataField()
 120288            {
 120289                Id = (int)e,
 120290                Item = entity,
 120291                ItemId = entity.Id
 120292            })
 120293            .ToArray() : null;
 294
 120295        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
 120303        if (dto is LiveTvChannel liveTvChannel)
 304        {
 0305            entity.ExternalServiceId = liveTvChannel.ServiceName;
 306        }
 307
 120308        if (dto is Video video)
 309        {
 0310            entity.PrimaryVersionId = video.PrimaryVersionId;
 311        }
 312
 120313        if (dto is IHasSeries hasSeriesName)
 314        {
 0315            entity.SeriesName = hasSeriesName.SeriesName;
 0316            entity.SeriesId = hasSeriesName.SeriesId;
 0317            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 318        }
 319
 120320        if (dto is Episode episode)
 321        {
 0322            entity.SeasonName = episode.SeasonName;
 0323            entity.SeasonId = episode.SeasonId;
 324        }
 325
 120326        if (dto is IHasArtist hasArtists)
 327        {
 0328            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists.Distinct(StringCompare
 329        }
 330
 120331        if (dto is IHasAlbumArtist hasAlbumArtists)
 332        {
 0333            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 334        }
 335
 120336        if (dto is LiveTvProgram program)
 337        {
 0338            entity.ShowId = program.ShowId;
 339        }
 340
 120341        if (dto.ImageInfos is not null)
 342        {
 120343            entity.Images = dto.ImageInfos.Select(f => MapImageToEntity(dto.Id, f)).ToArray();
 344        }
 345
 120346        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
 120356        entity.MediaType = dto.MediaType.ToString();
 120357        if (dto is IHasStartDate hasStartDate)
 358        {
 0359            entity.StartDate = hasStartDate.StartDate;
 360        }
 361
 120362        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
 120367        if (dto is Folder folder)
 368        {
 120369            entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdde
 120370            entity.IsFolder = folder.IsFolder;
 371        }
 372
 120373        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    {
 153424        ArgumentException.ThrowIfNullOrEmpty(typeName);
 425
 153426        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 153427            .Select(a => a.GetType(k))
 153428            .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    {
 196438        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    {
 78451        var type = GetType(baseItemEntity.Type);
 78452        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
 76461        BaseItemDto? dto = null;
 76462        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 463        {
 464            try
 465            {
 14466                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 14467            }
 0468            catch (JsonException ex)
 469            {
 0470                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 0471            }
 472        }
 473
 76474        if (dto is null)
 475        {
 62476            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 477        }
 478
 76479        return Map(baseItemEntity, dto, appHost);
 480    }
 481
 482    private static string? GetPathToSave(string path, IServerApplicationHost appHost)
 483    {
 120484        if (path is null)
 485        {
 10486            return null;
 487        }
 488
 110489        return appHost.ReverseVirtualPath(path);
 490    }
 491}