Browse Source

Merge pull request #4458 from crobibero/dotnet-5

Upgrade to Net5
Joshua M. Boniface 4 years ago
parent
commit
2f426dfc97
94 changed files with 265 additions and 193 deletions
  1. 1 1
      .ci/azure-pipelines-abi.yml
  2. 1 1
      .ci/azure-pipelines-main.yml
  3. 2 2
      .ci/azure-pipelines-test.yml
  4. 1 1
      .ci/azure-pipelines.yml
  5. 2 2
      Dockerfile
  6. 2 2
      Dockerfile.arm
  7. 2 2
      Dockerfile.arm64
  8. 1 1
      DvdLib/DvdLib.csproj
  9. 1 1
      Emby.Dlna/Emby.Dlna.csproj
  10. 1 1
      Emby.Drawing/Emby.Drawing.csproj
  11. 1 1
      Emby.Naming/Emby.Naming.csproj
  12. 1 1
      Emby.Notifications/Emby.Notifications.csproj
  13. 5 5
      Emby.Notifications/NotificationEntryPoint.cs
  14. 1 1
      Emby.Photos/Emby.Photos.csproj
  15. 4 2
      Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
  16. 2 1
      Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
  17. 8 6
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  18. 3 2
      Emby.Server.Implementations/Session/WebSocketController.cs
  19. 5 3
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  20. 6 0
      Jellyfin.Api/Controllers/EnvironmentController.cs
  21. 1 1
      Jellyfin.Api/Controllers/GenresController.cs
  22. 3 1
      Jellyfin.Api/Controllers/HlsSegmentController.cs
  23. 1 1
      Jellyfin.Api/Controllers/ImageByNameController.cs
  24. 2 1
      Jellyfin.Api/Controllers/ImageController.cs
  25. 10 2
      Jellyfin.Api/Controllers/ItemLookupController.cs
  26. 3 3
      Jellyfin.Api/Controllers/LibraryController.cs
  27. 1 1
      Jellyfin.Api/Controllers/LiveTvController.cs
  28. 2 2
      Jellyfin.Api/Controllers/MusicGenresController.cs
  29. 5 0
      Jellyfin.Api/Controllers/PackageController.cs
  30. 13 5
      Jellyfin.Api/Controllers/RemoteImageController.cs
  31. 1 1
      Jellyfin.Api/Controllers/SearchController.cs
  32. 3 1
      Jellyfin.Api/Controllers/VideoHlsController.cs
  33. 8 1
      Jellyfin.Api/Helpers/AudioHelper.cs
  34. 8 3
      Jellyfin.Api/Helpers/DynamicHlsHelper.cs
  35. 2 2
      Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
  36. 3 3
      Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
  37. 5 0
      Jellyfin.Api/Helpers/HlsHelpers.cs
  38. 6 0
      Jellyfin.Api/Helpers/ProgressiveFileCopier.cs
  39. 4 0
      Jellyfin.Api/Helpers/StreamingHelpers.cs
  40. 10 9
      Jellyfin.Api/Helpers/TranscodingJobHelper.cs
  41. 5 3
      Jellyfin.Api/Jellyfin.Api.csproj
  42. 2 2
      Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
  43. 1 1
      Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
  44. 1 1
      Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
  45. 3 3
      Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs
  46. 7 7
      Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
  47. 3 3
      Jellyfin.Data/Jellyfin.Data.csproj
  48. 1 1
      Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
  49. 7 4
      Jellyfin.Drawing.Skia/SkiaEncoder.cs
  50. 4 4
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  51. 2 1
      Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
  52. 4 4
      Jellyfin.Server/Filters/FileResponseFilter.cs
  53. 2 1
      Jellyfin.Server/Formatters/CssOutputFormatter.cs
  54. 2 1
      Jellyfin.Server/Formatters/XmlOutputFormatter.cs
  55. 5 5
      Jellyfin.Server/Jellyfin.Server.csproj
  56. 3 3
      MediaBrowser.Common/MediaBrowser.Common.csproj
  57. 3 3
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  58. 1 1
      MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
  59. 1 1
      MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
  60. 1 1
      MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
  61. 3 1
      MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
  62. 1 1
      MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
  63. 2 2
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  64. 1 1
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  65. 1 1
      MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
  66. 0 6
      MediaBrowser.Model/Extensions/StringHelper.cs
  67. 4 4
      MediaBrowser.Model/MediaBrowser.Model.csproj
  68. 4 4
      MediaBrowser.Model/Net/MimeTypes.cs
  69. 4 4
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  70. 1 1
      MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
  71. 1 1
      README.md
  72. 1 1
      RSSDP/RSSDP.csproj
  73. 1 1
      deployment/Dockerfile.debian.amd64
  74. 1 1
      deployment/Dockerfile.debian.arm64
  75. 1 1
      deployment/Dockerfile.debian.armhf
  76. 1 1
      deployment/Dockerfile.linux.amd64
  77. 1 1
      deployment/Dockerfile.macos
  78. 1 1
      deployment/Dockerfile.portable
  79. 1 1
      deployment/Dockerfile.ubuntu.amd64
  80. 1 1
      deployment/Dockerfile.ubuntu.arm64
  81. 1 1
      deployment/Dockerfile.ubuntu.armhf
  82. 1 1
      deployment/Dockerfile.windows.amd64
  83. 4 4
      tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
  84. 2 2
      tests/Jellyfin.Api.Tests/BrandingServiceTests.cs
  85. 3 3
      tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
  86. 16 16
      tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs
  87. 1 1
      tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs
  88. 1 1
      tests/Jellyfin.Api.Tests/TestHelpers.cs
  89. 1 1
      tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
  90. 1 1
      tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
  91. 1 1
      tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
  92. 1 1
      tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
  93. 1 1
      tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
  94. 1 1
      tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj

+ 1 - 1
.ci/azure-pipelines-abi.yml

@@ -7,7 +7,7 @@ parameters:
   default: "ubuntu-latest"
 - name: DotNetSdkVersion
   type: string
-  default: 3.1.100
+  default: 5.0.100
 
 jobs:
   - job: CompatibilityCheck

+ 1 - 1
.ci/azure-pipelines-main.yml

@@ -1,7 +1,7 @@
 parameters:
   LinuxImage: 'ubuntu-latest'
   RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
-  DotNetSdkVersion: 3.1.100
+  DotNetSdkVersion: 5.0.100
 
 jobs:
   - job: Build

+ 2 - 2
.ci/azure-pipelines-test.yml

@@ -10,7 +10,7 @@ parameters:
   default: "tests/**/*Tests.csproj"
 - name: DotNetSdkVersion
   type: string
-  default: 3.1.100
+  default: 5.0.100
 
 jobs:
   - job: Test
@@ -94,5 +94,5 @@ jobs:
         displayName: 'Publish OpenAPI Artifact'
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
         inputs:
-          targetPath: "tests/Jellyfin.Api.Tests/bin/Release/netcoreapp3.1/openapi.json"
+          targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json"
           artifactName: 'OpenAPI Spec'

+ 1 - 1
.ci/azure-pipelines.yml

@@ -6,7 +6,7 @@ variables:
 - name: RestoreBuildProjects
   value: 'Jellyfin.Server/Jellyfin.Server.csproj'
 - name: DotNetSdkVersion
-  value: 3.1.100
+  value: 5.0.100
 
 pr:
   autoCancel: true

+ 2 - 2
Dockerfile

@@ -1,4 +1,4 @@
-ARG DOTNET_VERSION=3.1
+ARG DOTNET_VERSION=5.0
 
 FROM node:alpine as web-builder
 ARG JELLYFIN_WEB_VERSION=master
@@ -8,7 +8,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
  && yarn install \
  && mv dist /dist
 
-FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster as builder
 WORKDIR /repo
 COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1

+ 2 - 2
Dockerfile.arm

@@ -2,7 +2,7 @@
 #####################################
 # Requires binfm_misc registration
 # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=3.1
+ARG DOTNET_VERSION=5.0
 
 
 FROM node:alpine as web-builder
@@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
  && mv dist /dist
 
 
-FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
 WORKDIR /repo
 COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1

+ 2 - 2
Dockerfile.arm64

@@ -2,7 +2,7 @@
 #####################################
 # Requires binfm_misc registration
 # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=3.1
+ARG DOTNET_VERSION=5.0
 
 
 FROM node:alpine as web-builder
@@ -14,7 +14,7 @@ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-
  && mv dist /dist
 
 
-FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
 WORKDIR /repo
 COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1

+ 1 - 1
DvdLib/DvdLib.csproj

@@ -10,7 +10,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 1 - 1
Emby.Dlna/Emby.Dlna.csproj

@@ -17,7 +17,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 1 - 1
Emby.Drawing/Emby.Drawing.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 1 - 1
Emby.Naming/Emby.Naming.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 1 - 1
Emby.Notifications/Emby.Notifications.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 5 - 5
Emby.Notifications/NotificationEntryPoint.cs

@@ -83,7 +83,7 @@ namespace Emby.Notifications
             return Task.CompletedTask;
         }
 
-        private async void OnAppHostHasPendingRestartChanged(object sender, EventArgs e)
+        private async void OnAppHostHasPendingRestartChanged(object? sender, EventArgs e)
         {
             var type = NotificationType.ServerRestartRequired.ToString();
 
@@ -99,7 +99,7 @@ namespace Emby.Notifications
             await SendNotification(notification, null).ConfigureAwait(false);
         }
 
-        private async void OnActivityManagerEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
+        private async void OnActivityManagerEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
         {
             var entry = e.Argument;
 
@@ -132,7 +132,7 @@ namespace Emby.Notifications
             return _config.GetConfiguration<NotificationOptions>("notifications");
         }
 
-        private async void OnAppHostHasUpdateAvailableChanged(object sender, EventArgs e)
+        private async void OnAppHostHasUpdateAvailableChanged(object? sender, EventArgs e)
         {
             if (!_appHost.HasUpdateAvailable)
             {
@@ -151,7 +151,7 @@ namespace Emby.Notifications
             await SendNotification(notification, null).ConfigureAwait(false);
         }
 
-        private void OnLibraryManagerItemAdded(object sender, ItemChangeEventArgs e)
+        private void OnLibraryManagerItemAdded(object? sender, ItemChangeEventArgs e)
         {
             if (!FilterItem(e.Item))
             {
@@ -197,7 +197,7 @@ namespace Emby.Notifications
             return item.SourceType == SourceType.Library;
         }
 
-        private async void LibraryUpdateTimerCallback(object state)
+        private async void LibraryUpdateTimerCallback(object? state)
         {
             List<BaseItem> items;
 

+ 1 - 1
Emby.Photos/Emby.Photos.csproj

@@ -19,7 +19,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 4 - 2
Emby.Server.Implementations/AppBase/ConfigurationHelper.cs

@@ -3,6 +3,7 @@
 using System;
 using System.IO;
 using System.Linq;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.Serialization;
 
 namespace Emby.Server.Implementations.AppBase
@@ -35,7 +36,7 @@ namespace Emby.Server.Implementations.AppBase
             }
             catch (Exception)
             {
-                configuration = Activator.CreateInstance(type);
+                configuration = Activator.CreateInstance(type) ?? throw new ArgumentException($"Provided path ({type}) is not valid.", nameof(type));
             }
 
             using var stream = new MemoryStream(buffer?.Length ?? 0);
@@ -48,8 +49,9 @@ namespace Emby.Server.Implementations.AppBase
             // If the file didn't exist before, or if something has changed, re-save
             if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer))
             {
-                Directory.CreateDirectory(Path.GetDirectoryName(path));
+                var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path));
 
+                Directory.CreateDirectory(directory);
                 // Save it after load in case we got new items
                 using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
                 {

+ 2 - 1
Emby.Server.Implementations/Cryptography/CryptographyProvider.cs

@@ -3,6 +3,7 @@
 using System;
 using System.Collections.Generic;
 using System.Security.Cryptography;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.Cryptography;
 using static MediaBrowser.Common.Cryptography.Constants;
 
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Cryptography
                 throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
             }
 
-            using var h = HashAlgorithm.Create(hashMethod);
+            using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException($"Unknown hash method: {hashMethod}.");
             if (salt.Length == 0)
             {
                 return h.ComputeHash(bytes);

+ 8 - 6
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -32,13 +32,13 @@
     <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.9" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
     <PackageReference Include="Mono.Nat" Version="3.0.0" />
     <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
-    <PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
+    <PackageReference Include="ServiceStack.Text.Core" Version="5.10.0" />
     <PackageReference Include="sharpcompress" Version="0.26.0" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
     <PackageReference Include="DotNet.Glob" Version="3.1.0" />
@@ -49,10 +49,12 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
+    <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
+    <NoWarn>AD0001</NoWarn>
   </PropertyGroup>
 
   <!-- Code Analyzers-->

+ 3 - 2
Emby.Server.Implementations/Session/WebSocketController.cs

@@ -8,6 +8,7 @@ using System.Linq;
 using System.Net.WebSockets;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Net;
@@ -55,9 +56,9 @@ namespace Emby.Server.Implementations.Session
             connection.Closed += OnConnectionClosed;
         }
 
-        private void OnConnectionClosed(object sender, EventArgs e)
+        private void OnConnectionClosed(object? sender, EventArgs e)
         {
-            var connection = (IWebSocketConnection)sender;
+            var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
             _logger.LogDebug("Removing websocket from session {Session}", _session.Id);
             _sockets.Remove(connection);
             connection.Closed -= OnConnectionClosed;

+ 5 - 3
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -14,6 +14,7 @@ using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Models.PlaybackDtos;
 using Jellyfin.Api.Models.StreamingDtos;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
@@ -1347,7 +1348,9 @@ namespace Jellyfin.Api.Controllers
 
             var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
 
-            var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer);
+            var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+
+            var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer);
 
             var segmentFormat = GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.');
             if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
@@ -1565,8 +1568,7 @@ namespace Jellyfin.Api.Controllers
 
         private string GetSegmentPath(StreamState state, string playlist, int index)
         {
-            var folder = Path.GetDirectoryName(playlist);
-
+            var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist));
             var filename = Path.GetFileNameWithoutExtension(playlist);
 
             return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request.SegmentContainer));

+ 6 - 0
Jellyfin.Api/Controllers/EnvironmentController.cs

@@ -5,6 +5,7 @@ using System.IO;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Models.EnvironmentDtos;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.IO;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
@@ -103,6 +104,11 @@ namespace Jellyfin.Api.Controllers
 
                 if (validatePathDto.ValidateWritable)
                 {
+                    if (validatePathDto.Path == null)
+                    {
+                        throw new ResourceNotFoundException(nameof(validatePathDto.Path));
+                    }
+
                     var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString());
                     try
                     {

+ 1 - 1
Jellyfin.Api/Controllers/GenresController.cs

@@ -176,7 +176,7 @@ namespace Jellyfin.Api.Controllers
             return _dtoService.GetBaseItemDto(item, dtoOptions);
         }
 
-        private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
+        private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
             where T : BaseItem, new()
         {
             var result = libraryManager.GetItemList(new InternalItemsQuery

+ 3 - 1
Jellyfin.Api/Controllers/HlsSegmentController.cs

@@ -8,6 +8,7 @@ using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.IO;
@@ -134,7 +135,8 @@ namespace Jellyfin.Api.Controllers
             var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath)
                 .FirstOrDefault(i =>
                     string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase)
-                    && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
+                    && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
+                ?? throw new ResourceNotFoundException($"Provided path ({transcodeFolderPath}) is not valid.");
 
             return GetFileResult(file, playlistPath);
         }

+ 1 - 1
Jellyfin.Api/Controllers/ImageByNameController.cs

@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="theme">Theme to search.</param>
         /// <param name="name">File name to search for.</param>
         /// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
-        private ActionResult GetImageFile(string basePath, string? theme, string? name)
+        private ActionResult GetImageFile(string basePath, string theme, string? name)
         {
             var themeFolder = Path.Combine(basePath, theme);
             if (Directory.Exists(themeFolder))

+ 2 - 1
Jellyfin.Api/Controllers/ImageController.cs

@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Net.Mime;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
@@ -1268,7 +1269,7 @@ namespace Jellyfin.Api.Controllers
                 Response.Headers.Add(key, value);
             }
 
-            Response.ContentType = imageContentType;
+            Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
             Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
             Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept);
 

+ 10 - 2
Jellyfin.Api/Controllers/ItemLookupController.cs

@@ -334,10 +334,16 @@ namespace Jellyfin.Api.Controllers
         private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
         {
             using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
+            if (result.Content.Headers.ContentType?.MediaType == null)
+            {
+                throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
+            }
+
             var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
             var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 
-            Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+            var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
+            Directory.CreateDirectory(directory);
             using (var stream = result.Content)
             {
                 await using var fileStream = new FileStream(
@@ -351,7 +357,9 @@ namespace Jellyfin.Api.Controllers
                 await stream.CopyToAsync(fileStream).ConfigureAwait(false);
             }
 
-            Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+            var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
+
+            Directory.CreateDirectory(pointerCacheDirectory);
             await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
         }
 

+ 3 - 3
Jellyfin.Api/Controllers/LibraryController.cs

@@ -456,7 +456,7 @@ namespace Jellyfin.Api.Controllers
                 : null;
 
             var dtoOptions = new DtoOptions().AddClientFields(Request);
-            BaseItem parent = item.GetParent();
+            BaseItem? parent = item.GetParent();
 
             while (parent != null)
             {
@@ -467,7 +467,7 @@ namespace Jellyfin.Api.Controllers
 
                 baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
 
-                parent = parent.GetParent();
+                parent = parent?.GetParent();
             }
 
             return baseItemDtos;
@@ -893,7 +893,7 @@ namespace Jellyfin.Api.Controllers
             return _libraryManager.GetItemsResult(query).TotalRecordCount;
         }
 
-        private BaseItem TranslateParentItem(BaseItem item, User user)
+        private BaseItem? TranslateParentItem(BaseItem item, User user)
         {
             return item.GetParent() is AggregateFolder
                 ? _libraryManager.GetUserRootFolder().GetChildren(user, true)

+ 1 - 1
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -1073,7 +1073,7 @@ namespace Jellyfin.Api.Controllers
             var client = _httpClientFactory.CreateClient(NamedClient.Default);
             // https://json.schedulesdirect.org/20141201/available/countries
             // Can't dispose the response as it's required up the call chain.
-            var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
+            var response = await client.GetAsync(new Uri("https://json.schedulesdirect.org/20141201/available/countries"))
                 .ConfigureAwait(false);
 
             return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json);

+ 2 - 2
Jellyfin.Api/Controllers/MusicGenresController.cs

@@ -139,7 +139,7 @@ namespace Jellyfin.Api.Controllers
         {
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
-            MusicGenre item;
+            MusicGenre? item;
 
             if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
             {
@@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers
             return _dtoService.GetBaseItemDto(item, dtoOptions);
         }
 
-        private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
+        private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
             where T : BaseItem, new()
         {
             var result = libraryManager.GetItemList(new InternalItemsQuery

+ 5 - 0
Jellyfin.Api/Controllers/PackageController.cs

@@ -54,6 +54,11 @@ namespace Jellyfin.Api.Controllers
                     string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid))
                 .FirstOrDefault();
 
+            if (result == null)
+            {
+                return NotFound();
+            }
+
             return result;
         }
 

+ 13 - 5
Jellyfin.Api/Controllers/RemoteImageController.cs

@@ -157,9 +157,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
-        public async Task<ActionResult> GetRemoteImage([FromQuery, Required] string imageUrl)
+        public async Task<ActionResult> GetRemoteImage([FromQuery, Required] Uri imageUrl)
         {
-            var urlHash = imageUrl.GetMD5();
+            var urlHash = imageUrl.ToString().GetMD5();
             var pointerCachePath = GetFullCachePath(urlHash.ToString());
 
             string? contentPath = null;
@@ -245,17 +245,25 @@ namespace Jellyfin.Api.Controllers
         /// <param name="urlHash">The URL hash.</param>
         /// <param name="pointerCachePath">The pointer cache path.</param>
         /// <returns>Task.</returns>
-        private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
+        private async Task DownloadImage(Uri url, Guid urlHash, string pointerCachePath)
         {
             var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
             using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
+            if (response.Content.Headers.ContentType?.MediaType == null)
+            {
+                throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType));
+            }
+
             var ext = response.Content.Headers.ContentType.MediaType.Split('/')[^1];
             var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 
-            Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+            var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
+            Directory.CreateDirectory(fullCacheDirectory);
             await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
             await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
-            Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+
+            var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
+            Directory.CreateDirectory(pointerCacheDirectory);
             await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None)
                 .ConfigureAwait(false);
         }

+ 1 - 1
Jellyfin.Api/Controllers/SearchController.cs

@@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers
             }
         }
 
-        private T GetParentWithImage<T>(BaseItem item, ImageType type)
+        private T? GetParentWithImage<T>(BaseItem item, ImageType type)
             where T : BaseItem
         {
             return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type));

+ 3 - 1
Jellyfin.Api/Controllers/VideoHlsController.cs

@@ -11,6 +11,7 @@ using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Models.PlaybackDtos;
 using Jellyfin.Api.Models.StreamingDtos;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
@@ -361,7 +362,8 @@ namespace Jellyfin.Api.Controllers
             var threads = _encodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec);
             var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions);
             var format = !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) ? "." + state.Request.SegmentContainer : ".ts";
-            var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format;
+            var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+            var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format;
 
             var segmentFormat = format.TrimStart('.');
             if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))

+ 8 - 1
Jellyfin.Api/Helpers/AudioHelper.cs

@@ -1,8 +1,10 @@
-using System.Net.Http;
+using System;
+using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Models.StreamingDtos;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
@@ -98,6 +100,11 @@ namespace Jellyfin.Api.Helpers
             TranscodingJobType transcodingJobType,
             StreamingRequestDto streamingRequest)
         {
+            if (_httpContextAccessor.HttpContext == null)
+            {
+                throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
+            }
+
             bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head;
             var cancellationTokenSource = new CancellationTokenSource();
 

+ 8 - 3
Jellyfin.Api/Helpers/DynamicHlsHelper.cs

@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Helpers
             StreamingRequestDto streamingRequest,
             bool enableAdaptiveBitrateStreaming)
         {
-            var isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == WebRequestMethods.Http.Head;
+            var isHeadRequest = _httpContextAccessor.HttpContext?.Request.Method == WebRequestMethods.Http.Head;
             var cancellationTokenSource = new CancellationTokenSource();
             return await GetMasterPlaylistInternal(
                 streamingRequest,
@@ -130,6 +130,11 @@ namespace Jellyfin.Api.Helpers
             TranscodingJobType transcodingJobType,
             CancellationTokenSource cancellationTokenSource)
         {
+            if (_httpContextAccessor.HttpContext == null)
+            {
+                throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
+            }
+
             using var state = await StreamingHelpers.GetStreamingState(
                     streamingRequest,
                     _httpContextAccessor.HttpContext.Request,
@@ -487,14 +492,14 @@ namespace Jellyfin.Api.Helpers
 
             if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
             {
-                string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
+                string? profile = state.GetRequestedProfiles("h264").FirstOrDefault();
                 return HlsCodecStringHelpers.GetH264String(profile, level);
             }
 
             if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
                 || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
             {
-                string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
+                string? profile = state.GetRequestedProfiles("h265").FirstOrDefault();
 
                 return HlsCodecStringHelpers.GetH265String(profile, level);
             }

+ 2 - 2
Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs

@@ -37,8 +37,8 @@ namespace Jellyfin.Api.Helpers
             }
 
             // Can't dispose the response as it's required up the call chain.
-            var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false);
-            var contentType = response.Content.Headers.ContentType.ToString();
+            var response = await httpClient.GetAsync(new Uri(state.MediaPath)).ConfigureAwait(false);
+            var contentType = response.Content.Headers.ContentType?.ToString();
 
             httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";
 

+ 3 - 3
Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs

@@ -23,7 +23,7 @@ namespace Jellyfin.Api.Helpers
         /// </summary>
         /// <param name="profile">AAC profile.</param>
         /// <returns>AAC codec string.</returns>
-        public static string GetAACString(string profile)
+        public static string GetAACString(string? profile)
         {
             StringBuilder result = new StringBuilder("mp4a", 9);
 
@@ -46,7 +46,7 @@ namespace Jellyfin.Api.Helpers
         /// <param name="profile">H.264 profile.</param>
         /// <param name="level">H.264 level.</param>
         /// <returns>H.264 string.</returns>
-        public static string GetH264String(string profile, int level)
+        public static string GetH264String(string? profile, int level)
         {
             StringBuilder result = new StringBuilder("avc1", 11);
 
@@ -80,7 +80,7 @@ namespace Jellyfin.Api.Helpers
         /// <param name="profile">H.265 profile.</param>
         /// <param name="level">H.265 level.</param>
         /// <returns>H.265 string.</returns>
-        public static string GetH265String(string profile, int level)
+        public static string GetH265String(string? profile, int level)
         {
             // The h265 syntax is a bit of a mystery at the time this comment was written.
             // This is what I've found through various sources:

+ 5 - 0
Jellyfin.Api/Helpers/HlsHelpers.cs

@@ -45,6 +45,11 @@ namespace Jellyfin.Api.Helpers
                         while (!reader.EndOfStream)
                         {
                             var line = await reader.ReadLineAsync().ConfigureAwait(false);
+                            if (line == null)
+                            {
+                                // Nothing currently in buffer.
+                                break;
+                            }
 
                             if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
                             {

+ 6 - 0
Jellyfin.Api/Helpers/ProgressiveFileCopier.cs

@@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Models.PlaybackDtos;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.IO;
 
@@ -90,6 +91,11 @@ namespace Jellyfin.Api.Helpers
                     allowAsyncFileRead = true;
                 }
 
+                if (_path == null)
+                {
+                    throw new ResourceNotFoundException(nameof(_path));
+                }
+
                 await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
 
                 var eofCount = 0;

+ 4 - 0
Jellyfin.Api/Helpers/StreamingHelpers.cs

@@ -83,6 +83,10 @@ namespace Jellyfin.Api.Helpers
             }
 
             streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
+            if (httpRequest.Path.Value == null)
+            {
+                throw new ResourceNotFoundException(nameof(httpRequest.Path));
+            }
 
             var url = httpRequest.Path.Value.Split('.')[^1];
 

+ 10 - 9
Jellyfin.Api/Helpers/TranscodingJobHelper.cs

@@ -12,6 +12,7 @@ using Jellyfin.Api.Models.PlaybackDtos;
 using Jellyfin.Api.Models.StreamingDtos;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
@@ -102,7 +103,7 @@ namespace Jellyfin.Api.Helpers
         /// </summary>
         /// <param name="playSessionId">Playback session id.</param>
         /// <returns>The transcoding job.</returns>
-        public TranscodingJobDto GetTranscodingJob(string playSessionId)
+        public TranscodingJobDto? GetTranscodingJob(string playSessionId)
         {
             lock (_activeTranscodingJobs)
             {
@@ -116,7 +117,7 @@ namespace Jellyfin.Api.Helpers
         /// <param name="path">Path to the transcoding file.</param>
         /// <param name="type">The <see cref="TranscodingJobType"/>.</param>
         /// <returns>The transcoding job.</returns>
-        public TranscodingJobDto GetTranscodingJob(string path, TranscodingJobType type)
+        public TranscodingJobDto? GetTranscodingJob(string path, TranscodingJobType type)
         {
             lock (_activeTranscodingJobs)
             {
@@ -193,10 +194,9 @@ namespace Jellyfin.Api.Helpers
         /// Called when [transcode kill timer stopped].
         /// </summary>
         /// <param name="state">The state.</param>
-        private async void OnTranscodeKillTimerStopped(object state)
+        private async void OnTranscodeKillTimerStopped(object? state)
         {
-            var job = (TranscodingJobDto)state;
-
+            var job = state as TranscodingJobDto ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJobDto)}", nameof(state));
             if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
             {
                 var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
@@ -489,7 +489,8 @@ namespace Jellyfin.Api.Helpers
             CancellationTokenSource cancellationTokenSource,
             string? workingDirectory = null)
         {
-            Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+            var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+            Directory.CreateDirectory(directory);
 
             await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
 
@@ -523,7 +524,7 @@ namespace Jellyfin.Api.Helpers
                     RedirectStandardInput = true,
                     FileName = _mediaEncoder.EncoderPath,
                     Arguments = commandLineArguments,
-                    WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
+                    WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? string.Empty : workingDirectory,
                     ErrorDialog = false
                 },
                 EnableRaisingEvents = true
@@ -827,7 +828,7 @@ namespace Jellyfin.Api.Helpers
         {
             lock (_transcodingLocks)
             {
-                if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim result))
+                if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim? result))
                 {
                     result = new SemaphoreSlim(1, 1);
                     _transcodingLocks[outputPath] = result;
@@ -837,7 +838,7 @@ namespace Jellyfin.Api.Helpers
             }
         }
 
-        private void OnPlaybackProgress(object sender, PlaybackProgressEventArgs e)
+        private void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs e)
         {
             if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
             {

+ 5 - 3
Jellyfin.Api/Jellyfin.Api.csproj

@@ -6,17 +6,19 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>
+    <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
+    <NoWarn>AD0001</NoWarn>
   </PropertyGroup>
 
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
-    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.9" />
+    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.0" />
     <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
-    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
     <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.6.3" />
   </ItemGroup>

+ 2 - 2
Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs

@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
         /// Start kill timer.
         /// </summary>
         /// <param name="callback">Callback action.</param>
-        public void StartKillTimer(Action<object> callback)
+        public void StartKillTimer(Action<object?> callback)
         {
             StartKillTimer(callback, PingTimeout);
         }
@@ -206,7 +206,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
         /// </summary>
         /// <param name="callback">Callback action.</param>
         /// <param name="intervalMs">Callback interval.</param>
-        public void StartKillTimer(Action<object> callback, int intervalMs)
+        public void StartKillTimer(Action<object?> callback, int intervalMs)
         {
             if (HasExited)
             {

+ 1 - 1
Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs

@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
             return _config.GetConfiguration<EncodingOptions>("encoding");
         }
 
-        private async void TimerCallback(object state)
+        private async void TimerCallback(object? state)
         {
             if (_job.HasExited)
             {

+ 1 - 1
Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs

@@ -56,7 +56,7 @@ namespace Jellyfin.Api.WebSocketListeners
             base.Dispose(dispose);
         }
 
-        private void OnEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
+        private void OnEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
         {
             SendData(true);
         }

+ 3 - 3
Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs

@@ -64,19 +64,19 @@ namespace Jellyfin.Api.WebSocketListeners
             base.Dispose(dispose);
         }
 
-        private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
+        private void OnTaskCompleted(object? sender, TaskCompletionEventArgs e)
         {
             SendData(true);
             e.Task.TaskProgress -= OnTaskProgress;
         }
 
-        private void OnTaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e)
+        private void OnTaskExecuting(object? sender, GenericEventArgs<IScheduledTaskWorker> e)
         {
             SendData(true);
             e.Argument.TaskProgress += OnTaskProgress;
         }
 
-        private void OnTaskProgress(object sender, GenericEventArgs<double> e)
+        private void OnTaskProgress(object? sender, GenericEventArgs<double> e)
         {
             SendData(false);
         }

+ 7 - 7
Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs

@@ -66,37 +66,37 @@ namespace Jellyfin.Api.WebSocketListeners
             base.Dispose(dispose);
         }
 
-        private async void OnSessionManagerSessionActivity(object sender, SessionEventArgs e)
+        private async void OnSessionManagerSessionActivity(object? sender, SessionEventArgs e)
         {
             await SendData(false).ConfigureAwait(false);
         }
 
-        private async void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e)
+        private async void OnSessionManagerCapabilitiesChanged(object? sender, SessionEventArgs e)
         {
             await SendData(true).ConfigureAwait(false);
         }
 
-        private async void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e)
+        private async void OnSessionManagerPlaybackProgress(object? sender, PlaybackProgressEventArgs e)
         {
             await SendData(!e.IsAutomated).ConfigureAwait(false);
         }
 
-        private async void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e)
+        private async void OnSessionManagerPlaybackStopped(object? sender, PlaybackStopEventArgs e)
         {
             await SendData(true).ConfigureAwait(false);
         }
 
-        private async void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e)
+        private async void OnSessionManagerPlaybackStart(object? sender, PlaybackProgressEventArgs e)
         {
             await SendData(true).ConfigureAwait(false);
         }
 
-        private async void OnSessionManagerSessionEnded(object sender, SessionEventArgs e)
+        private async void OnSessionManagerSessionEnded(object? sender, SessionEventArgs e)
         {
             await SendData(true).ConfigureAwait(false);
         }
 
-        private async void OnSessionManagerSessionStarted(object sender, SessionEventArgs e)
+        private async void OnSessionManagerSessionStarted(object? sender, SessionEventArgs e)
         {
             await SendData(true).ConfigureAwait(false);
         }

+ 3 - 3
Jellyfin.Data/Jellyfin.Data.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -41,8 +41,8 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.9" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.9" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 7 - 4
Jellyfin.Drawing.Skia/SkiaEncoder.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.IO;
 using BlurHashSharp.SkiaSharp;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Model.Drawing;
@@ -227,8 +228,8 @@ namespace Jellyfin.Drawing.Skia
             }
 
             var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path));
-
-            Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
+            var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid.");
+            Directory.CreateDirectory(directory);
             File.Copy(path, tempPath, true);
 
             return tempPath;
@@ -493,7 +494,8 @@ namespace Jellyfin.Drawing.Skia
             // If all we're doing is resizing then we can stop now
             if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator)
             {
-                Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+                var outputDirectory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+                Directory.CreateDirectory(outputDirectory);
                 using var outputStream = new SKFileWStream(outputPath);
                 using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels());
                 resizedBitmap.Encode(outputStream, skiaOutputFormat, quality);
@@ -540,7 +542,8 @@ namespace Jellyfin.Drawing.Skia
                 DrawIndicator(canvas, width, height, options);
             }
 
-            Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+            var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+            Directory.CreateDirectory(directory);
             using (var outputStream = new SKFileWStream(outputPath))
             {
                 using (var pixmap = new SKPixmap(new SKImageInfo(width, height), saveBitmap.GetPixels()))

+ 4 - 4
Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -24,12 +24,12 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="System.Linq.Async" Version="4.1.1" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.9">
+    <PackageReference Include="System.Linq.Async" Version="5.0.0" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.9">
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>

+ 2 - 1
Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs

@@ -57,7 +57,8 @@ namespace Jellyfin.Server.Implementations.Users
                 SerializablePasswordReset spr;
                 await using (var str = File.OpenRead(resetFile))
                 {
-                    spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
+                    spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false)
+                        ?? throw new ResourceNotFoundException($"Provided path ({resetFile}) is not valid.");
                 }
 
                 if (spr.ExpirationDate < DateTime.UtcNow)

+ 4 - 4
Jellyfin.Server/Filters/FileResponseFilter.cs

@@ -26,22 +26,22 @@ namespace Jellyfin.Server.Filters
                 if (attribute is ProducesFileAttribute producesFileAttribute)
                 {
                     // Get operation response values.
-                    var (_, value) = operation.Responses
+                    var response = operation.Responses
                         .FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
 
                     // Operation doesn't have a response.
-                    if (value == null)
+                    if (response.Value == null)
                     {
                         continue;
                     }
 
                     // Clear existing responses.
-                    value.Content.Clear();
+                    response.Value.Content.Clear();
 
                     // Add all content-types as file.
                     foreach (var contentType in producesFileAttribute.GetContentTypes())
                     {
-                        value.Content.Add(contentType, _openApiMediaType);
+                        response.Value.Content.Add(contentType, _openApiMediaType);
                     }
 
                     break;

+ 2 - 1
Jellyfin.Server/Formatters/CssOutputFormatter.cs

@@ -30,7 +30,8 @@ namespace Jellyfin.Server.Formatters
         /// <returns>Write stream task.</returns>
         public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
         {
-            return context.HttpContext.Response.WriteAsync(context.Object?.ToString());
+            var stringResponse = context.Object?.ToString();
+            return stringResponse == null ? Task.CompletedTask : context.HttpContext.Response.WriteAsync(stringResponse);
         }
     }
 }

+ 2 - 1
Jellyfin.Server/Formatters/XmlOutputFormatter.cs

@@ -26,7 +26,8 @@ namespace Jellyfin.Server.Formatters
         /// <inheritdoc />
         public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
         {
-            return context.HttpContext.Response.WriteAsync(context.Object?.ToString());
+            var stringResponse = context.Object?.ToString();
+            return stringResponse == null ? Task.CompletedTask : context.HttpContext.Response.WriteAsync(stringResponse);
         }
     }
 }

+ 5 - 5
Jellyfin.Server/Jellyfin.Server.csproj

@@ -8,7 +8,7 @@
   <PropertyGroup>
     <AssemblyName>jellyfin</AssemblyName>
     <OutputType>Exe</OutputType>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -38,10 +38,10 @@
 
   <ItemGroup>
     <PackageReference Include="CommandLineParser" Version="2.8.0" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.9" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.0" />
     <PackageReference Include="prometheus-net" Version="4.0.0" />
     <PackageReference Include="prometheus-net.AspNetCore" Version="4.0.0" />
     <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />

+ 3 - 3
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -18,8 +18,8 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.9" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
     <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
   </ItemGroup>
@@ -29,7 +29,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 3 - 3
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -14,8 +14,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.9" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
   </ItemGroup>
 
@@ -29,7 +29,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>

+ 1 - 1
MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs

@@ -486,7 +486,7 @@ namespace MediaBrowser.LocalMetadata.Images
             return false;
         }
 
-        private FileSystemMetadata GetImage(IEnumerable<FileSystemMetadata> files, string name)
+        private FileSystemMetadata? GetImage(IEnumerable<FileSystemMetadata> files, string name)
         {
             return files.FirstOrDefault(i => !i.IsDirectory && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase) && i.Length > 0);
         }

+ 1 - 1
MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj

@@ -11,7 +11,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 1 - 1
MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs

@@ -683,7 +683,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                 default:
                 {
                     string readerName = reader.Name;
-                    if (_validProviderIds!.TryGetValue(readerName, out string providerIdValue))
+                    if (_validProviderIds!.TryGetValue(readerName, out string? providerIdValue))
                     {
                         var id = reader.ReadElementContentAsString();
                         if (!string.IsNullOrWhiteSpace(id))

+ 3 - 1
MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Xml;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
@@ -127,7 +128,8 @@ namespace MediaBrowser.LocalMetadata.Savers
 
         private void SaveToFile(Stream stream, string path)
         {
-            Directory.CreateDirectory(Path.GetDirectoryName(path));
+            var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path));
+            Directory.CreateDirectory(directory);
             // On Windows, savint the file will fail if the file is hidden or readonly
             FileSystem.SetAttributes(path, false, false);
 

+ 1 - 1
MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs

@@ -178,7 +178,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
 
                 process.Start();
 
-                var ranToCompletion = await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
+                var ranToCompletion = await ProcessExtensions.WaitForExitAsync(process, cancellationToken).ConfigureAwait(false);
 
                 if (!ranToCompletion)
                 {

+ 2 - 2
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -25,7 +25,7 @@
   <ItemGroup>
     <PackageReference Include="BDInfo" Version="0.7.6.1" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
-    <PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
+    <PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
     <PackageReference Include="UTF.Unknown" Version="2.3.0" />
   </ItemGroup>
 

+ 1 - 1
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -758,7 +758,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 case MediaProtocol.Http:
                 {
                     using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
-                        .GetAsync(path, cancellationToken)
+                        .GetAsync(new Uri(path), cancellationToken)
                         .ConfigureAwait(false);
                     return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
                 }

+ 1 - 1
MediaBrowser.Model/Entities/ProviderIdsExtensions.cs

@@ -48,7 +48,7 @@ namespace MediaBrowser.Model.Entities
                 return null;
             }
 
-            instance.ProviderIds.TryGetValue(name, out string id);
+            instance.ProviderIds.TryGetValue(name, out string? id);
             return id;
         }
 

+ 0 - 6
MediaBrowser.Model/Extensions/StringHelper.cs

@@ -22,11 +22,6 @@ namespace MediaBrowser.Model.Extensions
                 return str;
             }
 
-#if NETSTANDARD2_0
-            char[] a = str.ToCharArray();
-            a[0] = char.ToUpperInvariant(a[0]);
-            return new string(a);
-#else
             return string.Create(
                 str.Length,
                 str,
@@ -38,7 +33,6 @@ namespace MediaBrowser.Model.Extensions
                         chars[i] = buf[i];
                     }
                 });
-#endif
         }
     }
 }

+ 4 - 4
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -14,7 +14,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
@@ -32,11 +32,11 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
-    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.9" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
     <PackageReference Include="System.Globalization" Version="4.3.0" />
-    <PackageReference Include="System.Text.Json" Version="5.0.0-preview.8.20407.11" />
+    <PackageReference Include="System.Text.Json" Version="5.0.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 4 - 4
MediaBrowser.Model/Net/MimeTypes.cs

@@ -177,7 +177,7 @@ namespace MediaBrowser.Model.Net
 
             var ext = Path.GetExtension(path);
 
-            if (_mimeTypeLookup.TryGetValue(ext, out string result))
+            if (_mimeTypeLookup.TryGetValue(ext, out string? result))
             {
                 return result;
             }
@@ -210,9 +210,9 @@ namespace MediaBrowser.Model.Net
             return enableStreamDefault ? "application/octet-stream" : null;
         }
 
-        public static string? ToExtension(string mimeType)
+        public static string? ToExtension(string? mimeType)
         {
-            if (mimeType.Length == 0)
+            if (string.IsNullOrEmpty(mimeType))
             {
                 throw new ArgumentException("String can't be empty.", nameof(mimeType));
             }
@@ -220,7 +220,7 @@ namespace MediaBrowser.Model.Net
             // handle text/html; charset=UTF-8
             mimeType = mimeType.Split(';')[0];
 
-            if (_extensionLookup.TryGetValue(mimeType, out string result))
+            if (_extensionLookup.TryGetValue(mimeType, out string? result))
             {
                 return result;
             }

+ 4 - 4
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -16,9 +16,9 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
     <PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
     <PackageReference Include="PlaylistsNET" Version="1.1.2" />
     <PackageReference Include="TMDbLib" Version="1.7.3-alpha" />
@@ -26,7 +26,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>

+ 1 - 1
MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj

@@ -15,7 +15,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 1 - 1
README.md

@@ -81,7 +81,7 @@ These instructions will help you get set up with a local development environment
 
 ### Prerequisites
 
-Before the project can be built, you must first install the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) on your system.
+Before the project can be built, you must first install the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) on your system.
 
 Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download).
 

+ 1 - 1
RSSDP/RSSDP.csproj

@@ -10,7 +10,7 @@
   </ItemGroup>
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>

+ 1 - 1
deployment/Dockerfile.debian.amd64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.debian.arm64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.debian.armhf

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.linux.amd64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.macos

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.portable

@@ -15,7 +15,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.amd64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.arm64

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.ubuntu.armhf

@@ -16,7 +16,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 1 - 1
deployment/Dockerfile.windows.amd64

@@ -15,7 +15,7 @@ RUN apt-get update \
 
 # Install dotnet repository
 # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/fdd9ecec-56b4-40f4-b762-d7efe24fc3cd/ffef51844c92afa6714528e10609a30f/dotnet-sdk-3.1.403-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/820db713-c9a5-466e-b72a-16f2f5ed00e2/628aa2a75f6aa270e77f4a83b3742fb8/dotnet-sdk-5.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
  && mkdir -p dotnet-sdk \
  && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
  && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

+ 4 - 4
tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs

@@ -81,7 +81,7 @@ namespace Jellyfin.Api.Tests.Auth
             var authenticateResult = await _sut.AuthenticateAsync();
 
             Assert.False(authenticateResult.Succeeded);
-            Assert.Equal(errorMessage, authenticateResult.Failure.Message);
+            Assert.Equal(errorMessage, authenticateResult.Failure?.Message);
         }
 
         [Fact]
@@ -100,7 +100,7 @@ namespace Jellyfin.Api.Tests.Auth
             var authorizationInfo = SetupUser();
             var authenticateResult = await _sut.AuthenticateAsync();
 
-            Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
+            Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
         }
 
         [Theory]
@@ -112,7 +112,7 @@ namespace Jellyfin.Api.Tests.Auth
             var authenticateResult = await _sut.AuthenticateAsync();
 
             var expectedRole = authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
-            Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
+            Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Role, expectedRole));
         }
 
         [Fact]
@@ -121,7 +121,7 @@ namespace Jellyfin.Api.Tests.Auth
             SetupUser();
             var authenticatedResult = await _sut.AuthenticateAsync();
 
-            Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
+            Assert.Equal(_scheme.Name, authenticatedResult.Ticket?.AuthenticationScheme);
         }
 
         private AuthorizationInfo SetupUser(bool isAdmin = false)

+ 2 - 2
tests/Jellyfin.Api.Tests/BrandingServiceTests.cs

@@ -25,7 +25,7 @@ namespace Jellyfin.Api.Tests
 
             // Assert
             response.EnsureSuccessStatusCode();
-            Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
+            Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
             var responseBody = await response.Content.ReadAsStreamAsync();
             _ = await JsonSerializer.DeserializeAsync<BrandingOptions>(responseBody);
         }
@@ -43,7 +43,7 @@ namespace Jellyfin.Api.Tests
 
             // Assert
             response.EnsureSuccessStatusCode();
-            Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType.ToString());
+            Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType?.ToString());
         }
     }
 }

+ 3 - 3
tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>
@@ -16,8 +16,8 @@
     <PackageReference Include="AutoFixture" Version="4.14.0" />
     <PackageReference Include="AutoFixture.AutoMoq" Version="4.14.0" />
     <PackageReference Include="AutoFixture.Xunit2" Version="4.14.0" />
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.9" />
-    <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.9" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />

+ 16 - 16
tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs

@@ -18,7 +18,7 @@ namespace Jellyfin.Api.Tests.ModelBinders
         public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedStringArrayQuery()
         {
             var queryParamName = "test";
-            var queryParamValues = new[] { "lol", "xd" };
+            IReadOnlyList<string> queryParamValues = new[] { "lol", "xd" };
             var queryParamString = "lol,xd";
             var queryParamType = typeof(string[]);
 
@@ -36,14 +36,14 @@ namespace Jellyfin.Api.Tests.ModelBinders
             await modelBinder.BindModelAsync(bindingContextMock.Object);
 
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Equal((string[])bindingContextMock.Object.Result.Model, queryParamValues);
+            Assert.Equal((IReadOnlyList<string>?)bindingContextMock.Object?.Result.Model, queryParamValues);
         }
 
         [Fact]
         public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedIntArrayQuery()
         {
             var queryParamName = "test";
-            var queryParamValues = new[] { 42, 0 };
+            IReadOnlyList<int> queryParamValues = new[] { 42, 0 };
             var queryParamString = "42,0";
             var queryParamType = typeof(int[]);
 
@@ -61,14 +61,14 @@ namespace Jellyfin.Api.Tests.ModelBinders
             await modelBinder.BindModelAsync(bindingContextMock.Object);
 
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Equal((int[])bindingContextMock.Object.Result.Model, queryParamValues);
+            Assert.Equal((IReadOnlyList<int>?)bindingContextMock.Object.Result.Model, queryParamValues);
         }
 
         [Fact]
         public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery()
         {
             var queryParamName = "test";
-            var queryParamValues = new[] { TestType.How, TestType.Much };
+            IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
             var queryParamString = "How,Much";
             var queryParamType = typeof(TestType[]);
 
@@ -86,14 +86,14 @@ namespace Jellyfin.Api.Tests.ModelBinders
             await modelBinder.BindModelAsync(bindingContextMock.Object);
 
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
+            Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
         }
 
         [Fact]
         public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQueryWithDoubleCommas()
         {
             var queryParamName = "test";
-            var queryParamValues = new[] { TestType.How, TestType.Much };
+            IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
             var queryParamString = "How,,Much";
             var queryParamType = typeof(TestType[]);
 
@@ -111,14 +111,14 @@ namespace Jellyfin.Api.Tests.ModelBinders
             await modelBinder.BindModelAsync(bindingContextMock.Object);
 
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
+            Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
         }
 
         [Fact]
         public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery()
         {
             var queryParamName = "test";
-            var queryParamValues = new[] { TestType.How, TestType.Much };
+            IReadOnlyList<TestType> queryParamValues = new[] { TestType.How, TestType.Much };
             var queryParamString1 = "How";
             var queryParamString2 = "Much";
             var queryParamType = typeof(TestType[]);
@@ -141,14 +141,14 @@ namespace Jellyfin.Api.Tests.ModelBinders
             await modelBinder.BindModelAsync(bindingContextMock.Object);
 
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
+            Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
         }
 
         [Fact]
         public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery()
         {
             var queryParamName = "test";
-            var queryParamValues = Array.Empty<TestType>();
+            IReadOnlyList<TestType> queryParamValues = Array.Empty<TestType>();
             var queryParamType = typeof(TestType[]);
 
             var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
@@ -169,7 +169,7 @@ namespace Jellyfin.Api.Tests.ModelBinders
             await modelBinder.BindModelAsync(bindingContextMock.Object);
 
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues);
+            Assert.Equal((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model, queryParamValues);
         }
 
         [Fact]
@@ -177,7 +177,7 @@ namespace Jellyfin.Api.Tests.ModelBinders
         {
             var queryParamName = "test";
             var queryParamString = "🔥,😢";
-            var queryParamType = typeof(TestType[]);
+            var queryParamType = typeof(IReadOnlyList<TestType>);
 
             var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
             var valueProvider = new QueryStringValueProvider(
@@ -192,7 +192,7 @@ namespace Jellyfin.Api.Tests.ModelBinders
 
             await modelBinder.BindModelAsync(bindingContextMock.Object);
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Empty((TestType[])bindingContextMock.Object.Result.Model);
+            Assert.Empty((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
         }
 
         [Fact]
@@ -201,7 +201,7 @@ namespace Jellyfin.Api.Tests.ModelBinders
             var queryParamName = "test";
             var queryParamString1 = "How";
             var queryParamString2 = "😱";
-            var queryParamType = typeof(TestType[]);
+            var queryParamType = typeof(IReadOnlyList<TestType>);
 
             var modelBinder = new CommaDelimitedArrayModelBinder(new NullLogger<CommaDelimitedArrayModelBinder>());
 
@@ -220,7 +220,7 @@ namespace Jellyfin.Api.Tests.ModelBinders
 
             await modelBinder.BindModelAsync(bindingContextMock.Object);
             Assert.True(bindingContextMock.Object.Result.IsModelSet);
-            Assert.Single((TestType[])bindingContextMock.Object.Result.Model);
+            Assert.Single((IReadOnlyList<TestType>?)bindingContextMock.Object.Result.Model);
         }
     }
 }

+ 1 - 1
tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs

@@ -30,7 +30,7 @@ namespace Jellyfin.Api.Tests
 
             // Assert
             response.EnsureSuccessStatusCode();
-            Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
+            Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
 
             // Write out for publishing
             var responseBody = await response.Content.ReadAsStringAsync();

+ 1 - 1
tests/Jellyfin.Api.Tests/TestHelpers.cs

@@ -60,7 +60,7 @@ namespace Jellyfin.Api.Tests
                 .Returns(user);
 
             httpContextAccessorMock
-                .Setup(h => h.HttpContext.Connection.RemoteIpAddress)
+                .Setup(h => h.HttpContext!.Connection.RemoteIpAddress)
                 .Returns(new IPAddress(0));
 
             return new ClaimsPrincipal(identity);

+ 1 - 1
tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>

+ 1 - 1
tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>

+ 1 - 1
tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>

+ 1 - 1
tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>

+ 1 - 1
tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <Nullable>enable</Nullable>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 1 - 1
tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <IsPackable>false</IsPackable>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <Nullable>enable</Nullable>