I've started writing DataFilters a while ago after I discovered and used elastic search with it's wonderful lucene syntax.
The idea behind that project was first to learn new stuff but I was also wondering if I could create something similar to work with other datasources.
Long story short, I now have something that work pretty well (I think) with the BCL classes and I now want to extend it to support third party libraries like NodaTime.
The main parts are IFilter interface
using System;
namespace DataFilters
{
/// <summary>
/// Defines the basic shape of a filter
/// </summary>
public interface IFilter : IEquatable<IFilter>
{
/// <summary>
/// Gets the JSON representation of the filter
/// </summary>
/// <returns></returns>
string ToJson();
/// <summary>
/// Computes a new <see cref="IFilter"/> instance which is the exact opposite of the current instance.
/// </summary>
/// <returns>The exact opposite of the current instance.</returns>
IFilter Negate();
#if NETSTANDARD2_1
public virtual void ToString() => ToJson();
#endif
}
}
with two implementations :
Filter
using DataFilters.Converters;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using static Newtonsoft.Json.DefaultValueHandling;
using static Newtonsoft.Json.Required;
using System.Text.RegularExpressions;
#if !NETSTANDARD1_3
using System.Text.Json.Serialization;
#endif
namespace DataFilters
{
/// <summary>
/// An instance of this class holds a filter
/// </summary>
#if NETSTANDARD1_3
[JsonObject]
[JsonConverter(typeof(FilterConverter))]
#else
[System.Text.Json.Serialization.JsonConverter(typeof(FilterConverter))]
#endif
public class Filter : IFilter, IEquatable<Filter>
{
/// <summary>
/// Filter that always returns <c>true</c>
/// </summary>
public static Filter True => new Filter(default, default);
/// <summary>
/// Pattern that field name should respect.
/// </summary>
/// <returns></returns>
public const string ValidFieldNamePattern = #"[a-zA-Z_]+((\[""[a-zA-Z0-9_]+""]|(\.[a-zA-Z0-9_]+))*)";
/// <summary>
/// Regular expression used to validate
/// </summary>
/// <returns></returns>
public static readonly Regex ValidFieldNameRegex = new Regex(ValidFieldNamePattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
/// <summary>
/// Name of the json property that holds the field name
/// </summary>
public const string FieldJsonPropertyName = "field";
/// <summary>
/// Name of the json property that holds the operator
/// </summary>
public const string OperatorJsonPropertyName = "op";
/// <summary>
/// Name of the json property that holds the value
/// </summary>
public const string ValueJsonPropertyName = "value";
/// <summary>
/// <see cref="FilterOperator"/>s that required <see cref="Value"/> to be null.
/// </summary>
public static IEnumerable<FilterOperator> UnaryOperators { get; } = new[]{
FilterOperator.IsEmpty,
FilterOperator.IsNotEmpty,
FilterOperator.IsNotNull,
FilterOperator.IsNull
};
/// <summary>
/// Generates the <see cref="JSchema"/> for the specified <see cref="FilterOperator"/>.
/// </summary>
/// <param name="op"></param>
/// <returns></returns>
public static JSchema Schema(FilterOperator op)
{
JSchema schema;
switch (op)
{
case FilterOperator.Contains:
case FilterOperator.StartsWith:
case FilterOperator.EndsWith:
schema = new JSchema
{
Type = JSchemaType.Object,
Properties =
{
[FieldJsonPropertyName] = new JSchema { Type = JSchemaType.String },
[OperatorJsonPropertyName] = new JSchema { Type = JSchemaType.String },
[ValueJsonPropertyName] = new JSchema { Type = JSchemaType.String }
},
Required = { FieldJsonPropertyName, OperatorJsonPropertyName }
};
break;
case FilterOperator.IsEmpty:
case FilterOperator.IsNotEmpty:
case FilterOperator.IsNotNull:
case FilterOperator.IsNull:
schema = new JSchema
{
Type = JSchemaType.Object,
Properties =
{
[FieldJsonPropertyName] = new JSchema { Type = JSchemaType.String },
[OperatorJsonPropertyName] = new JSchema { Type = JSchemaType.String }
},
Required = { FieldJsonPropertyName, OperatorJsonPropertyName }
};
break;
default:
schema = new JSchema
{
Type = JSchemaType.Object,
Properties =
{
[FieldJsonPropertyName] = new JSchema { Type = JSchemaType.String, },
[OperatorJsonPropertyName] = new JSchema { Type = JSchemaType.String },
[ValueJsonPropertyName] = new JSchema {
Not = new JSchema() { Type = JSchemaType.Null }
}
},
Required = { FieldJsonPropertyName, OperatorJsonPropertyName, ValueJsonPropertyName }
};
break;
}
schema.AllowAdditionalProperties = false;
return schema;
}
/// <summary>
/// Name of the field the filter will be applied to
/// </summary>
#if NETSTANDARD1_3
[JsonProperty(FieldJsonPropertyName, Required = Always)]
#else
[JsonPropertyName(FieldJsonPropertyName)]
#endif
public string Field { get; }
/// <summary>
/// Operator to apply to the filter
/// </summary>
#if NETSTANDARD1_3
[JsonProperty(OperatorJsonPropertyName, Required = Always)]
[JsonConverter(typeof(CamelCaseEnumTypeConverter))]
#else
[JsonPropertyName(OperatorJsonPropertyName)]
//[System.Text.Json.Serialization.JsonConverter(typeof(FilterOperatorConverter))]
#endif
public FilterOperator Operator { get; }
/// <summary>
/// Value of the filter
/// </summary>
#if NETSTANDARD1_3
[JsonProperty(ValueJsonPropertyName,
Required = AllowNull,
DefaultValueHandling = IgnoreAndPopulate,
NullValueHandling = NullValueHandling.Ignore)]
#else
[JsonPropertyName(ValueJsonPropertyName)]
#endif
public object Value { get; }
/// <summary>
/// Builds a new <see cref="Filter"/> instance.
/// </summary>
/// <param name="field">name of the field</param>
/// <param name="operator"><see cref="Filter"/> to apply</param>
/// <param name="value">value of the filter</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="field"/> does not conform with <see cref="ValidFieldNamePattern"/></exception>
public Filter(string field, FilterOperator #operator, object value = null)
{
if (!string.IsNullOrEmpty(field) && !ValidFieldNameRegex.IsMatch(field))
{
throw new ArgumentOutOfRangeException(nameof(field), field, $"field name is not valid ({ValidFieldNamePattern}).");
}
Field = field;
switch (#operator)
{
case FilterOperator.EqualTo when value is null:
Operator = FilterOperator.IsNull;
break;
case FilterOperator.NotEqualTo when value is null:
Operator = FilterOperator.IsNotNull;
break;
default:
Operator = #operator;
Value = value;
break;
}
}
#if NETSTANDARD1_3
public string ToJson()
{
return this.Jsonify(new JsonSerializerSettings());
}
#else
public string ToJson() => this.Jsonify();
#endif
public override string ToString() => ToJson();
public bool Equals(Filter other)
=> other != null
&& (ReferenceEquals(other, this)
|| (Equals(other.Field, Field) && Equals(other.Operator, Operator) && Equals(other.Value, Value)));
public override bool Equals(object obj) => Equals(obj as Filter);
#if NETSTANDARD1_3 || NETSTANDARD2_0
public override int GetHashCode() => (Field, Operator, Value).GetHashCode();
#else
public override int GetHashCode() => HashCode.Combine(Field, Operator, Value);
#endif
public IFilter Negate()
{
FilterOperator #operator = Operator switch
{
FilterOperator.EqualTo => FilterOperator.NotEqualTo,
FilterOperator.NotEqualTo => FilterOperator.EqualTo,
FilterOperator.IsNull => FilterOperator.IsNotNull,
FilterOperator.IsNotNull => FilterOperator.IsNull,
FilterOperator.LessThan => FilterOperator.GreaterThan,
FilterOperator.GreaterThan => FilterOperator.LessThan,
FilterOperator.GreaterThanOrEqual => FilterOperator.LessThanOrEqualTo,
FilterOperator.StartsWith => FilterOperator.NotStartsWith,
FilterOperator.NotStartsWith => FilterOperator.StartsWith,
FilterOperator.EndsWith => FilterOperator.NotEndsWith,
FilterOperator.NotEndsWith => FilterOperator.EndsWith,
FilterOperator.Contains => FilterOperator.NotContains,
FilterOperator.IsEmpty => FilterOperator.IsNotEmpty,
FilterOperator.IsNotEmpty => FilterOperator.IsEmpty,
FilterOperator.LessThanOrEqualTo => FilterOperator.GreaterThanOrEqual,
_ => throw new ArgumentOutOfRangeException(nameof(Operator), "Unknown operator"),
};
return new Filter(Field, #operator, Value);
}
public bool Equals(IFilter other) => Equals(other as Filter)
;
public void Deconstruct(out string field, out FilterOperator #operator, out object value)
{
field = Field;
#operator = Operator;
value = Value;
}
}
}
MultiFilter
using DataFilters.Converters;
using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using System;
using System.Collections.Generic;
using System.Linq;
using static Newtonsoft.Json.DefaultValueHandling;
using static Newtonsoft.Json.Required;
#if !NETSTANDARD1_3
using System.Text.Json.Serialization;
#endif
namespace DataFilters
{
/// <summary>
/// An instance of this class holds combination of <see cref="IFilter"/>
/// </summary>
[JsonObject]
#if NETSTANDARD1_3
[JsonConverter(typeof(MultiFilterConverter))]
#else
[System.Text.Json.Serialization.JsonConverter(typeof(MultiFilterConverter))]
#endif
public class MultiFilter : IFilter, IEquatable<MultiFilter>
{
/// <summary>
/// Name of the json property that holds filter's filters collection.
/// </summary>
public const string FiltersJsonPropertyName = "filters";
/// <summary>
/// Name of the json property that holds the composite filter's logic
/// </summary>
public const string LogicJsonPropertyName = "logic";
public static JSchema Schema => new JSchema
{
Type = JSchemaType.Object,
Properties =
{
[FiltersJsonPropertyName] = new JSchema { Type = JSchemaType.Array, MinimumItems = 2 },
[LogicJsonPropertyName] = new JSchema { Type = JSchemaType.String, Default = "and"}
},
Required = { FiltersJsonPropertyName },
AllowAdditionalProperties = false
};
/// <summary>
/// Collections of filters
/// </summary>
#if NETSTANDARD1_3
[JsonProperty(PropertyName = FiltersJsonPropertyName, Required = Always)]
#else
[JsonPropertyName(FiltersJsonPropertyName)]
#endif
public IEnumerable<IFilter> Filters { get; set; } = Enumerable.Empty<IFilter>();
/// <summary>
/// Operator to apply between <see cref="Filters"/>
/// </summary>
#if NETSTANDARD1_3
[JsonProperty(PropertyName = LogicJsonPropertyName, DefaultValueHandling = IgnoreAndPopulate)]
[JsonConverter(typeof(CamelCaseEnumTypeConverter))]
#else
[JsonPropertyName(LogicJsonPropertyName)]
#endif
public FilterLogic Logic { get; set; }
public virtual string ToJson() => this.Jsonify();
public IFilter Negate()
{
MultiFilter filter = new MultiFilter
{
Logic = Logic switch
{
FilterLogic.And => FilterLogic.Or,
FilterLogic.Or => FilterLogic.And,
_ => throw new ArgumentOutOfRangeException($"Unsupported {Logic}")
},
Filters = Filters.Select(f => f.Negate())
#if DEBUG
.ToArray()
#endif
};
return filter;
}
#if NETSTANDARD1_3 || NETSTANDARD2_0
public override int GetHashCode() => (Logic, Filters).GetHashCode();
#else
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Logic);
foreach (IFilter filter in Filters)
{
hash.Add(filter);
}
return hash.ToHashCode();
}
#endif
public bool Equals(IFilter other) => Equals(other as MultiFilter);
public override bool Equals(object obj) => Equals(obj as MultiFilter);
public bool Equals(MultiFilter other)
=> Logic == other?.Logic
&& Filters.Count() == other?.Filters?.Count()
&& Filters.All(filter => other?.Filters?.Contains(filter) ?? false)
&& (other?.Filters.All(filter => Filters.Contains(filter)) ?? false);
}
}
DataFilters.Expressions and DataFilters.Queries are two libraries that I also wrote and that allow to create C# Expressions or WHERE SQLs given an IFilter instance as a input (extension methods).
What i'm trying to do now is to provide an extension point so that I could write a new library (called DataFilters.NodaTime for example) that could handle NodaTime types somehow while deferring everything else to DataFilters.Expressions (a library that I already released).
That extension point should add ability to handle nodatime type but I have no clue how to get started on this
For now, I'm thinking about something like this :
create a new library DataFilters.Expressions.NodaTime
create a new IFilter extension method in it : it will be tailored to handle NodaTime types.
The goal is to be able to handle NodaTime types both with DataFilters.Expressions and DataFilters.Queries for example.
Could it be a good approach or is there a better way to handle this ?
Thanks in advance to anyone who could help me on this
Related
I am currently writing some code to try to experiment with separating and abstracting two parts of our storage strategies at work. We currently use JSON format stored into a file and then retrieve it as our persistent storage. I am trying to experiment with separating the two concepts:
1) Concept one keeps the serialization separate from the storage type
2) Concept two keeps the storage type separate from the serialization strategy.
I found a good way that works doing some research on various threads, such as using TextWriter/TextReader instead of directly using Files so that any Stream type can be used (FileStream/MemoryStream/etc) so that the unit tests can be done without files. However, I am running into a problem since the TextWriter/TextReader classes which wrap the streams automatically close and dispose of the streams when they are themselves disposed, which is what I want in practice, but gets me stuck in unit testing.
Here is the code I have so far... this is for concept 1, the serialization process. Here are the interfaces for it:
/// <summary>
/// Interface for a serializer which reads from a stream and creates a type
/// </summary>
public interface IInSerializer
{
/// <summary>
/// Load type from a stream
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
bool Load(TextReader reader);
}
/// <summary>
/// Interface for writing a type out into a stream
/// </summary>
public interface IOutSerializer
{
/// <summary>
/// Save to the stream
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
bool Save(TextWriter writer);
}
/// <summary>
/// Helper interface which provides interface <see cref="IInSerializer"/>
/// and <see cref="IOutSerializer"/> for both reading/writing
/// </summary>
public interface IInOutSerializer : IInSerializer, IOutSerializer
{
}
Here is an abstract implementation of the serializer for JSON format:
/// <summary>
/// Implementation of <see cref="IInOutSerializer"/> which serializes into JSON format
/// </summary>
/// <typeparam name="T">Type to be serialized</typeparam>
public abstract class JSONSerializer<T> : IInOutSerializer
{
/// <summary>
/// Source of serialization
/// </summary>
public T Source { get; set; }
/// <summary>
/// Provided by very specific type to load the Jobject into type T
/// </summary>
/// <param name="jObject"></param>
/// <returns></returns>
protected abstract bool LoadJObject(JObject jObject);
/// <summary>
/// Provided by very specific type to save type T into a Jobject
/// </summary>
/// <param name="jObject"></param>
/// <returns></returns>
protected abstract bool Serialize(JObject jObject);
/// <summary>
/// <see cref="IInOutSerializer.Load"/>
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
public bool Load(TextReader reader)
{
using (var json = new JsonTextReader(reader))
{
var jObject = JToken.ReadFrom(json) as JObject;
if (jObject != null)
return LoadJObject(jObject);
}
return false;
}
/// <summary>
/// <see cref="IInOutSerializer.Save"/>
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
public bool Save(TextWriter writer)
{
var jObject = new JObject();
if (Serialize(jObject))
{
using (var json = new JsonTextWriter(writer))
{
json.Formatting = Formatting.Indented;
jObject.WriteTo(json);
return true;
}
}
return false;
}
}
And here is one of the concrete types for serializing my class MetroLineDetails:
public class MetroLineJSONSerializationStrategy : JSONSerializer<MetroLineDetails>
{
private class MetroLineHelper : IMetroLine, IMetroLineWritable
{
public string DestinationStation
{
get;
set;
}
public Color LineColor
{
get;
set;
}
public char LineLetter
{
get;
set;
}
public string Name
{
get;
set;
}
public bool SaturdayService
{
get;
set;
}
public string SourceStation
{
get;
set;
}
public bool SundayHolidayService
{
get;
set;
}
public static explicit operator MetroLineDetails(MetroLineHelper source)
{
return new MetroLineDetails(source.Name, source.LineColor, source.SourceStation, source.DestinationStation, source.SaturdayService, source.SundayHolidayService);
}
}
protected override bool LoadJObject(JObject jObject)
{
var helper = new MetroLineHelper();
jObject.Read(nameof(MetroLineDetails.Name), (t) => (string)t, (v) => helper.Name = v);
jObject.Read(nameof(MetroLineDetails.LineLetter), (t) => (char)t, (v) => helper.LineLetter = v);
jObject.Read(nameof(MetroLineDetails.SourceStation), (t) => (string)t, (v) => helper.SourceStation = v);
jObject.Read(nameof(MetroLineDetails.DestinationStation), (t) => (string)t, (v) => helper.DestinationStation = v);
jObject.Read(nameof(MetroLineDetails.SaturdayService), (t) => (bool)t, (v) => helper.SaturdayService = v);
jObject.Read(nameof(MetroLineDetails.SundayHolidayService), (t) => (bool)t, (v) => helper.SundayHolidayService = v);
var color = jObject.Read(nameof(MetroLineDetails.LineColor), (t) => (JObject)t);
helper.LineColor = color.ToColor();
Source = (MetroLineDetails)helper;
return true;
}
protected override bool Serialize(JObject jObject)
{
jObject.Add(nameof(MetroLineDetails.Name), Source.Name);
jObject.Add(nameof(MetroLineDetails.LineLetter), Source.LineLetter);
jObject.Add(nameof(MetroLineDetails.SourceStation), Source.SourceStation);
jObject.Add(nameof(MetroLineDetails.DestinationStation), Source.DestinationStation);
jObject.Add(nameof(MetroLineDetails.SaturdayService), Source.SaturdayService);
jObject.Add(nameof(MetroLineDetails.SundayHolidayService), Source.SundayHolidayService);
jObject.Add(nameof(MetroLineDetails.LineColor), Source.LineColor.ToJObject());
return true;
}
}
And now here are my storage type interfaces:
/// <summary>
/// Interface for the storage medium
/// </summary>
public interface IStorageMedium
{
/// <summary>
/// Save the information in the serializer
/// </summary>
/// <param name="serializer"></param>
void Save(IOutSerializer serializer);
/// <summary>
/// Load the information to the serializer
/// </summary>
/// <param name="serializer"></param>
void Load(IInSerializer serializer);
}
And the type specifically for files:
/// <summary>
/// Implementation of <see cref="IStorageMedium"/> which stores into a file
/// </summary>
public class FileStorageMedium : IStorageMedium
{
private readonly string _fileName;
public FileStorageMedium(string fileName)
{
_fileName = fileName;
}
public void Save(IOutSerializer serializer)
{
using (var stream = new FileStream(_fileName, FileMode.Truncate))
{
using (var writer = new StreamWriter(stream))
{
serializer.Save(writer);
}
}
}
public void Load(IInSerializer serializer)
{
using (var stream = new FileStream(_fileName, FileMode.Open))
{
using (var reader = new StreamReader(stream))
{
serializer.Load(reader);
}
}
}
}
As you can see in each layer I want to follow best practices and make sure each method closes and flushes the stream for the caller and not leave things open for the sake of unit testing (I know I could probably change the code to not close the streams, but I don't think that is appropriate).
So, now, using the ideas I've found on the forums to not have anything tied specifically to file streams to help with unit testing, I'm still running into problems finding the best way to unit test this. Here is the unit test I am trying to write:
[TestClass]
public class MetroLine
{
[TestMethod]
public void TestSerialize()
{
var serializer = new MetroLineJSONSerializationStrategy();
serializer.Source = new MetroLineDetails("A", Colors.Blue, "LA Union Station", "San Bernardino", true, true);
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
serializer.Save(writer);
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream))
{
var text = reader.ReadToEnd();
}
}
}
}
The stream is closed no matter what I do in the serializer.Save() call since that method uses a disposable which closes the stream (as I believe it should to prevent leaks). The problem is, I can no longer unit test the stream in any way to test whether any of this works. I get exceptions thrown saying you cannot access closed streams anymore, which makes sense. But how can I test the contents of my stream in any meaningful way?
I found GetBuffer on the MemoryStream which allows me to convert the raw buffer into a string and I can unit test the actual JSON blob however I want... here is what I wrote:
[TestMethod]
public void TestSerialize()
{
var serializer = new MetroLineJSONSerializationStrategy();
serializer.Source = new MetroLineDetails("Inland Empire Line", Colors.Blue, 'A', "LA Union Station", "San Bernardino", true, true);
using (var stream = new MemoryStream())
{
using (var writer = new StreamWriter(stream))
{
serializer.Save(writer);
}
var bytes = stream.GetBuffer();
var json = System.Text.Encoding.UTF8.GetString(bytes);
Assert.AreEqual('{', json[0]);
}
}
My hopes are someone will find this useful!
I have recently updated PostSharp to v4.1.13 and I have started receiving this error when I try to build my solution:
The custom attribute 'True.Kentico.Caching.KenticoCacheAttribute' constructor threw the exception EntryPointNotFoundException: Entry point was not found.
This attribute implements caching, and has worked before, so I'm wondering what would be the cause of it breaking. I am using this attribute in other assemblies referencing the aspect's containing assembly, and I get that error in there as well.
The following is the code for the attribute. Apologies, it contains a number of constructors.
[Serializable]
public class KenticoCacheAttribute : MethodInterceptionAspect
{
public int CacheMinutes { get; set; }
/// <summary>
/// the string value of the cache dependency key. If it uses parameters from the method, include a {0} to format with the method parameter
/// </summary>
public string CacheDependency { get; set; }
public string[] CacheDependencyStrings { get; set; }
public KenticoCacheDependencyObtainFrom ObtainCacheDependencyFrom { get; set; }
/// <summary>
/// whether caching is enabled - default from app settings
/// </summary>
public bool CacheEnabled { get; set; }
/// <summary>
/// this is the index of the parameter that will be used to format the cachedependency, if required
/// </summary>
public int CacheDependencyParameterIndex { get; set; }
public string CacheDependencyObjectProperty { get; set; }
private string _methodName;
/// <summary>
/// initializes the cache attribute to use a static dependency
/// </summary>
/// <param name="cacheDependency"></param>
public KenticoCacheAttribute(string cacheDependency)
{
CacheMinutes = EngineContext.Current.Resolve<AppSettings>().Cache.TTL.GlobalSetting;
CacheEnabled = !EngineContext.Current.Resolve<AppSettings>().Cache.IgnoreCache;
CacheDependencyParameterIndex = -1;
CacheDependency = cacheDependency;
if (String.IsNullOrEmpty(cacheDependency))
ObtainCacheDependencyFrom = KenticoCacheDependencyObtainFrom.NoDependency;
else
{
ObtainCacheDependencyFrom = KenticoCacheDependencyObtainFrom.Static;
}
}
/// <summary>
/// initializes the cache attribute to use a static dependency using multiple depndencies
/// </summary>
/// <param name="cacheDependencystrings">an array of strings</param>
public KenticoCacheAttribute(string[] cacheDependencystrings)
{
CacheMinutes = EngineContext.Current.Resolve<AppSettings>().Cache.TTL.GlobalSetting;
CacheEnabled = !EngineContext.Current.Resolve<AppSettings>().Cache.IgnoreCache;
CacheDependencyParameterIndex = -1;
CacheDependencyStrings = cacheDependencystrings;
ObtainCacheDependencyFrom = KenticoCacheDependencyObtainFrom.MultipleDependenciesStatic;
}
/// <summary>
/// initializes the cache attribute to take the dependency from the input parameter
/// </summary>
/// <param name="cacheDependency">the static string to be used for the cache dependency. you may include {CurrentSiteName} to be replaced with the current site name, and {0} to be replaced with the value passed in as one of the method parameters</param>
/// <param name="cacheDependencyParameterIndex">the index of the parameter in the method parameters that will be used to create the cache dependency key</param>
public KenticoCacheAttribute(string cacheDependency, int cacheDependencyParameterIndex)
{
CacheMinutes = EngineContext.Current.Resolve<AppSettings>().Cache.TTL.GlobalSetting;
CacheEnabled = !EngineContext.Current.Resolve<AppSettings>().Cache.IgnoreCache;
CacheDependencyParameterIndex = cacheDependencyParameterIndex;
CacheDependency = cacheDependency;
if (String.IsNullOrEmpty(cacheDependency))
{
ObtainCacheDependencyFrom = KenticoCacheDependencyObtainFrom.MultipleDependenciesFromParameter;
}
else
{
ObtainCacheDependencyFrom = KenticoCacheDependencyObtainFrom.FromMethodParameter;
}
}
/// <summary>
/// initialize the cache attribute to obtain the cache dependency from the named property of the return object
/// </summary>
/// <param name="cacheDependency">the static string to be used for the cache dependency. you may include {CurrentSiteName} to be replaced with the current site name, and {0} to be replaced with the value in the named parameter </param>
/// <param name="cacheDependencyObjectPropertyName">the name of the property of the return object that will be used to replace the placeholder in the static string to build the cache dependency string</param>
public KenticoCacheAttribute(string cacheDependency, string cacheDependencyObjectPropertyName)
{
CacheMinutes = EngineContext.Current.Resolve<AppSettings>().Cache.TTL.GlobalSetting;
CacheEnabled = !EngineContext.Current.Resolve<AppSettings>().Cache.IgnoreCache;
CacheDependencyParameterIndex = -1;
CacheDependency = cacheDependency;
ObtainCacheDependencyFrom = KenticoCacheDependencyObtainFrom.FromReturnObject;
CacheDependencyObjectProperty = cacheDependencyObjectPropertyName;
}
/// <summary>
/// initialize the cache attribute to obtain the cache dependency from the named property of the return object
/// </summary>
/// <param name="cacheDependency">the static string to be used for the cache dependency. you may include {CurrentSiteName} to be replaced with the current site name, and {0} to be replaced with the value in the named parameter </param>
/// <param name="cacheDependencyParameterIndex"></param>
/// <param name="cacheDependencyObjectPropertyName">the name of the property of the return object that will be used to replace the placeholder in the static string to build the cache dependency string</param>
public KenticoCacheAttribute(string cacheDependency, int cacheDependencyParameterIndex, string cacheDependencyObjectPropertyName)
{
CacheMinutes = EngineContext.Current.Resolve<AppSettings>().Cache.TTL.GlobalSetting;
CacheEnabled = !EngineContext.Current.Resolve<AppSettings>().Cache.IgnoreCache;
CacheDependencyParameterIndex = cacheDependencyParameterIndex;
CacheDependency = cacheDependency;
ObtainCacheDependencyFrom = KenticoCacheDependencyObtainFrom.FromMethodParameterObjectProperty;
CacheDependencyObjectProperty = cacheDependencyObjectPropertyName;
}
public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
{
_methodName = method.Name;
}
public override void OnInvoke(MethodInterceptionArgs args)
{
var methodInfo = args.Method as MethodInfo;
if (methodInfo != null && (methodInfo.ReturnType != typeof(void) && CacheEnabled))
{
var cacheKey = BuildCacheKey(args.Arguments);
var cacheSettings = new CacheSettings(CacheMinutes, cacheKey);
var data = CacheHelper.Cache(cs => GetData(cs, args), cacheSettings);
args.ReturnValue = data;
}
else
base.OnInvoke(args);
}
private object GetData(CacheSettings cs, MethodInterceptionArgs args)
{
var data = args.Invoke(args.Arguments);
// Checks whether data was loaded and whether the data should be cached (based on the CacheSettings)
if ((data != null) && cs.Cached)
{
// Sets a cache dependency for the data
// The data is removed from the cache if the objects represented by the dummy key are modified (all user objects in this case)
var dependencyResolver = CacheDependencyFactory.GetDependecyFormatter(ObtainCacheDependencyFrom);
var dependencyString = dependencyResolver.Format(new CacheDependencyFormatParameters()
{
CacheDependencyBase = CacheDependency,
CacheDependencybaseString = CacheDependencyStrings,
InputParameterIndex = CacheDependencyParameterIndex,
ReturnParameterName = CacheDependencyObjectProperty,
InputParameterData = args,
ReturnParameterData = data
});
cs.CacheDependency = CacheHelper.GetCacheDependency(dependencyString);
}
return data;
}
private string BuildCacheKey(Arguments arguments)
{
var sb = new StringBuilder();
sb.Append(_methodName);
foreach (var argument in arguments.ToArray())
{
sb.Append(argument == null ? "_" : argument.ToString());
}
sb.Append(String.Format("{0}_{1}", SiteContext.CurrentSiteName,
SiteContext.CurrentSite.DefaultVisitorCulture));
return sb.ToString();
}
}
Any help would be appreciated!
So it turns out that the issue is with these lines of code in the constructors:
CacheMinutes = EngineContext.Current.Resolve<AppSettings>().Cache.TTL.GlobalSetting;
CacheEnabled = !EngineContext.Current.Resolve<AppSettings>().Cache.IgnoreCache;
When PostSharp runs, The dependencyInjection framework has not yet been initialised, so the execution fails. It's strange that it was working before, but hey. I moved this code into the OnInvoke method, which should only run when the DI Enginecontext has been initialised :
public override void OnInvoke(MethodInterceptionArgs args)
{
CacheMinutes = EngineContext.Current.Resolve<AppSettings>().Cache.TTL.GlobalSetting;
CacheEnabled = !EngineContext.Current.Resolve<AppSettings>().Cache.IgnoreCache;
var methodInfo = args.Method as MethodInfo;
......
I'm working on a client-server system at the moment, and I'm trying to get a collection to synchronise across a websocket. Everything is in C# + .Net 4.5, and I was wondering if there was a particular best practise for synchronising data over a websocket. It's a one way sync:
Server: BindingCollection< MyClass > ----- Websocket -----> Client: BindingCollection< MyClass >
The collection could be up to 1000 objects with 20 fields each so sending the whole lot each time seems a little wasteful.
I would use a observer pattern and only send the changed object to be synced.
So I finally took the time to write a small example.
I am using a in-memory generic repository that invokes events on changes. The changes is then sent to all clients so that you do not have to send the complete list/collection.
A simple model to monitor
using System;
namespace SynchronizingCollection.Common.Model
{
public class MyModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
A Generic Repository
Notice the event OnChange that is called when something is added/updated/removed. The event is "subscribed" to in a XSockets long running controller (a singleton) See the "RepoMonitor" class
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// A static generic thread-safe repository for in-memory storage
/// </summary>
/// <typeparam name="TK">Key Type</typeparam>
/// <typeparam name="T">Value Type</typeparam>
public static class Repository<TK, T>
{
/// <summary>
/// When something changes
/// </summary>
public static event EventHandler<OnChangedArgs<TK,T>> OnChange;
private static ConcurrentDictionary<TK, T> Container { get; set; }
static Repository()
{
Container = new ConcurrentDictionary<TK, T>();
}
/// <summary>
/// Adds or updates the entity T with key TK
/// </summary>
/// <param name="key"></param>
/// <param name="entity"></param>
/// <returns></returns>
public static T AddOrUpdate(TK key, T entity)
{
var obj = Container.AddOrUpdate(key, entity, (s, o) => entity);
if(OnChange != null)
OnChange.Invoke(null,new OnChangedArgs<TK, T>(){Key = key,Value = entity, Operation = Operation.AddUpdate});
return obj;
}
/// <summary>
/// Removes the entity T with key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Remove(TK key)
{
T entity;
var result = Container.TryRemove(key, out entity);
if (result)
{
if (OnChange != null)
OnChange.Invoke(null, new OnChangedArgs<TK, T>() { Key = key, Value = entity, Operation = Operation.Remove});
}
return result;
}
/// <summary>
/// Removes all entities matching the expression f
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static int Remove(Func<T, bool> f)
{
return FindWithKeys(f).Count(o => Remove(o.Key));
}
/// <summary>
/// Find all entities T matching the expression f
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static IEnumerable<T> Find(Func<T, bool> f)
{
return Container.Values.Where(f);
}
/// <summary>
/// Find all entities T matching the expression f and returns a Dictionary TK,T
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static IDictionary<TK, T> FindWithKeys(Func<T, bool> f)
{
var y = from x in Container
where f.Invoke(x.Value)
select x;
return y.ToDictionary(x => x.Key, x => x.Value);
}
/// <summary>
/// Returns all entities as a Dictionary TK,T
/// </summary>
/// <returns></returns>
public static IDictionary<TK, T> GetAllWithKeys()
{
return Container;
}
/// <summary>
/// Returns all entities T from the repository
/// </summary>
/// <returns></returns>
public static IEnumerable<T> GetAll()
{
return Container.Values;
}
/// <summary>
/// Get a single entity T with the key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static T GetById(TK key)
{
return Container.ContainsKey(key) ? Container[key] : default(T);
}
/// <summary>
/// Get a single entity T as a KeyValuePair TK,T with the key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static KeyValuePair<TK, T> GetByIdWithKey(TK key)
{
return Container.ContainsKey(key) ? new KeyValuePair<TK, T>(key, Container[key]) : new KeyValuePair<TK, T>(key, default(T));
}
/// <summary>
/// Checks if the repository has a key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool ContainsKey(TK key)
{
return Container.ContainsKey(key);
}
}
}
Event argument and an enum to know what change just happend
using System;
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// To send changes in the repo
/// </summary>
/// <typeparam name="TK"></typeparam>
/// <typeparam name="T"></typeparam>
public class OnChangedArgs<TK,T> : EventArgs
{
public Operation Operation { get; set; }
public TK Key { get; set; }
public T Value { get; set; }
}
}
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// What kind of change was performed
/// </summary>
public enum Operation
{
AddUpdate,
Remove
}
}
The Controller that send changes to the clients...
using System;
using SynchronizingCollection.Common.Model;
using SynchronizingCollection.Server.Repository;
using XSockets.Core.XSocket;
using XSockets.Core.XSocket.Helpers;
using XSockets.Plugin.Framework;
using XSockets.Plugin.Framework.Attributes;
namespace SynchronizingCollection.Server
{
/// <summary>
/// Long running controller that will send information to clients about the collection changes
/// </summary>
[XSocketMetadata(PluginRange = PluginRange.Internal, PluginAlias = "RepoMonitor")]
public class RepositoryMonitor : XSocketController
{
public RepositoryMonitor()
{
Repository<Guid, MyModel>.OnChange += RepositoryOnChanged;
}
private void RepositoryOnChanged(object sender, OnChangedArgs<Guid, MyModel> e)
{
switch (e.Operation)
{
case Operation.Remove:
this.InvokeTo<Demo>(p => p.SendUpdates, e.Value,"removed");
break;
case Operation.AddUpdate:
this.InvokeTo<Demo>(p => p.SendUpdates, e.Value, "addorupdated");
break;
}
}
}
}
The XSockets controller that clients call to add/remove/update the collection.
using System;
using SynchronizingCollection.Common.Model;
using SynchronizingCollection.Server.Repository;
using XSockets.Core.XSocket;
namespace SynchronizingCollection.Server
{
public class Demo : XSocketController
{
public bool SendUpdates { get; set; }
public Demo()
{
//By default all clients get updates
SendUpdates = true;
}
public void AddOrUpdateModel(MyModel model)
{
Repository<Guid, MyModel>.AddOrUpdate(model.Id, model);
}
public void RemoveModel(MyModel model)
{
Repository<Guid, MyModel>.Remove(model.Id);
}
}
}
And a demo client in C# that adds and removed 10 different objects... But it would be easy to use the JavaScript API as well. Especially with knockoutjs for manipulating the collection on the client.
using System;
using System.Threading;
using SynchronizingCollection.Common.Model;
using XSockets.Client40;
namespace SynchronizingCollection.Client
{
class Program
{
static void Main(string[] args)
{
var c = new XSocketClient("ws://127.0.0.1:4502","http://localhost","demo");
c.Controller("demo").OnOpen += (sender, connectArgs) => Console.WriteLine("Demo OPEN");
c.Controller("demo").On<MyModel>("addorupdated", model => Console.WriteLine("Updated " + model.Name));
c.Controller("demo").On<MyModel>("removed", model => Console.WriteLine("Removed " + model.Name));
c.Open();
for (var i = 0; i < 10; i++)
{
var m = new MyModel() {Id = Guid.NewGuid(), Name = "Person Nr" + i, Age = i};
c.Controller("demo").Invoke("AddOrUpdateModel", m);
Thread.Sleep(2000);
c.Controller("demo").Invoke("RemoveModel", m);
Thread.Sleep(2000);
}
Console.ReadLine();
}
}
}
You can download the project from my dropbox: https://www.dropbox.com/s/5ljbedovx6ufkww/SynchronizingCollection.zip?dl=0
Regards
Uffe
I've have a a abstract Base class
public abstract class absTerminalStrategy
{
//....
}
and two child class lets call them Class A and Class B
Class A
{
//Some Params
public A (/*Some Params*/)
}
Class B
{
//Some Params
Public B (/*Some Params*/)
}
in "upper" layer class I'll call Class Control
I've those objects
{
//..
public static absTerminalStrategy terminal = null;
/// <summary>
/// stores all instencese of A_tarategy to be used --> string A_IP
/// </summary>
public static Dictionary<string, A> terminal_A_Dictionary = new Dictionary<string, A_Strategy>();
/// <summary>
/// stores all instencese of B_Scritping to be used --> string B_Port
/// </summary>
public static Dictionary<string, B> terminal_B_Dictionary = new Dictionary<string, B_trategy>();
//..
}
Now in run time I add some instances to the dictionaries and later I need to "jump" between
various instances using this method
public static bool Terminalset(string terminalName)
{
string InterfaceType = terminalName.Split(':')[0];//cutting the root name for switch
switch(InterfaceType)
{
case "A":
{
A New_A =null;
if (terminal_A_Dictionary.TryGetValue(terminalName, out New_A))//return bool
terminal = New_A;
else return false;
}
break;
case "B":
{
B New_B =null;
if (terminal_B_Dictionary.TryGetValue(terminalName, out New_B))//return bool
terminal = New_B;
else return false;
}
break;
}
return true;
}
My problem is , when I change between class A to class B everything works fine
when I change between instances of class A using the dictionary it seems to work fine
But when I do the same with Class B I doesn't work and stays on it last property to entered to he dictionary (the last new instance to entered)
what can be the problem ?
Also this is how add to the dictionary
public static bool TermialCheckCreate (TerminalType terminalType , GlobalParam.A a = null , GlobalParam.B b= null)
{
switch (terminalType)
{
case TerminalType.A:
{
if (terminal_A_Dictionary.ContainsKey(string.Format("A:{0}", _A_Param.AIp))) break;
if (a == null) return false;
A_Strategy terminal = new A_Strategy(_A_Param.AIp, GlobalParam.A_PORT, 60);
terminalTelnetDictionary.Add(string.Format("A:{0}",_A_Param.AIp), terminal);
}
break;
case TerminalType.B:
{
if (terminal_B_Dictionary.ContainsKey(string.Format("B:{0}", _B_Param.Bcom))) break;
if (b == null) return false;
B_strategy terminal = new B_Strategy(GlobalParam.AppDirectory, _B_Param.BCom, _B_Param.BRate);
terminal_B_Dictionary.Add(string.Format("B:{0}",_B_Param.BCom), terminal);
}
break;
}
return true;
}
EDIT small corrections
copy of the classes involved
class to control all the dictionaries
public enum TerminalType {serial , Telent };
public static class TerminalControl
{
/// <summary>
/// stores all instencese of TelnetStarategy to be used --> string telnetIP
/// </summary>
public static Dictionary<string, TelnetStrategy> terminalTelnetDictionary = new Dictionary<string, TelnetStrategy>();
/// <summary>
/// stores all instencese of SerialScritping to be used --> string SerailPort
/// </summary>
public static Dictionary<string, SerialStrategy> terminalSerialDictionary = new Dictionary<string, SerialStrategy>();
/// <summary>
/// abstract instance , chooses between serial and telent
/// </summary>
public static absTerminalStrategy terminal = null;
/// <summary>
/// static constructor
/// </summary>
static TerminalControl()
{
}
/// <summary>
/// Inherits from serial/telnet strategy will run this class of commands
/// </summary>
/// <param name="terminalType"></param>
/// <param name="TelnetIP"></param>
/// <param name="SerialPort"></param>
/// <param name="SerialBaudRate"></param>
/// <param name="Command"></param>
/// <returns></returns>
public static List<string> TerminalSendAndWaitForList(string Command,string terminalName,string fullpathLog="")
{
if (!Terminalset(terminalName)) return new List<string>(new string[] { "ERROR : Device Not Found !!! Check it and add" });
return terminal.SendAndWaitForList(Command);
}
public static bool Terminalset(string terminalName)
{
string InterfaceType = terminalName.Split(':')[0];
switch(InterfaceType)
{
case "Telnet":
{
TelnetStrategy NewTelnet =null;
terminal = NewTelnet;
if (terminalTelnetDictionary.TryGetValue(terminalName, out NewTelnet))//return bool
terminal = NewTelnet;
else return false;
}
break;
case "Serial":
{
SerialStrategy NewSerial =null;
terminal = NewSerial;
if (terminalSerialDictionary.TryGetValue(terminalName, out NewSerial))//return bool
terminal = NewSerial;
else return false;
}
break;
}
return true;
}
/// <summary>
/// added new terminal to inner dictionary
/// </summary>
/// <param name="terminalType"></param>
/// <param name="telentParam"></param>
/// <param name="serialParam"></param>
/// <returns></returns>
public static bool TermialCheckCreate (TerminalType terminalType , GlobalParam.TelentParams telentParam = null , GlobalParam.SerialParams serialParam= null)
{
switch (terminalType)
{
case TerminalType.Telent:
{
if (terminalTelnetDictionary.ContainsKey(string.Format("Telnet:{0}", telentParam.telnetIp))) break;
if (telentParam == null) return false;
TelnetStrategy terminal = new TelnetStrategy(telentParam.telnetIp, GlobalParam.TELNET_PORT, 60);
terminalTelnetDictionary.Add(string.Format("Telnet:{0}",telentParam.telnetIp), terminal);
}
break;
case TerminalType.serial:
{
if (terminalSerialDictionary.ContainsKey(string.Format("Serial:{0}", serialParam.SerialCom))) break;
if (serialParam == null) return false;
SerialStrategy terminal = new SerialStrategy(GlobalParam.AppDirectory, serialParam.SerialCom, serialParam.SerialBaudRate);
terminalSerialDictionary.Add(string.Format("Serial:{0}",serialParam.SerialCom), terminal);
}
break;
}
return true;
}
}
abstract base and child showing only there names and constructors (if needed i"ll publish the entire code....)
public abstract class absTerminalStrategy
{
public abstract List<string> SendAndWaitForList(string Command);
public abstract bool WaitForOutPut(string Blocker, int secTimeOut);//implement Wait for Output string before releasing lock (Monitor.wait/Pulse)
}
public class SerialStrategy : absTerminalStrategy
{
public ScriptingSerial serailAgent = null;//Infrastructure
public SerialStrategy(string fullPathLog , string PortName , int Baudrate)
{
serailAgent = new ScriptingSerial(fullPathLog, PortName, Baudrate);
}
//....
}
public class TelnetStrategy : absTerminalStrategy
{
public static event SerialDataInput_EventHandler onDataInput;
public static ScriptingTelnet telnetAgent = null;//Infrastructure
public string TelnetIp = string.Empty;
public TelnetStrategy(string Ip, int Port, int CommandTimeOut)
{
TelnetIp = Ip;
int port = Port;
telnetAgent = new ScriptingTelnet(Ip, port, CommandTimeOut);
}
}
I have been reading on how to compare a list with one annother. I have tried to implement the IEquatable interface. Here is what i have done so far:
/// <summary>
/// A object holder that contains a service and its current failcount
/// </summary>
public class ServiceHolder : IEquatable<ServiceHolder>
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="service"></param>
public ServiceHolder(Service service)
{
Service = service;
CurrentFailCount = 0;
}
public Service Service { get; set; }
public UInt16 CurrentFailCount { get; set; }
/// <summary>
/// Public equal method
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
ServiceHolder tmp = obj as ServiceHolder;
if (tmp == null)
{
return false;
}
else
{
return Equals(tmp);
}
}
/// <summary>
/// Checks the internal components compared to one annother
/// </summary>
/// <param name="serviceHolder"></param>
/// <returns>tru eif they are the same else false</returns>
public bool Equals(ServiceHolder serviceHolder)
{
if (serviceHolder == null)
{
return false;
}
if (this.Service.Id == serviceHolder.Service.Id)
{
if (this.Service.IpAddress == serviceHolder.Service.IpAddress)
{
if (this.Service.Port == serviceHolder.Service.Port)
{
if (this.Service.PollInterval == serviceHolder.Service.PollInterval)
{
if (this.Service.ServiceType == serviceHolder.Service.ServiceType)
{
if (this.Service.Location == serviceHolder.Service.Location)
{
if (this.Service.Name == this.Service.Name)
{
return true;
}
}
}
}
}
}
}
return false;
}
}
and this is where I use it:
private void CheckIfServicesHaveChangedEvent()
{
IList<ServiceHolder> tmp;
using (var db = new EFServiceRepository())
{
tmp = GetServiceHolders(db.GetAll());
}
if (tmp.Equals(Services))
{
StateChanged = true;
}
else
{
StateChanged = false;
}
}
Now when I debug and I put a break point in the equals function it never gets hit.
This leads me to think I have implemented it incorrectly or Im not calling it correctly?
If you want to compare the contents of two lists then the best method is SequenceEqual.
if (tmp.SequenceEquals(Services))
This will compare the contents of both lists using equality semantics on the values in the list. In this case the element type is ServiceHolder and as you've already defined equality semantics for this type it should work just fine
EDIT
OP commented that order of the collections shouldn't matter. For that scenario you can do the following
if (!tmp.Except(Services).Any())
You can compare lists without the order most easily with linq.
List<ServiceHolder> result = tmp.Except(Services).ToList();