web-dev-qa-db-fra.com

Le type est une interface ou une classe abstraite et ne peut pas être instancié

Je préfère ceci en disant que je sais quel est le problème, je ne sais pas comment le résoudre. Je communique avec une couche de données .NET SOA qui renvoie des données au format JSON. Une de ces méthodes renvoie un objet qui contient plusieurs collections. L'objet ressemble essentiellement à ceci:

{
  "Name":"foo",
  "widgetCollection":[{"name","foo"}, {"name","foo"},],
  "cogCollection": [{"name","foo"}, {"childCogs",<<new collection>>},],
}

Ma classe qui représente cet objet ressemble à ceci:

public class SuperWidget : IWidget
{
    public string Name { get; set; }

    public ICollection<IWidget> WidgetCollection { get; set; }
    public ICollection<ICog> CogCollection { get; set; }

    public SuperWidget()
    {
    }

    [JsonConstructor]
    public SuperWidget(IEnumerable<Widget> widgets, IEnumerable<Cog> cogs)
    {
        WidgetCollection = new Collection<IWidget>();
        CogCollection = new Collection<ICog>();

        foreach (var w in widgets)
        {
            WidgetCollection.Add(w);
        }
        foreach (var c in cogs)
        {
            CogCollection.Add(c);
        }
    }
}

Ce constructeur a bien fonctionné jusqu'à ce que cogCollection ajoute une collection enfant, et maintenant j'obtiens l'erreur ci-dessus. Une classe de rouage en béton ressemble à ceci:

[Serializable]
public class Cog : ICog
{
    public string name { get; set; }

    public ICollection<ICog> childCogs { get; set; }        
}  

Je ne veux pas changer la collection en type concret parce que j'utilise IoC. Parce que j'utilise l'IoC, je voudrais vraiment m'éloigner de la nécessité d'avoir des JsonConstructors qui prennent des paramètres concrets, mais je n'ai pas trouvé de moyen de le faire. Tout avis serait grandement apprécié!

Mise à jour:

La suggestion de Yuval Itzchakov selon laquelle cette question est probablement un double est quelque peu vraie (semble-t-il). Dans le message référencé, l'une des réponses en bas de la page fournit la même solution que celle fournie ici. Je n'ai pas remarqué cette réponse, car la question du PO était différente de celle que j'avais ici. Mon erreur.

------- ma solution --------

Comme je l'ai dit: la solution de Matt a pris un peu de travail, mais j'ai obtenu quelque chose qui fonctionne pour moi. La seule chose que je n'ai pas aimé dans sa solution initiale, c'était des lignes comme celles-ci:

 return objectType == typeof(ICog); 

En suivant ce modèle, vous devez disposer d'un JsonConverter pour chaque type abstrait que vous recevez sur le fil. Ce n'est pas idéal dans ma situation, j'ai donc créé un JsonConverter générique en tant que tel:

public class GenericJsonConverter<T>: JsonConverter, IBaseJsonConverter<T>
{
    private readonly IUnityContainer Container;
    public TalonJsonConverter(IUnityContainer container)
    {
        Container = container;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(T);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var target = serializer.Deserialize<Newtonsoft.Json.Linq.JObject>(reader);
        var result = Container.Resolve<T>();
        serializer.Populate(target.CreateReader(), result);
        return result;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

Ensuite, juste avant de désérialiser mes données, je fais quelque chose comme ceci:

 var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
 settings.Converters.Add((JsonConverter)Container.Resolve<IBaseJsonConverter<ICog>>());

Protip: si vous utilisez Resharper, (JsonConverter) vous donnera un avertissement de distribution suspecte dans ce scénario.

J'espère que quelqu'un d'autre trouvera cela utile sur la route!

23
dparsons

Vous devrez fournir un sérialiseur personnalisé à Json.Net pour lui indiquer comment gérer les rouages ​​d'enfant. Par exemple:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new CogConverter());

Votre CogConverter devra hériter de JsonConverter et spécifier qu'il CanConvert votre ICog interface. Peut-être quelque chose dans le sens de:

public class CogConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(ICog);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize(reader, typeof(Cog));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

Je recommanderais d'enregistrer votre JsonSerializerSettings avec votre conteneur IoC dans ce cas. Vous pouvez également envisager d'accorder à CogConverter l'accès au conteneur, si le sérialiseur ne peut pas être responsable de la construction de Cog lui-même; tout dépend de votre architecture particulière.

Modifier

À la lecture, il semble que vous cherchiez spécifiquement comment utiliser l'IoC créé ICog pour la population. J'utilise les éléments suivants dans le cadre de mon ReadJson:

var target = serializer.Deserialize<Newtonsoft.Json.Linq.JObject>(reader);
var objectType = DetermineConcreteType(target);
var result = iocContainer.Resolve(objectType);
serializer.Populate(target.CreateReader(), result);
return result;

Cela vous permet d'utiliser N'IMPORTE QUEL objet et de le remplir à partir du JSON d'origine, en utilisant des types personnalisés comme vous le souhaitez dans votre méthode DetermineConcreteType.

25
Matt DeKrey