Does anyone know how to set / read vocabulary based annotations in the metatdata for OData V4 to define things like max string length?
There is an article Client Annotation Support but it does not show any sample code and I'm not 100% sure they are even talking about data annotations.
It has code like the following:
var person = dsc.People.ByKey("russellwhyte").GetValue();
// Try to get an annotation for a property
dsc.TryGetAnnotation<Func<ObservableCollection<string>>, string>(() => person.Emails, fullQualifiedTermName, qualifier, out annotation);
But it does not explain what to use for "fullQualifiedTermName" or "qualifier".
I am adding the "odata.inculde-annotations=*" but that does not seem to help.
I have also tried the following.
dsc.TryGetAnnotation<Func<string>, string>(() => person.FirstName, "System.ComponentModel.DataAnnotations", out annotation);
But that only returns null.
I found an article Vocabularies in WCF Data Services from 2012 that talks about support for validation metadata.
I'm going to give it a shot.
I would hope there is an easy / better way of doing this in OData V4.
Update 1
Well the WCF vocabularies example does not work in OData V4 because the config.AnnotationsBuilder is missing from System.Web.Http.HttpConfiguration.
OData V4 supports Vocabularies as defined here http://www.odata.org/vocabularies/ and even offers what I need "metadata annotation may define ranges of valid values for a particular property" but there does not seem to be any sample code on any articles other than SAP https://blogs.sap.com/2013/10/07/vocabulary-based-annotations/ using it.
Update 2
Well after looking at the TripPinService I noticed that they had some annotations on budget like the following.
<Property Name="Budget" Type="Edm.Single" Nullable="false">
<Annotation Term="Org.OData.Measures.V1.ISOCurrency" String="USD"/>
<Annotation Term="Org.OData.Measures.V1.Scale" Int="2"/>
</Property>
Luckily the source code for that project is ODataSamples TripPin
After looking over the service I found that I first have to create my own xml vocabularies file. (ValidationVocabularies.xml)
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Org.OData.Validation.V1" Alias="Validation">
<Term Name="StringLength" Type="Edm.String" AppliesTo="Property">
<Annotation Term="Core.Description" String="Set max length of string." />
</Term>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
In the WebApiConfig file I must get to the xml file and add the vocabulary annotation to the metadata.
Again I was lucky in that the source code for TripPin had a nice helper class for this. (I modified it to look like the following)
public static class DataValidationHelpers
{
public static readonly IEdmModel Instance;
public static readonly IEdmTerm StringLengthTerm;
internal const string StringLength = "Org.OData.Validation.V1.StringLength";
static DataValidationHelpers()
{
using (var stream = System.IO.File.OpenRead(System.Web.HttpContext.Current.Server.MapPath("~/bin") + @"\App_Start\ValidationVocabularies.xml"))
{
IEnumerable<EdmError> errors;
System.Xml.XmlReader reader = System.Xml.XmlReader.Create(stream);
CsdlReader.TryParse(reader, out Instance, out errors);
}
StringLengthTerm = Instance.FindDeclaredTerm(StringLength);
}
public static void SetStringLengthAnnotation(this EdmModel model, IEdmProperty property, int length)
{
if (model == null) throw new ArgumentNullException("model");
if (property == null) throw new ArgumentNullException("property");
var target = property;
var term = StringLengthTerm;
var expression = new EdmIntegerConstant(length);
var annotation = new EdmVocabularyAnnotation(target, term, "StringLength", expression);
annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline);
model.AddVocabularyAnnotation(annotation);
}
}
I then call this from inside the GetEdmModel() method like the following:
var target = ((EdmEntityType)edmModel.FindDeclaredType("Test.Models.Person")).FindProperty("FirstName");
((EdmModel)edmModel).SetStringLengthAnnotation(target, 50);
It would be nice if anyone knows how to get to the property by the Name rather than some string value.
I did try builder.EntityType().Property(p => p.FirstName) but that type is not of IEdmProperty.
Anyway moving on...
So now where use my browser to get to the $metadata I can clearly see the string length attribute on the FirstName.
<Property Name="FirstName" Type="Edm.String">
<Annotation Term="Org.OData.Validation.V1.StringLength" Qualifier="StringLength" Int="50"/>
</Property>
This is nice but now on to the next problem. How to get to it from the client?
Well at first I tried.
DbContext.TryGetAnnotation<string>(personQueryResponse.FirstName, "Org.OData.Validation.V1.StringLength", out annotation);
But that gives me the following error.
Value cannot be null. Parameter name: element Then I tried:
DbContext.TryGetAnnotation<Func<string>, string>(() => personQueryResponse.FirstName, "Org.OData.Validation.V1.StringLength", out annotation);
But annotation is null.
I noticed that calling TryGetAnnotation does not call the OData service again.
So I then thought I would have to read the client side CSDL file so I then looked back at the WCF article but they don't tell you where they got the "annotations" collection.
I'm still digging but this is becoming a lost cause.
Such a shame that Microsoft wanted to push OData out as such a great service but there is little documentation and a very small support group.
I have 4 open OData questions and very little hits.
Don't get me wrong, I love OData, but this is a love / hate relationship.
Update 3
My current workaround is to create a partial class that looks like my entity and then create an interface to contain the data annotations.
Add data annotations to a class generated by entity framework