Génération d'objets dynamiques à l'aide d'ExpandoObject
J’ai eu besoin de convertir un fichier Excel avec ses propres colonnes définies en un nouveau avec des colonnes dynamiques, et j’étais un peu confus quant à la façon de le faire correctement sans problèmes de performances sérieux.
Je suis tombé sur un tutoriel que vous pouvez trouver ici qui utilise .NET ExpandoObject qui vous permet à peu près de créer un objet et d’ajouter des membres dynamiques.
ExpandoObject
La définition de Microsoft est :
Représente un objet dont les membres peuvent être ajoutés et supprimés dynamiquement au moment de l’exécution.
Et il contient quelques remarques :
La classe ExpandoObject vous permet d’ajouter et de supprimer des membres de ses instances au moment de l’exécution et également de définir et d’obtenir les valeurs de ces membres. Cette classe prend en charge la liaison dynamique, qui vous permet d’utiliser une syntaxe standard telle que sampleObject.sampleMember au lieu d’une syntaxe plus complexe telle que sampleObject.GetAttribute(“sampleMember”).
Comportement actuel
Nous avons un ExcelExportService qui, en passant un List<T>, dans ce cas ExcelItem, utilisera Reflection pour créer un fichier xlsx.
Jusqu’à présent, notre code ressemble à ceci :
class ExcelItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Age { get; set; }
public string CreatedAt { get; set; }
public string UpdatedAt { get; set; }
}
ExcelItem est l’objet avec toutes les propriétés utilisées pour générer le fichier Excel.
public static byte[] GetExcelBytes() {
List<ExcelItem> excelItems = new List<ExcelItem>();
ExcelExportService exportService = new ExcelExportService();
foreach (var items in itemsToExcel)
{
excelItems.Add(new ExcelItem()
{
Id = item.Id,
Name = item.Name,
Surname = item.Surname,
Age = item.Age,
CreatedAt = item.CreatedAt
UpdatedAt = item.UpdatedAt
});
}
return exportService.ExportToExcel(excelItems);
}
Cette approche fonctionne parfaitement, le fichier xlsx est généré sans problème, chaque colonne étant chaque propriété, vous utiliseriez cette méthode GetExcelBytes comme ceci, par exemple, pour l’enregistrer dans un fichier :
File.WriteAllBytes("path-to-somewhere/my-file.xlsx", GetExcelBytes());
Le problème
Puisque ce que nous faisons, c’est d’utiliser toutes les propriétés de l’objet ExcelItem, tout ce que je veux, c’est ne pas les utiliser toutes, mais simplement en utiliser peut-être 2 ou 3.
Une autre exigence est que je ne veux pas changer le code, tout doit être fait par le administrator, qui décidera quelles colonnes doivent être affichées, et il peut ou non connaître les propriétés du code.
TLDR : tout doit être dynamique, nous avons un objet avec des propriétés et nous devons nous assurer que le fichier généré a les N propriétés de cet objet, mais évidemment pas codées en dur.
Colonnes dynamiques
Le changement serait d’utiliser un objet dynamic, car nous aimerions définir quelles propriétés de l’objet seront utilisées pour générer la liste des colonnes.
Disons que nous avons un objet avec beaucoup de propriétés, comme une tonne d’entre elles, et que nous ne voulons pas vraiment changer l’objet ExcelItem à chaque fois que nous apportons une modification, nous créons une table ColumnExcelItem, qui sera utilisée pour générer ce ExcelItem.
Structure de la base de données
Nous enregistrons cette définition dans notre base de données avec quelque chose comme ceci :
Id PropertyName
---------------------
1 Name
2 Surname
3 Age
Notez que nous avons moins de valeurs que les propriétés existantes de l’objet ExcelItem, celui-ci ayant les propriétés 6 et nous n’avons que 3 répertoriés dans la base de données.
Notez également que le PropertyName doit correspondre au nom de propriété de l’objet que j’utiliserai pour l’affectation dynamique.
Construire l’objet
Maintenant que nous avons la table de base de données, nous devons construire le référentiel pour l’obtenir et pouvoir les utiliser dans le code.Je ne ferai pas de tutoriel sur cette partie, je passerai directement au début de la nouvelle fonction GetExcelBytes().
public static byte[] GetExcelBytes(List<string> columns) {
List<ExcelItem> excelItems = new List<ExcelItem>();
List<dynamic> objectsToExcel = new List<dynamic>();
ExcelExportService exportService = new ExcelExportService();
foreach (var item in itemsToExcel)
{
dynamic newObject = new ExpandoObject();
foreach (var col in columns)
{
this.AddProperty(newObject, col, product.GetType().GetProperty(col).GetValue(item, null));
}
objectsToExcel.Add(newObject);
}
return exportService.ExportToExcel(objectsToExcel);
}
Nous allons maintenant créer un objet avec certaines des propriétés de ExcelItem, mais complètement dynamique. Au lieu d’utiliser les 6 propriétés, nous n’en utilisons que 3, celle que nous voulons uniquement envoyer.
Supposons maintenant que cet objet ExcelItem possède 200 propriétés, fou, mais cela pourrait arriver.
La seule chose que je dois faire est d’insérer les propriétés que je souhaite restituer dans le fichier Excel dans ce tableau ColumnExcelItem et c’est tout.
Cas
Dans ce cas, nous n’avons utilisé qu’un seul cas, mais disons que vous souhaitez avoir des rapports différents pour certains utilisateurs. Disons que le rôle admin devrait obtenir toutes les propriétés, vous créeriez alors quelque chose comme cette structure dans la base de données
ReportingTemplates
Id PropertyName
---------------------
1 Admin
2 Users
ReportingTemplateItems
Id TemplateId PropertyName
---------------------
1 1 Id
2 1 Name
3 1 Surname
4 1 Age
5 1 CreatedAt
6 1 UpdatedAt
7 2 Name
8 2 Surname
9 2 Age
Ensuite, en obtenant le modèle, vous pourriez avoir différentes colonnes, toutes dynamiques et sans rapport avec le code.
Si nous avons un utilisateur avec le rôle Admins, le fichier contiendra les colonnes Id, Name, Surname, Age, CreatedAt, UpdatedAt.
Dans le cas où vous le générez en utilisant le rôle Users, il aura Name, Age. Surname.
Conclusion
C’est une bonne façon d’apprendre comment fonctionne le dynamic dans .NET, et c’est une très bonne solution lorsque vous avez besoin d’avoir différents rôles ou modèles pour différents utilisateurs, et que vous n’êtes pas vraiment intéressé à investir du temps dans le codage en dur.