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);
Related
I'm trying to convert milliseconds timestamp to a DateTime, but it throws an exception at reader.GetString():
System.InvalidOperationException: 'Cannot get the value of a token type 'Number' as a string.'
It means I'm trying to read it as a string when it is a value. If I replace it with reader.GetInt64() or reader.GetDouble(), it works, but the reason I'm writing that question here is because I took this class from one of dotnet's open source projects on GitHub and I doubt that I really need to change the class. I believe the problem could be in my JsonSerializerOptions.
JSON response
https://pastebin.com/9AjwSp5L (Pastebin because it exceeds SO's limits)
Snippet
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace QSGEngine.Web.Platforms.Converters
{
/// <summary>
/// Used for deserializing json with Microsoft date format.
/// </summary>
internal sealed class UnixEpochDateTimeConverter : JsonConverter<DateTime>
{
private static readonly DateTime EpochDateTime = new(1970, 1, 1, 0, 0, 0);
private static readonly Regex Regex = new("^/Date\\(([^+-]+)\\)/$", RegexOptions.CultureInvariant);
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var formatted = reader.GetString();
var match = Regex.Match(formatted!);
return !match.Success || !long.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var unixTime) ?
throw new JsonException() : EpochDateTime.AddMilliseconds(unixTime);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
var unixTime = Convert.ToInt64((value - EpochDateTime).TotalMilliseconds);
var formatted = FormattableString.Invariant($"/Date({unixTime})/");
writer.WriteStringValue(formatted);
}
}
}
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Ardalis.GuardClauses;
using QSGEngine.Web.Platforms.Extensions;
using RestSharp;
namespace QSGEngine.Web.Platforms.Binance;
/// <summary>
/// Binance REST API implementation.
/// </summary>
internal class BinanceRestApiClient : IDisposable
{
/// <summary>
/// The base point url.
/// </summary>
private const string BasePointUrl = "https://api.binance.com";
/// <summary>
/// The key header.
/// </summary>
private const string KeyHeader = "X-MBX-APIKEY";
/// <summary>
/// REST Client.
/// </summary>
private readonly IRestClient _restClient = new RestClient(BasePointUrl);
/// <summary>
/// Initializes a new instance of the <see cref="BinanceRestApiClient"/> class.
/// </summary>
/// <param name="apiKey">Binance API key.</param>
/// <param name="apiSecret">Binance Secret key.</param>
public BinanceRestApiClient(string apiKey, string apiSecret)
{
Guard.Against.NullOrWhiteSpace(apiKey, nameof(apiKey));
Guard.Against.NullOrWhiteSpace(apiSecret, nameof(apiSecret));
ApiKey = apiKey;
ApiSecret = apiSecret;
}
/// <summary>
/// The API key.
/// </summary>
public string ApiKey { get; }
/// <summary>
/// The secret key.
/// </summary>
public string ApiSecret { get; }
/// <summary>
/// Gets the total account cash balance for specified account type.
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public AccountInformation? GetBalances()
{
var queryString = $"timestamp={GetNonce()}";
var endpoint = $"/api/v3/account?{queryString}&signature={AuthenticationToken(queryString)}";
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"{nameof(BinanceRestApiClient)}: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
var jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
var deserialize = JsonSerializer.Deserialize<AccountInformation>(response.Content, jsonSerializerOptions);
return deserialize;
}
/// <summary>
/// If an IP address exceeds a certain number of requests per minute
/// HTTP 429 return code is used when breaking a request rate limit.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private IRestResponse ExecuteRestRequest(IRestRequest request)
{
const int maxAttempts = 10;
var attempts = 0;
IRestResponse response;
do
{
// TODO: RateLimiter
//if (!_restRateLimiter.WaitToProceed(TimeSpan.Zero))
//{
// Log.Trace("Brokerage.OnMessage(): " + new BrokerageMessageEvent(BrokerageMessageType.Warning, "RateLimit",
// "The API request has been rate limited. To avoid this message, please reduce the frequency of API calls."));
// _restRateLimiter.WaitToProceed();
//}
response = _restClient.Execute(request);
// 429 status code: Too Many Requests
} while (++attempts < maxAttempts && (int)response.StatusCode == 429);
return response;
}
/// <summary>
/// Timestamp in milliseconds.
/// </summary>
/// <returns>The current timestamp in milliseconds.</returns>
private long GetNonce()
{
return DateTime.UtcNow.ToTimestamp();
}
/// <summary>
/// Creates a signature for signed endpoints.
/// </summary>
/// <param name="payload">The body of the request.</param>
/// <returns>A token representing the request params.</returns>
private string AuthenticationToken(string payload)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(ApiSecret));
var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return BitConverter.ToString(computedHash).Replace("-", "").ToLowerInvariant();
}
/// <summary>
/// The standard dispose destructor.
/// </summary>
~BinanceRestApiClient() => Dispose(false);
/// <summary>
/// Returns true if it is already disposed.
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="disposing">If this method is called by a user's code.</param>
private void Dispose(bool disposing)
{
if (IsDisposed) return;
if (disposing)
{
}
IsDisposed = true;
}
/// <summary>
/// Throw if disposed.
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
private void ThrowIfDisposed()
{
if (IsDisposed)
{
throw new ObjectDisposedException($"{nameof(BinanceRestApiClient)} has been disposed.");
}
}
}
using System.Text.Json.Serialization;
using QSGEngine.Web.Platforms.Converters;
namespace QSGEngine.Web.Platforms.Binance;
/// <summary>
/// Information about the account.
/// </summary>
public class AccountInformation
{
/// <summary>
/// Commission percentage to pay when making trades.
/// </summary>
public decimal MakerCommission { get; set; }
/// <summary>
/// Commission percentage to pay when taking trades.
/// </summary>
public decimal TakerCommission { get; set; }
/// <summary>
/// Commission percentage to buy when buying.
/// </summary>
public decimal BuyerCommission { get; set; }
/// <summary>
/// Commission percentage to buy when selling.
/// </summary>
public decimal SellerCommission { get; set; }
/// <summary>
/// Boolean indicating if this account can trade.
/// </summary>
public bool CanTrade { get; set; }
/// <summary>
/// Boolean indicating if this account can withdraw.
/// </summary>
public bool CanWithdraw { get; set; }
/// <summary>
/// Boolean indicating if this account can deposit.
/// </summary>
public bool CanDeposit { get; set; }
/// <summary>
/// The time of the update.
/// </summary>
[JsonConverter(typeof(UnixEpochDateTimeConverter))]
public DateTime UpdateTime { get; set; }
/// <summary>
/// The type of the account.
/// </summary>
public string? AccountType { get; set; }
/// <summary>
/// List of assets with their current balances.
/// </summary>
public IEnumerable<Balance>? Balances { get; set; }
/// <summary>
/// Permission types.
/// </summary>
public IEnumerable<string>? Permissions { get; set; }
}
/// <summary>
/// Information about an asset balance.
/// </summary>
public class Balance
{
/// <summary>
/// The asset this balance is for.
/// </summary>
public string? Asset { get; set; }
/// <summary>
/// The amount that isn't locked in a trade.
/// </summary>
public decimal Free { get; set; }
/// <summary>
/// The amount that is currently locked in a trade.
/// </summary>
public decimal Locked { get; set; }
/// <summary>
/// The total balance of this asset (Free + Locked).
/// </summary>
public decimal Total => Free + Locked;
}
internal static class DateTimeExtensions
{
/// <summary>
/// To Unix Timestamp in milliseconds.
/// </summary>
/// <param name="datetime">The <see cref="DateTime"/>.</param>
/// <returns>The unix timestamp</returns>
public static long ToTimestamp(this DateTime datetime) => new DateTimeOffset(datetime).ToUnixTimeMilliseconds();
}
Let's try to make your converter a bit more forgiving:
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TryGetInt64(out long x))
return EpochDateTime.AddMilliseconds(x);
var formatted = reader.GetString();
var match = Regex.Match(formatted!);
return !match.Success || !long.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var unixTime) ?
throw new JsonException() : EpochDateTime.AddMilliseconds(unixTime);
}
I found some old Unity projects, but when I open them in Unity I get this errors:
The type or namespace name `FullSerializer' could not be found.
The type or namespace name `fsSerializer' could not be found.
This is the code:
using UnityEngine;
using UnityEngine.Assertions;
using FullSerializer;
namespace Game.Core
{
/// <summary>
/// Miscellaneous file utilities.
/// </summary>
public static class FileUtils
{
/// <summary>
/// Loads the specified json file.
/// </summary>
/// <param name="serializer">The FullSerializer serializer to use.</param>
/// <param name="path">The json file path.</param>
/// <typeparam name="T">The type of the data to load.</typeparam>
/// <returns>The loaded json data.</returns>
public static T LoadJsonFile<T>(fsSerializer serializer, string path) where T : class
{
var textAsset = Resources.Load<TextAsset>(path);
Assert.IsNotNull((textAsset));
var data = fsJsonParser.Parse(textAsset.text);
object deserialized = null;
serializer.TryDeserialize(data, typeof(T), ref deserialized).AssertSuccessWithoutWarnings();
return deserialized as T;
}
/// <summary>
/// Returns true if the specified path exists and false otherwise.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>True if the specified path exists; false otherwise.</returns>
public static bool FileExists(string path)
{
var textAsset = Resources.Load<TextAsset>(path);
return textAsset != null;
}
}
}
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);
}
}
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);
});
}
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"];
}
}
}
}