¿Es posible establecer el nombre del parámetro de consulta en un controlador basado en atributos de una clase?

Estoy creando un controlador que usa genéricos. Este controlador padre es heredado por otros controladores infantiles de manera genérica pasando el tipo de la clase con la que se ocupan. Las acciones, en el controlador padre, requieren un parámetro atado de un parámetro de consulta llamado particiónKey (Cosmos). Ahora cada parámetro tipo, utilizado con el controlador genérico, tiene una propiedad diferente para la particiónKey (aunque todos son Guids, los nombres cambian). El problema es que cuando el consumidor de API ve "partitionKey" como la descripción del parámetro de consulta, no tienen idea de qué propiedad de todas las propiedades de clase Guid es la particiónKey.

Pensé que tal vez podría haber una manera de establecer dinámicamente el nombre del parámetro de consulta basado en un atributo establecido en la clase de propiedad que corresponde a la particiónKey. ¿Es posible establecer el nombre del parámetro de consulta en atributos de propiedad de clase basados en controlador? ¿O hay una mejor manera de hacerlo?

Controlador de parientes

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace x.API.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class GenericController : ControllerBase
    {
        [HttpGet("{id}")]
        public async Task> Get(Guid id, Guid partitionKey)
        {
                var item = await GetItemAsync(id, partitionKey);

                return Ok(item);
        }

Control infantil

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;


namespace x.API.Controllers
{
    public class GameController : GenericController
    {
    }
}

Pregunta hecha hace 3 años, 4 meses, 26 días - Por codecrafty


3 Respuestas:

  • Tienes que especificar el nombre del parámetro de consulta en algún momento, no hay duda de eso. ¿Pero realmente vale la pena hacerlo dinámico? Suena como exagerar para mí, cuando todavía hay otras soluciones disponibles alcanzable con poco esfuerzo.

    Dije: soluciones porque no hay una sola manera de hacerlo. Pero la forma en que lo haría, es hacer el Get() método de GenericController protegidos y eliminar [HttpGet] atributo.

    [ApiController]
    [Route("[controller]")]
    public class GenericController : ControllerBase
    {
        private readonly IEntityService _entityService;
    
        public GenericController(IEntityService entityService)
        {
            _entityService = entityService;
        }
    
        protected async Task> GetEntity(Guid id, Guid partitionKey)
        {
            try
            {
                var item = await _entityService.GetItemAsync(id, partitionKey);
    
                return Ok(item);
            }
            catch (EntityNotFoundException ex)
            {
                return NotFound(ex.Message);
            }
        }
    }
    

    Controladores procedentes de GenericController (hereutilizando Foo como ejemplo) llamaría la base Get() método. Cada controlador derivado tiene control total sobre el camino del método y son nombres de parámetros de consulta.

    public class FooController : GenericController
    {
        public FooController(IEntityService entityService) : base(entityService)
        {
    
        }
    
        [HttpGet("{id}")]
        public async Task> Get(Guid id, Guid fooKey)
        {
            return await GetEntity(id, fooKey);
        }
    }
    

    Muestra Foo entidad:

    public class Foo
    {
        public Guid FooKey { get; set; }
    }
    

    Respondida el Dec 19, 2020 a las 21:04 - por bughunterx

    Votos positivos: 0 | Votos negativos: 0

  • Una forma de lograr lo que estás buscando es mediante el uso de atributos personalizados en las propiedades de clase que representan la clave de partición. Puedes crear un atributo personalizado como "PartitionKeyAttribute" y aplicarlo a las propiedades de clase que deseas que se utilicen como claves de partición. Luego, en el controlador genérico, puedes utilizar la reflexión para obtener el nombre de la propiedad que tiene el atributo "PartitionKeyAttribute" y utilizar ese nombre como el nombre del parámetro de consulta en la acción del controlador.

    Aquí te muestro un ejemplo de cómo podrías implementar esto:

    csharp
    using System;
    using System.Reflection;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    
    namespace x.API.Controllers
    {
        [ApiController]
        [Route("[controller]")]
        public class GenericController : ControllerBase
        {
            [HttpGet("{id}")]
            public async Task> Get(Guid id, [FromQuery]string partitionKey)
            {
                PropertyInfo partitionKeyProperty = typeof(T).GetProperties()
                    .Where(prop => Attribute.IsDefined(prop, typeof(PartitionKeyAttribute)))
                    .FirstOrDefault();
    
                if (partitionKeyProperty == null)
                {
                    return BadRequest("PartitionKey not defined in class properties");
                }
    
                Guid partitionKeyValue = (Guid)partitionKeyProperty.GetValue(id);
    
                var item = await GetItemAsync(id, partitionKeyValue);
    
                return Ok(item);
            }
    
            private Task GetItemAsync(Guid id, Guid partitionKey)
            {
                // Implementación para obtener el elemento según el id y la clave de partición
                throw new NotImplementedException();
            }
        }
    
        [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
        public class PartitionKeyAttribute : Attribute
        {
        }
    
        public class Game
        {
            public Guid Id { get; set; }
    
            [PartitionKey]
            public Guid GamePartitionKey { get; set; }
        }
    }
    

    En este ejemplo, se define un atributo "PartitionKeyAttribute" que se aplica a la propiedad "GamePartitionKey" en la clase "Game". Luego, en el controlador genérico, se utiliza la reflexión para obtener la propiedad marcada con ese atributo y se usa su nombre como el nombre del parámetro de consulta en la acción "Get".

    Es importante destacar que este es solo un ejemplo básico y que puedes ajustarlo según tus necesidades específicas. ¡Espero que esta solución sea útil para resolver tu problema!

    Respondida el Dec 20, 2020 a las 21:05 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • Sí, es posible establecer dinámicamente el nombre del parámetro de consulta en atributos de propiedad de clase basados en controlador.

    Para lograr esto, puedes utilizar el atributo ApiExplorerSettingsAttribute con la propiedad GroupName.

    Paso a paso:

    • Crear un atributo personalizado:

    Define un atributo personalizado que herede de ApiExplorerSettingsAttribute y sobrescriba la propiedad GroupName para devolver dinámicamente el nombre de la propiedad de particiónKey de la clase actual.

    csharp
       public class PartitionKeyAttribute : ApiExplorerSettingsAttribute
       {
           public PartitionKeyAttribute()
           {
               // Obtiene el tipo de clase actual
               var type = typeof(ControllerBase).GetCurrentMethod()!.DeclaringType!;

    // Obtiene la propiedad de particiónKey var property = type.GetProperties() .FirstOrDefault(p => p.PropertyType == typeof(Guid) && p.GetCustomAttribute() != null);

    // Establece el nombre del grupo según el nombre de la propiedad de particiónKey GroupName = property?.Name; } }

    • Aplicar el atributo a la propiedad de particiónKey:

    En la clase de modelo que representa el tipo genérico T, aplica el atributo PartitionKeyAttribute a la propiedad que representa la particiónKey.

    csharp
       public class Game
       {
           [PartitionKey]
           public Guid GameId { get; set; }
       }
       

    • Actualizar el controlador genérico:

    En el controlador genérico, decora el parámetro de particiónKey con el atributo [FromQuery(Name = "partitionKey")]. Esto asegurará que el nombre del parámetro de consulta se derive del nombre del grupo establecido por el atributo PartitionKeyAttribute.

    csharp
       public class GenericController : ControllerBase
       {
           [HttpGet("{id}")]
           public async Task> Get(Guid id, [FromQuery(Name = "partitionKey")] Guid partitionKey)
           {
               var item = await GetItemAsync(id, partitionKey);

    return Ok(item); } }

    Ahora, cuando los consumidores de API vean la documentación de la API, verán el nombre del parámetro de consulta como el nombre de la propiedad de particiónKey correspondiente.

    Ejemplo de salida de documentación de la API:

    
    GET /api/game/{id}
    
    Query Parameters:
    
     id (required): The ID of the game.
     GameId (required): The partition key of the game.
    

    Recuerda que necesitarás registrar el atributo personalizado en el sistema de servicios de ASP.NET Core para que funcione correctamente. Puedes hacerlo agregándolo a la lista de atributos de convención global en Startup.ConfigureServices:

    csharp
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore()
            .AddApiExplorer(options => options.AttributeDocumentationSources.Add(new PartitionKeyAttribute()));
    }
    

    Respondida el Dec 20, 2020 a las 21:17 - por Gemini

    Votos positivos: 0 | Votos negativos: 0