无法将验证器添加到EF生成的元数据中
本文关键字:元数据 EF 验证 添加 | 更新日期: 2023-09-27 18:26:23
我在Entity Framework中有一个数据库,它有一组由它创建的DTO,然后由Breeze从客户端使用。
我们使用服务器上的DataAnnotations来验证来自Breeze的数据,我希望能够在客户端上复制这些验证器。由于Breeze已经实现了这些验证器,并且显然支持在元数据中添加验证器,我想我应该尝试扩展Breeze服务器项目。
我已经知道EDMXFriter只支持一小部分DataAnnotations。
基本上,我的项目所做的就是在生成后将所需的验证器添加到Breeze发送的json中。
这是一个"表"的一部分,该表在Title属性上具有StringLength的DataAnnotation(Breeze确实支持)
{
"name":"Table",
"customannotation:ClrType":"...",
"key":{
"propertyRef":{
"name":"Id"
}
},
"property":[
{
"name":"Title",
"type":"Edm.String",
"fixedLength":"false",
"unicode":"true",
"validators":[
{
"validatorName":"stringLength",
"maxLength":"Max",
"minLength":1
}
]
}
]
}
我已经格式化了输出生成,以匹配微风网站上的方案设置的要求:http://www.breezejs.com/documentation/metadata-schema
但是Breeze并没有解释我添加到元数据中的这些验证器。
我注意到Breeze Server为EF提供的模式与上面web链接上设置的模式有不同的设计。BreezeJS是否不解释EF提供的元数据的验证器?如果是这样的话,有一种简单的方法可以启用它,或者我也必须将它写入客户端。
我知道Breeze团队确实说过他们计划实施更好的EF DataAnnotation支持,但我没有看到任何结果。也许这已经实现了,而我错过了什么人们只能希望事情会那么容易
谨致问候,Oliver Baker
breeze可以理解两种元数据格式。第一个是EDM(实体框架)模型的默认版本,是EDMXCSDL的json序列化版本。这是一种无法轻易扩展的MS格式,并且只支持上面列出的有限数量的数据注释。
另一种选择是breeze的原生元数据格式。这种格式通常由任何基于非实体框架的微风服务器使用。这也是应用MetadataStore.exportMetadata和MetadataStore.importMetadata方法调用时使用的格式。如果您的服务器以这种格式提供元数据,那么您可以包含您想要的任何验证。研究这种格式的最佳方法是简单地导出当前应用程序的元数据并查看。结果只是字符串化的本地元数据json。
一些breeze开发人员采用的一种方法是使用预构建过程,通过breeze客户端从EF服务器往返CSDL格式的元数据,将其转换为本机格式,然后简单地将此结果存储在服务器上(在您的情况下,添加了一些验证器),并在元数据调用期间简单地将预存储的元数据返回到生产中的客户端。
此外,您还可以扩展微风元数据格式:请参阅:
http://www.breezejs.com/documentation/custom-metadata
我们有许多开发人员将这种扩展元数据用于各种目的,包括添加验证元数据。
似乎EFContextProvider对验证注释的支持非常有限,基本上只有:
- 必需-如果!isNullable
- maxLength-如果指定了maxLength
上列出的输出http://www.breezejs.com/documentation/metadata-schema是客户端库中的元数据对象的。
http://www.breezejs.com/documentation/validation显示了如何手动编辑此信息,并注意到以下内容:
其中许多验证器与.NET数据注释相关。在未来的版本中,Breeze.NET EFContextProvider将能够自动为您在元数据中包含这些验证。现在,您必须将它们添加到客户端的属性中,如下所示。
因此,如果使用额外的元数据扩展EFContextProvider,则必须手动处理它,并将其添加到元数据存储中的属性信息中的验证器对象中。
一些微风开发人员采用的一种方法是使用预构建过程,通过微风客户端从EF服务器往返CSDL格式的元数据,将其转换为本地格式,然后简单地将结果存储在服务器上
这是我在github上使用jint的解决方案。显然计算成本很高,所以调用它的方法有一个[Conditional["DEBUG"]]属性
public static class MedSimDtoMetadata
{
const string breezeJsPath = @"C:'Users'OEM'Documents'Visual Studio 2015'Projects'SimManager'SM.Web'Scripts'breeze.min.js";
public static string GetBreezeMetadata(bool pretty = false)
{
var engine = new Engine().Execute("var setInterval;var setTimeout = setInterval = function(){}"); //if using an engine like V8.NET, would not be required - not part of DOM spec
engine.Execute(File.ReadAllText(breezeJsPath));
engine.Execute("breeze.NamingConvention.camelCase.setAsDefault();" + //mirror here what you are doing in the client side code
"var edmxMetadataStore = new breeze.MetadataStore();" +
"edmxMetadataStore.importMetadata(" + MedSimDtoRepository.GetEdmxMetadata() + ");" +
"edmxMetadataStore.exportMetadata();");
var exportedMeta = JObject.Parse(engine.GetCompletionValue().AsString());
AddValidators(exportedMeta);
return exportedMeta.ToString(pretty ? Formatting.Indented : Formatting.None);
}
//http://stackoverflow.com/questions/26570638/how-to-add-extend-breeze-entity-types-with-metadata-pulled-from-property-attribu
static void AddValidators(JObject metadata)
{
Assembly thisAssembly = typeof(ParticipantDto).Assembly; //any type in the assembly containing the Breeze entities.
var attrValDict = GetValDictionary();
var unaccountedVals = new HashSet<string>();
foreach (var breezeEntityType in metadata["structuralTypes"])
{
string shortEntityName = breezeEntityType["shortName"].ToString();
string typeName = breezeEntityType["namespace"].ToString() + '.' + shortEntityName;
Type entityType = thisAssembly.GetType(typeName, true);
Type metaTypeFromAttr = ((MetadataTypeAttribute)entityType.GetCustomAttributes(typeof(MetadataTypeAttribute), false).Single()).MetadataClassType;
foreach (var breezePropertyInfo in breezeEntityType["dataProperties"])
{
string propName = breezePropertyInfo["name"].ToString();
propName = char.ToUpper(propName[0]) + propName.Substring(1); //IF client using breeze.NamingConvention.camelCase & server using PascalCase
var propInfo = metaTypeFromAttr.GetProperty(propName);
if (propInfo == null)
{
Debug.WriteLine("No metadata property attributes available for " + breezePropertyInfo["dataType"] + " "+ shortEntityName +'.' + propName);
continue;
}
var validators = breezePropertyInfo["validators"].Select(bp => bp.ToObject<Dictionary<string, object>>()).ToDictionary(key => (string)key["name"]);
//usingMetaProps purely on property name - could also use the DTO object itself
//if metadataType not found, or in reality search the entity framework entity
//for properties with the same name (that is certainly how I am mapping)
foreach (Attribute attr in propInfo.GetCustomAttributes())
{
Type t = attr.GetType();
if (t.Namespace == "System.ComponentModel.DataAnnotations.Schema") {
continue;
}
Func<Attribute, Dictionary<string,object>> getVal;
if (attrValDict.TryGetValue(t, out getVal))
{
var validatorsFromAttr = getVal(attr);
if (validatorsFromAttr != null)
{
string jsValidatorName = (string)validatorsFromAttr["name"];
if (jsValidatorName == "stringLength")
{
validators.Remove("maxLength");
}
Dictionary<string, object> existingVals;
if (validators.TryGetValue(jsValidatorName, out existingVals))
{
existingVals.AddOrOverwrite(validatorsFromAttr);
}
else
{
validators.Add(jsValidatorName, validatorsFromAttr);
}
}
}
else
{
unaccountedVals.Add(t.FullName);
}
}
breezePropertyInfo["validators"] = JToken.FromObject(validators.Values);
}
}
foreach (var u in unaccountedVals)
{
Debug.WriteLine("unaccounted attribute:" + u);
}
}
static Dictionary<Type, Func<Attribute, Dictionary<string, object>>> GetValDictionary()
{
var ignore = new Func<Attribute, Dictionary<string, object>>(x => null);
return new Dictionary<Type, Func<Attribute, Dictionary<string, object>>>
{
[typeof(RequiredAttribute)] = x => new Dictionary<string, object>
{
["name"] = "required",
["allowEmptyStrings"] = ((RequiredAttribute)x).AllowEmptyStrings
//["message"] = ((RequiredAttribute)x).ErrorMessage
},
[typeof(EmailAddressAttribute)] = x => new Dictionary<string, object>
{
["name"] = "emailAddress",
},
[typeof(PhoneAttribute)] = x => new Dictionary<string, object>
{
["name"] = "phone",
},
[typeof(RegularExpressionAttribute)] = x => new Dictionary<string, object>
{
["name"] = "regularExpression",
["expression"] = ((RegularExpressionAttribute)x).Pattern
},
[typeof(StringLengthAttribute)] = x => {
var sl = (StringLengthAttribute)x;
return GetStrLenDictionary(sl.MaximumLength, sl.MinimumLength);
},
[typeof(MaxLengthAttribute)] = x => GetStrLenDictionary(((MaxLengthAttribute)x).Length),
[typeof(UrlAttribute)] = x => new Dictionary<string, object>
{
["name"] = "url",
},
[typeof(CreditCardAttribute)] = x=> new Dictionary<string, object>
{
["name"] = "creditCard",
},
[typeof(FixedLengthAttribute)] = x => //note this is one of my attributes to force fixed length
{
var len = ((FixedLengthAttribute)x).Length;
return GetStrLenDictionary(len, len);
},
[typeof(RangeAttribute)] = x => {
var ra = (RangeAttribute)x;
return new Dictionary<string, object>
{
["name"] = "range",
["min"] = ra.Minimum,
["max"] = ra.Maximum
};
},
[typeof(KeyAttribute)] = ignore
};
}
static Dictionary<string,object> GetStrLenDictionary(int maxLength, int minLength = 0)
{
if (minLength == 0)
{
return new Dictionary<string, object>
{
["name"] = "maxLength",
["maxLength"] = maxLength
};
}
return new Dictionary<string, object>
{
["name"] = "stringLength",
["minLength"] = minLength,
["maxLength"] = maxLength
};
}
static void AddOrOverwrite<K,V>(this Dictionary<K,V> oldValues, Dictionary<K,V> newValues)
{
foreach (KeyValuePair<K,V> kv in newValues)
{
if (oldValues.ContainsKey(kv.Key))
{
oldValues[kv.Key] = kv.Value;
}
else
{
oldValues.Add(kv.Key, kv.Value);
}
}
}
}