无法将验证器添加到EF生成的元数据中

时间:2014-10-13 12:12:24

标签: c# entity-framework breeze

我在Entity Framework中有一个数据库,它有一组从中创建的DTO,然后由Breeze从客户端使用。

我们使用服务器上的DataAnnotations来验证来自Breeze的数据,我希望能够在客户端上复制这些验证器。由于Breeze已经实现了这些验证器,并且显然支持在元数据中添加验证器,我认为我会扩展Breeze Server Project。

我已经知道EDMXWriter只支持一小组DataAnnotations。

基本上我的所有项目都是将生成后所需的验证器添加到Breeze发送的json中。

以下是标题属性中具有StringLength(Breeze确实支持)的DataAnnotation的'Table'的一部分。

{  
    "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
             }
          ]
       }
    ]
}

我已将格式化输出生成以符合breeze网站上方案设置的要求:http://www.breezejs.com/documentation/metadata-schema

但是Breeze没有解释我将添加到元数据中的这些验证器。

我注意到Breeze Server for EF提供的架构与上面Web链接上的Schema集的设计不同。 BreezeJS不解释EF提供的元数据的验证者吗?如果是这种情况,有一种简单的方法可以实现这一点,或者我也必须将其写入客户端。

我知道Breeze团队确实表示他们正在计划实施更好的EF DataAnnotation支持,但是我没有看到任何结果。也许这已经实施了,我错过了什么? 人们只能希望它会那么容易。

此致 奥利弗贝克

3 个答案:

答案 0 :(得分:3)

Breeze有两种元数据格式可供理解。第一个是基于EDM(实体框架)的模型的默认值,是EDMX CSDL的json序列化版本。这是一种MS格式,无法轻松扩展,仅支持上面列出的有限数量的数据注释。

另一种选择是breeze的原生元数据格式。此格式通常由任何基于非实体框架的breeze服务器使用。这也是应用 MetadataStore.exportMetadata MetadataStore.importMetadata 方法调用时使用的格式。如果您的服务器以此格式提供元数据,那么您可以包含所需的任何验证。调查此格式的最佳方法是简单地导出当前应用程序的元数据并查看。结果只是字符串化的本机元数据json。

一些微风开发人员采用的方法是使用预构建过程,通过微风客户端将来自EF服务器的CSDL格式化元数据往返,将其转换为本机格式,然后将此结果存储在服务器上(在您的情况下)一些添加的验证器)并在元数据调用期间简单地将预先存储的元数据返回到生产中的客户端。

此外,您还可以扩展breeze元数据格式:请参阅:

http://www.breezejs.com/documentation/custom-metadata

我们有许多开发人员将这些扩展元数据用于各种目的,包括添加验证元数据。

答案 1 :(得分:2)

似乎EFContextProvider具有非常有限的验证注释支持,基本上只是:

  • 必需 - if!isNullable
  • maxLength - 如果指定了maxLength

http://www.breezejs.com/documentation/metadata-schema中列出的输出是客户端库中的元数据对象,一旦处理完毕。

http://www.breezejs.com/documentation/validation显示了如何手动编辑此信息,并注意到以下内容:

  

许多验证器与.NET数据注释相关。在将来的版本中,Breeze.NET EFContextProvider将能够自动为您在元数据中包含这些验证。现在,您必须将它们添加到客户端的属性中,如下所示。

因此,如果您使用其他元数据扩展EFContextProvider,则必须手动处理它并将其添加到元数据存储中属性信息中的验证器对象。

答案 2 :(得分:0)

  

一些微风开发人员采用的方法是使用预构建过程,通过轻微客户端将来自EF服务器的CSDL格式化元数据往返,将其转换为本机格式,然后将此结果存储在服务器上

以下是我在github上使用jint的解决方案。显然计算成本很高,所以调用它的方法有[条件[“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);
            }
        }

    }
}