FluentValidation.NET equivalent to [Display(Name)] - c#

Before FluentValidation.NET I could give a custom label to a properly like so:
[Display(Name="Blah")]
public string BlahBlahBlah { get; set; }
And I could consume this in several ways:
#Html.LabelFor(m => m.BlahBlahBlah)
#Html.DisplayNameFor(m => m.BlahBlahBlah)
<label asp-for="BlahBlahBlah"></label>
Now I want to remove all data annotations from my models, and move to fluent validation. In my validator, I have this:
RuleFor(o => o.BlahBlahBlah)
.NotEmpty()
.WithName("Blah");
But this does not work. Why?

WithName method in FluentValidation is used ONLY to tune validation error message if you want to replace C# property name to smth more user friendly (see
Overriding the Default Property Name for details).
So the answer is - you cannot replace Display with WithName() in general, only for validation error message.

In case it helps, I threw together a little attribute and helper to make this more dynamic.
WithDisplayNameAttribute.cs
[AttributeUsage(AttributeTargets.Property)]
public class WithDisplayNameAttribute : Attribute
{
public WithDisplayNameAttribute(string displayName)
{
DisplayName = displayName;
}
/// <summary>
/// The preferred friendly name to display for this property during validation.
/// </summary>
public string DisplayName { get; set; }
}
WithDisplayNameHelper.cs
internal static class WithDisplayNameHelper
{
public static IReadOnlyDictionary<string, string> Map { get; }
static WithDisplayNameHelper()
{
var core = typeof(WithDisplayNameHelper).Assembly;
var map = new Dictionary<string, string>();
foreach (var parentModelType in core.GetExportedTypes().Where(x => x.IsClass))
{
foreach (var prop in parentModelType.GetProperties())
{
var att = prop.GetCustomAttribute<WithDisplayNameAttribute>();
if (att == null) continue;
var key = GetKey(parentModelType, prop);
if (!map.ContainsKey(key))
{
map.Add(key, att.DisplayName);
}
}
}
Map = new ReadOnlyDictionary<string, string>(map);
}
/// <summary>
/// Gets the key to use for this property.
/// </summary>
/// <param name="parent">The parent class containing the property.</param>
/// <param name="prop">The property described by the display name.</param>
/// <returns></returns>
private static string GetKey(Type parent, PropertyInfo prop) => GetKey(parent, prop.Name);
/// <inheritdoc cref="GetKey(System.Type,System.Reflection.PropertyInfo)"/>
private static string GetKey(Type parent, string prop) => $"{parent.FullName}.{prop}";
/// <summary>
/// Retrieves the display name if one was set using the <see cref="WithDisplayNameAttribute"/>. Otherwise will return the given <paramref name="propertyName"/>.
/// </summary>
/// <param name="parent">The parent class containing the property.</param>
/// <param name="propertyName">The property name.</param>
/// <returns></returns>
public static string GetDisplayNameOrDefault(Type parent, string propertyName) =>
Map.TryGetValue(GetKey(parent, propertyName), out var value)
? value
: propertyName;
/// <summary>
/// Attempts to retrieve the display name if one was set using the <see cref="WithDisplayNameAttribute"/>.
/// </summary>
/// <inheritdoc cref="GetDisplayNameOrDefault"/>
/// <returns></returns>
public static bool TryGetDisplayName(Type parent, string propertyName, out string result) =>
Map.TryGetValue(GetKey(parent, propertyName), out result);
}
And the extension method:
/// <summary>
/// Specifies a custom property name to use within the error message.
/// </summary>
/// <param name="rule">The current rule</param>
/// <returns></returns>
public static IRuleBuilderOptions<T, TProperty> WithDisplayName<T, TProperty>(
this IRuleBuilderOptions<T, TProperty> rule)
{
return rule.Configure(x =>
{
if (WithDisplayNameHelper.TryGetDisplayName(typeof(T), x.PropertyName, out var displayName))
x.DisplayName = new StaticStringSource(displayName);
});
}

Related

c# - Can I refactor functions that have similar inputs but call different services into one generic function?

I am looking for a way to combine x amount of very similar CRUD functions into one without having to use x amount of if else statements to check the type of a generic.
I have Web API controllers that I want to make calls from like this:
Service.Get<FooModel>(number, type, part, version);
This is to prevent having to have an extremely similar function for 40+ API endpoints. The issue is when I receive this in my service, I have to check the type of the generic given and compare with those 40+ object types in the one function. All of the models currently inherit from a base inherited model.
Current generic function
(Create, Update, Delete functions are similar):
public T Get<T>(string documentNr, string type, string part, string version) where T : InheritedModel, new()
{
try
{
T model = new T();
if (typeof(T) == typeof(InheritedModel))
{
using (var repo = new InheritedModelConsumer(ref _helper))
{
model = (T)repo.Get(documentNr, type, part, version);
}
}
else if (typeof(T) == typeof(FooModel))
{
using (var repo = new FooModelConsumer(ref _helper))
{
model = (T)(object)repo.Get(documentNr, type, part, version);
}
}
else if (typeof(T) == typeof(ComponentModel))
{
using (var repo = new ComponentModelConsumer(ref _helper))
{
model = (T)(object)repo.Get(documentNr, type, part, version);
}
}
else if (typeof(T) == typeof(BarModel))
{
using (var repo = new BarModelConsumer(ref _helper))
{
model = (T)(object)repo.Get(documentNr, type, part, version);
}
}
... and so on
... and so on
...
else
throw new Exception("Type T structure not defined");
return model;
}
catch (Exception)
{
throw;
}
finally
{
_helper.Dispose();
}
}
This does work, but if it is possible I am looking for something where I can say at run time, "oh I have this object of Type T, and well since I know the functions all have the same inputs I'm going to instantiate this consumer of Type TConsumer, call consumer.Get(inputs), and then return an object of T to whatever API controller called me."
Edit
Example of a simple consumer class in use
internal sealed class FooConsumer : RepositoryConsumer<Foo, FooRepository, FooFilter>
{
public FooConsumer(ref SqlHelper helper) : base(ref helper) { }
public List<Foo> GetAll(string token)
{
return _repo.Get().Where(x => Extensions.StringContainsToken(x.AccountName, token)).ToList();
}
}
Repository Consumer that all consumers inherit from .
T is the model, K is the Repository (custom ORM class), and O is Filter for the WHERE clause the ORM executes.
public abstract class RepositoryConsumer<T, K, O> : IDisposable, IRepositoryConsumer<T> where T : class, new() where K : Repository<T, O>, new() where O : QueryFilter, new()
{
/// <summary>
/// Repository instance
/// </summary>
protected K _repo;
/// <summary>
/// Only constructor avaialble. MUst pass SqlHelper instance for transaction support
/// </summary>
/// <param name="sql"></param>
public RepositoryConsumer(ref SqlHelper sql)
{
_repo = Activator.CreateInstance(typeof(K), new object[] { sql }) as K;
}
/// <summary>
/// Allow consumer initializations in using statements
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Create instance of T
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public virtual int Create(T data)
{
return _repo.Create(data);
}
/// <summary>
/// Bulk create instances of T
/// </summary>
/// <param name="contract"></param>
/// <returns></returns>
public virtual int Create(BaseBulkable<T> contract)
{
return _repo.BulkCreate(contract);
}
/// <summary>
/// Get an instance of T based on a single PK field id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T Get(long id)
{
return _repo.Get(id);
}
/// <summary>
/// Gets all instances of T
/// </summary>
/// <returns></returns>
public virtual List<T> GetAll()
{
return _repo.Get();
}
/// <summary>
/// Updates an instance of T
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public virtual int Update(T data)
{
return _repo.Update(data);
}
/// <summary>
/// Updates an instance of T based on a single PK field id
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
/// <returns></returns>
public virtual int Update(long id, T data)
{
return _repo.Update(id, data);
}
/// <summary>
/// Deletes an instance of T
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public virtual int Delete(T data)
{
return _repo.Delete(data);
}
/// <summary>
/// Deletes an instance of T based on a single PK field id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual int Delete(long id)
{
return _repo.Delete(id);
}
}

Entity Framework : deleting child entities

I am using EF, but I have lazy loading disabled. Instead I am using eager loading, so I created my own service:
/// <summary>
/// Generic service for entity framework
/// </summary>
/// <typeparam name="T">An entity model</typeparam>
public class Service<T> : IService<T> where T : class
{
// Create our private properties
private readonly DbContext _context;
private readonly DbSet<T> _dbEntitySet;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="context">The database context</param>
public Service(DbContext context)
{
// Assign our context and entity set
_context = context ?? throw new ArgumentNullException("context");
_dbEntitySet = context.Set<T>();
}
/// <summary>
/// Gets all the entities
/// </summary>
/// <param name="includes">Option includes for eager loading</param>
/// <returns></returns>
public IQueryable<T> List(params string[] includes)
{
// Create a query
IQueryable<T> query = _dbEntitySet;
// For each include, append to our query
if (includes != null)
foreach (var include in includes)
query = query.Include(include);
// Return our query
return query;
}
/// <summary>
/// Creates an entity
/// </summary>
/// <param name="model"></param>
public void Create(T model) => _dbEntitySet.Add(model);
/// <summary>
/// Updates an entity
/// </summary>
/// <param name="model"></param>
public void Update(T model) => _context.Entry<T>(model).State = EntityState.Modified;
/// <summary>
/// Removes an entity
/// </summary>
/// <param name="model"></param>
public void Remove(T model) => _context.Entry<T>(model).State = EntityState.Deleted;
/// <summary>
/// Saves the database context changes
/// </summary>
/// <returns></returns>
public async Task SaveChangesAsync()
{
try
{
// Save the changes to the database
await _context.SaveChangesAsync();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
catch (DbUpdateException ex)
{
throw;
}
}
/// <summary>
/// Executes a stored procedure in sql
/// </summary>
/// <param name="procedure">The name of the sproc</param>
/// <param name="parameters">the sql params for the sproc</param>
/// <returns></returns>
public DbRawSqlQuery<T> ExecuteProcedure(string procedure, List<SqlParameter> parameters)
{
var results = _context.Database.SqlQuery<T>($"exec {procedure} { CreateQueryStringFromParams(parameters) }");
return results;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Creates the input string to run sprocs in sql with EF by converting the sql params into a nice string
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
private static string CreateQueryStringFromParams(IEnumerable<SqlParameter> parameters)
{
var response = "";
var list = parameters as IList<SqlParameter> ?? parameters.ToList();
var length = list.Count;
for (var i = 0; i < length; i++)
{
response += $"{list[i].ParameterName}=\"{list[i].Value}\"";
if (i != length - 1)
response += ", ";
}
return response;
}
/// <summary>
/// Disposes of any attached resources
/// </summary>
/// <param name="disposing">A boolean indicating whether the object is being disposed</param>
protected virtual void Dispose(bool disposing)
{
// If we are disposing, dispose of our context
if (disposing)
_context.Dispose();
}
}
Each service then inherits this class:
/// <summary>
/// Handles all Group related methods
/// </summary>
public class GroupService : Service<Group>, IGroupService
{
/// <summary>
/// The default constructor
/// </summary>
/// <param name="unitOfWork"></param>
public GroupService(DbContext context) : base(context)
{
}
/// <summary>
/// Lists groups by category
/// </summary>
/// <param name="categoryId">The id of the category</param>
/// <param name="includes"></param>
/// <returns></returns>
public IQueryable<Group> List(int categoryId, params string[] includes) => List(includes).Where(m => m.CategoryId == categoryId);
/// <summary>
/// Gets a single Group by id
/// </summary>
/// <param name="id">The id of the Group</param>
/// <returns></returns>
public async Task<Group> GetAsync(int id, params string[] includes) => await List(includes).Where(model => model.Id == id).SingleOrDefaultAsync();
}
Each "entity" has a class similar to this GroupService.
I also have providers for each entity type too and here is my delete method:
/// <summary>
/// Delete a Group
/// </summary>
/// <param name="id">The Group id</param>
/// <returns></returns>
public async Task<bool> DeleteAsync(int id)
{
// Get our model
var model = await _service.GetAsync(id, "Questions");
// For each question, remove from the database
if (model.Questions != null)
foreach (var question in model.Questions.ToList())
if (!await _questionProvider.Value.DeleteAsync(question.Id, false))
throw new Exception("Failed to delete the questions");
// Update our Questions
model.Questions = null;
// Save our model
_service.Remove(model);
// Save the database changes
await _service.SaveChangesAsync();
// Return true
return true;
}
As you can see, I just pull back the questions.
If there are some questions, then I invoke the questionProvider's delete method, which is very similar:
/// <summary>
/// Delete a question
/// </summary>
/// <param name="id">The question id</param>
/// <param name="saveChanges">Saves the changes to the database after the delete (default true)</param>
/// <returns></returns>
public async Task<bool> DeleteAsync(int id, bool saveChanges = true)
{
// Get our model
var model = await _service.GetAsync(id, "Answers");
// For each answer, delete from the database
if (model.Answers != null)
foreach (var answer in model.Answers.ToList())
if (!await _answerProvider.Value.DeleteAsync(answer.Id, false))
throw new Exception("Failed to delete the answers");
// Update our Answers
model.Answers = null;
// Save our model
_service.Remove(model);
// Save the database changes
if (saveChanges) await _service.SaveChangesAsync();
// Return true
return true;
}
As you can see, I do not save the context changes until all children have been removed. Now I must point out that I am not removing the child directly from the parent. Instead I am removing the entity from it's own collection and then setting the property to null. After it is all done I save the changes, but I am getting this error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
Does anyone know how I can delete the entities in a method similar to what I am trying to achieve?
This was rather easy to fix after reading some other people with similar issues. I just changed one line of code in my Service.cs from:
public void Remove(T model) => _context.Entry<T>(model).State = EntityState.Deleted;
to:
public void Remove(T model) => _dbEntitySet.Remove(model);
And that worked.
Update
I also found that the eager load needs to include all the navigation properties that will be affected.
So if I was to delete a Question which has Answers and Answers have Formulas, the Get invocation would have to be:
var model = await _service.GetAsync(id, "Answers.Formulas");
If you don't include that, you will get an error.

Why does this code cause xsd.exe error, "You must implement a default accessor on System.Configuration.ConfigurationLockCollection"?

Hello StackOverFlow users,
Iam trying to create an XSD from a dll that i created, but when i try to generate the XSD i get the following error message:
You must implement a default accessor on System.Configuration.ConfigurationLockCollection because it inherits from ICollection.
(Also tried different framework version (Did not work))
Command that gets executed:
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools>xsd "J:\
Programming\C#\NightBitsLogger Library\NightBitsLogger\bin\Debug\NightBitsLogger
.dll"
Does anyone know what i have to do to have a default accessor for the ConfigurationLockCollection?
(Because it seems that this is a bug in the .NET framework)
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Collections;
namespace NightBitsLogger.Configuration
{
/// <summary>
/// This class is an abstract class for a Collection of Config Elements
/// </summary>
/// <typeparam name="T"></typeparam>
///
[ConfigurationCollection(typeof(ConfigurationItem), AddItemName = "ConfigurationElement" )]
public abstract class CollectionOfElements<T> : ConfigurationElementCollection
where T : ConfigurationItem, new()
{
/// <summary>
/// Default Accessor for the collections
/// </summary>
[ConfigurationProperty("ConfigurationCollection", IsRequired = true)]
public CollectionOfElements<ConfigurationItem> ConfigurationCollection
{
get
{
return base["ConfigurationCollection"] as CollectionOfElements<ConfigurationItem>;
}
}
/// <summary>
/// Create and return a new Configuration Element
/// </summary>
/// <returns></returns>
protected override ConfigurationElement CreateNewElement()
{
return new T();
}
/// <summary>
/// Return the element key.
/// </summary>
/// <param name="element"></param>
/// <returns>(ConfigurationElement)key</returns>
protected override object GetElementKey(ConfigurationElement element)
{
return ((ConfigurationItem)element).Name;
}
/// <summary>
/// Return the element with the given index
/// Basic accessor for elements
/// </summary>
/// <param name="index"></param>
/// <returns>[Element]byIndex</returns>
public T this[int index]
{
get
{
return (T)BaseGet(index);
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
/// <summary>
/// Add a element to the collection
/// </summary>
/// <param name="collectionOfLoggerElement"></param>
public void Add(T collectionOfLoggerElement)
{
BaseAdd(collectionOfLoggerElement);
}
/// <summary>
/// Return the element with the given index
/// </summary>
/// <param name="name"></param>
/// <returns>[Element]byName</returns>
public new T this[string name]
{
get
{
return (T)BaseGet(name);
}
}
}
/// <summary>
/// The CollectionOfLoggers Collection
/// </summary>
[ConfigurationCollection(typeof(CollectionOfLoggersElement), AddItemName = "CollectionOfLoggers", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class CollectionOfLoggers : CollectionOfElements<CollectionOfLoggersElement>
{
// Do nothing
}
/// <summary>
/// The FileLogger Collection
/// </summary>
[ConfigurationCollection(typeof(FileLoggerElement), AddItemName = "fileLogger", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class FileLoggers : CollectionOfElements<FileLoggerElement>
{
// Do nothing
}
/// <summary>
/// The RollingDateFileLogger Collection
/// </summary>
[ConfigurationCollection(typeof(RollingDateFileLoggerElement), AddItemName = "rollingDateFileLogger", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class RollingDateFileLoggers : CollectionOfElements<RollingDateFileLoggerElement>
{
// Do nothing
}
/// <summary>
/// The RollingSizeFileLogger Collection
/// </summary>
[ConfigurationCollection(typeof(RollingSizeFileLoggerElement), AddItemName = "rollingSizeFileLogger", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class RollingSizeFileLoggers : CollectionOfElements<RollingSizeFileLoggerElement>
{
// Do nothing
}
/// <summary>
/// The EmailLogger Collection
/// </summary>
[ConfigurationCollection(typeof(EmailLoggerElement), AddItemName = "emailLogger", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class EmailLoggers : CollectionOfElements<EmailLoggerElement>
{
// Do nothing
}
/// <summary>
/// The SocketLogger Collection
/// </summary>
[ConfigurationCollection(typeof(SocketLoggerElement), AddItemName = "socketLogger", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class SocketLoggers : CollectionOfElements<SocketLoggerElement>
{
// Do nothing
}
/// <summary>
/// The WindowsEventLogLogger Collection
/// </summary>
[ConfigurationCollection(typeof(WindowsEventLogLoggerElement), AddItemName = "WindowsEventLogLogger", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class WindowsEventLogLoggers : CollectionOfElements<WindowsEventLogLoggerElement>
{
// Do nothing
}
/// <summary>
/// The ConsoleLogger Collection
/// </summary>
[ConfigurationCollection(typeof(ConsoleLoggerElement), AddItemName = "consoleLogger", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class ConsoleLoggers : CollectionOfElements<ConsoleLoggerElement>
{
// Do nothing
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.ComponentModel;
namespace NightBitsLogger.Configuration
{
/// <summary>
/// This is the baseClass that represents a Config Item (Logger)
/// </summary>
public class ConfigurationItem : ConfigurationSection
{
/// <summary>
/// Get the configuration
/// </summary>
/// <returns></returns>
public static ConfigurationItem GetConfiguration()
{
ConfigurationItem configuration = ConfigurationManager.GetSection("NightBitsLogger.Configuration") as ConfigurationItem;
if (configuration != null)
{
return configuration;
}
return new ConfigurationItem();
}
/// <summary>
/// Occurs after the element is deserialized
/// </summary>
///
protected override void PostDeserialize()
{
base.PostDeserialize();
Validate();
}
/// <summary>
/// Validate the element. Throw a exception if not valid.
/// </summary>
protected virtual void Validate()
{
if ((IncludeCategories.Trim() != "") && (ExcludeCategories.Trim() != ""))
{
throw new ConfigurationErrorsException("logging element can have either includeCategories or excludeCategories, but not both.");
}
}
/// <summary>
/// Return true if the Logger Element is configured for the current machine; otherwise return false.
/// </summary>
/// <returns></returns>
public bool IsConfiguredForThisMachine()
{
var machineNames = Machine.Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return (machineNames.Length == 0) || new List<string>(machineNames).Exists(name => name.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase));
}
/// <summary>
/// Get the name of the Logger
/// </summary>
[ConfigurationProperty("name", DefaultValue = "", IsKey = true, IsRequired = true)]
[Description("The name of the logger")]
public string Name
{
get
{
return (string)this["name"];
}
}
/// <summary>
/// The machine names (separated by commas) for which the Logger will be created. An empty value creates it on all machines.
/// </summary>
[ConfigurationProperty("machine", DefaultValue = "", IsRequired = false)]
[Description("The machine names (separated by commas) for which this logger will be created. Leaving it empty will create it on all machines")]
public string Machine
{
get
{
return (string)this["machine"];
}
}
/// <summary>
/// Check if the Internal Exception Logging is enabled
/// </summary>
[ConfigurationProperty("enableInternalExceptionLogging", DefaultValue = false, IsRequired = false)]
[Description("true if the internal exceptionLogging is enabled; otherwise false")]
public bool IsInternalLoggingEnabled
{
get
{
return (bool)this["isInternalLoggingEnabled"];
}
}
/// <summary>
/// Check if the Logger is enabled
/// </summary>
[ConfigurationProperty("isEnabled", DefaultValue = true, IsRequired = false)]
[Description("true if the logger is enabled; otherwise false")]
public bool IsEnabled
{
get
{
return (bool)this["isEnabled"];
}
}
/// <summary>
/// The LogLevel of the Logger
/// </summary>
[ConfigurationProperty("logLevel", DefaultValue = LogLevel.Debug, IsRequired = false)]
[Description("The logLevel of the logger")]
public LogLevel LogLevel
{
get
{
return (LogLevel)this["logLevel"];
}
}
/// <summary>
/// Categories to include (leave blank for all)
/// </summary>
[ConfigurationProperty("includeCategories", DefaultValue = "", IsRequired = false)]
[Description("The categories, separated by commas, to include when logging. Leave blank to include everything.")]
public string IncludeCategories
{
get
{
return (string)this["includeCategories"];
}
}
/// <summary>
/// Categories to exclude
/// </summary>
[ConfigurationProperty("excludeCategories", DefaultValue = "", IsRequired = false)]
[Description("The categories, separated by commas, to exclude when logging.")]
public string ExcludeCategories
{
get
{
return (string)this["excludeCategories"];
}
}
/// <summary>
/// If true, wrap the Logger in an KeepLoggingLogger.
/// </summary>
[ConfigurationProperty("keepLogging", DefaultValue = false, IsRequired = false)]
[Description("if true, the logger will be wrapped in a KeepLoggingLogger")]
public bool keepLogging
{
get
{
return (bool)this["keepLogging"];
}
}
/// <summary>
/// If true, wrap the Logger in an AsynchronousLogger.
/// </summary>
[ConfigurationProperty("isAsynchronous", DefaultValue = false, IsRequired = false)]
[Description("if true, the logger will be wrapped in a AsynchronousLogger")]
public bool IsAsynchronous
{
get
{
return (bool)this["isAsynchronous"];
}
}
/// <summary>
/// The FormatString for the Logger
/// </summary>
[ConfigurationProperty("formatString", DefaultValue = "", IsRequired = false)]
[Description("The format string of the logger. If blank, it will use the format string of the enclosing section (the CollectionOfLoggers).")]
public virtual string FormatString
{
get
{
return (string)this["formatString"];
}
}
}
}

Mapping a series of identical JSON objects to a dictionary

I'm having trouble mapping a certain JSON string to a Dictionary<T, T2> using JSON.NET.
My JSON string looks like this:
{
"map_waypoint": { "file_id": 157353, "signature": "32633AF8ADEA696A1EF56D3AE32D617B10D3AC57" },
"map_waypoint_contested": { "file_id": 102349, "signature": "5EF051273B40CFAC4AEA6C1F1D0DA612C1B0776C" },
"map_waypoint_hover": { "file_id": 157354, "signature": "95CE3F6B0502232AD90034E4B7CE6E5B0FD3CC5F" }
}
Rather than making 3 identical classes for each object, I made 1 class Asset that works for all of them:
public class Asset
{
/// <summary>
/// Initializes a new instance of the <see cref="Asset"/> class.
/// </summary>
public Asset()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Asset"/> class.
/// </summary>
/// <param name="fileId">The file ID.</param>
/// <param name="signature">The file signature.</param>
[JsonConstructor]
public Asset(string fileId, string signature)
{
this.FileId = fileId;
this.Signature = signature;
}
/// <summary>
/// Gets the file ID to be used with the render service.
/// </summary>
[JsonProperty("file_id")]
public string FileId { get; private set; }
/// <summary>
/// Gets file signature to be used with the render service.
/// </summary>
[JsonProperty("signature")]
public string Signature { get; private set; }
/// <summary>
/// Gets the JSON representation of this instance.
/// </summary>
/// <returns>Returns a JSON <see cref="String"/>.</returns>
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Now in another class FilesResponse, I'm keeping a property Files of type Dictionary<String, Asset>.
public class FilesResponse
{
/// <summary>
/// Initializes a new instance of the <see cref="FilesResponse"/> class.
/// </summary>
public FilesResponse()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FilesResponse"/> class.
/// </summary>
/// <param name="files">A collection of assets by their name.</param>
[JsonConstructor]
public FilesResponse(Dictionary<string, Asset> files)
{
this.Files = files;
}
/// <summary>
/// Gets the collection of assets by their name.
/// </summary>
[JsonProperty]
public Dictionary<string, Asset> Files { get; private set; }
/// <summary>
/// Gets the JSON representation of this instance.
/// </summary>
/// <returns>Returns a JSON <see cref="String"/>.</returns>
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
The thing is that I'm not quite sure how to let JSON.NET know that the data from my JSON string should go inside the dictionary...?
Ideally, I'd like to be able to do this:
var filesResponse = JsonConvert.DeserializeObject<FilesResponse>(jsonString);
foreach (var file in filesResponse.Files)
{
Console.WriteLine("Name = {0}, ID = {1}", file.Key, file.Value.FileId);
}
Can I make this work somehow?
You need to implement your own converter if you want to have GUIDs. I end up with something like this.
public class StringGuidConverter: JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
return new Guid((string)reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(((Guid)value).ToString("N"));
}
}
public class Asset {
/// <summary>
/// Initializes a new instance of the <see cref="Asset"/> class.
/// </summary>
public Asset() {
}
/// <summary>
/// Initializes a new instance of the <see cref="Asset"/> class.
/// </summary>
/// <param name="fileId">The file ID.</param>
/// <param name="signature">The file signature.</param>
[JsonConstructor]
public Asset(string fileId, Guid signature) {
this.FileId = fileId;
this.Signature = signature;
}
/// <summary>
/// Gets the file ID to be used with the render service.
/// </summary>
[JsonProperty("file_id")]
public string FileId { get; private set; }
/// <summary>
/// Gets file signature to be used with the render service.
/// </summary>
[JsonProperty("signature")]
[JsonConverter(typeof(StringGuidConverter))]
public Guid Signature { get; private set; }
/// <summary>
/// Gets the JSON representation of this instance.
/// </summary>
/// <returns>Returns a JSON <see cref="String"/>.</returns>
public override string ToString() {
return JsonConvert.SerializeObject(this);
}
}
public class FilesResponse {
/// <summary>
/// Initializes a new instance of the <see cref="FilesResponse"/> class.
/// </summary>
public FilesResponse() {
}
/// <summary>
/// Initializes a new instance of the <see cref="FilesResponse"/> class.
/// </summary>
/// <param name="files">A collection of assets by their name.</param>
[JsonConstructor]
public FilesResponse(Dictionary<string, Asset> files) {
this.Files = files;
}
/// <summary>
/// Gets the collection of assets by their name.
/// </summary>
[JsonProperty]
public Dictionary<string, Asset> Files { get; private set; }
/// <summary>
/// Gets the JSON representation of this instance.
/// </summary>
/// <returns>Returns a JSON <see cref="String"/>.</returns>
public override string ToString() {
return JsonConvert.SerializeObject(this);
}
}
class Test {
public static void Run() {
var json = #"{
""map_waypoint"": { ""file_id"": 157353, ""signature"": ""32633AF8ADEA696AE32D617B10D3AC57"" },
""map_waypoint_contested"": { ""file_id"": 102349, ""signature"": ""32633AF8ADEA696AE32D617B10D3AC57"" },
""map_waypoint_hover"": { ""file_id"": 157354, ""signature"": ""32633AF8ADEA696AE32D617B10D3AC57"" }
}";
var result2 = JsonConvert.DeserializeObject<FilesResponse>(json);
var result3 = new FilesResponse(JsonConvert.DeserializeObject<Dictionary<string, Asset>>(json));
}
}
Unfotunatelly result2 does not work
EDIT
Btw. your data are incorrect. GUIDs are 32-chars long and you have 40-chars long. That's the reason I had to modify the test data.
EDIT2
I would make FilesResponse inherit from a dictionary, like this:
public class FilesResponse2: Dictionary<string, Asset>
{
/// <summary>
/// Initializes a new instance of the <see cref="FilesResponse"/> class.
/// </summary>
public FilesResponse2() {
}
/// <summary>
/// Gets the collection of assets by their name.
/// </summary>
public Dictionary<string, Asset> Files { get { return this; } }
/// <summary>
/// Gets the JSON representation of this instance.
/// </summary>
/// <returns>Returns a JSON <see cref="String"/>.</returns>
public override string ToString() {
return JsonConvert.SerializeObject(this);
}
}
// deserialization:
var result22 = JsonConvert.DeserializeObject<FilesResponse2>(json);

How to inject property dependencies on a .net attribute?

I'm trying to apply some behavior using a home grown type of "aspect", really a .net Attribute. I have a base class (BankingServiceBase) that reflects on itself at startup to see what "aspects" are applied to it. It then can execute custom behavior before or after operations. I'm using Autofac as my IOC container. I'm trying to apply the PropertiesAutowired method to the aspect's registration. In the below sample code I want Autofac to inject an ILog instance to my aspect/attribute. It isn't doing that however. My guess is that when I call GetCustomAttributes, it's creating a new instance instead of getting the registered instance from Autofac. Thoughts? Here is some usable sample code to display the problem:
internal class Program
{
private static void Main()
{
var builder = new ContainerBuilder();
builder
.RegisterType<ConsoleLog>()
.As<ILog>();
builder
.RegisterType<BankingService>()
.As<IBankingService>();
builder
.RegisterType<LogTransfer>()
.As<LogTransfer>()
.PropertiesAutowired();
var container = builder.Build();
var bankingService = container.Resolve<IBankingService>();
bankingService.Transfer("ACT 1", "ACT 2", 180);
System.Console.ReadKey();
}
public interface IBankingService
{
void Transfer(string from, string to, decimal amount);
}
public interface ILog
{
void LogMessage(string message);
}
public class ConsoleLog : ILog
{
public void LogMessage(string message)
{
System.Console.WriteLine(message);
}
}
[AttributeUsage(AttributeTargets.Class)]
public abstract class BankingServiceAspect : Attribute
{
public virtual void PreTransfer(string from, string to, decimal amount)
{
}
public virtual void PostTransfer(bool success)
{
}
}
public class LogTransfer : BankingServiceAspect
{
// Note: this is never getting set from Autofac!
public ILog Log { get; set; }
public override void PreTransfer(string from, string to, decimal amount)
{
Log.LogMessage(string.Format("About to transfer from {0}, to {1}, for amount {2}", from, to, amount));
}
public override void PostTransfer(bool success)
{
Log.LogMessage(success ? "Transfer completed!" : "Transfer failed!");
}
}
public abstract class BankingServiceBase : IBankingService
{
private readonly List<BankingServiceAspect> aspects;
protected BankingServiceBase()
{
// Note: My guess is that this "GetCustomAttributes" is happening before the IOC dependency map is built.
aspects =
GetType().GetCustomAttributes(typeof (BankingServiceAspect), true).Cast<BankingServiceAspect>().
ToList();
}
void IBankingService.Transfer(string from, string to, decimal amount)
{
aspects.ForEach(a => a.PreTransfer(from, to, amount));
try
{
Transfer(from, to, amount);
aspects.ForEach(a => a.PostTransfer(true));
}
catch (Exception)
{
aspects.ForEach(a => a.PostTransfer(false));
}
}
public abstract void Transfer(string from, string to, decimal amount);
}
[LogTransfer]
public class BankingService : BankingServiceBase
{
public override void Transfer(string from, string to, decimal amount)
{
// Simulate some latency..
Thread.Sleep(1000);
}
}
}
You're correct that GetCustomAttributes doesn't resolve the custom attributes via Autofac - if you think about it, how could FCL code such as GetCustomAttributes know about Autofac? The custom attributes are actually retrieved from assembly metadata, so they never go through Autofac's resolution process and therefore your registration code is never used.
What you can do is to inject the services into the attribute instance yourself. Begin with the code in Oliver's answer to generate the list of aspect attributes. However, before returning the list, you can process each attribute and inject services into any dependent fields and properties. I have a class called AttributedDependencyInjector, which I use via an extension method. It uses reflection to scan for fields and properties that are decorated with the InjectDependencyAttribute and then set the value of those properties. There's rather a lot of code to cope with various scenarios, but here it is.
The attribute class:
/// <summary>
/// Attribute that signals that a dependency should be injected.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class InjectDependencyAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref = "InjectDependencyAttribute" /> class.
/// </summary>
public InjectDependencyAttribute()
{
this.PreserveExistingValue = false;
}
/// <summary>
/// Gets or sets a value indicating whether to preserve an existing non-null value.
/// </summary>
/// <value>
/// <c>true</c> if the injector should preserve an existing value; otherwise, <c>false</c>.
/// </value>
public bool PreserveExistingValue { get; set; }
}
The injector class:
public class AttributedDependencyInjector
{
/// <summary>
/// The component context.
/// </summary>
private readonly IComponentContext context;
/// <summary>
/// Initializes a new instance of the <see cref="AttributedDependencyInjector"/> class.
/// </summary>
/// <param name="context">The context.</param>
public AttributedDependencyInjector(IComponentContext context)
{
this.context = context;
}
/// <summary>
/// Injects dependencies into an instance.
/// </summary>
/// <param name="instance">The instance.</param>
public void InjectDependencies(object instance)
{
this.InjectAttributedFields(instance);
this.InjectAttributedProperties(instance);
}
/// <summary>
/// Gets the injectable fields.
/// </summary>
/// <param name="instanceType">
/// Type of the instance.
/// </param>
/// <param name="injectableFields">
/// The injectable fields.
/// </param>
private static void GetInjectableFields(
Type instanceType, ICollection<Tuple<FieldInfo, InjectDependencyAttribute>> injectableFields)
{
const BindingFlags BindingsFlag =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
IEnumerable<FieldInfo> fields = instanceType.GetFields(BindingsFlag);
// fields
foreach (FieldInfo field in fields)
{
Type fieldType = field.FieldType;
if (fieldType.IsValueType)
{
continue;
}
// Check if it has an InjectDependencyAttribute
var attribute = field.GetAttribute<InjectDependencyAttribute>(false);
if (attribute == null)
{
continue;
}
var info = new Tuple<FieldInfo, InjectDependencyAttribute>(field, attribute);
injectableFields.Add(info);
}
}
/// <summary>
/// Gets the injectable properties.
/// </summary>
/// <param name="instanceType">
/// Type of the instance.
/// </param>
/// <param name="injectableProperties">
/// A list into which are appended any injectable properties.
/// </param>
private static void GetInjectableProperties(
Type instanceType, ICollection<Tuple<PropertyInfo, InjectDependencyAttribute>> injectableProperties)
{
// properties
foreach (var property in instanceType.GetProperties(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
Type propertyType = property.PropertyType;
// Can't inject value types
if (propertyType.IsValueType)
{
continue;
}
// Can't inject non-writeable properties
if (!property.CanWrite)
{
continue;
}
// Check if it has an InjectDependencyAttribute
var attribute = property.GetAttribute<InjectDependencyAttribute>(false);
if (attribute == null)
{
continue;
}
// If set to preserve existing value, we must be able to read it!
if (attribute.PreserveExistingValue && !property.CanRead)
{
throw new BoneheadedException("Can't preserve an existing value if it is unreadable");
}
var info = new Tuple<PropertyInfo, InjectDependencyAttribute>(property, attribute);
injectableProperties.Add(info);
}
}
/// <summary>
/// Determines whether the <paramref name="propertyType"/> can be resolved in the specified context.
/// </summary>
/// <param name="propertyType">
/// Type of the property.
/// </param>
/// <returns>
/// <c>true</c> if <see cref="context"/> can resolve the specified property type; otherwise, <c>false</c>.
/// </returns>
private bool CanResolve(Type propertyType)
{
return this.context.IsRegistered(propertyType) || propertyType.IsAssignableFrom(typeof(ILog));
}
/// <summary>
/// Injects dependencies into the instance's fields.
/// </summary>
/// <param name="instance">
/// The instance.
/// </param>
private void InjectAttributedFields(object instance)
{
Type instanceType = instance.GetType();
// We can't get information about the private members of base classes through reflecting a subclass,
// so we must walk up the inheritance hierarchy and reflect at each level
var injectableFields = new List<Tuple<FieldInfo, InjectDependencyAttribute>>();
var type = instanceType;
while (type != null)
{
GetInjectableFields(type, injectableFields);
type = type.BaseType;
}
// fields
foreach (var fieldDetails in injectableFields)
{
var field = fieldDetails.Item1;
var attribute = fieldDetails.Item2;
if (!this.CanResolve(field.FieldType))
{
continue;
}
// Check to preserve existing value
if (attribute.PreserveExistingValue && (field.GetValue(instance) != null))
{
continue;
}
object fieldValue = this.Resolve(field.FieldType, instanceType);
field.SetValue(instance, fieldValue);
}
}
/// <summary>
/// Injects dependencies into the instance's properties.
/// </summary>
/// <param name="instance">
/// The instance.
/// </param>
private void InjectAttributedProperties(object instance)
{
Type instanceType = instance.GetType();
// We can't get information about the private members of base classes through reflecting a subclass,
// so we must walk up the inheritance bierarchy and reflect at each level
var injectableProperties = new List<Tuple<PropertyInfo, InjectDependencyAttribute>>();
var type = instanceType;
while (type != typeof(object))
{
Debug.Assert(type != null, "type != null");
GetInjectableProperties(type, injectableProperties);
type = type.BaseType;
}
// Process the list and inject properties as appropriate
foreach (var details in injectableProperties)
{
var property = details.Item1;
var attribute = details.Item2;
// Check to preserve existing value
if (attribute.PreserveExistingValue && (property.GetValue(instance, null) != null))
{
continue;
}
var propertyValue = this.Resolve(property.PropertyType, instanceType);
property.SetValue(instance, propertyValue, null);
}
}
/// <summary>
/// Resolves the specified <paramref name="propertyType"/> within the context.
/// </summary>
/// <param name="propertyType">
/// Type of the property that is being injected.
/// </param>
/// <param name="instanceType">
/// Type of the object that is being injected.
/// </param>
/// <returns>
/// The object instance to inject into the property value.
/// </returns>
private object Resolve(Type propertyType, Type instanceType)
{
if (propertyType.IsAssignableFrom(typeof(ILog)))
{
return LogManager.GetLogger(instanceType);
}
return this.context.Resolve(propertyType);
}
}
The extension method:
public static class RegistrationExtensions
{
/// <summary>
/// Injects dependencies into the instance's properties and fields.
/// </summary>
/// <param name="context">
/// The component context.
/// </param>
/// <param name="instance">
/// The instance into which to inject dependencies.
/// </param>
public static void InjectDependencies(this IComponentContext context, object instance)
{
Enforce.ArgumentNotNull(context, "context");
Enforce.ArgumentNotNull(instance, "instance");
var injector = new AttributedDependencyInjector(context);
injector.InjectDependencies(instance);
}
}
Try to implement a lazy loading of the aspects
private readonly List<BankingServiceAspect> _aspects;
private List<BankingServiceAspect> Aspects
{
get
{
if (_aspects == null) {
_aspects = GetType()
.GetCustomAttributes(typeof(BankingServiceAspect), true)
.Cast<BankingServiceAspect>()
.ToList();
}
return _aspects;
}
}
Then use it like this
Aspects.ForEach(a => a.PreTransfer(from, to, amount));
...

Categories

Resources