attribute可以说是Microsoft .NET Framework提出的最具创意的技术之一了。利用attribute,可以声明性的为自己的代码构造添加注解,从而实现一些特殊的功能。attribute允许将定义的信息应用于几乎每一个元数据表的记录项。这种可扩展的元数据信息能在运行时查询,从而动态改变代码的执行方式。
 
  一、使用attribute
  attribute可运用于类型和成员。Microsoft采取了一种机制提供对用户自定义的attribute的支持。这种机制叫做定制attribute。
  关于自定义attribute,首先应该知道:它们只是将一些附加信息与某个目标元素关联起来的方式。编译器会在托管模块中生成这些额外的信息。
  CLR允许将attribute应用于可在文件的元数据中表示的几乎所有元素。不过,最常应用attribute的还是以下定义表中的记录项:TypeDef(类、结构、枚举、接口和委托),MethodDef(含构造器)、ParamDef(方法参数)、FiledDef(字段)、PropertyDef(属性)、EventDef(事件),AssemblyDef(程序集)和MouduleDef(模块)。
  具体的说,在C#中只允许将attribute应用于对以下任何一个目标元素进行定义的源代码:程序集、模块、类型(类、结构、枚举、接口、委托)、字段、方法(含构造器)、方法参数、方法返回值、属性、事件和泛型类型参数。
  应用一个attribute时,C#允许用一个前缀明确指定attribute要应用于的目标元素。以下代码展示了所有可能的前缀。在许多情况下,即使省略前缀,编译器一样能判断一个attribute要应用于的目标元素。但在另一些情况下,必须指定前缀向编译器清楚表达我们的移除。下面倾斜显示的前缀是必须的。
  1. [assembly: MyAttr()] // 应用于程序集
  2. [module: MyAttr()] // 应用于模块
  3.  
  4. [type: MyAttr()] // 应用于类型
  5. internal sealed class SomeType<[typevar: MyAttr()] T> { // 应用于泛型类型变量
  6.  
  7. [field: MyAttr()] // 应用于字段
  8. public Int32 SomeField = ;
  9.  
  10. [return: MyAttr()] // 应用于返回值
  11. [method: MyAttr()] // 应用于方法
  12. public Int32 SomeMethod(
  13. [param: MyAttr()] // 应用于方法参数
  14. Int32 SomeParam) { return SomeParam; }
  15.  
  16. [property: MyAttr()] // 应用于属性
  17. public String SomeProp {
  18. [method: MyAttr()] // 应用于get访问器方法
  19. get { return null; }
  20. }
  21.  
  22. [event: MyAttr()] // 应用于事件
  23. [field: MyAttr()] // 应用于编译器生成的字段
  24. [method: MyAttr()] // 应用于编译器生成的add&&remove方法
  25. public event EventHandler SomeEvent;
  26. }
  27.  
  28. [AttributeUsage(AttributeTargets.All)]
  29. public class MyAttr : Attribute {
  30. public MyAttr(Int32 x) { }
  31. }

   前面介绍了如何应用一个attribute,接下来看看attribute到底是什么?

  attribute实际是一个类的实例。为了符合"公共语言规范"(CLS)的要求,attribute类必须直接或间接地从公共抽象类System.Attribute派生。C#只允许使用符合CLS规范的attribute。attribute类是可以在任何命名空间中定义的。

  如前所述,attribute是类的一个实例。类必须有一个公共构造器,这样才能创建它的实例。所以,将一个attribute应用于一个目标元素时,语法类似于调用类的某个实例构造器。除此之外,语言可能支持一些特殊的语法,允许你设置于attribute类关联的公共字段或属性。比如,我们将DllImport这个attribute应用于GetVersionEx方法:
  1. [DllImport("kernel32",CharSet = CharSet.Auto, SetLastError = true)]

  这一行代码语法表面上很奇怪,因为调用构造器时永远不会出现这样的语法。查阅DllImportAttbute类的文档,会发现它只接受一个String类型的参数。在这个例子中,"Kernel32"这个String类型的参数已经传给它了。构造器的参数称为"定位参数",而且是强制性的。也就是说,应用attribute时,必须指定参数。

  那么,另外两个"参数"是什么?这种特殊的语法允许在DllImportAttbute对象构造好之后,设置对象的任何公共字段和属性。在这个例子中,当DllImportAttbute对象构造好,而且将"Kernel32"传给构造器之后,对象的公共实例字段CharSet和SetListError被分别设置为CharSet.Auto和true。用于设置字段或属性的"参数"被称为"命名参数"。这种参数是可选的,因为在应用attribute的一个实例时,不一定要指定命名参数。
  另外,还可以将多个attribute应用于一个目标元素。将多个attribute应用一个目标元素时,attribute的顺序是无关紧要的。在C#中,可将每个attribute都封闭到一对方括号中,也可以在一对方括号中封闭多个以逗号分隔的attribute。
 
  二、定义自己的attibude类
  现在我们已经知道attribute是从System.Attribute派生的一个类的实例,并知道如何应用一个attribute。接着我们来研究下如何定制attribute。假定你是Microsoft的一位员工,并负责为枚举类型添加位标志(biit flag)支持。为此,我们,要做的第一件事情是定义一个FlagAttribute类:
namespace System {
    public class FlagsAttribute : System.Attribute {
        public FlagsAttribute(){
        }
    }
}
  注意,FlagsAttribute类是从Attribute继承的。所以,才使FlagsAttribute类成为符合CLS要求的一个attribute。除此之外,注意类名有一个Attribute后缀;这是为了保持于标准的相容性,但并不是必须的。最后,所有的非抽象attribute都至少要包括一个公共构造器。
  提示:attribute类型是一个类,但这个类应该非常简单。这个类应该只提供一个公共构造器,它接受attribute的强制性状态消息,而且这个类可以提供公共字段和属性,以接受attribute的可选状态信息。这个类不应提供任何公共方法、事件或其他成员。
 
  三、attribute的构造器和字段/属性的数据类型
  定义一个attribute类时,可定义构造器来获取参数。开发人员在应用该attribute类型的一个实例时,必须指定这些参数。除此之外,可在自己的类型中定义非静态公共字段和属性,使开发人员能够为attribute类的实例选择恰当的设置。
  定义attribute类的实例构造器、字段和属性时,数据类型只能限制在一个小的子集内。具体的说,合法的数据类型只有:Boolean,Char,Byte,Sbyte,Int16,UInt16,Int32,Uint32,Int64,Uint64,Single,Double,String,Type,Object或枚举类型。除此之外,还可使用上述任意类型的一维0基数组。然而,要尽量避免使用数组,因为对于attribute类来说,如果它的构造器要获取一个数组作为参数,就会失去与CLS的相容性。
  应用一个attribute时,必须传递一个编译时常量表达式,它与attribute类定义的类型相匹配。在attribute类定义一个Type参数,Type字段或者Type属性的任何地方,都必须使用C#的typeof操作符。在attribute类定义一个Object参数、Object字段或Object属性的任何地方,都可以传递一个Int32、String或者其他任何常量表达式(包括null)。如果常量表达式代表一个值类型,那么在运行时构造一个attribute的实例时,会对值类型进行装箱。以下是一个示例attribute及用法:

  1. public enum Color { Red }
  2.  
  3. [AttributeUsage(AttributeTargets.All)]
  4. internal sealed class SomeAttribute : Attribute
  5. {
  6. public SomeAttribute(String name, Object o, Type[] types)
  7. {
  8. // 'name' 引用了一个String类型
  9. // 'o' 引用了一个合法类型(如有必要,就进行装箱)
  10. // 'types' 引用一个一维0基Type数组
  11. }
  12. }
  13.  
  14. [Some("Jeff", Color.Red, new Type[] { typeof(Math), typeof(Console) })]
  15. internal sealed class SomeType
  16. {
  17. }

  逻辑上,当编译器检测到一个目标元素应用了一个attribute时,编译器会调用attribute类的构造器,向它传递任何指定的参数,从而构造attribute类的一个实例。然后,编译器会采用增强型构造器语法所指定的值,对任何公共字段和属性进行初始化。在构造并初始化好定制attribute类的对象之后,编译器会将这个attribute对象序列化到目标元素的元数据表记录项中。

  提示:所谓"attribute",就是一个类的实例,它被序列化成驻留在元数据中的一个字节流。在运行时,可以对元数据中包含的字节进行反序列化,从而构造类的一个实例。实际发生的事情是:编译器在元数据中生成创建attribute类的一个实例所需的信息。每个构造器参数都采取这样的格式写入:一个1字节长度的类型ID,后跟具体的值。在对构造器参数进行"序列化"之后,编译器写入字段/属性名称,后跟一个1字节的类型ID,再跟上具体的值,从而生成指定的每个字段和属性的值。对于数组,会先保存数组元素的个数,后跟每个单独的元素。
 
  四、检测定制的attribute
  可利用反射的技术来检查attribute的存在。以后我们会完整探讨这种技术。
  假定你是Microsoft的员工,负责实现Enum的Format方法(Format方法会更根据是否有FlagsAttribute输出不同的值),你会像下面这样实现它:
  1. public static String Format(Type enumType, Object value, String format) {
  2.  
  3. // 枚举类型是否应用了FlagsAttrobute类型的一个实例
  4. if(enumType.IsDefined(typeof(FlagsAttribute),false)){
  5. //如果是,就执行代码,将值视为一个位标志枚举类型
  6. ...
  7. }else {
  8. // 如果不是,就执行代码,将值视为一个普通枚举类型
  9. }
  10. }

  上述代码调用Type的IsDefined方法,要求系统查看枚举类型的元数据,检查是否关联了FlagsAttribute类的一个实例。如果IsDefined返回true,表面FlagsAttribute的一个实例已于枚举类型关联,Format方法会认为值包含了一个位标志集合。如果IsDefined放回false,Format方法会认为值是一个普通的枚举类型。

  定义定制attribute时,也必须实现一些代码来检查某个目标上是否存在该attribute类的实例,然后执行一些逻辑分支代码。正因为能做到这一点,定制attribute才如此有用。
  FCL提供了多种方式检查一个attribute的存在。我们知道所有于CLS相容的attribute都是从System.Attribute派生的。这个类定义了三个静态方法来获取与一个目标关联的attribute:IsDefined,GetCustomAttribute和GetCustomAttributes。每个方法都有几个重载版本。
方法名称 说明
IsDefined 如果至少有一个指定的Attribute派生类的实例如目标关联,就放回true。这个方法效率很高,因为它不构造(反序列化)attribute类的任何实例
GetCustomAttribute
返回引用于目标的指定attribute类的一个实例。实例使用编译时指定的参数、字段和属性来构造。如果目标没有引用任何attribute类的实例,就返回null。如果目标应用了指定attribute的多个实例,就抛出异常。方法通常用于已将AllowMultiple设为false的attribute。
GetCustomAttributes 返回一个数组,其中每个元素都是应用于目标的指定attribute类的一个实例。如果不为这个方法指定具体的attribute类,数组中包含的就是已应用的所有attribute的实例,不管它们是什么类。每个实例都使用编译时指定的参数、字段和属性来构造(反序列化)。如果目标没有应用任何attribute类的实例,就返回一个空数组。该方法通常用于已将AllowMultiple设为true的attribute,或者用于列出已应用的所有attribute。
  如果只想知道一个attribute是否应用于一个目标,那么应该调用IsDefined,因为它的效率比另外两个方法高的多。我们知道,将一个attribute应用于一个目标时,可以为attribute的构造器指定参数,并可以选择设置字段或属性。使用IsDefined不会构造一个attribute对象,不会调用它的构造器,也不会设置它的字段和属性。
  每次使用GetCustomAttribute和GetCustomAttributes方法时,都会为构造attribute对象的新实例,并根据源代码中指定的值来设置每个实例的字段和属性。这个两个方法返回的都是一个引用,执行完全构造好的attribute类的实例。
  调用上述任何一个方法时,它们内部必须扫描托管模块的元数据,执行字符串比较来定位指定的attribute类。显然,这些操作会耗费一定的事件。假如对性能要求高,可以考虑缓存这些方法调用的返回结果。
  System.Reflection 命名空间定义了几个类允许你检查一个模块的元数据的内容。这些类包括Assmbly,Module,ParameterInfo,MemberInfo,Type,MethodInfo,ConstructorInfo,FiledInfo,EeventInfo,PropertyInfo及其各自的*Builder类。所以这些方法还提供了IsDefined和GetCustomAttributes方法。只有System.Attribute提供了非常方便的GetCustomAttribute方法。
  反射类提供的那个版本的GetCustomAttributes方法返回的是有Object实例构成的一个数据(Object[]),而不是由Attribute实例构成的一个数组(Attribute[])。
  注意:将一个类传给IsDefined,GetCustomAttribute或GetCustomAttributes方法时,这些方法会搜索指定的attribute类或它的派生类,如果代码要搜索一个具体的attribute类,应该针对返回值执行一个额外的检查,确保这些方法返回的正是向搜索的莪累。还可以考虑将自己的attribute类定义成sealed,减少可能存在的混淆,并避免这个检查。
    以下示例代码列出了一个类型中定义的所有方法,并显示应用于每个方法的attribute代码。

  1. [assembly: CLSCompliant(true)]
  2.  
  3. [Serializable]
  4. [DefaultMemberAttribute("Main")]
  5. [DebuggerDisplayAttribute("Richter", Name = "Jeff", Target = typeof(Program))]
  6. public sealed class Program
  7. {
  8. [Conditional("Debug")]
  9. [Conditional("Release")]
  10. public void DoSomething() { }
  11.  
  12. public Program()
  13. {
  14. }
  15.  
  16. [assembly: CLSCompliant(true)]
  17. [STAThread]
  18. public static void Main()
  19. {
  20. // 显示应用于这个类型的attribute集
  21. ShowAttributes(typeof(Program));
  22.  
  23. // 获取与类型关联的方法集
  24. MemberInfo[] members = typeof(Program).FindMembers(
  25. MemberTypes.Constructor | MemberTypes.Method,
  26. BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static,
  27. Type.FilterName, "*");
  28.  
  29. foreach (MemberInfo member in members)
  30. {
  31. // 显示应用于这个成员的attribute集
  32. ShowAttributes(member);
  33. }
  34.  
  35. Console.Read();
  36. }
  37. private static void ShowAttributes(MemberInfo attributeTarget)
  38. {
  39. Attribute[] attributes = Attribute.GetCustomAttributes(attributeTarget);
  40.  
  41. Console.WriteLine("Attributes applied to {0}: {1}",
  42. attributeTarget.Name, (attributes.Length == ? "None" : String.Empty));
  43.  
  44. foreach (Attribute attribute in attributes)
  45. {
  46. // 显示已应用的每个attribute的类型
  47. Console.WriteLine(" {0}", attribute.GetType().ToString());
  48.  
  49. if (attribute is DefaultMemberAttribute)
  50. Console.WriteLine(" MemberName={0}",
  51. ((DefaultMemberAttribute)attribute).MemberName);
  52.  
  53. if (attribute is ConditionalAttribute)
  54. Console.WriteLine(" ConditionString={0}",
  55. ((ConditionalAttribute)attribute).ConditionString);
  56.  
  57. if (attribute is CLSCompliantAttribute)
  58. Console.WriteLine(" IsCompliant={0}",
  59. ((CLSCompliantAttribute)attribute).IsCompliant);
  60.  
  61. DebuggerDisplayAttribute dda = attribute as DebuggerDisplayAttribute;
  62. if (dda != null)
  63. {
  64. Console.WriteLine(" Value={0}, Name={1}, Target={2}",
  65. dda.Value, dda.Name, dda.Target);
  66. }
  67. }
  68. Console.WriteLine();
  69. }
  70. }

输出结果为:

Attributes applied to Program:
  System.SerializableAttribute
  System.Diagnostics.DebuggerDisplayAttribute
    Value=Richter, Name=Jeff, Target=ConsoleTest.Program
  System.Reflection.DefaultMemberAttribute
    MemberName=Main
 
Attributes applied to DoSomething:
  System.Diagnostics.ConditionalAttribute
    ConditionString=Debug
  System.Diagnostics.ConditionalAttribute
    ConditionString=Release
 
Attributes applied to Main:
  System.STAThreadAttribute
 
Attributes applied to .ctor: None
 
  五、两个attribute实例的相互匹配
  现在,我们的代码能判断是否将一个attribute的实例应用于一个目标了。除此之外,可能还需要检查attribute的字段来确定它们的值。为此,一个方法就是老老实实地写代码来检查attribute类的字段的值。System.Attribute重写了Object的Equals方法。这个方法内部会比较两个对象的类型。如果 不一致,Equals会返回false。如果类型一致,Equals会利用反射来比较两个attribute对象中的字段值(为每个字段调用Equals)。如果所有字段都匹配,就返回true;否则返回false。但我们可以在自己attribute类中重写Equals来移除反射的使用,从而提升性能。
  System.Attribute还公开了虚方法Match,可重写它来提供更丰富的语义。Match的默认实现只是调用的Equals方法并返回它的结果。下面演示了如何重写Equals和Match,后者在一个attribute代码另一个attribute的子集的前提下返回true。另外,还演示了如何使用Match。
  1. [Flags]
  2. internal enum Accounts
  3. {
  4. Savings = 0x0001,
  5. Checking = 0x0002,
  6. Brokerage = 0x0004
  7. }
  8.  
  9. [AttributeUsage(AttributeTargets.Class)]
  10. internal sealed class AccountsAttribute : Attribute
  11. {
  12. private Accounts m_accounts;
  13.  
  14. public AccountsAttribute(Accounts accounts)
  15. {
  16. m_accounts = accounts;
  17. }
  18.  
  19. public override Boolean Match(Object obj)
  20. {
  21. // 如果基类实现了Match,而且基类
  22. // 不是Attribute,就取消对下面这行的注释
  23. //if (!base.Match(obj))
  24. //{
  25. // return false;
  26. //}
  27.  
  28. if (obj == null)
  29. {
  30. return false;
  31. }
  32.  
  33. if (this.GetType() != obj.GetType())
  34. {
  35. return false;
  36. }
  37.  
  38. AccountsAttribute other = (AccountsAttribute)obj;
  39.  
  40. // 比较字段,判断它们是否有相同的值
  41. if ((other.m_accounts & m_accounts) != m_accounts)
  42. {
  43. return false;
  44. }
  45.  
  46. return true; // 对象匹配
  47. }
  48.  
  49. public override Boolean Equals(Object obj)
  50. {
  51. //如果基类实现了Equals,而且基类不能Object
  52. //就取消对下面的注释
  53. //if (!base.Equals(obj))
  54. //{
  55. // return false;
  56. //}
  57.  
  58. if (obj == null)
  59. {
  60. return false;
  61. }
  62.  
  63. if (this.GetType() != obj.GetType())
  64. {
  65. return false;
  66. }
  67.  
  68. AccountsAttribute other = (AccountsAttribute)obj;
  69.  
  70. // 比较字段,判断它们是否有相同的值
  71. if (other.m_accounts != m_accounts)
  72. {
  73. return false;
  74. }
  75.  
  76. return true; // Objects are equal
  77. }
  78.  
  79. // 还需要重写GetHashCode,因为我们重写了Equals
  80. public override Int32 GetHashCode()
  81. {
  82. return (Int32)m_accounts;
  83. }
  84. }
  85.  
  86. [Accounts(Accounts.Savings)]
  87. internal sealed class ChildAccount { }
  88.  
  89. [Accounts(Accounts.Savings | Accounts.Checking | Accounts.Brokerage)]
  90. internal sealed class AdultAccount { }
  91.  
  92. public class Program
  93. {
  94.  
  95. public static void Main()
  96. {
  97.  
  98. CanWriteCheck(new ChildAccount());
  99. CanWriteCheck(new AdultAccount());
  100.  
  101. // 只是为了演示在一个没有应用AccountsAttribute的方法也能正确工作
  102. CanWriteCheck(new Program());
  103.  
  104. Console.Read();
  105. }
  106.  
  107. private static void CanWriteCheck(Object obj)
  108. {
  109. // 构造attribute类型的一个实例,并把它初始化我们要
  110. // 显示查找的内容
  111. Attribute checking = new AccountsAttribute(Accounts.Checking);
  112.  
  113. // 构造应用于类型的attribute实例
  114. Attribute validAccounts = Attribute.GetCustomAttribute(
  115. obj.GetType(), typeof(AccountsAttribute), false);
  116.  
  117. // 如果attribute应用于类型,而且attribute指定了
  118. // "Checking" 账户, 表面该类可以开支票
  119. if ((validAccounts != null) && checking.Match(validAccounts))
  120. {
  121. Console.WriteLine("{0} types can write checks.", obj.GetType());
  122. }
  123. else
  124. {
  125. Console.WriteLine("{0} types can NOT write checks.", obj.GetType());
  126. }
  127. }
  128. }

输出结果:

ConsoleTest.ChildAccount types can NOT write checks.
ConsoleTest.AdultAccount types can write checks.
ConsoleTest.Program types can NOT write checks.
 
  六、检测定制attribute时不创建从Attribute派生的对象
  本节将讨论如何利用另一种技术来检测应用于一个元数据记录项的attribute。在某些安全性要求严格的场合,这个技术能保证不会执行从Attribute派生的类中的代码。毕竟,调用Attribute的GetCustomAttribute或者GetCustomAttributes方法时,这些方法会在内部调用attribute类的构造器,而且可能调用属性的set方法。除此之外,首次访问一个类型会造成CLR调用类型的类型构造器(如果有的化话)。在构造器、set构造器方法以及类型构造器中,可能包含每次查找一个attribute时都要执行的代码,这样一来,就相当于允许未知的代码在APPDomain中运行,可以说是一个安全隐患。
  使用System.Reflection.CustomAttributeData类,可以在查找attribute时同时禁止执行attribute类中的代码。这个类定义了一个静态方法GetCustomAttributes来获取与一个目标关联的attribute。该方法有4个重载版本:一个接受一个Assembly,一个接受一个Module,一个接受一个ParameterInfo,还有一个接受一个Memberinfo。这个类是在System.Reflection命名空间定义的。通常,是先用Assembly的静态方法ReflectionOnlyLoad加载一个程序集,在用CustomAttributeData类分析这个程序集的元数据中的attribute。简单的说,ReflectionOnlyLoad以一种特方式加载程序集,期间会禁止CLR执行程序集中的任何代码,包括类型构造器。
  CustomAttributeData的GetCustomAttributes方法相当于一个工厂方法。也就是说,调用它会返回IList<CustomAttributeData>类型的对象,其中包括了一个有CustomAttributeData构成的一个集合。在集合中,应用于指定目标的每个定制attribute都有一个对象的元素。针对每个CustomAttribute对象都可以查询一些只读属性,判断attribute对象是如何构造和初始化的。具体的说,Customctor属性指出构造器方法"要"如何调用。ComstructorArguments属性以一个IList<CustomAttributeTypedArgument>实例的形式返回"要"传给这个构造器的实参。NamedArguments属性以一个IList<CustomAttributeNamedArgument>实例的形式,返回"要"设置的字段或属性。注意,这里之所以说"要",是因为不会实际地调用构造器和set访问器方法。通过禁止执行attribute类的任何方法,我们获得了增强的安全性。
 
  七、条件attribute类
  如果一个attribute类应用了System.Diagnostics.ConditionalAttribute,就称为条件attribute类。下面是一个例子:
  1. //#define TEST
  2. #define VERIFY
  3. namespace ConsoleTest
  4. {
  5. using System;
  6. using System.Diagnostics;
  7.  
  8. [Conditional("TEST")]
  9. [Conditional("VERIFY")]
  10. public sealed class CondAttribute : Attribute
  11. {
  12.  
  13. }
  14.  
  15. [Cond]
  16. public class Program
  17. {
  18. public static void Main()
  19. {
  20. Console.WriteLine("CondAttribute is {0} applied to Program type.",
  21. Attribute.IsDefined(typeof (Program), typeof (CondAttribute)) ? "" : "not");
  22. Console.Read();
  23. }
  24. }
  25. }

  编译器如果发现向目标元素应用了CondAttribute的一个实例,那么当含有目标元素的代码编译时,只有定义TEST或VERIFY符号的前提下,编译器才会在元数据中生成attribute信息。虽然如此,attribute类的定义元数据和实现存在于程序集中。

 

[CLR via C#]18. Attribute的更多相关文章

  1. CLR via C#(18)——Enum

    1. Enum定义 枚举类型是经常用的一种“名称/值”的形式,例如: public enum FeedbackStatus     {         New,         Processing, ...

  2. 一百多道.NET面试题!

    1.a=10,b=15,在不用第三方变量的前提下,把a,b的值互换 方法一: a=a+b;  b=a-b;  a=a-b;  方法二: a^=b^(b^=a^b);  2.已知数组int[] max= ...

  3. .Net Core 之 图形验证码 本文介绍.Net Core下用第三方ZKWeb.System.Drawing实现验证码功能。

    本文介绍.Net Core下用第三方ZKWeb.System.Drawing实现验证码功能. 通过测试的系统: Windows 8.1 64bit Ubuntu Server 16.04 LTS 64 ...

  4. java 处理XML(dom4j-1.6.1)

    java 处理XML(dom4j-1.6.1) Java 处理xml有很多框架,今天使用主流框架dom4j-1.6.1 下载地址:http://www.dom4j.org/dom4j-1.6.1/ D ...

  5. .Net Core 之 图形验证码

    本文介绍.Net Core下用第三方ZKWeb.System.Drawing实现验证码功能. 通过测试的系统: Windows 8.1 64bit Ubuntu Server 16.04 LTS 64 ...

  6. Dynamic CRM 2013学习笔记(三)快速创建实体 EntityCreater

    一.实体简介 实体用于在 Microsoft Dynamics CRM 中建立业务数据模型和管理业务数据.例如,可以使用客户.市场活动和事件(案例)等实体跟踪和支持销售.市场营销和服务活动.实体具有一 ...

  7. javaweb学习总结(二十六)——jsp简单标签标签库开发(二)

    一.JspFragment类介绍 javax.servlet.jsp.tagext.JspFragment类是在JSP2.0中定义的,它的实例对象代表JSP页面中的一段符合JSP语法规范的JSP片段, ...

  8. Hibernate常见错误整理

    Hibernate常见错误合集   1.错误:object references an unsaved transient instance - save the transient instance ...

  9. MVC中验证码的生成

    在项目中验证码的生成通常是需要页面无刷新的,所以验证码图片实际是跟在某个input后面的img,通过控制该img来控制验证码显示的位置,例如: <div> <input id=&qu ...

随机推荐

  1. ubuntu 休眠之后网络间接失败 can not connect to network after suspend (wake up)

    ubuntu for laptop系统在系统休眠后wakeup 之后,网络连接失败, 有线网络无法连接, 无线wifi无法连接, 只能重启后才能恢复, 此时可以采用以下方法处理: 1. 在/etc/p ...

  2. 【转帖】四种BI 开源工具介绍-SpagoBI,openI,JasperSoft,Pentaho

    四种BI 开源工具介绍-SpagoBI,openI,JasperSoft,Pentaho 1 BI系统的简述 从技术角度来说 BI 包含了 ETL.DW.OLAP.DM等多环节.简单的说就是把交易系统 ...

  3. SQL的主键和外键约束

    SQL的主键和外键的作用: 外键取值规则:空值或参照的主键值. (1)插入非空值时,如果主键表中没有这个值,则不能插入. (2)更新时,不能改为主键表中没有的值. (3)删除主键表记录时,你可以在建外 ...

  4. 优先队列求解Huffman编码 c++

    优先队列小析      优先队列的模板: template <class T, class Container = vector<T>,class Compare = less< ...

  5. IOS开发 图形绘制,绘制线条,矩形,和垂直和居中绘制文字

    概述 吐槽下IOS下 的图形绘图,代码冗长,不得不自己重新封装方法.整理形成本文. 绘制线 // 绘制直线 + (void)toDrawLineFromX:(CGFloat)x1 Y:(CGFloat ...

  6. 日本超人气洛比(Robi)声控机器人

    1.日本超人气洛比(Robi)声控机器人. http://technews.cn/2015/04/18/interview-with-robi-creator-tomotaka-takahashi/ ...

  7. MINE

    MINE MINE is an app for the nearly 1.2 million songwriters, composers, musicians, and publishers who ...

  8. Dbvisualizer9.0.6 解决中文乱码

    一.设置编辑器的编码 Tools->Tools Properties ->General->File Encoding 设置为UTF-8 二.如果数据库为UTF-8,则要在连接时做以 ...

  9. 自己动手搭建 CAS(Central Authentication Service) 环境,为了单点登录(Single Sign On , 简称 SSO )

    介绍 刚刚搭建 CAS 成功了,现在记录下来,怕以后忘记,同时也给需要帮助的人.CAS 搭建需要服务端和客户端,服务端是 Java 写的,如果自己搭建则需要安装与配置 Java 环境.客户端可以是多种 ...

  10. Git使用日记

    git是个分布式的版本管理工具,现在我们前端这边用它做版本管理.之前也看过一些相关资料,不过没有使用它管理过项目代码.如今,用它也有段时间了所以就写些东西,仅供参考. ###快速上手 工作经常用到的几 ...