I'm trying to change how our system generates PDF files. Currently I have a class for each PDF that goes through the model for the PDF and fills in the properties. I want to change that to a self contained class that can be called in the background through Hangfire. To that end I've settled on decorating the properties that should be rendered to the PDF with attributes. I then reflect through the model object looking for those properties and rendering them on the PDF. I've been testing it out in LINQPad and the code I have works, but I feel that it can be improved on, especially in the multiple foreach loops I have.I'm looking for some input and suggestions if it can be improved for security, performance, reliability, etc.void Main() {    var obj = new AuthorizationPdfModel {        Name = John Doe,        Phone = 8885555555,        PhoneType = PhoneType.Mobile,        AmountPaid = 378.92M,        PaidWith = PaidWith.CreditCard,        LineItems = new List<LineItem> {                new LineItem {                    Amount = 382.21M,                    Quantity = 1,                    Description = Blargh!                },                new LineItem {                    Amount = 568.39M,                    Quantity = 2,                    Description = More blargh!                },                new LineItem {                    Amount = 927.38M,                    Quantity = 4,                    Description = EVEN MORE BLARGH!                }            },        Signature = <svg></svg>    };    GetAllFields(obj).Dump();    GetSignatures(obj).Dump();}static IEnumerable<KeyValuePair<string, string>> GetAllFields(    object obj) {    var dictionary = new Dictionary<string, string>();    var fields = GetFields(obj);    foreach (var field in fields) {        dictionary.Add(field.Key, field.Value);    }    var lists = GetLists(obj);    foreach (var list in lists) {        foreach (var item in list) {            var i = list.IndexOf(item);            var itemFields = GetFields(item, i);            foreach (var itemField in itemFields) {                dictionary.Add(itemField.Key, itemField.Value);            }        }    }    return dictionary.ToList();}static IEnumerable<KeyValuePair<string, string>> GetFields(    object obj,    int? i = null) {    return GetProperties<PdfFieldAttribute>(obj).Select(        p => {            var display = p.Property.GetCustomAttribute<DisplayAttribute>();            var format = p.Property.GetCustomAttribute<DisplayFormatAttribute>();            var key = display == null ? p.Property.Name : display.Name;            var value = GetValue(p.Property, format, p.Value);            key = i.HasValue ? key + . + i : key;            return new KeyValuePair<string, string>(key, value);        });}static IEnumerable<IList> GetLists(    object obj) {    return GetProperties<PdfListAttribute>(obj).Select(        p =>            p.Value).Cast<IList>();}static IEnumerable<KeyValuePair<string, string>> GetSignatures(    object obj) {    return GetProperties<PdfSignatureAttribute>(obj).Select(        p => {            var display = p.Property.GetCustomAttribute<DisplayAttribute>();            var key = display == null ? p.Property.Name : display.Name;            var value = GetValue(p.Property, null, p.Value);            return new KeyValuePair<string, string>(key, value);        });}static IEnumerable<PropertyDetails> GetProperties<TAttribute>(    object obj)    where TAttribute : PdfAttribute {    return obj.GetType().GetProperties().Where(        p =>            p.GetGetMethod() != null).Select(        p =>            new {                Attribute = p.GetCustomAttribute<TAttribute>(),                Property = p,                Value = p.GetValue(obj)            }).Where(        p =>            p.Attribute != null            && p.Value != null).Select(        p =>            new PropertyDetails {                Property = p.Property,                Value = p.Value            });}internal sealed class PropertyDetails {    public PropertyInfo Property { get; set; }    public object Value { get; set; }}static readonly Type _booleanType = typeof(bool);static string GetBooleanValue(    Type type,    object value) {    if (type.UnderlyingSystemType != _booleanType) {        return null;    }    return Convert.ToBoolean(value).ToString();}static string GetEnumValue(    Type type,    object value) {    if (!type.IsEnum) {        return null;    }    var name = Enum.GetName(type, value);    var member = type.GetMember(name).SingleOrDefault();    if (member == null) {        return null;    }    var display = member.GetCustomAttribute<DisplayAttribute>();    if (display == null) {        return null;    }    return display.Name;}static string GetFormattedValue(    Type type,    DisplayFormatAttribute format,    object value) {    if (type.IsEnum        || type.IsNullableEnum()        || format == null) {        return value.ToString();    }    return string.Format(format.DataFormatString, value);}static string GetNullableEnumValue(    Type type,    object value) {    if (!type.IsNullableEnum()) {        return null;    }    var underlyingType = Nullable.GetUnderlyingType(type);    return GetEnumValue(underlyingType, value);}static string GetValue(    PropertyInfo property,    DisplayFormatAttribute format,    object value) {    var type = property.PropertyType;    var normalizedValue = GetFormattedValue(type, format, value);    normalizedValue = GetBooleanValue(type, value) ?? normalizedValue;    normalizedValue = GetEnumValue(type, value) ?? normalizedValue;    normalizedValue = GetNullableEnumValue(type, value) ?? normalizedValue;    return normalizedValue;}public abstract class DocumentPdfModel {    public abstract string TemplateName { get; }}public class AuthorizationPdfModel : DocumentPdfModel {    public override string TemplateName {        get {            return Authorization;        }    }    #region PDF Fields    [PdfField, Display(Name = NameOfCustomer)]    public string Name { get; set; }    [PdfField, DisplayFormat(DataFormatString = {0:(###) ###-####})]    public long Phone { get; set; }    [PdfField]    public PhoneType PhoneType { get; set; }    [PdfField, DisplayFormat(DataFormatString = {0:c})]    public decimal? AmountDue {        get {            return LineItems.Sum(li => li.TotalAmount);        }    }    [PdfField, DisplayFormat(DataFormatString = {0:c})]    public decimal? AmountPaid { get; set; }    [PdfField, DisplayFormat(DataFormatString = {0:c})]    public decimal? BalanceRemaining {        get {            if (!AmountDue.HasValue                || !AmountPaid.HasValue) {                return null;            }            return AmountDue - AmountPaid;        }    }    [PdfField]    public PaidWith PaidWith { get; set; }    [PdfList]    public IList<LineItem> LineItems { get; set; }    [PdfSignature, Display(Name = SignatureOfCustomer)]    public string Signature { get; set; }    #endregion}public sealed class LineItem {    [PdfField]    public string Description { get; set; }    [PdfField, DisplayFormat(DataFormatString = {0:c})]    public decimal Amount { get; set; }    [PdfField]    public int Quantity { get; set; }    [PdfField, DisplayFormat(DataFormatString = {0:c})]    public decimal TotalAmount {        get {            return Amount * Quantity;        }    }}public enum PhoneType {    None,    Mobile,    Work}public enum PaidWith {    None,    Cash,    Check,    [Display(Name = Credit Card)]    CreditCard}[AttributeUsage(AttributeTargets.Property)]public class PdfAttribute : Attribute {}public sealed class PdfFieldAttribute : PdfAttribute {}public sealed class PdfListAttribute : PdfAttribute {}public sealed class PdfSignatureAttribute : PdfAttribute {}internal static class TypeExtensions {    public static bool IsNullableEnum(        this Type type) {        var underlyingType = Nullable.GetUnderlyingType(type);        return underlyingType != null            && underlyingType.IsEnum;    }}
Data model decomposition into key value pairs using reflection and attributes
c#;.net;reflection