瀏覽代碼

Make all FromRoute required

crobibero 4 年之前
父節點
當前提交
59d47ec3f5
共有 40 個文件被更改,包括 244 次插入240 次删除
  1. 3 2
      Jellyfin.Api/Controllers/AlbumsController.cs
  2. 2 1
      Jellyfin.Api/Controllers/ArtistsController.cs
  3. 3 2
      Jellyfin.Api/Controllers/AudioController.cs
  4. 2 2
      Jellyfin.Api/Controllers/ChannelsController.cs
  5. 2 2
      Jellyfin.Api/Controllers/CollectionController.cs
  6. 2 2
      Jellyfin.Api/Controllers/ConfigurationController.cs
  7. 2 2
      Jellyfin.Api/Controllers/DisplayPreferencesController.cs
  8. 3 3
      Jellyfin.Api/Controllers/DlnaController.cs
  9. 9 9
      Jellyfin.Api/Controllers/DlnaServerController.cs
  10. 16 16
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  11. 1 1
      Jellyfin.Api/Controllers/GenresController.cs
  12. 6 6
      Jellyfin.Api/Controllers/HlsSegmentController.cs
  13. 78 78
      Jellyfin.Api/Controllers/ImageController.cs
  14. 6 6
      Jellyfin.Api/Controllers/InstantMixController.cs
  15. 2 2
      Jellyfin.Api/Controllers/ItemLookupController.cs
  16. 1 1
      Jellyfin.Api/Controllers/ItemRefreshController.cs
  17. 3 3
      Jellyfin.Api/Controllers/ItemUpdateController.cs
  18. 2 2
      Jellyfin.Api/Controllers/ItemsController.cs
  19. 8 8
      Jellyfin.Api/Controllers/LibraryController.cs
  20. 13 13
      Jellyfin.Api/Controllers/LiveTvController.cs
  21. 2 2
      Jellyfin.Api/Controllers/MediaInfoController.cs
  22. 1 1
      Jellyfin.Api/Controllers/MusicGenresController.cs
  23. 3 3
      Jellyfin.Api/Controllers/PackageController.cs
  24. 2 1
      Jellyfin.Api/Controllers/PersonsController.cs
  25. 14 14
      Jellyfin.Api/Controllers/PlaylistsController.cs
  26. 9 9
      Jellyfin.Api/Controllers/PlaystateController.cs
  27. 5 5
      Jellyfin.Api/Controllers/PluginsController.cs
  28. 3 3
      Jellyfin.Api/Controllers/RemoteImageController.cs
  29. 1 1
      Jellyfin.Api/Controllers/ScheduledTasksController.cs
  30. 3 3
      Jellyfin.Api/Controllers/SessionController.cs
  31. 1 1
      Jellyfin.Api/Controllers/StudiosController.cs
  32. 8 8
      Jellyfin.Api/Controllers/SubtitleController.cs
  33. 1 1
      Jellyfin.Api/Controllers/SuggestionsController.cs
  34. 2 2
      Jellyfin.Api/Controllers/UniversalAudioController.cs
  35. 7 7
      Jellyfin.Api/Controllers/UserController.cs
  36. 10 10
      Jellyfin.Api/Controllers/UserLibraryController.cs
  37. 2 2
      Jellyfin.Api/Controllers/UserViewsController.cs
  38. 1 1
      Jellyfin.Api/Controllers/VideoHlsController.cs
  39. 4 4
      Jellyfin.Api/Controllers/VideosController.cs
  40. 1 1
      Jellyfin.Api/Controllers/YearsController.cs

+ 3 - 2
Jellyfin.Api/Controllers/AlbumsController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Helpers;
@@ -52,7 +53,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Albums/{albumId}/Similar")]
         [HttpGet("Albums/{albumId}/Similar")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
-            [FromRoute] string albumId,
+            [FromRoute][Required] string albumId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] int? limit)
             [FromQuery] int? limit)
@@ -84,7 +85,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Artists/{artistId}/Similar")]
         [HttpGet("Artists/{artistId}/Similar")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
-            [FromRoute] string artistId,
+            [FromRoute][Required] string artistId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] int? limit)
             [FromQuery] int? limit)

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

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
@@ -469,7 +470,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the artist.</returns>
         /// <returns>An <see cref="OkResult"/> containing the artist.</returns>
         [HttpGet("{name}")]
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetArtistByName([FromRoute] string name, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetArtistByName([FromRoute][Required] string name, [FromQuery] Guid? userId)
         {
         {
             var dtoOptions = new DtoOptions().AddClientFields(Request);
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
 

+ 3 - 2
Jellyfin.Api/Controllers/AudioController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Models.StreamingDtos;
 using Jellyfin.Api.Models.StreamingDtos;
@@ -89,8 +90,8 @@ namespace Jellyfin.Api.Controllers
         [HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
         [HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetAudioStream(
         public async Task<ActionResult> GetAudioStream(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,

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

@@ -90,7 +90,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Channel features returned.</response>
         /// <response code="200">Channel features returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the channel features.</returns>
         /// <returns>An <see cref="OkResult"/> containing the channel features.</returns>
         [HttpGet("{channelId}/Features")]
         [HttpGet("{channelId}/Features")]
-        public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute] string channelId)
+        public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute][Required] string channelId)
         {
         {
             return _channelManager.GetChannelFeatures(channelId);
             return _channelManager.GetChannelFeatures(channelId);
         }
         }
@@ -114,7 +114,7 @@ namespace Jellyfin.Api.Controllers
         /// </returns>
         /// </returns>
         [HttpGet("{channelId}/Items")]
         [HttpGet("{channelId}/Items")]
         public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems(
         public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems(
-            [FromRoute] Guid channelId,
+            [FromRoute][Required] Guid channelId,
             [FromQuery] Guid? folderId,
             [FromQuery] Guid? folderId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,

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

@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("{collectionId}/Items")]
         [HttpPost("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> AddToCollection([FromRoute][Required] Guid collectionId, [FromQuery, Required] string? itemIds)
         {
         {
             await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
             await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
             return NoContent();
             return NoContent();
@@ -103,7 +103,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpDelete("{collectionId}/Items")]
         [HttpDelete("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> RemoveFromCollection([FromRoute][Required] Guid collectionId, [FromQuery, Required] string? itemIds)
         {
         {
             await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
             await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
             return NoContent();
             return NoContent();

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

@@ -73,7 +73,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>Configuration.</returns>
         /// <returns>Configuration.</returns>
         [HttpGet("Configuration/{key}")]
         [HttpGet("Configuration/{key}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<object> GetNamedConfiguration([FromRoute] string? key)
+        public ActionResult<object> GetNamedConfiguration([FromRoute][Required] string? key)
         {
         {
             return _configurationManager.GetConfiguration(key);
             return _configurationManager.GetConfiguration(key);
         }
         }
@@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Configuration/{key}")]
         [HttpPost("Configuration/{key}")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string? key)
+        public async Task<ActionResult> UpdateNamedConfiguration([FromRoute][Required] string? key)
         {
         {
             var configurationType = _configurationManager.GetConfigurationType(key);
             var configurationType = _configurationManager.GetConfigurationType(key);
             var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);
             var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);

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

@@ -43,7 +43,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         public ActionResult<DisplayPreferencesDto> GetDisplayPreferences(
         public ActionResult<DisplayPreferencesDto> GetDisplayPreferences(
-            [FromRoute] string? displayPreferencesId,
+            [FromRoute][Required] string? displayPreferencesId,
             [FromQuery] [Required] Guid userId,
             [FromQuery] [Required] Guid userId,
             [FromQuery] [Required] string? client)
             [FromQuery] [Required] string? client)
         {
         {
@@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         public ActionResult UpdateDisplayPreferences(
         public ActionResult UpdateDisplayPreferences(
-            [FromRoute] string? displayPreferencesId,
+            [FromRoute][Required] string? displayPreferencesId,
             [FromQuery, Required] Guid userId,
             [FromQuery, Required] Guid userId,
             [FromQuery, Required] string? client,
             [FromQuery, Required] string? client,
             [FromBody, Required] DisplayPreferencesDto displayPreferences)
             [FromBody, Required] DisplayPreferencesDto displayPreferences)

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

@@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Profiles/{profileId}")]
         [HttpGet("Profiles/{profileId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<DeviceProfile> GetProfile([FromRoute] string profileId)
+        public ActionResult<DeviceProfile> GetProfile([FromRoute][Required] string profileId)
         {
         {
             var profile = _dlnaManager.GetProfile(profileId);
             var profile = _dlnaManager.GetProfile(profileId);
             if (profile == null)
             if (profile == null)
@@ -80,7 +80,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("Profiles/{profileId}")]
         [HttpDelete("Profiles/{profileId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteProfile([FromRoute] string profileId)
+        public ActionResult DeleteProfile([FromRoute][Required] string profileId)
         {
         {
             var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
             var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
             if (existingDeviceProfile == null)
             if (existingDeviceProfile == null)
@@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Profiles/{profileId}")]
         [HttpPost("Profiles/{profileId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UpdateProfile([FromRoute] string profileId, [FromBody] DeviceProfile deviceProfile)
+        public ActionResult UpdateProfile([FromRoute][Required] string profileId, [FromBody] DeviceProfile deviceProfile)
         {
         {
             var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
             var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
             if (existingDeviceProfile == null)
             if (existingDeviceProfile == null)

+ 9 - 9
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -45,7 +45,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
         [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult GetDescriptionXml([FromRoute] string serverId)
+        public ActionResult GetDescriptionXml([FromRoute][Required] string serverId)
         {
         {
             var url = GetAbsoluteUri();
             var url = GetAbsoluteUri();
             var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
             var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
@@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
-        public ActionResult GetContentDirectory([FromRoute] string serverId)
+        public ActionResult GetContentDirectory([FromRoute][Required] string serverId)
         {
         {
             return Ok(_contentDirectory.GetServiceXml());
             return Ok(_contentDirectory.GetServiceXml());
         }
         }
@@ -81,7 +81,7 @@ namespace Jellyfin.Api.Controllers
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
-        public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId)
+        public ActionResult GetMediaReceiverRegistrar([FromRoute][Required] string serverId)
         {
         {
             return Ok(_mediaReceiverRegistrar.GetServiceXml());
             return Ok(_mediaReceiverRegistrar.GetServiceXml());
         }
         }
@@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
-        public ActionResult GetConnectionManager([FromRoute] string serverId)
+        public ActionResult GetConnectionManager([FromRoute][Required] string serverId)
         {
         {
             return Ok(_connectionManager.GetServiceXml());
             return Ok(_connectionManager.GetServiceXml());
         }
         }
@@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Control response.</returns>
         /// <returns>Control response.</returns>
         [HttpPost("{serverId}/ContentDirectory/Control")]
         [HttpPost("{serverId}/ContentDirectory/Control")]
-        public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute] string serverId)
+        public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute][Required] string serverId)
         {
         {
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
         }
         }
@@ -119,7 +119,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Control response.</returns>
         /// <returns>Control response.</returns>
         [HttpPost("{serverId}/ConnectionManager/Control")]
         [HttpPost("{serverId}/ConnectionManager/Control")]
-        public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute] string serverId)
+        public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute][Required] string serverId)
         {
         {
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
         }
         }
@@ -130,7 +130,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Control response.</returns>
         /// <returns>Control response.</returns>
         [HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
         [HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
-        public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute] string serverId)
+        public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute][Required] string serverId)
         {
         {
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
         }
         }
@@ -185,7 +185,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>Icon stream.</returns>
         /// <returns>Icon stream.</returns>
         [HttpGet("{serverId}/icons/{fileName}")]
         [HttpGet("{serverId}/icons/{fileName}")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
-        public ActionResult GetIconId([FromRoute] string serverId, [FromRoute] string fileName)
+        public ActionResult GetIconId([FromRoute][Required] string serverId, [FromRoute][Required] string fileName)
         {
         {
             return GetIconInternal(fileName);
             return GetIconInternal(fileName);
         }
         }
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="fileName">The icon filename.</param>
         /// <param name="fileName">The icon filename.</param>
         /// <returns>Icon stream.</returns>
         /// <returns>Icon stream.</returns>
         [HttpGet("icons/{fileName}")]
         [HttpGet("icons/{fileName}")]
-        public ActionResult GetIcon([FromRoute] string fileName)
+        public ActionResult GetIcon([FromRoute][Required] string fileName)
         {
         {
             return GetIconInternal(fileName);
             return GetIconInternal(fileName);
         }
         }

+ 16 - 16
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -167,8 +167,8 @@ namespace Jellyfin.Api.Controllers
         [HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
         [HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetMasterHlsVideoPlaylist(
         public async Task<ActionResult> GetMasterHlsVideoPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -334,8 +334,8 @@ namespace Jellyfin.Api.Controllers
         [HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
         [HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetMasterHlsAudioPlaylist(
         public async Task<ActionResult> GetMasterHlsAudioPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -499,8 +499,8 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Videos/{itemId}/main.m3u8")]
         [HttpGet("Videos/{itemId}/main.m3u8")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetVariantHlsVideoPlaylist(
         public async Task<ActionResult> GetVariantHlsVideoPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -664,8 +664,8 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Audio/{itemId}/main.m3u8")]
         [HttpGet("Audio/{itemId}/main.m3u8")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetVariantHlsAudioPlaylist(
         public async Task<ActionResult> GetVariantHlsAudioPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -832,10 +832,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> GetHlsVideoSegment(
         public async Task<ActionResult> GetHlsVideoSegment(
-            [FromRoute] Guid itemId,
-            [FromRoute] string playlistId,
-            [FromRoute] int segmentId,
-            [FromRoute] string container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string playlistId,
+            [FromRoute][Required] int segmentId,
+            [FromRoute][Required] string container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -1001,10 +1001,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> GetHlsAudioSegment(
         public async Task<ActionResult> GetHlsAudioSegment(
-            [FromRoute] Guid itemId,
-            [FromRoute] string playlistId,
-            [FromRoute] int segmentId,
-            [FromRoute] string container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string playlistId,
+            [FromRoute][Required] int segmentId,
+            [FromRoute][Required] string container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,

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

@@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the genre.</returns>
         /// <returns>An <see cref="OkResult"/> containing the genre.</returns>
         [HttpGet("{genreName}")]
         [HttpGet("{genreName}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetGenre([FromRoute] string genreName, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetGenre([FromRoute][Required] string genreName, [FromQuery] Guid? userId)
         {
         {
             var dtoOptions = new DtoOptions()
             var dtoOptions = new DtoOptions()
                 .AddClientFields(Request);
                 .AddClientFields(Request);

+ 6 - 6
Jellyfin.Api/Controllers/HlsSegmentController.cs

@@ -55,7 +55,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
         [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
-        public ActionResult GetHlsAudioSegmentLegacy([FromRoute] string itemId, [FromRoute] string segmentId)
+        public ActionResult GetHlsAudioSegmentLegacy([FromRoute][Required] string itemId, [FromRoute][Required] string segmentId)
         {
         {
             // TODO: Deprecate with new iOS app
             // TODO: Deprecate with new iOS app
             var file = segmentId + Path.GetExtension(Request.Path);
             var file = segmentId + Path.GetExtension(Request.Path);
@@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
-        public ActionResult GetHlsPlaylistLegacy([FromRoute] string itemId, [FromRoute] string playlistId)
+        public ActionResult GetHlsPlaylistLegacy([FromRoute][Required] string itemId, [FromRoute][Required] string playlistId)
         {
         {
             var file = playlistId + Path.GetExtension(Request.Path);
             var file = playlistId + Path.GetExtension(Request.Path);
             file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
             file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
@@ -114,10 +114,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         public ActionResult GetHlsVideoSegmentLegacy(
         public ActionResult GetHlsVideoSegmentLegacy(
-            [FromRoute] string itemId,
-            [FromRoute] string playlistId,
-            [FromRoute] string segmentId,
-            [FromRoute] string segmentContainer)
+            [FromRoute][Required] string itemId,
+            [FromRoute][Required] string playlistId,
+            [FromRoute][Required] string segmentId,
+            [FromRoute][Required] string segmentContainer)
         {
         {
             var file = segmentId + Path.GetExtension(Request.Path);
             var file = segmentId + Path.GetExtension(Request.Path);
             var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();
             var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();

+ 78 - 78
Jellyfin.Api/Controllers/ImageController.cs

@@ -90,9 +90,9 @@ namespace Jellyfin.Api.Controllers
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> PostUserImage(
         public async Task<ActionResult> PostUserImage(
-            [FromRoute] Guid userId,
-            [FromRoute] ImageType imageType,
-            [FromRoute] int? index = null)
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] int? index = null)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             {
             {
@@ -137,9 +137,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public ActionResult DeleteUserImage(
         public ActionResult DeleteUserImage(
-            [FromRoute] Guid userId,
-            [FromRoute] ImageType imageType,
-            [FromRoute] int? index = null)
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] int? index = null)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             {
             {
@@ -175,9 +175,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> DeleteItemImage(
         public async Task<ActionResult> DeleteItemImage(
-            [FromRoute] Guid itemId,
-            [FromRoute] ImageType imageType,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -205,9 +205,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> SetItemImage(
         public async Task<ActionResult> SetItemImage(
-            [FromRoute] Guid itemId,
-            [FromRoute] ImageType imageType,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -238,9 +238,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> UpdateItemImageIndex(
         public async Task<ActionResult> UpdateItemImageIndex(
-            [FromRoute] Guid itemId,
-            [FromRoute] ImageType imageType,
-            [FromRoute] int imageIndex,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] int imageIndex,
             [FromQuery] int newIndex)
             [FromQuery] int newIndex)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
@@ -264,7 +264,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute] Guid itemId)
+        public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute][Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -352,10 +352,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetItemImage(
         public async Task<ActionResult> GetItemImage(
-            [FromRoute] Guid itemId,
-            [FromRoute] ImageType imageType,
-            [FromRoute] int? maxWidth,
-            [FromRoute] int? maxHeight,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] int? maxWidth,
+            [FromRoute][Required] int? maxHeight,
             [FromQuery] int? width,
             [FromQuery] int? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -368,7 +368,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -430,23 +430,23 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetItemImage2(
         public async Task<ActionResult> GetItemImage2(
-            [FromRoute] Guid itemId,
-            [FromRoute] ImageType imageType,
-            [FromRoute] int? maxWidth,
-            [FromRoute] int? maxHeight,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] int? maxWidth,
+            [FromRoute][Required] int? maxHeight,
             [FromQuery] int? width,
             [FromQuery] int? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
-            [FromRoute] string tag,
+            [FromRoute][Required] string tag,
             [FromQuery] bool? cropWhitespace,
             [FromQuery] bool? cropWhitespace,
-            [FromRoute] string format,
+            [FromRoute][Required] string format,
             [FromQuery] bool? addPlayedIndicator,
             [FromQuery] bool? addPlayedIndicator,
-            [FromRoute] double? percentPlayed,
-            [FromRoute] int? unplayedCount,
+            [FromRoute][Required] double? percentPlayed,
+            [FromRoute][Required] int? unplayedCount,
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -508,14 +508,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetArtistImage(
         public async Task<ActionResult> GetArtistImage(
-            [FromRoute] string name,
-            [FromRoute] ImageType imageType,
-            [FromRoute] string tag,
-            [FromRoute] string format,
-            [FromRoute] int? maxWidth,
-            [FromRoute] int? maxHeight,
-            [FromRoute] double? percentPlayed,
-            [FromRoute] int? unplayedCount,
+            [FromRoute][Required] string name,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] string tag,
+            [FromRoute][Required] string format,
+            [FromRoute][Required] int? maxWidth,
+            [FromRoute][Required] int? maxHeight,
+            [FromRoute][Required] double? percentPlayed,
+            [FromRoute][Required] int? unplayedCount,
             [FromQuery] int? width,
             [FromQuery] int? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -524,7 +524,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetArtist(name);
             var item = _libraryManager.GetArtist(name);
             if (item == null)
             if (item == null)
@@ -586,14 +586,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetGenreImage(
         public async Task<ActionResult> GetGenreImage(
-            [FromRoute] string name,
-            [FromRoute] ImageType imageType,
-            [FromRoute] string tag,
-            [FromRoute] string format,
-            [FromRoute] int? maxWidth,
-            [FromRoute] int? maxHeight,
-            [FromRoute] double? percentPlayed,
-            [FromRoute] int? unplayedCount,
+            [FromRoute][Required] string name,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] string tag,
+            [FromRoute][Required] string format,
+            [FromRoute][Required] int? maxWidth,
+            [FromRoute][Required] int? maxHeight,
+            [FromRoute][Required] double? percentPlayed,
+            [FromRoute][Required] int? unplayedCount,
             [FromQuery] int? width,
             [FromQuery] int? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -602,7 +602,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetGenre(name);
             var item = _libraryManager.GetGenre(name);
             if (item == null)
             if (item == null)
@@ -664,14 +664,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetMusicGenreImage(
         public async Task<ActionResult> GetMusicGenreImage(
-            [FromRoute] string name,
-            [FromRoute] ImageType imageType,
-            [FromRoute] string tag,
-            [FromRoute] string format,
-            [FromRoute] int? maxWidth,
-            [FromRoute] int? maxHeight,
-            [FromRoute] double? percentPlayed,
-            [FromRoute] int? unplayedCount,
+            [FromRoute][Required] string name,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] string tag,
+            [FromRoute][Required] string format,
+            [FromRoute][Required] int? maxWidth,
+            [FromRoute][Required] int? maxHeight,
+            [FromRoute][Required] double? percentPlayed,
+            [FromRoute][Required] int? unplayedCount,
             [FromQuery] int? width,
             [FromQuery] int? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -680,7 +680,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetMusicGenre(name);
             var item = _libraryManager.GetMusicGenre(name);
             if (item == null)
             if (item == null)
@@ -742,14 +742,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetPersonImage(
         public async Task<ActionResult> GetPersonImage(
-            [FromRoute] string name,
-            [FromRoute] ImageType imageType,
-            [FromRoute] string tag,
-            [FromRoute] string format,
-            [FromRoute] int? maxWidth,
-            [FromRoute] int? maxHeight,
-            [FromRoute] double? percentPlayed,
-            [FromRoute] int? unplayedCount,
+            [FromRoute][Required] string name,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] string tag,
+            [FromRoute][Required] string format,
+            [FromRoute][Required] int? maxWidth,
+            [FromRoute][Required] int? maxHeight,
+            [FromRoute][Required] double? percentPlayed,
+            [FromRoute][Required] int? unplayedCount,
             [FromQuery] int? width,
             [FromQuery] int? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -758,7 +758,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetPerson(name);
             var item = _libraryManager.GetPerson(name);
             if (item == null)
             if (item == null)
@@ -820,14 +820,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetStudioImage(
         public async Task<ActionResult> GetStudioImage(
-            [FromRoute] string name,
-            [FromRoute] ImageType imageType,
-            [FromRoute] string tag,
-            [FromRoute] string format,
-            [FromRoute] int? maxWidth,
-            [FromRoute] int? maxHeight,
-            [FromRoute] double? percentPlayed,
-            [FromRoute] int? unplayedCount,
+            [FromRoute][Required] string name,
+            [FromRoute][Required] ImageType imageType,
+            [FromRoute][Required] string tag,
+            [FromRoute][Required] string format,
+            [FromRoute][Required] int? maxWidth,
+            [FromRoute][Required] int? maxHeight,
+            [FromRoute][Required] double? percentPlayed,
+            [FromRoute][Required] int? unplayedCount,
             [FromQuery] int? width,
             [FromQuery] int? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -836,7 +836,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetStudio(name);
             var item = _libraryManager.GetStudio(name);
             if (item == null)
             if (item == null)
@@ -898,8 +898,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetUserImage(
         public async Task<ActionResult> GetUserImage(
-            [FromRoute] Guid userId,
-            [FromRoute] ImageType imageType,
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] ImageType imageType,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
             [FromQuery] string? format,
             [FromQuery] string? format,
             [FromQuery] int? maxWidth,
             [FromQuery] int? maxWidth,
@@ -914,7 +914,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute][Required] int? imageIndex = null)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             if (user == null)
             if (user == null)

+ 6 - 6
Jellyfin.Api/Controllers/InstantMixController.cs

@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Songs/{id}/InstantMix")]
         [HttpGet("Songs/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
-            [FromRoute] Guid id,
+            [FromRoute][Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Albums/{id}/InstantMix")]
         [HttpGet("Albums/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
-            [FromRoute] Guid id,
+            [FromRoute][Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Playlists/{id}/InstantMix")]
         [HttpGet("Playlists/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
-            [FromRoute] Guid id,
+            [FromRoute][Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -211,7 +211,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Artists/InstantMix")]
         [HttpGet("Artists/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
-            [FromRoute] Guid id,
+            [FromRoute][Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -248,7 +248,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("MusicGenres/InstantMix")]
         [HttpGet("MusicGenres/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
-            [FromRoute] Guid id,
+            [FromRoute][Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Items/{id}/InstantMix")]
         [HttpGet("Items/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
-            [FromRoute] Guid id,
+            [FromRoute][Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,

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

@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute] Guid itemId)
+        public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute][Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -294,7 +294,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/RemoteSearch/Apply/{id}")]
         [HttpPost("Items/RemoteSearch/Apply/{id}")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         public async Task<ActionResult> ApplySearchCriteria(
         public async Task<ActionResult> ApplySearchCriteria(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromBody, Required] RemoteSearchResult searchResult,
             [FromBody, Required] RemoteSearchResult searchResult,
             [FromQuery] bool replaceAllImages = true)
             [FromQuery] bool replaceAllImages = true)
         {
         {

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

@@ -53,7 +53,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult Post(
         public ActionResult Post(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
             [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
             [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
             [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
             [FromQuery] bool replaceAllMetadata = false,
             [FromQuery] bool replaceAllMetadata = false,

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

@@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}")]
         [HttpPost("Items/{itemId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult> UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request)
+        public async Task<ActionResult> UpdateItem([FromRoute][Required] Guid itemId, [FromBody, Required] BaseItemDto request)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Items/{itemId}/MetadataEditor")]
         [HttpGet("Items/{itemId}/MetadataEditor")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute] Guid itemId)
+        public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute][Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
 
 
@@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}/ContentType")]
         [HttpPost("Items/{itemId}/ContentType")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, Required] string? contentType)
+        public ActionResult UpdateItemContentType([FromRoute][Required] Guid itemId, [FromQuery, Required] string? contentType)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)

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

@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{uId}/Items", Name = "GetItems_2")]
         [HttpGet("Users/{uId}/Items", Name = "GetItems_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetItems(
         public ActionResult<QueryResult<BaseItemDto>> GetItems(
-            [FromRoute] Guid? uId,
+            [FromRoute][Required] Guid? uId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] bool? hasThemeSong,
             [FromQuery] bool? hasThemeSong,
@@ -529,7 +529,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Items/Resume")]
         [HttpGet("Users/{userId}/Items/Resume")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
         public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? searchTerm,
             [FromQuery] string? searchTerm,

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

@@ -104,7 +104,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult GetFile([FromRoute] Guid itemId)
+        public ActionResult GetFile([FromRoute][Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<ThemeMediaResult> GetThemeSongs(
         public ActionResult<ThemeMediaResult> GetThemeSongs(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] bool inheritFromParent = false)
             [FromQuery] bool inheritFromParent = false)
         {
         {
@@ -210,7 +210,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<ThemeMediaResult> GetThemeVideos(
         public ActionResult<ThemeMediaResult> GetThemeVideos(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] bool inheritFromParent = false)
             [FromQuery] bool inheritFromParent = false)
         {
         {
@@ -275,7 +275,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] bool inheritFromParent = false)
             [FromQuery] bool inheritFromParent = false)
         {
         {
@@ -438,7 +438,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid? userId)
+        public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute][Required] Guid itemId, [FromQuery] Guid? userId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
 
 
@@ -555,7 +555,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Library/Movies/Updated")]
         [HttpPost("Library/Movies/Updated")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult PostUpdatedMovies([FromRoute] string? tmdbId, [FromRoute] string? imdbId)
+        public ActionResult PostUpdatedMovies([FromRoute][Required] string? tmdbId, [FromRoute][Required] string? imdbId)
         {
         {
             var movies = _libraryManager.GetItemList(new InternalItemsQuery
             var movies = _libraryManager.GetItemList(new InternalItemsQuery
             {
             {
@@ -618,7 +618,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.Download)]
         [Authorize(Policy = Policies.Download)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult> GetDownload([FromRoute] Guid itemId)
+        public async Task<ActionResult> GetDownload([FromRoute][Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -687,7 +687,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,

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

@@ -209,7 +209,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Channels/{channelId}")]
         [HttpGet("Channels/{channelId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult<BaseItemDto> GetChannel([FromRoute] Guid channelId, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetChannel([FromRoute][Required] Guid channelId, [FromQuery] Guid? userId)
         {
         {
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
                 ? _userManager.GetUserById(userId.Value)
                 ? _userManager.GetUserById(userId.Value)
@@ -406,7 +406,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Recordings/{recordingId}")]
         [HttpGet("Recordings/{recordingId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult<BaseItemDto> GetRecording([FromRoute] Guid recordingId, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetRecording([FromRoute][Required] Guid recordingId, [FromQuery] Guid? userId)
         {
         {
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
                 ? _userManager.GetUserById(userId.Value)
                 ? _userManager.GetUserById(userId.Value)
@@ -428,7 +428,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Tuners/{tunerId}/Reset")]
         [HttpPost("Tuners/{tunerId}/Reset")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult ResetTuner([FromRoute] string tunerId)
+        public ActionResult ResetTuner([FromRoute][Required] string tunerId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             _liveTvManager.ResetTuner(tunerId, CancellationToken.None);
             _liveTvManager.ResetTuner(tunerId, CancellationToken.None);
@@ -744,7 +744,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<BaseItemDto>> GetProgram(
         public async Task<ActionResult<BaseItemDto>> GetProgram(
-            [FromRoute] string programId,
+            [FromRoute][Required] string programId,
             [FromQuery] Guid? userId)
             [FromQuery] Guid? userId)
         {
         {
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -765,7 +765,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteRecording([FromRoute] Guid recordingId)
+        public ActionResult DeleteRecording([FromRoute][Required] Guid recordingId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
 
 
@@ -792,7 +792,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("Timers/{timerId}")]
         [HttpDelete("Timers/{timerId}")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> CancelTimer([FromRoute] string timerId)
+        public async Task<ActionResult> CancelTimer([FromRoute][Required] string timerId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
             await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
@@ -810,7 +810,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
-        public async Task<ActionResult> UpdateTimer([FromRoute] string timerId, [FromBody] TimerInfoDto timerInfo)
+        public async Task<ActionResult> UpdateTimer([FromRoute][Required] string timerId, [FromBody] TimerInfoDto timerInfo)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
             await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
@@ -844,7 +844,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute] string timerId)
+        public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute][Required] string timerId)
         {
         {
             var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
             var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
             if (timer == null)
             if (timer == null)
@@ -884,7 +884,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("SeriesTimers/{timerId}")]
         [HttpDelete("SeriesTimers/{timerId}")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> CancelSeriesTimer([FromRoute] string timerId)
+        public async Task<ActionResult> CancelSeriesTimer([FromRoute][Required] string timerId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
             await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
@@ -902,7 +902,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
-        public async Task<ActionResult> UpdateSeriesTimer([FromRoute] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
+        public async Task<ActionResult> UpdateSeriesTimer([FromRoute][Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
             await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
@@ -934,7 +934,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [Obsolete("This endpoint is obsolete.")]
         [Obsolete("This endpoint is obsolete.")]
-        public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute] Guid? groupId)
+        public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute][Required] Guid? groupId)
         {
         {
             return NotFound();
             return NotFound();
         }
         }
@@ -1176,7 +1176,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("LiveRecordings/{recordingId}/stream")]
         [HttpGet("LiveRecordings/{recordingId}/stream")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult> GetLiveRecordingFile([FromRoute] string recordingId)
+        public async Task<ActionResult> GetLiveRecordingFile([FromRoute][Required] string recordingId)
         {
         {
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
 
 
@@ -1206,7 +1206,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("LiveStreamFiles/{streamId}/stream.{container}")]
         [HttpGet("LiveStreamFiles/{streamId}/stream.{container}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult> GetLiveStreamFile([FromRoute] string streamId, [FromRoute] string container)
+        public async Task<ActionResult> GetLiveStreamFile([FromRoute][Required] string streamId, [FromRoute][Required] string container)
         {
         {
             var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(streamId, CancellationToken.None).ConfigureAwait(false);
             var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(streamId, CancellationToken.None).ConfigureAwait(false);
             if (liveStreamInfo == null)
             if (liveStreamInfo == null)

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

@@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
         /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
         [HttpGet("Items/{itemId}/PlaybackInfo")]
         [HttpGet("Items/{itemId}/PlaybackInfo")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery, Required] Guid? userId)
+        public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute][Required] Guid itemId, [FromQuery, Required] Guid? userId)
         {
         {
             return await _mediaInfoHelper.GetPlaybackInfo(
             return await _mediaInfoHelper.GetPlaybackInfo(
                     itemId,
                     itemId,
@@ -100,7 +100,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}/PlaybackInfo")]
         [HttpPost("Items/{itemId}/PlaybackInfo")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
         public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] long? maxStreamingBitrate,
             [FromQuery] long? maxStreamingBitrate,
             [FromQuery] long? startTimeTicks,
             [FromQuery] long? startTimeTicks,

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

@@ -258,7 +258,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns>
         /// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns>
         [HttpGet("{genreName}")]
         [HttpGet("{genreName}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetMusicGenre([FromRoute] string genreName, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetMusicGenre([FromRoute][Required] string genreName, [FromQuery] Guid? userId)
         {
         {
             var dtoOptions = new DtoOptions().AddClientFields(Request);
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
 

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

@@ -44,7 +44,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Packages/{name}")]
         [HttpGet("Packages/{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<PackageInfo>> GetPackageInfo(
         public async Task<ActionResult<PackageInfo>> GetPackageInfo(
-            [FromRoute] [Required] string? name,
+            [FromRoute][Required] string? name,
             [FromQuery] string? assemblyGuid)
             [FromQuery] string? assemblyGuid)
         {
         {
             var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
             var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         public async Task<ActionResult> InstallPackage(
         public async Task<ActionResult> InstallPackage(
-            [FromRoute] [Required] string? name,
+            [FromRoute][Required] string? name,
             [FromQuery] string? assemblyGuid,
             [FromQuery] string? assemblyGuid,
             [FromQuery] string? version)
             [FromQuery] string? version)
         {
         {
@@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult CancelPackageInstallation(
         public ActionResult CancelPackageInstallation(
-            [FromRoute] [Required] Guid packageId)
+            [FromRoute][Required] Guid packageId)
         {
         {
             _installationManager.CancelInstallation(packageId);
             _installationManager.CancelInstallation(packageId);
             return NoContent();
             return NoContent();

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

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
@@ -262,7 +263,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{name}")]
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<BaseItemDto> GetPerson([FromRoute] string name, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetPerson([FromRoute][Required] string name, [FromQuery] Guid? userId)
         {
         {
             var dtoOptions = new DtoOptions()
             var dtoOptions = new DtoOptions()
                 .AddClientFields(Request);
                 .AddClientFields(Request);

+ 14 - 14
Jellyfin.Api/Controllers/PlaylistsController.cs

@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("{playlistId}/Items")]
         [HttpPost("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> AddToPlaylist(
         public async Task<ActionResult> AddToPlaylist(
-            [FromRoute] Guid playlistId,
+            [FromRoute][Required] Guid playlistId,
             [FromQuery] string? ids,
             [FromQuery] string? ids,
             [FromQuery] Guid? userId)
             [FromQuery] Guid? userId)
         {
         {
@@ -103,9 +103,9 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
         [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> MoveItem(
         public async Task<ActionResult> MoveItem(
-            [FromRoute] string? playlistId,
-            [FromRoute] string? itemId,
-            [FromRoute] int newIndex)
+            [FromRoute][Required] string? playlistId,
+            [FromRoute][Required] string? itemId,
+            [FromRoute][Required] int newIndex)
         {
         {
             await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
             await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
             return NoContent();
             return NoContent();
@@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpDelete("{playlistId}/Items")]
         [HttpDelete("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
+        public async Task<ActionResult> RemoveFromPlaylist([FromRoute][Required] string? playlistId, [FromQuery] string? entryIds)
         {
         {
             await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false);
             await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false);
             return NoContent();
             return NoContent();
@@ -143,15 +143,15 @@ namespace Jellyfin.Api.Controllers
         /// <returns>The original playlist items.</returns>
         /// <returns>The original playlist items.</returns>
         [HttpGet("{playlistId}/Items")]
         [HttpGet("{playlistId}/Items")]
         public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
         public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
-            [FromRoute] Guid playlistId,
-            [FromRoute] Guid userId,
-            [FromRoute] int? startIndex,
-            [FromRoute] int? limit,
-            [FromRoute] string? fields,
-            [FromRoute] bool? enableImages,
-            [FromRoute] bool? enableUserData,
-            [FromRoute] int? imageTypeLimit,
-            [FromRoute] string? enableImageTypes)
+            [FromRoute][Required] Guid playlistId,
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] int? startIndex,
+            [FromRoute][Required] int? limit,
+            [FromRoute][Required] string? fields,
+            [FromRoute][Required] bool? enableImages,
+            [FromRoute][Required] bool? enableUserData,
+            [FromRoute][Required] int? imageTypeLimit,
+            [FromRoute][Required] string? enableImageTypes)
         {
         {
             var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
             var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
             if (playlist == null)
             if (playlist == null)

+ 9 - 9
Jellyfin.Api/Controllers/PlaystateController.cs

@@ -71,8 +71,8 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Users/{userId}/PlayedItems/{itemId}")]
         [HttpPost("Users/{userId}/PlayedItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<UserItemDataDto> MarkPlayedItem(
         public ActionResult<UserItemDataDto> MarkPlayedItem(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] DateTime? datePlayed)
             [FromQuery] DateTime? datePlayed)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
@@ -96,7 +96,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
         [HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
             var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@@ -195,8 +195,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         public async Task<ActionResult> OnPlaybackStart(
         public async Task<ActionResult> OnPlaybackStart(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
@@ -245,8 +245,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         public async Task<ActionResult> OnPlaybackProgress(
         public async Task<ActionResult> OnPlaybackProgress(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] long? positionTicks,
             [FromQuery] long? positionTicks,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? audioStreamIndex,
@@ -297,8 +297,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         public async Task<ActionResult> OnPlaybackStopped(
         public async Task<ActionResult> OnPlaybackStopped(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid userId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? nextMediaType,
             [FromQuery] string? nextMediaType,
             [FromQuery] long? positionTicks,
             [FromQuery] long? positionTicks,

+ 5 - 5
Jellyfin.Api/Controllers/PluginsController.cs

@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UninstallPlugin([FromRoute] Guid pluginId)
+        public ActionResult UninstallPlugin([FromRoute][Required] Guid pluginId)
         {
         {
             var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId);
             var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId);
             if (plugin == null)
             if (plugin == null)
@@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{pluginId}/Configuration")]
         [HttpGet("{pluginId}/Configuration")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute] Guid pluginId)
+        public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute][Required] Guid pluginId)
         {
         {
             if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
             if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
             {
             {
@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("{pluginId}/Configuration")]
         [HttpPost("{pluginId}/Configuration")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult> UpdatePluginConfiguration([FromRoute] Guid pluginId)
+        public async Task<ActionResult> UpdatePluginConfiguration([FromRoute][Required] Guid pluginId)
         {
         {
             if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
             if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
             {
             {
@@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
         [Obsolete("This endpoint should not be used.")]
         [Obsolete("This endpoint should not be used.")]
         [HttpPost("RegistrationRecords/{name}")]
         [HttpPost("RegistrationRecords/{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string? name)
+        public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute][Required] string? name)
         {
         {
             return new MBRegistrationRecord
             return new MBRegistrationRecord
             {
             {
@@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
         [Obsolete("Paid plugins are not supported")]
         [Obsolete("Paid plugins are not supported")]
         [HttpGet("Registrations/{name}")]
         [HttpGet("Registrations/{name}")]
         [ProducesResponseType(StatusCodes.Status501NotImplemented)]
         [ProducesResponseType(StatusCodes.Status501NotImplemented)]
-        public ActionResult GetRegistration([FromRoute] string? name)
+        public ActionResult GetRegistration([FromRoute][Required] string? name)
         {
         {
             // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
             // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
             // delete all these registration endpoints. They are only kept for compatibility.
             // delete all these registration endpoints. They are only kept for compatibility.

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

@@ -70,7 +70,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult<RemoteImageResult>> GetRemoteImages(
         public async Task<ActionResult<RemoteImageResult>> GetRemoteImages(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] ImageType? type,
             [FromQuery] ImageType? type,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
@@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute] Guid itemId)
+        public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute][Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -209,7 +209,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> DownloadRemoteImage(
         public async Task<ActionResult> DownloadRemoteImage(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery, Required] ImageType type,
             [FromQuery, Required] ImageType type,
             [FromQuery] string? imageUrl)
             [FromQuery] string? imageUrl)
         {
         {

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

@@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Running/{taskId}")]
         [HttpPost("Running/{taskId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult StartTask([FromRoute] string? taskId)
+        public ActionResult StartTask([FromRoute][Required] string? taskId)
         {
         {
             var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
             var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
                 o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
                 o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));

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

@@ -336,7 +336,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult AddUserToSession(
         public ActionResult AddUserToSession(
             [FromRoute, Required] string? sessionId,
             [FromRoute, Required] string? sessionId,
-            [FromRoute] Guid userId)
+            [FromRoute][Required] Guid userId)
         {
         {
             _sessionManager.AddAdditionalUser(sessionId, userId);
             _sessionManager.AddAdditionalUser(sessionId, userId);
             return NoContent();
             return NoContent();
@@ -353,8 +353,8 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult RemoveUserFromSession(
         public ActionResult RemoveUserFromSession(
-            [FromRoute] string? sessionId,
-            [FromRoute] Guid userId)
+            [FromRoute][Required] string? sessionId,
+            [FromRoute][Required] Guid userId)
         {
         {
             _sessionManager.RemoveAdditionalUser(sessionId, userId);
             _sessionManager.RemoveAdditionalUser(sessionId, userId);
             return NoContent();
             return NoContent();

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

@@ -259,7 +259,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the studio.</returns>
         /// <returns>An <see cref="OkResult"/> containing the studio.</returns>
         [HttpGet("{name}")]
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetStudio([FromRoute] string name, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetStudio([FromRoute][Required] string name, [FromQuery] Guid? userId)
         {
         {
             var dtoOptions = new DtoOptions().AddClientFields(Request);
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
 

+ 8 - 8
Jellyfin.Api/Controllers/SubtitleController.cs

@@ -86,8 +86,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<Task> DeleteSubtitle(
         public ActionResult<Task> DeleteSubtitle(
-            [FromRoute] Guid itemId,
-            [FromRoute] int index)
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] int index)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
 
 
@@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
         public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromRoute, Required] string? language,
             [FromRoute, Required] string? language,
             [FromQuery] bool? isPerfectMatch)
             [FromQuery] bool? isPerfectMatch)
         {
         {
@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> DownloadRemoteSubtitles(
         public async Task<ActionResult> DownloadRemoteSubtitles(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromRoute, Required] string? subtitleId)
             [FromRoute, Required] string? subtitleId)
         {
         {
             var video = (Video)_libraryManager.GetItemById(itemId);
             var video = (Video)_libraryManager.GetItemById(itemId);
@@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] long? endPositionTicks,
             [FromQuery] long? endPositionTicks,
             [FromQuery] bool copyTimestamps = false,
             [FromQuery] bool copyTimestamps = false,
             [FromQuery] bool addVttTimeMap = false,
             [FromQuery] bool addVttTimeMap = false,
-            [FromRoute] long startPositionTicks = 0)
+            [FromRoute][Required] long startPositionTicks = 0)
         {
         {
             if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
             {
             {
@@ -253,9 +253,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> GetSubtitlePlaylist(
         public async Task<ActionResult> GetSubtitlePlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] int index,
-            [FromRoute] string? mediaSourceId,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] int index,
+            [FromRoute][Required] string? mediaSourceId,
             [FromQuery, Required] int segmentLength)
             [FromQuery, Required] int segmentLength)
         {
         {
             var item = (Video)_libraryManager.GetItemById(itemId);
             var item = (Video)_libraryManager.GetItemById(itemId);

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

@@ -53,7 +53,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Suggestions")]
         [HttpGet("Users/{userId}/Suggestions")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
         public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromQuery] string? mediaType,
             [FromQuery] string? mediaType,
             [FromQuery] string? type,
             [FromQuery] string? type,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,

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

@@ -92,8 +92,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status302Found)]
         [ProducesResponseType(StatusCodes.Status302Found)]
         public async Task<ActionResult> GetUniversalAudioStream(
         public async Task<ActionResult> GetUniversalAudioStream(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string? container,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? deviceId,
             [FromQuery] string? deviceId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,

+ 7 - 7
Jellyfin.Api/Controllers/UserController.cs

@@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.IgnoreParentalControl)]
         [Authorize(Policy = Policies.IgnoreParentalControl)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
+        public ActionResult<UserDto> GetUserById([FromRoute][Required] Guid userId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteUser([FromRoute] Guid userId)
+        public ActionResult DeleteUser([FromRoute][Required] Guid userId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             _sessionManager.RevokeUserTokens(user.Id, null);
             _sessionManager.RevokeUserTokens(user.Id, null);
@@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> UpdateUserPassword(
         public async Task<ActionResult> UpdateUserPassword(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromBody] UpdateUserPassword request)
             [FromBody] UpdateUserPassword request)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult UpdateUserEasyPassword(
         public ActionResult UpdateUserEasyPassword(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromBody] UpdateUserEasyPassword request)
             [FromBody] UpdateUserEasyPassword request)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@@ -365,7 +365,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public async Task<ActionResult> UpdateUser(
         public async Task<ActionResult> UpdateUser(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromBody] UserDto updateUser)
             [FromBody] UserDto updateUser)
         {
         {
             if (updateUser == null)
             if (updateUser == null)
@@ -409,7 +409,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public ActionResult UpdateUserPolicy(
         public ActionResult UpdateUserPolicy(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromBody] UserPolicy newPolicy)
             [FromBody] UserPolicy newPolicy)
         {
         {
             if (newPolicy == null)
             if (newPolicy == null)
@@ -464,7 +464,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public ActionResult UpdateUserConfiguration(
         public ActionResult UpdateUserConfiguration(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromBody] UserConfiguration userConfig)
             [FromBody] UserConfiguration userConfig)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))

+ 10 - 10
Jellyfin.Api/Controllers/UserLibraryController.cs

@@ -70,7 +70,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the d item.</returns>
         /// <returns>An <see cref="OkResult"/> containing the d item.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}")]
         [HttpGet("Users/{userId}/Items/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
@@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns>
         /// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns>
         [HttpGet("Users/{userId}/Items/Root")]
         [HttpGet("Users/{userId}/Items/Root")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetRootFolder([FromRoute] Guid userId)
+        public ActionResult<BaseItemDto> GetRootFolder([FromRoute][Required] Guid userId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             var item = _libraryManager.GetUserRootFolder();
             var item = _libraryManager.GetUserRootFolder();
@@ -110,7 +110,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
         /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}/Intros")]
         [HttpGet("Users/{userId}/Items/{itemId}/Intros")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
@@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpPost("Users/{userId}/FavoriteItems/{itemId}")]
         [HttpPost("Users/{userId}/FavoriteItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             return MarkFavorite(userId, itemId, true);
             return MarkFavorite(userId, itemId, true);
         }
         }
@@ -152,7 +152,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")]
         [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             return MarkFavorite(userId, itemId, false);
             return MarkFavorite(userId, itemId, false);
         }
         }
@@ -166,7 +166,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpDelete("Users/{userId}/Items/{itemId}/Rating")]
         [HttpDelete("Users/{userId}/Items/{itemId}/Rating")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             return UpdateUserItemRatingInternal(userId, itemId, null);
             return UpdateUserItemRatingInternal(userId, itemId, null);
         }
         }
@@ -181,7 +181,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpPost("Users/{userId}/Items/{itemId}/Rating")]
         [HttpPost("Users/{userId}/Items/{itemId}/Rating")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool? likes)
+        public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId, [FromQuery] bool? likes)
         {
         {
             return UpdateUserItemRatingInternal(userId, itemId, likes);
             return UpdateUserItemRatingInternal(userId, itemId, likes);
         }
         }
@@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>The items local trailers.</returns>
         /// <returns>The items local trailers.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")]
         [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
@@ -230,7 +230,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the special features.</returns>
         /// <returns>An <see cref="OkResult"/> containing the special features.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")]
         [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute] Guid userId, [FromRoute] Guid itemId)
+        public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute][Required] Guid userId, [FromRoute][Required] Guid itemId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
@@ -264,7 +264,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Items/Latest")]
         [HttpGet("Users/{userId}/Items/Latest")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
         public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromQuery] Guid? parentId,
             [FromQuery] Guid? parentId,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
             [FromQuery] string? includeItemTypes,
             [FromQuery] string? includeItemTypes,

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

@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Views")]
         [HttpGet("Users/{userId}/Views")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
         public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
-            [FromRoute] Guid userId,
+            [FromRoute][Required] Guid userId,
             [FromQuery] bool? includeExternalContent,
             [FromQuery] bool? includeExternalContent,
             [FromQuery] string? presetViews,
             [FromQuery] string? presetViews,
             [FromQuery] bool includeHidden = false)
             [FromQuery] bool includeHidden = false)
@@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/GroupingOptions")]
         [HttpGet("Users/{userId}/GroupingOptions")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute] Guid userId)
+        public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute][Required] Guid userId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             if (user == null)
             if (user == null)

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

@@ -162,7 +162,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Videos/{itemId}/live.m3u8")]
         [HttpGet("Videos/{itemId}/live.m3u8")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetLiveHlsStream(
         public async Task<ActionResult> GetLiveHlsStream(
-            [FromRoute] Guid itemId,
+            [FromRoute][Required] Guid itemId,
             [FromQuery] string? container,
             [FromQuery] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,

+ 4 - 4
Jellyfin.Api/Controllers/VideosController.cs

@@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{itemId}/AdditionalParts")]
         [HttpGet("{itemId}/AdditionalParts")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid? userId)
+        public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute][Required] Guid itemId, [FromQuery] Guid? userId)
         {
         {
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
                 ? _userManager.GetUserById(userId.Value)
                 ? _userManager.GetUserById(userId.Value)
@@ -162,7 +162,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public async Task<ActionResult> DeleteAlternateSources([FromRoute] Guid itemId)
+        public async Task<ActionResult> DeleteAlternateSources([FromRoute][Required] Guid itemId)
         {
         {
             var video = (Video)_libraryManager.GetItemById(itemId);
             var video = (Video)_libraryManager.GetItemById(itemId);
 
 
@@ -331,8 +331,8 @@ namespace Jellyfin.Api.Controllers
         [HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
         [HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetVideoStream(
         public async Task<ActionResult> GetVideoStream(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute][Required] Guid itemId,
+            [FromRoute][Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,

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

@@ -179,7 +179,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{year}")]
         [HttpGet("{year}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<BaseItemDto> GetYear([FromRoute] int year, [FromQuery] Guid? userId)
+        public ActionResult<BaseItemDto> GetYear([FromRoute][Required] int year, [FromQuery] Guid? userId)
         {
         {
             var item = _libraryManager.GetYear(year);
             var item = _libraryManager.GetYear(year);
             if (item == null)
             if (item == null)