NHinernate - Serializing Criteria to json and back. for a web interface usage - nhibernate

I have a web interface with a lot of data grids that allow the user passing a search criteria to the server.
I don't want to actually build an explicit method that will handle each grid individually, so I thought to allow passing JSON criteria from the client to the server.
This code
string res = JsonConvert.SerializeObject(Restrictions.Eq("id", "1"));
will return will return {"PropertyName":"id","Value":"1"} which is exactly what I wanted to pass from the client - perfect.
but this code with or statement:
string res = JsonConvert.SerializeObject(Restrictions.Or(Restrictions.Eq("id", "1"),Restrictions.Eq("id", "2")))
will return {}
What is the way to convert JSON into a Criteria and back in nHibernate.
To be more accurate, I have something like this in the client, and I want my server to support it. I can serialize the client ui to json.
Thanks

The empty json serialization happens because newtonsoft can only serialize public fields.
The code below will serialize all fields of a class regardless of its visibility.
public class MyContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
...
var myOrs = Restrictions.Or(Restrictions.Eq("id", "1"), Restrictions.Eq("id", "2"));
var settings = new JsonSerializerSettings()
{
ContractResolver = new MyContractResolver()
};
string json = JsonConvert.SerializeObject(myOrs, settings);
Now to deserialize this json we have a problem, the NHibernate classes are either abstract or don't have a public empty constructor.
You will need to create a custom class to deserialize.
public class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not implemented yet");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (obj.Value<string>("Op") == "or")
{
var leftJObject = obj["LeftHandSide"];
var rightJObject = obj["RightHandSide"];
var left = new SimpleExpression(leftJObject.Value<string>("PropertyName"), GetJTokenValue(leftJObject["Value"]), leftJObject.Value<string>("Op"));
var right = new SimpleExpression(rightJObject.Value<string>("PropertyName"), GetJTokenValue(rightJObject["Value"]), rightJObject.Value<string>("Op"));
return Restrictions.Or(left, right);
}
//TODO: The rest of the restrictions
return null;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return true;
}
private object GetJTokenValue(JToken token)
{
if (token.Type == JTokenType.String)
{
return token.Value<string>();
}
else if (token.Type == JTokenType.Integer)
{
return token.Value<Int32>();
}
//TODO: The rest of the types
return null;
}
}
...
var result = JsonConvert.DeserializeObject<AbstractCriterion>(json, new MyConverter());

Related

How can I bind a comma separated list in a URL to an array of objects?

I have a class named VerseRangeReference that has the properties Chapter, FirstVerse and LastVerse.
I have decorated it with a TypeConverterAttribute [TypeConverter(typeof(VerseRangeReferenceConverter))]
I have an action on a controller like this
public Task<ViewResult> Verses(VerseRangeReference[] verses)
But the value of verses is always a single element with the value null. Here is my type converter
public class VerseRangeReferenceConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.GetType() == typeof(string))
{
string source = (string)value;
return VerseRangeReference.ParseMultiple(source);
}
return null;
}
}
The result of VerseRangeReference.ParseMultiple(source) is a valid array of instances of VerseRange.
I had to implement a custom model binder. If someone can think of a way to do this with a TypeConverter then I will accept that answer instead because model binders are more complicated.
public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult != ValueProviderResult.None)
{
VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
}
return Task.CompletedTask;
}
}
public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
return null;
}
}
This must be registered.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You can use a type converter to bind a comma separated string to a sequence of values. However, the type converter should convert from the string to the sequence directly. This means that the type converter should be configured for something like IEnumerable<T> or T[]. To simplify matters I will continue my explanation for IEnumerable<int> but if you want to use arrays instead you should just make sure that the type converter converts to an array instead of something that implements IEnumerable<T>.
You can configure a type converter for IEnumerable<int> using TypeDescriptor.AddAttributes:
TypeDescriptor.AddAttributes(
typeof(IEnumerable<int>),
new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));
This configures EnumerableIntTypeConverter as a type converter that can convert IEnumerable<int>.
This call has to be made when the process starts and in the case of ASP.NET Core this can conveniently be done in the Startup.Configure method.
Here is the EnumerableIntTypeConverter that converts the comma separated string of numbers to a list of ints:
internal class EnumerableIntTypeConverter : TypeConverter
{
private const char Separator = ',';
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string #string))
throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");
if (#string.Length == 0)
return Enumerable.Empty<int>();
var numbers = new List<int>();
var start = 0;
var end = GetEnd(#string, start);
while (true)
{
if (!int.TryParse(
#string.AsSpan(start, end - start),
NumberStyles.AllowLeadingSign,
culture,
out var number))
throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
numbers.Add(number);
if (end == #string.Length)
break;
start = end + 1;
end = GetEnd(#string, start);
}
return numbers;
}
private static int GetEnd(string #string, int start)
{
var end = #string.IndexOf(Separator, start);
return end >= 0 ? end : #string.Length;
}
}
The parsing uses System.Memory to avoid allocating a new string for each number in the list. If your framework doesn't have the int.TryParse overload that accepts a Span<char> you can use string.Substring instead.

Issue with Web Api Custom Model Binder in MVC4

I am using Mvc4 with WebApi.
I am using Dto objects for the webApi.
I am having enum as below.
public enum Status
{
[FlexinumDefault]
Unknown = -1,
Active = 0,
Inactive = 100,
}
Dto structure is as follows.
[DataContract]
public class abc()
{
[DataMemebr]
[Required]
int Id{get;set;}
[DataMember]
[Required]
Status status{get;set}
}
I have created Custom Model Binder which will validate the enum(status) property in the dto object and return false if the enum value is not passed.
if the status enum property is not passed in the dto object,we should throw exception
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (input != null && !string.IsNullOrEmpty(input.AttemptedValue))
{
if (bindingContext.ModelType == typeof(Enum))
{
//var actualValue = null;
var value = input.RawValue;
in the api controller,i have action method like
public void Create([FromUri(BinderType = typeof(EnumCustomModelBinder))]abcdto abc)
{
In global.asax.cs
i have set like
GlobalConfiguration.Configuration.BindParameter(typeof(Enum), new EnumCustomModelBinder());
the issue i am facing is the custombinder
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
,the input value is coming as null.
Please sugggest
I found the solution
This works fine,but the default implementation of model binder is missing.
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var json = actionContext.Request.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(json))
{
var jsonObject = (JObject) Newtonsoft.Json.JsonConvert.DeserializeObject(json);
var jsonPropertyNames = jsonObject.Properties().Select(p => p.Name).ToList();
var requiredProperties = bindingContext.ModelType.GetProperties().Where(p =>p.GetCustomAttributes(typeof(RequiredAttribute),
false).Any()).ToList();
var missingProperties = requiredProperties.Where(bindingProperty => !jsonPropertyNames.Contains(bindingProperty.Name)).ToList();
if (missingProperties.Count > 0)
{
missingProperties.ForEach(
prop =>
{
if (prop.PropertyType.IsEnum)
actionContext.ModelState.AddModelError(prop.Name, prop.Name + " is Required");
});
}
var nullProperties = requiredProperties.Except(missingProperties).ToList();
if (nullProperties.Count > 0)
{
nullProperties.ForEach(p =>
{
var jsonvalue = JObject.Parse(json);
var value = (JValue)jsonvalue[p.Name];
if (value.Value == null)
{
actionContext.ModelState.AddModelError(p.Name, p.Name + " is Required");
}
});
}
}
// Now we can try to eval the object's properties using reflection.
return true;
}

Web API Help pages - customizing Property documentation

I have my web api and I added the web api help pages to auto-generate my documentation. It's working great for methods where my parameters are listed out, but I have a method like this:
public SessionResult PostLogin(CreateSessionCommand request)
And, on my help page, it is only listing the command parameter in the properties section. However, in the sample request section, it lists out all of the properties of my CreateSessionCommand class.
Parameters
Name | Description | Additional information
request | No documentation available. | Define this parameter in the request body.
I would like it instead to list all of the properties in my CreateSessionCommand class. Is there an easy way to do this?
So, I managed to devise a workaround for this problem, in case anyone is interested.
In HelpPageConfigurationExtensions.cs I added the following extension method:
public static void AlterApiDescription(this ApiDescription apiDescription, HttpConfiguration config)
{
var docProvider = config.Services.GetDocumentationProvider();
var addParams = new List<ApiParameterDescription>();
var removeParams = new List<ApiParameterDescription>();
foreach (var param in apiDescription.ParameterDescriptions)
{
var type = param.ParameterDescriptor.ParameterType;
//string is some special case that is not a primitive type
//also, compare by full name because the type returned does not seem to match the types generated by typeof
bool isPrimitive = type.IsPrimitive || String.Compare(type.FullName, typeof(string).FullName) == 0;
if (!isPrimitive)
{
var properties = from p in param.ParameterDescriptor.ParameterType.GetProperties()
let s = p.SetMethod
where s.IsPublic
select p;
foreach (var property in properties)
{
var documentation = docProvider.GetDocumentation(new System.Web.Http.Controllers.ReflectedHttpParameterDescriptor()
{
ActionDescriptor = param.ParameterDescriptor.ActionDescriptor,
ParameterInfo = new CustomParameterInfo(property)
});
addParams.Add(new ApiParameterDescription()
{
Documentation = documentation,
Name = property.Name,
Source = ApiParameterSource.FromBody,
ParameterDescriptor = param.ParameterDescriptor
});
}
//since this is a complex type, select it to be removed from the api description
removeParams.Add(param);
}
}
//add in our new items
foreach (var item in addParams)
{
apiDescription.ParameterDescriptions.Add(item);
}
//remove the complex types
foreach (var item in removeParams)
{
apiDescription.ParameterDescriptions.Remove(item);
}
}
And here is the Parameter info instanced class I use
internal class CustomParameterInfo : ParameterInfo
{
public CustomParameterInfo(PropertyInfo prop)
{
base.NameImpl = prop.Name;
}
}
Then, we call the extension in another method inside the extensions class
public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId)
{
object model;
string modelId = ApiModelPrefix + apiDescriptionId;
if (!config.Properties.TryGetValue(modelId, out model))
{
Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions;
ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase));
if (apiDescription != null)
{
apiDescription.AlterApiDescription(config);
HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator();
model = GenerateApiModel(apiDescription, sampleGenerator);
config.Properties.TryAdd(modelId, model);
}
}
return (HelpPageApiModel)model;
}
The comments that are used for this must be added to the controller method and not the properties of the class object. This might be because my object is part of an outside library
this should go as an addition to #Josh answer. If you want not only to list properties from the model class, but also include documentation for each property, Areas/HelpPage/XmlDocumentationProvider.cs file should be modified as follows:
public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
{
ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
if (reflectedParameterDescriptor != null)
{
if (reflectedParameterDescriptor.ParameterInfo is CustomParameterInfo)
{
const string PropertyExpression = "/doc/members/member[#name='P:{0}']";
var pi = (CustomParameterInfo) reflectedParameterDescriptor.ParameterInfo;
string selectExpression = String.Format(CultureInfo.InvariantCulture, PropertyExpression, pi.Prop.DeclaringType.FullName + "." + pi.Prop.Name);
XPathNavigator methodNode = _documentNavigator.SelectSingleNode(selectExpression);
if (methodNode != null)
{
return methodNode.Value.Trim();
}
}
else
{
XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor);
if (methodNode != null)
{
string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName));
if (parameterNode != null)
{
return parameterNode.Value.Trim();
}
}
}
}
return null;
}
and CustomParameterInfo class should keep property info as well:
internal class CustomParameterInfo : ParameterInfo
{
public PropertyInfo Prop { get; private set; }
public CustomParameterInfo(PropertyInfo prop)
{
Prop = prop;
base.NameImpl = prop.Name;
}
}
This is currently not supported out of the box. Following bug is kind of related to that:
http://aspnetwebstack.codeplex.com/workitem/877

How to access property of anonymous type?

Considering this IronPython script
def SensorEvent(d):
print d
print d.Message
... how do I access properties of d?
First line of the SensorEvent method successfully prints
{ Message = blah blubb }
however second line throws an exception:
'<>f_anonymousType[str]' object has no attribute 'Message'
Explanation
d is an instance of an anonymous type provided by an invoke from a C# method. I'm invoking it like this:
public static async void ExecutePyFunc(string name, dynamic data)
{
try
{
var f = strategyScope.GetVariable<Action<object>>(name);
if (f != null)
{
await Task.Run(() => f((object)data));
}
}
catch (Exception x)
{
StaticLog("[Callback Exception] Fehler beim Ausführen einer Python Funktion: {0}", x.Message);
}
}
d is a dictionary. Access it like so:
d['Message']
My solution using DynamicObject: I've introduced a class that converts an anonymous type into a known type by copying its properties via reflection (I don't need anything but the properties but it could probably be enhanced for use with fields, methods, functions as well).
Here's what I've come up with:
public class IronPythonKnownType : DynamicObject
{
public IronPythonKnownType(dynamic obj)
{
var properties = obj.GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
var val = prop.GetValue(obj);
this.Set(prop.Name, val);
}
}
private Dictionary<string, object> _dict = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (_dict.ContainsKey(binder.Name))
{
result = _dict[binder.Name];
return true;
}
return base.TryGetMember(binder, out result);
}
private void Set(string name, object value)
{
_dict[name] = value;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_dict[binder.Name] = value;
return true;
}
}
which effectively converts the anonymous object into something IronPython can handle.
Now I can do that:
def Blubb(a):
print a.Message
without getting the mentioned exception.

deserialize classes which inherit from superclass with Json.NET?

i have a problem and want to know if it is or if it will be possible with json.net
i have a superclass called A and two classes which inherit from it, B1 and B2
when i made a list of type A and then add some B1 and B2, after serialize and deserialize they are all of type A.
Is it possible that each class which inherit will be casted to its former class type like B1 or B2?
Yes, it should work as long as you inform JSON.Net that you want to use TypeNameHandling.All and use appropriate casting. The following works in my project:
public class MembaseJsonSerializer<T>
{
private IContractResolver resolver;
public MembaseJsonSerializer(IContractResolver resolver)
{
this.resolver = resolver;
}
public R FromJson<R>(object json)
{
if (typeof(T).IsAssignableFrom(typeof(R)))
{
object res = JsonConvert.DeserializeObject(json.ToString(), new JsonSerializerSettings() {
ContractResolver = resolver,
TypeNameHandling = TypeNameHandling.All
});
return (R)res;
}
throw new NotImplementedException("Type is not assignable.");
}
public string ToJson(object obj)
{
if (typeof(T).IsAssignableFrom(obj.GetType()))
{
string json = JsonConvert.SerializeObject(obj, Formatting.None, new JsonSerializerSettings() {
ContractResolver = resolver,
TypeNameHandling = TypeNameHandling.All
});
return json;
}
throw new NotImplementedException("Type is not assignable.");
}
}
Where T is the base class and R is the sub class.