5 votes

Créer un ModelBinder pour MongoDB ObjectId sur Asp.Net Core

J'essaie de créer un liant de modèle très simple pour les types ObjectId dans mes modèles mais je n'arrive pas à le faire fonctionner jusqu'à présent.

Voici le classeur des modèles :

public class ObjectIdModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var result = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
        return Task.FromResult(new ObjectId(result.FirstValue));
    }
}

Voici le ModelBinderProvider que j'ai codé :

public class ObjectIdModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) throw new ArgumentNullException(nameof(context));

        if (context.Metadata.ModelType == typeof(ObjectId))
        {
            return new BinderTypeModelBinder(typeof(ObjectIdModelBinder));
        }

        return null;
    }
}

Voici la classe à laquelle j'essaie de lier le paramètre body :

public class Player
{
    [BsonId]
    [ModelBinder(BinderType = typeof(ObjectIdModelBinder))]
    public ObjectId Id { get; set; }
    public Guid PlatformId { get; set; }
    public string Name { get; set; }
    public int Score { get; set; }
    public int Level { get; set; }
}

Il s'agit de la méthode d'action :

[HttpPost("join")]
public async Task<SomeThing> Join(Player player)
{
    return await _someService.DoSomethingOnthePlayer(player);
}

Pour que ce code fonctionne, c'est-à-dire pour que le modèle binder s'exécute, j'ai hérité du contrôleur de Controller et j'ai supprimé l'attribut [FromBody] du paramètre Player.

Lorsque je l'exécute, je peux entrer dans la méthode BindModelAsync du binder de modèle, mais je ne parviens pas à obtenir la valeur du paramètre Id à partir des données du message. Je peux voir le paramètre bindingContext.FieldName est correct ; il est réglé sur Id mais résultat.FirstValue est nulle.

Je me suis éloigné d'Asp.Net MVC pendant un certain temps, et il semble que beaucoup de choses ont été modifiées et sont devenues plus confuses :-)

EDIT Sur la base des commentaires, je pense que je devrais fournir plus de contexte.

Si je place [FromBody] avant le paramètre d'action du joueur, joueur est définie comme nulle. Si je supprime [FromBody], joueur est réglé sur une valeur par défaut, et non sur les valeurs que j'affiche. Le corps du message est présenté ci-dessous, il s'agit d'un simple JSON :

{
    "Id": "507f1f77bcf86cd799439011"
    "PlatformId": "9c8aae0f-6aad-45df-a5cf-4ca8f729b70f"
}

5voto

Kirk Larkin Points 29405

Si je supprime [FromBody], joueur est réglé sur une valeur par défaut, et non sur les valeurs que j'affiche.

La lecture des données du corps est opt-in (à moins que vous n'utilisiez [ApiController] ). Lorsque vous retirez [FromBody] de votre Player le processus de liaison de modèle cherchera à remplir les propriétés de l'élément suivant Player en utilisant la route, la chaîne de requête et les valeurs de formulaire, par défaut. Dans votre exemple, il n'y a pas de propriétés de ce type à ces endroits et donc aucun des éléments suivants n'est utilisé Player Les propriétés de ce dernier sont définies.

Si je place [FromBody] avant le paramètre d'action du joueur, joueur est défini comme nul.

Avec la présence de la [FromBody] le processus de liaison de modèle tente de lire le corps en fonction de l'attribut Content-Type fourni avec la demande. Si cela est application/json le corps sera analysé en tant que JSON et mis en correspondance avec votre fichier Player Les propriétés de l'entreprise. Dans votre exemple, le processus d'analyse JSON échoue, car il ne sait pas comment convertir un fichier de type string a un ObjectId . Lorsque cela se produit, ModelState.IsValid dans votre contrôleur retournera false et votre Player sera null .

Pour que ce code fonctionne, c'est-à-dire pour que le modèle binder s'exécute, j'ai hérité du contrôleur de Controller et supprimé l'attribut [FromBody] du paramètre Player.

Lorsque vous retirez [FromBody] El [ModelBinder(...)] que vous avez défini dans votre Id est respectée et votre code s'exécute donc. Cependant, avec la présence de [FromBody ], cet attribut est effectivement ignoré. Il y a beaucoup de choses qui se passent dans les coulisses ici, mais cela se résume essentiellement au fait que vous avez déjà opté pour la liaison de modèle à partir du corps en tant que JSON et c'est là que la liaison de modèle s'arrête dans ce scénario.


J'ai mentionné plus haut que c'est le processus d'analyse JSON qui échoue ici parce qu'il ne comprend pas comment traiter les données. ObjectId . Comme cette analyse JSON est gérée par Newtonsoft.Json (alias JSON.NET), une solution possible est de créer un module personnalisé JsonConverter . Ce sujet est bien couvert ici sur Stack Overflow, donc je n'entrerai pas dans les détails de la procédure. comment ça marche. Voici un exemple complet (la gestion des erreurs a été omise pour des raisons de brièveté et de paresse) :

public class ObjectIdJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) =>
        objectType == typeof(ObjectId);

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
        ObjectId.Parse(reader.Value as string);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
        writer.WriteValue(((ObjectId)value).ToString());
}

Pour l'utiliser, il suffit de remplacer votre actuel [ModelBinder(...)] avec un attribut [JsonConverter(...)] comme ceci :

[BsonId]
[JsonConverter(typeof(ObjectIdJsonConverter))]    
public ObjectId Id { get; set; }

Vous pouvez également vous inscrire ObjectIdJsonConverter de manière globale afin qu'elle s'applique à tous les ObjectId en utilisant quelque chose comme ceci dans Startup.ConfigureServices :

services.AddMvc()
        .AddJsonOptions(options =>
            options.SerializerSettings.Converters.Add(new ObjectIdJsonConverter());
        );

0voto

razon Points 36

Vous vous êtes trompé de ModelBinder. Code correct :

public class ObjectIdModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var result = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);

        bindingContext.Result = ModelBindingResult.Success(new ObjectId(result.FirstValue));

        return Task.CompletedTask;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X