MVC Custom Validation String Array Client Side - arrays

There are examples out there of custom MVC validators that take an array parameter, but only server side - none of them show an example of implementing the client side with array parameter.
The problem is instead of outputting the array's contents in the html data- attribute, it outputs "System.String[]":
data-val-total-propertynames="System.String[]"
Here is my attribute class:
public class TotalAttribute : ValidationAttribute, IClientValidatable
{
private String[] PropertyNames { get; set; }
public TotalAttribute(String[] propertyNames)
{
PropertyNames = propertyNames;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
float total = 0;
foreach (var propertyName in PropertyNames)
total += (float)context.ObjectInstance.GetType().GetProperty(propertyName).GetValue(context.ObjectInstance, null);
if (total != (float)value)
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "total",
};
rule.ValidationParameters["propertynames"] = PropertyNames;
yield return rule;
}
}
Here it is implemented in the model:
[Total(new string[] { "SomeOtherField1", "SomeOtherField2" }, ErrorMessage = "'Line12Balance' must equal total of 'SomeOtherField1' and 'SomeOtherField2'")]
public decimal? Line12Balance { get; set; }
Here is the html data-val attribute output:
data-val-total-propertynames="System.String[]"
What am I doing wrong?

You get "System.String[]" string because the value of ValidationParameters["propertynames"] is written by calling ToString on it, so string[] variable returns "System.String[]" in this case. If you need to output specific value you need to format it by yourself in your validation attribute. For example, change
rule.ValidationParameters["propertynames"] = PropertyNames;
to
rule.ValidationParameters["propertynames"] = string.Join(",", PropertyNames);
and you will get
data-val-total-propertynames="SomeOtherField1,SomeOtherField2"

As Alexander mentioned in his answer, the issue you encountered is that string[] doesn't have its own ToString() implementation, and uses the base object.ToString() implementation, which just displays the type of the object rather than the contents of your array.
So you've got to somehow serialize your string array so that it can be stored in a string to then parse in your client-side JavaScript.
If you're already using JSON.Net in your solution (as many are), you can also do this by JSON serializing the array on the server side, then parsing the JSON client-side.
i.e. server-side:
rule.ValidationParameters["propertynames"] = JsonConvert.SerializeObject(PropertyNames);
Client-side: (
$.validator.addMethod('rule', function(value, element, params) {
var propertynames = JSON.parse(params.propertynames);
// do your validation
});
That prevents you from having to escape commas/delimiters in your parameters.

Related

How to deserialize dodgy JSON (with improperly quoted strings, and missing brackets)?

I am having to parse (and ultimately reserialize) some dodgy JSON. it looks like this:
{
name: "xyz",
id: "29573f59-85fb-4d06-9905-01a3acb2cdbd",
status: "astatus",
color: colors["Open"]
},
{
name: "abc",
id: "29573f59-85fb-4d06-9905-01a3acb2cdbd",
status: "astatus",
color: colors["Open"]
}
There are a number of problems here - starting with the most severe.
color: colors["Open"]
WTF even is that? If I drop 'colors' then I can get an array of strings out but I can't tweak to work out of the box.
It is an array without square brackets. I can fix this by wrapping in them. But is there a way to support out of the box?
Properties have no quotes. Deserializing is fine for these.. but reserializing is just no dice.
Any suggestions of handling both in and out of this structure?
Answering your questions #1 - #3 in order:
Json.NET does not support reading dodgy property values in the form colors["Open"] (which, as you correctly note, violates the JSON standard).
Instead, you will need to manually fix these values, e.g. through some sort of Regex:
var regex = new Regex(#"(colors\[)(.*)(\])");
var fixedJsonString = regex.Replace(jsonString,
m => string.Format(#"""{0}{1}{2}""", m.Groups[1].Value, m.Groups[2].Value.Replace("\"", "\\\""), m.Groups[3].Value));
This changes the color property values into properly escaped JSON strings:
color: "colors[\"Open\"]"
Json.NET does, however, have the capability to write dodgy property values by calling JsonWriter.WriteRawValue() from within a custom JsonConverter.
Define the following converter:
public class RawStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (string)value;
writer.WriteRawValue(s);
}
}
Then define your RootObject as follows:
public class RootObject
{
public string name { get; set; }
public string id { get; set; }
public string status { get; set; }
[JsonConverter(typeof(RawStringConverter))]
public string color { get; set; }
}
Then, when re-serialized, you will get the original dodgy values in your JSON.
Support for deserializing comma-delimited JSON without outer brackets will be in the next release of Json.NET after 10.0.3. see Issue 1396 and Issue 1355 for details. You will need to set JsonTextReader.SupportMultipleContent = true to make it work.
In the meantime, as a workaround, you could grab ChainedTextReader and public static TextReader Extensions.Concat(this TextReader first, TextReader second) from the answer to How to string multiple TextReaders together? by Rex M and surround your JSON with brackets [ and ].
Thus you would deserialize your JSON as follows:
List<RootObject> list;
using (var reader = new StringReader("[").Concat(new StringReader(fixedJsonString)).Concat(new StringReader("]")))
using (var jsonReader = new JsonTextReader(reader))
{
list = JsonSerializer.CreateDefault().Deserialize<List<RootObject>>(jsonReader);
}
(Or you could just manually surround your JSON string with [ and ], but I prefer solutions that don't involve copying possibly large strings.)
Re-serializing a root collection without outer braces is possible if you serialize each item individually using its own JsonTextWriter with CloseOutput = false. You can also manually write a , between each serialized item to the underlying TextWriter shared by every JsonTextWriter.
Serializing JSON property names without a surrounding quote character is possible if you set JsonTextWriter.QuoteName = false.
Thus, to re-serialize your List<RootObject> without quoted property names or outer braces, do:
var sb = new StringBuilder();
bool first = true;
using (var textWriter = new StringWriter(sb))
{
foreach (var item in list)
{
if (!first)
{
textWriter.WriteLine(",");
}
first = false;
using (var jsonWriter = new JsonTextWriter(textWriter) { QuoteName = false, Formatting = Formatting.Indented, CloseOutput = false })
{
JsonSerializer.CreateDefault().Serialize(jsonWriter, item);
}
}
}
var reserializedJson = sb.ToString();
Sample .Net fiddle showing all this in action.

Unity C#: how to convert an Array of a class into JSON [duplicate]

This question already has answers here:
Serialize and Deserialize Json and Json Array in Unity
(9 answers)
Closed 5 years ago.
I need to save multiple player's data. I am doing it by making an array of PlayersInfo class and trying to convert the array into JSON. here is my code
PlayerInfo[] allPlayersArray = new PlayerInfo[1];
allPlayersArray[0] = new PlayerInfo();
allPlayersArray[0].playerName = "name 0";
string allPlayersArrayJson = JsonUtility.ToJson(allPlayersArray);
print(allPlayersArrayJson);
PlayerPrefs.SetString("allPlayersArrayJson", allPlayersArrayJson);
string newJson = PlayerPrefs.GetString("allPlayersArrayJson");
print(newJson);
PlayerInfo[] newArray = new PlayerInfo[1];
newArray = JsonUtility.FromJson<PlayerInfo[]>(newJson);
print(newArray[0].playerName);
First two print statements returns "{}" and 3rd one gives null reference error. TIA
Like I said in my comment, there is no direct support. Helper class is needed. This is only reason I am making this answer is because you are still having problems even after reading the link I provided.
Create a new script called JsonHelper. Copy and paste the code below inside it.
using UnityEngine;
using System.Collections;
using System;
public class JsonHelper
{
public static T[] FromJson<T>(string json)
{
Wrapper<T> wrapper = UnityEngine.JsonUtility.FromJson<Wrapper<T>>(json);
return wrapper.Items;
}
public static string ToJson<T>(T[] array)
{
Wrapper<T> wrapper = new Wrapper<T>();
wrapper.Items = array;
return UnityEngine.JsonUtility.ToJson(wrapper);
}
[Serializable]
private class Wrapper<T>
{
public T[] Items;
}
}
The code in your question should now work. All you have to do is to replace all JsonUtility words with JsonHelper. I did that for you below:
void Start()
{
PlayerInfo[] allPlayersArray = new PlayerInfo[1];
allPlayersArray[0] = new PlayerInfo();
allPlayersArray[0].playerName = "name 0";
string allPlayersArrayJson = JsonHelper.ToJson(allPlayersArray);
print(allPlayersArrayJson);
PlayerPrefs.SetString("allPlayersArrayJson", allPlayersArrayJson);
string newJson = PlayerPrefs.GetString("allPlayersArrayJson");
print(newJson);
PlayerInfo[] newArray = new PlayerInfo[1];
newArray = JsonHelper.FromJson<PlayerInfo>(newJson);
print(newArray[0].playerName);
}
Based on the JsonUtility documentation, naked arrays are not supported. Put the array inside a class.
Internally, this method uses the Unity serializer; therefore the
object you pass in must be supported by the serializer: it must be a
MonoBehaviour, ScriptableObject, or plain class/struct with the
Serializable attribute applied.
In general, you'll use this to serialize MonoBehaviour objects, or a custom class/struct with the Serializable attribute.
YES! It is not supported currently but there is always a work around. Use this class :
public static class JsonHelper
{
public static T[] getJsonArray<T>(string json)
{
try
{
string newJson = "{ \"array\": " + json + "}";
Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>> (newJson);
return wrapper.array;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
[Serializable]
private class Wrapper<T>
{
public T[] array = null;
}
}
I would use Json.NET
PlayerInfo[] allPlayersArray = new PlayerInfo[] { p1, p2, p3 };
string json = JsonConvert.SerializeObject(allPlayersArray);
More On SerializeObject
(Code example not yet tested)

mappingMongoConverter.setMapKeyDotReplacement doesnt seem to have any effects

I am trying to convert a custom object that contains a map containing dotted key string value using the latest 1.7.2 spring mongodb.
Setting a dot replacement doesnt seem to do the job. Here's my code :
class FakeUser {
Map<String, String> map = new LinkedHashMap<>();
void addValue(String key, String value) {
this.map.put(key, value);
}
}
FakeUser fakeUser = new FakeUser();
fakeUser.addValue("test.dot.for.key", "test.dot.for.value");
this.mappingMongoConverter.setMapKeyDotReplacement(":");
Object convertedObject = this.mappingMongoConverter.convertToMongoType(fakeUser);
System.out.println("convertedObject: " + convertedObject.getClass() + ":" + convertedObject);
And the output:
convertedObject: class com.mongodb.BasicDBObject:{ "map" : { "test.dot.for.key" : "test.dot.for.value"}}
And i also tried:
class FakeUser {
Map<String, String> map = new LinkedHashMap<>();
void addValue(String key, String value) {
this.map.put(key, value);
}
}
FakeUser fakeUser = new FakeUser();
fakeUser.addValue("test.dot.for.key", "test.dot.for.value");
this.mappingMongoConverter.setMapKeyDotReplacement(":");
BasicDBObject dbo = new BasicDBObject();
this.mappingMongoConverter.write(fakeUser, dbo);
System.out.println("dbo: " + ":" + dbo.toMap());
And with the output of dbo: :{_class=app.security.MyClass$1FakeUser, map={ "test.dot.for.key" : "test.dot.for.value"}}
I was expecting "test.dot.for.key" to become "test:dot:for:key", so what did i do wrong ?
You need make adjustments to the converter in your SpringMongoDBConfig (which should extend AbstractMongoConfiguration) config files, rather than in the class you're trying to serialize/deserialze. If you're using annotation driven settings, you can use a custom bean to set the converter like so:
#Bean
#Override
public MappingMongoConverter mappingMongoConverter() throws Exception
{
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext());
converter.setCustomConversions(customConversions());
//mongo won't accept key values with dots(.) in them, so configure it to store them as :
converter.setMapKeyDotReplacement("\\:");
return converter;
}
Once the converter is setup, it will handle both serialization and deserialization for you automagically.
If you really want to do the conversion in-line (not recommended), you can just use a string replace function to re-write the string like so:
class FakeUser
{
Map<String, String> map = new LinkedHashMap<>();
void addValue(String key, String value) {
key = key.replace(".",":");
this.map.put(key, value);
}
}
Although, if you do it inline you'll likely have issues deserializing when you get it back out of mongo.

Restrict Blocks in ContentArea

I'm having a issue restricting what kind of Block to be inserted in a ContentArea. What I want is that the SliderBlock's ContentArea property can only have insertion of a SlideItemBlock.
[ContentType(...)]
public class SlideItemBlock : BlockData
{
[Required]
Display(Name = "Image")]
public virtual string Image { get; set;}
}
[ContentType(...)]
public class SliderBlock : BlockData
{
[Required]
[Display(Name = "Slides")]
public virtual ContentArea Slides { get; set; }
//Should only accept insertion of SlideItemBlock
}
Or is this the wrong way to achive what I'm trying to restrict for the editor to not drag and drop wrong block types?
As of now, I can create a SliderBlock and insert a SlideItemBlocks in it. If I then insert the created SliderBlock in a new SliderBlock I get a forever and ever loop and It breaks the site. This is what I'm trying to control.
If you´re using EPiServer 7.5 restricting which blocks you can use in a content area is built in. For details take a look at this blog post: Restricting the allowed types in a content area.
Code example from the blog post:
[EditorDescriptorRegistration(TargetType = typeof(ContentArea), UIHint = "Gallery")]
public class ImageGalleryEditorDescriptor : EditorDescriptor
{
public ImageGalleryEditorDescriptor()
{
// Setup the types that are allowed to be dragged and dropped into the content
// area; in this case only images are allowed to be added.
AllowedTypes = new Type[] { typeof(IContentImage) };
// Unfortunetly the ContentAreaEditorDescriptor is located in the CMS module
// and thus can not be inherited from; these settings are copied from that
// descriptor. These settings determine which editor and overlay should be
// used by this property in edit mode.
ClientEditingClass = "epi-cms.contentediting.editors.ContentAreaEditor";
OverlayConfiguration.Add("customType", "epi-cms.widget.overlay.ContentArea");
}
}
As of EpiServer 8 theres a new attribute called [AllowedTypes]. This is now the best way of restricting blocks. It overcomes a lot of the limitations of [AvailableContentTypes]. When you drag blocks into a content area the validation actually works.
An example code snippet would be
[AllowedTypes(new []{ typeof(SlideBlock) })]
public virtual ContentArea Slides { get; set; }
Theres a good code example here How To Restrict The Blocks Allowed Within A Content Area Episerver
Also this one on EpiWorld http://world.episerver.com/blogs/Ben-McKernan/Dates/2015/2/the-new-and-improved-allowed-types/
Of you have not upgraded to 7.5 yet as Frederik suggest we have the following attribute we have created to do just this.
using EPiServer.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace xxx.Com.Core.Attributes
{
[AttributeUsage(AttributeTargets.Property)]
public class OurAvailableContentTypesAttribute : ValidationAttribute
{
public Type[] Include { get; set; }
public Type[] Exclude { get; set; }
public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
if (!(value is ContentArea))
{
throw new ValidationException("OurAvailableContentTypesAttribute is intended only for use with ContentArea properties");
}
var contentArea = value as ContentArea;
var notAllowedcontentNames = new List<string>();
if (contentArea != null)
{
if (Include != null)
{
var notAllowedContent = contentArea.Contents.Where(x => !ContainsType(Include, x.GetType()));
if (notAllowedContent.Any())
{
notAllowedcontentNames.AddRange(notAllowedContent.Select(x => string.Format("{0} ({1})", x.Name, x.ContentLink.ID)));
}
}
if (Exclude != null)
{
var notAllowedContent = contentArea.Contents.Where(x => ContainsType(Exclude, x.GetType()));
if (notAllowedContent.Any())
{
notAllowedcontentNames.AddRange(notAllowedContent.Select(x => string.Format("{0} ({1})", x.Name, x.ContentLink.ID)));
}
}
}
if (notAllowedcontentNames.Any())
{
ErrorMessage = "contains invalid content items :";
foreach (var notAllowedcontentName in notAllowedcontentNames)
{
ErrorMessage += " " + notAllowedcontentName + ",";
}
ErrorMessage = ErrorMessage.TrimEnd(',');
return false;
}
return true;
}
private bool ContainsType(Type[] include, Type type)
{
return include.Any(inc => inc.IsAssignableFrom(type));
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var result = base.IsValid(value, validationContext);
if (result != null && !string.IsNullOrEmpty(result.ErrorMessage))
{
result.ErrorMessage = string.Format("{0} {1}", validationContext.DisplayName, ErrorMessage);
}
return result;
}
}
}
the usage of this is then
public class OurBlock : BlockData
{
[CultureSpecific]
[Editable(true)]
[Display(Name = "",
Description = "",
GroupName = SiteConstants.GroupNames.ContentArea,
Order = 1)]
[OurAvailableContentTypes(Include = new[] { typeof(OurImageBlock) })]
public virtual ContentArea ImageContentArea { get; set; }
HTH
Adam
Create a validation class and implement the IValidate interface from EPiServer.validation. The validation of this is kept outside of the PageData and BlockData classes.
This should be what you are looking for
using System.Collections.Generic;
using System.Linq;
using EPiServer.Validation;
public class SliderBlockValidator : IValidate<SliderBlock>
{
public IEnumerable<ValidationError> Validate(SliderBlock instance)
{
var errors = new List<ValidationError>();
if (instance.Slides != null &&
instance.Slides.Contents.Any(x => x.GetType().BaseType != typeof (SlideItemBlock)))
{
errors.Add(new ValidationError()
{
ErrorMessage = "Only SlideItemBlocks are allowed in this area",
PropertyName = "Slides",
Severity = ValidationErrorSeverity.Error,
ValidationType = ValidationErrorType.StorageValidation
});
}
return errors;
}
}
More reading at http://sdkbeta.episerver.com/SDK-html-Container/?path=/SdkDocuments/CMS/7/Knowledge%20Base/Developer%20Guide/Validation/Validation.htm&vppRoot=/SdkDocuments//CMS/7/Knowledge%20Base/Developer%20Guide/
If you have upgraded to EPi 7.5 you can use the AllowedTypes annotation
[AllowedTypes(new [] {typeof(SlideItemBlock)})]
public virtual ContentArea Slides { get; set; }
I am unaware if you are able to customize any messages using the later solution. There are a few known limitations
Restriction does not work for overlays when editing on page. This is a bug that has been fixed and will be released in a patch in a few weeks.
No server validation. Currently, the attribute only adds restriction in the UI. We hope to be able to add support for server validation soon which would also give the posibility validate your custom properties.
No validation when creating local blocks in content areas. If you use the new feature to add local blocks to a content area, there is currently no filtering of the content types when you create your new block.
Read more at http://world.episerver.com/Blogs/Linus-Ekstrom/Dates/2013/12/Restriction-of-content-types-in-properties/
All in all the first solution is currently the better one.
You can add a validation attribute to the content area property to restrict the allowed block types.
See this link for a detailed example.
Then using the AvailableContentTypes attribute you can restrict to only allow SlideItemBlock types.
[Required]
[Display(Name = "Slides")]
[AvailableContentTypes(Include = new []{typeof(SlideItemBlock)})]
public virtual ContentArea Slides { get; set; }

Silverlight 4 Late bound operations cannot be performed... and no examples help?

I have tried all variaions that I can find others using, and frankly they all seem to boil down to what I already have.
I want a generic system for invoking methods based on generic inputs. Not sure that really captures it fully, but not sure how to state it.
Here we go:
I want to make breadcrumbs from a params of Expression>
here, SelectedDivisions is ObservableCollection and ModelId is long?.
So, the point is that I want to feed in a list of varying properties, have them processed by the data class such that each is processed by the appropriate method inside of data
data.MakeBreadCrumbs(() => dc.SelectedDivisions, () => dc.ModelId);
data contains the following code:
public void MakeBreadCrumbs(params Expression<Func<object>>[] propertyExpressions) {
foreach (Expression<Func<object>> propertyExpression in propertyExpressions) {
MemberExpression member = propertyExpression.Body as MemberExpression;
if (member == null) {
UnaryExpression uExp = propertyExpression.Body as UnaryExpression;
member = uExp.Operand as MemberExpression;
}
PropertyInfo propInfo = member.Member as PropertyInfo;
Type[] propTypes = propInfo.PropertyType.GetGenericArguments();/
MethodInfo methodInfo = typeof(BreadcrumbData).GetGenericMethod("MakeBreadCrumb", new Type[] { propInfo.PropertyType, typeof(string) }); //
if (methodInfo.IsGenericMethod) {
methodInfo.MakeGenericMethod(propTypes[0]);
}
ConstantExpression ce = Expression.Constant(propertyExpression.Compile().Invoke());
string criterionName = ReadCriterionName(propertyExpression);
methodInfo.Invoke(this, new object[] { ce.Value, criterionName });
}
the last line fails with "Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true." when I am processing the property expression for the ObservableCollection item.
Here are the methods defined in the data class which are available, and which are correctly selected, but the one for the ObservableCollection fails on invocation
(LookupTypeBase is a class particular to my solution, but insert any type here that works with the type of a fake ObservableCollection property)
public void MakeBreadCrumb<T>(ObservableCollection<T> selections, string criterionName) where T : LookupTypeBase {...}
public void MakeBreadCrumb(long? value, string criterionName) {...}
public static class xxx {
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] parameterTypes) {
var methods = type.GetMethods();
foreach (var method in methods.Where(m => m.Name == name)) {
var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
if (methodParameterTypes.SequenceEqual(parameterTypes, new SimpleTypeComparer())) {
return method;
}
}
return null;
}
private class SimpleTypeComparer : IEqualityComparer<Type> {
public bool Equals(Type x, Type y) {
return x.Assembly == y.Assembly && x.Namespace == y.Namespace && x.Name == y.Name;
}
public int GetHashCode(Type obj) {
throw new NotImplementedException();
}
}
}
It's silverlight 4 so you have latebound method invocation with the dynamic keyword, which should make this easy and fast:
BreadcrumbData.MakeBreadCrumb((dynamic)ce.Value, (dynamic)criterionName);
Aside from that i think your issue is that MakeGenericMethod returns a new methodinfo which you ignore instead of keeping around like so:
if (methodInfo.IsGenericMethod) {
methodInfo = methodInfo.MakeGenericMethod(propTypes[0]);
}

Resources