C#反射实现 C# 反射 判断类的延伸类型 使用代码生成工具Database2Sharp快速生成工作流模块控制器和视图代码 C# ADO.NET的SqlDataReader对象,判断是否包含指定字段 页面中添加锚点的几种方式 .net 简单实用Log4net(多个日志配置文件) C# 常用小点
C#反射实现
一、反射概念:
1、概念:
反射,通俗的讲就是我们在只知道一个对象的内部而不了解内部结构的情况下,通过反射这个技术可以使我们明确这个对象的内部实现。
在.NET中,反射是重要的机制,它可以动态的分析程序集Assembly,模块Module,类型Type等等,我们在不需要使用new关键的情况下,就可以动态
创建对象,使用对象。降低代码耦合性提高了程序的灵活性。那么,反射是怎么实现的呢?它的内部实现依赖于元数据。元数据,简单来说,在
公共语言运行时CLR中,是一种二进制信息,用来描述数据,数据的属性环境等等的一项数据,那么反射解析数据的内部实现通过元数据实现再
合适不过了。
2、实例:
首先先写一个你要反射的程序集:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace StudentClass { public class Student { public Student() { } public string Name { get; set; } public int Age { get; set; } public char Gender { get; set; } public string IdCard { get; set; } public string Address { get; set; } private string Mobile { get; set; } public void Eat() { Console.WriteLine("我今天吃啦好多东西"); } public void Sing() { Console.WriteLine("耶耶耶耶耶"); } public int Calculate(int a, int b) { return a + b; } private string PrivateMethod() { return "我是一个私有方法"; } } }
先来看一下程序街、模块、以及类等信息。
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace ReflectionInvoke { class Program { static void Main(string[] args) { //获取程序集信息 Assembly assembly = Assembly.LoadFile(@"E:\测试\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Console.WriteLine("程序集名字:"+assembly.FullName); Console.WriteLine("程序集位置:"+assembly.Location); Console.WriteLine("运行程序集需要的额CLR版本:"+assembly.ImageRuntimeVersion); Console.WriteLine("===================================================="); //获取模块信息 Module[] modules = assembly.GetModules(); foreach (Module item in modules) { Console.WriteLine("模块名称:"+item.Name); Console.WriteLine("模块版本ID"+item.ModuleVersionId); } Console.WriteLine("======================================================"); //获取类,通过模块和程序集都可以 Type[] types = assembly.GetTypes(); foreach (Type item in types) { Console.WriteLine("类型的名称:"+item.Name); Console.WriteLine("类型的完全命名:"+item.FullName); Console.WriteLine("类型的类别:"+item.Attributes); Console.WriteLine("类型的GUID:"+item.GUID); Console.WriteLine("====================================================="); } //获取主要类Student的成员信息等 Type studentType = assembly.GetType("StudentClass.Student");//完全命名 MemberInfo[] mi = studentType.GetMembers(); foreach (MemberInfo item in mi) { Console.WriteLine("成员的名称:"+item.Name); Console.WriteLine("成员类别:"+item.MemberType); } Console.WriteLine("====================================="); //获取方法 BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance; MethodInfo[] methodInfo = studentType.GetMethods(flags); foreach (MethodInfo item in methodInfo) { Console.WriteLine("public类型的,不包括基类继承的实例方法:"+item.Name); } Console.WriteLine("========================================"); BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic; MethodInfo[] methods = studentType.GetMethods(flag); foreach (MethodInfo item in methods) { Console.WriteLine("非public类型的,不包括基类继承的实例方法:"+item.Name); } Console.WriteLine("========================================"); //获取属性 BindingFlags flags2 = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance; PropertyInfo[] pi = studentType.GetProperties(flags2); foreach (PropertyInfo item in pi) { Console.WriteLine("属性名称:"+item.Name); } } } }
结果:
1、Assembly.Load()以及Assembly.LoadFile():
LoadFile这个方法的参数是程序集的绝对路径,通过点击程序集shift+鼠标右键复制路径即可。load方法有多个重载,还可以通过流的方式获取程序集,
在项目中,主要用来取相对路径,因为很多项目的程序集会被生成在一个文件夹里,此时取相对路径不容易出错。
2、GetTypes和GetType():
很明显第一个获取程序集下所有的类,返回一个数组,第二个要有参数,类名为完全类名:命名空间+类名,用于获取指定的类。
3、Type类下可以获取这个类的所有成员,也可以获取字段属性方法等,有:
ConstructorInfo获取构造函数, FieldInfo获取字段, MethodInfo获取方法,PropertyInfo获取属性,EventInfo获取事件,ParameterInfo获取参数,通过他们的
Get***获取,加s获取所有返回数组,不加s获取具体的。
4、BindFlags:用于对获取的成员的类型加以控制:
通过反编译工具,可以看到这个enum的具体:
BindingFlags.Public公共成员,NonPublic,非公有成员,DeclaredOnly仅仅反射类上声明的成员不包括简单继承的成员。CreateInstance调用构造函数,GetField获取字段值对setField无效。还有很多读者可以F12打开看一下用法以及注释。注意必须指定:BindingFlags.Instance或BindingFlags.Static,主要为了获取返回值,是静态的还是实例的。
二、反射的运用:
1、创建实例:
创建实例大体分为2种,Activator.CreateInstance和Assembly.CreateInstance。这2种方法都可以创建实例,但是又有区别,下面来通过实例具体说明。
首先分析第一种Activator.CreateInstance
这个方法有许多的重载,最常用的2种:(Type type)和(Type type,params object[] obj)第一种调用无参构造,第二种调用有参构造
在前面的实例Student中添加一个有参构造:
public Student(string name) { this.Name = name; }
然后反射创建实例
Assembly assmbly = Assembly.LoadFile(@"E:\测试\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object obj = Activator.CreateInstance(studentType, new object[] { "milktea" }); if (obj != null) { Console.WriteLine(obj.GetType()); }
这里就创建了一个实例,现在让我们用反编译工具查看它的底层实现:
public static object CreateInstance(Type type, params object[] args) { return CreateInstance(type, BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance, null, args, null, null); }
调用它的参数最多的一个重载后,发现他调用了下面这个方法:
这里我们就可以知道这里创建实例和new创建实例的第三步实现相同,new创建实例,先在堆中开辟新空间,然后创建对象调用它的构造函数,
所以我们可以知道Activator.CreateInstance的底层仍然是通过被调用的类别的构造创建的,那么如果没有参数就说明调用的是无参构造。
然后来看第二种Assembly.CreateInstance:
Assembly assmbly = Assembly.LoadFile(@"E:\测试\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName,true); Console.WriteLine(o.GetType());
运行程序,却发现此时抛出了MissingMethodException异常:
可是明明有一个构造函数,为什么还会说没有找到构造函数呢?
通过反编译工具,来看看什么原因:
我们发现Assembly这个类下的CreateInstance方法,居然返回的是Activator下的CreateInstance方法,那么就只有一种可能,他调用的
是反射类下的无参构造,而无参构造被我们新加的有参构造给替代了,因此也就找不到无参构造,为了证明结论的正确,我们把无参构造
加上,然后重新实验:
public Student() { }
果然和我们预想的一样,如果没有无参构造,那么使用Assembly类下的方法就会抛出异常。综合2种情况,既然Assembly下的CreateInstance
也是调用的Activator的方法,并且Assembly限制更大,那我们在创建实例的时候应当还是选Activator下的方法更不容易出错,不是吗。
2、调用方法,属性赋值等
创建了实例以后,就到了实际用途,怎么调用它的方法,怎么给它的字段赋值,怎么添加一个委托事件等,现在来看。
A、第一种方法:使用Type类的InvokeMember()方法,实例如下:
Assembly assmbly = Assembly.LoadFile(@"E:\测试\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName, true); Type instanceType = o.GetType(); //给属性赋值并检查 instanceType.InvokeMember("Name",BindingFlags.SetProperty,null,o,new object[]{"milktea"}); string propertyValue = instanceType.InvokeMember("Name",BindingFlags.GetProperty,null,o,null).ToString(); Console.WriteLine(propertyValue); //调用方法无返回值 instanceType.InvokeMember("Eat",BindingFlags.InvokeMethod,null,o,null); //调用方法有返回值 int sum = Convert.ToInt32(instanceType.InvokeMember("Calculate",BindingFlags.InvokeMethod,null,o,new object[]{2,3})); Console.WriteLine(sum);
几个重要的参数:第一个方法的名称,Enum的值,字段SetField,方法InvokeMethod,然后选择要使用的对象,即刚才反射创建的实例,最后一个要
赋的值或者方法参数等必须为一个object数组。
这个方法详情请看MSDN官方文档:
B、 第二种方法:使用FiledInfo,MethodInfo...等的Invoke方法,实例如下:
Assembly assmbly = Assembly.LoadFile(@"E:\测试\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName, true); Type instanceType = o.GetType(); //给属性赋值并检查 PropertyInfo ps = instanceType.GetProperty("Age",typeof(Int32)); ps.SetValue(o,5,null); PropertyInfo pi2 = instanceType.GetProperty("Age"); Console.WriteLine(pi2.GetValue(o,null)); //调用方法 MethodInfo mi = instanceType.GetMethod("Calculate", BindingFlags.Instance|BindingFlags.Public); object obj = mi.Invoke(o, new object[] { 1, 2 }); int result = Convert.ToInt32(mi.Invoke(o,new object[]{1,2})); Console.WriteLine(result);
方法的过程即先通过方法名取的方法,注意参数中的BindingFlags的2个参数都不可以丢,否则会报空引用异常,然后Invoke方法中
第一个参数为反射创建的对象,第二个参数为赋的值,或参数等。
C、第三种方法:对于反射的优化,通过使用委托:这里我们将使用Stopwatch对比和上次同样结果的时间:
Assembly assmbly = Assembly.LoadFile(@"E:\测试\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName, true); Type instanceType = o.GetType(); //给属性赋值并检查 Stopwatch sw = new Stopwatch(); sw.Start(); PropertyInfo ps = instanceType.GetProperty("Age",typeof(int)); ps.SetValue(o,5,null); PropertyInfo pi2 = instanceType.GetProperty("Age"); Console.WriteLine(pi2.GetValue(o,null)); Console.WriteLine("属性没启用优化:"+sw.Elapsed); //调用方法 sw.Reset(); sw.Restart(); MethodInfo mi = instanceType.GetMethod("Calculate", BindingFlags.Instance|BindingFlags.Public); object obj = mi.Invoke(o, new object[] { 1, 2 }); int result = Convert.ToInt32(mi.Invoke(o,new object[]{1,2})); Console.WriteLine(result); Console.WriteLine("方法没启用优化:" + sw.Elapsed); //给属性赋值并检查 sw.Reset(); sw.Restart(); PropertyInfo pi3 = instanceType.GetProperty("Age", typeof(int)); var piDele = (Action<int>)Delegate.CreateDelegate(typeof(Action<int>),o,pi3.GetSetMethod()); piDele(5); var result1 = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), o, pi3.GetGetMethod()); Console.WriteLine(result1()); Console.WriteLine("属性启用优化:"+sw.Elapsed); //调用方法 sw.Reset(); sw.Restart(); MethodInfo mi2 = instanceType.GetMethod("Calculate",BindingFlags.Instance|BindingFlags.Public); var miDele = (Func<int, int, int>)Delegate.CreateDelegate(typeof(Func<int,int,int>),o,mi2); int a = miDele(1,2); Console.WriteLine(a); Console.WriteLine("方法启用优化:"+sw.Elapsed);
这里可以很明显的看到使用优化以后,时间缩短了斤2/3,试想一下,这里只用了很少的代码,如果代码量很多的话就可以节省更多的时间。
当然也可以看出这里的代码量比较大而复杂,可以说不够漂亮简介,用空间换取效率,Delegate.CreateDelegate()方法具体请看: 详情链接
D、现在将最后一种,.NET 4.0出现了一个新的关键字:dynamic,和var有点类似的感觉,但实则不同。var是语法糖,在代码编译期就将真正的类型
已经替换了,Visual Studio可以推断出var的类型,而dynamic不会在编译期检查,被编译为object类型,而会在运行期做检查,并且这个效率虽然没
有优化后的反射快,但比普通的反射也要快一些。
Stopwatch watch1 = Stopwatch.StartNew(); Type type = Assembly.LoadFile(@"E:\C#优化实例\StudentClass\StudentClass\bin\Debug\StudentClass.dll").GetType("StudentClass.Student"); Object o1 = Activator.CreateInstance(type,new object[]{12}); var method1 = type.GetMethod("Add",BindingFlags.Public|BindingFlags.Instance); int num1 = (int)method1.Invoke(o1,new object[]{1,2}); Console.WriteLine(num1); Console.WriteLine("反射耗时"+watch1.ElapsedMilliseconds); Stopwatch watch2 = Stopwatch.StartNew(); Type type2 = Assembly.LoadFile(@"E:\C#优化实例\StudentClass\StudentClass\bin\Debug\StudentClass.dll").GetType("StudentClass.Student"); dynamic o2 = Activator.CreateInstance(type, new object[] { 13 }); int num2 = o2.Add(2,3); Console.WriteLine(num2); Console.WriteLine("dynamic耗时:"+watch2.ElapsedMilliseconds);
这里看到是比反射要快一些,而且代码精简了很多。综合考虑下来,代码精简度以及耗费时间,建议尽量使用dynamic关键字来处理反射。
C# 反射 判断类的延伸类型
添加测试类:
public class TestClass2 : TestClass1 { } public class TestClass1 : TestClass0 { public override void TestMethod() { } } public class TestClass0 : ITestInterface { public virtual void TestMethod() { } } public interface ITestInterface { }
1.判断是否实现了指定接口
//true
var hasInterface = typeof(TestClass2).GetInterfaces().Any(i => i.Name == nameof(ITestInterface));
2.判断是否继承了指定基类
//true
var isSubOfTestClassBase = typeof(TestClass2).IsSubclassOf(typeof(TestClass0));
3.判断是否重写了基类方法
获取指定方法的信息后,可以通过DeclaringType-声明此方法的成员类信息,判断与当前类信息是否一致。
//false
var hasTestClass2OverrideMethod = typeof(TestClass2).GetMethod(nameof(TestClass0.TestMethod)).DeclaringType.Equals(typeof(TestClass2));
//true
var hasTestClass1OverrideMethod = typeof(TestClass1).GetMethod(nameof(TestClass0.TestMethod)).DeclaringType.Equals(typeof(TestClass1));
4.判断类A是否为类B的嵌套类
public class TestClass { public class TestNestedClass { } }
测试:
//true
var isNestedInTestClass= typeof(TestClass).GetNestedTypes().Any(i=>i.Equals(typeof(TestClass.TestNestedClass)));
5. 类/接口的访问类型
- 判断类A是否可被类B继承
- 判断接口C是否可被类B继承
- 判断类A、类B是否在同一嵌套类中(类的访问类型 IsNested 以及 如上4中所述判断是否嵌套在某类中)
通过如下访问类型的组合,进行判断
- IsNested -- 是否表示其定义嵌套在另一个类型的定义之内的类型
- IsVisible -- 是否可由程序集之外的代码访问
- IsNotPublic -- 是否声明为公共类型(true:未声明为公共类型且不是嵌套类型)
- IsPublic -- 是否声明为公共类型 (true:声明为公共类型且不是嵌套类型)
- IsNestedPublic -- 是否是嵌套的并且声明为公共的(true:类是嵌套的并且声明为公共的)
- IsNestedPrivate --是否是嵌套的并声明为私有
- IsNestedFamily --否是嵌套的并且只能在它自己的家族内可见
- IsNestedAssembly --是否是嵌套的并且只能在它自己的程序集内可见
- IsNestedFamANDAssem --是否是嵌套的并且只对同时属于自己家族和自己程序集的类可见
- IsNestedFamORAssem --是否是嵌套的并且只对属于它自己的家族或属于它自己的程序集的类可见
使用代码生成工具Database2Sharp快速生成工作流模块控制器和视图代码
在前面随笔《基于Metronic的Bootstrap开发框架--工作流模块功能介绍》和《基于Metronic的Bootstrap开发框架--工作流模块功能介绍(2)》中介绍了Bootstrap开发框架的工作模块功能,前面文章也提及,通过代码生成工具直接生成对应的Create、ViewDetail、Index视图代码和控制器代码,本篇随笔介绍如何使用使用代码生成工具Database2Sharp快速生成工作流模块控制器和视图代码的过程。
1、工作流界面功能
工作流模块如果要增加一个业务表单的处理,那么界面包括了列表界面,创建和编辑申请单界面,查看申请单明细这几个界面,以及对应后台控制器的代码。其他共用的界面和代码,则是在整个工作流模块中通用的,不需要变化。
我们来关注下如果增加一个业务表单的情况下,需要的列表界面,创建和编辑申请单界面,查看申请单明细这几个界面。
这些使用代码生成工具Database2Sharp快速生成工作流模块界面,是集成了我们整个工作流处理方式,包括列表界面可以分页查询数据、编辑表单中选择用户、处理附件,以及查看明细界面中集成的各种流程处理步骤,包括审批、会签、退回、拒绝、查看流程日志、打印表单等等常规处理步骤。
2、使用代码生成工具Database2Sharp快速生成工作流界面
和常规的代码生成工具生成代码一样,我们打开代码生成工具,然后展开数据库表后,通过菜单的【Boostrap的Web界面代码生成】生成对应的代码即可。
通过选中对应的数据库表,就可以继续一步步处理了,最后确认代码生成即可。
生成代码后,我们可以看到在对应的目录有两个目录,MVCWebUI和WorkflowWebUI目录,如下所示。
两个目录MVCWebUI和WorkflowWebUI,其中MVCWebUI包含了常规Bootstrap框架的页面视图和控制器代码文件,如下所示。
而WorkflowWebUI目录则是我们这里需要重点关注的工作流视图页面代码文件,如下所示。
上面各个目录是对应我们业务表的内容,目录下面是有几个工作流模块中包括了列表界面,创建和编辑申请单界面,查看申请单明细这几个界面。
3、在项目中集成工作流界面代码
这几个工作流界面我们连同他们的目录一同复制到项目的视图目录里面即可,同时把常规Bootstrap界面中控制器复制到项目的控制器目录即可。
上面红框中就是我们一些工作流业务表单的视图目录,因此我们需要看看目录下面的几个文件。
集成这些页面代码后,我们还需要做一些基础的处理才能使用起来,就是需要定义一个业务表单信息。
1)流程模板定义
流程模板是我们开展一些工作流的基础,也就是说,我们先有特定流程的模板,然后才有具体的流程示例。
流程模板需要指定它的具体名称,另外有几个字段是必须注意的,就是它的对应业务表名和创建流程URL、查看流程URL这几个信息。
定义流程模板基本信息后,我们需要为这个流程模板设置对应的步骤,如下所示是增加一些流程步骤。
2)修改列表界面的表单ID
定义一个新的流程模板后,由于我们在流程管理界面中需要创建对应的申请单,那么我们需要知道这个流程模板的表单ID,因此需要在上面生成的工作流index.cshtml页面里面修改一个表单ID
创建定义完毕流程模板后,我们打开对应的表单记录,找到对应的表单ID
然后修改对应列表界面的formId为这个流程模板ID即可。
至此,这样整个界面就可以跑起来,而且也可以在列表页面里面直接创建对应表单的流程,类似下面的创建申请单界面。
创建业务申请单,那么也可以在业务受理列表里面创建。
专注于Winform开发框架/混合式开发框架、Web开发框架、Bootstrap开发框架、微信门户开发框架的研究及应用。
转载请注明出处:
撰写人:伍华聪 http://www.iqidi.com
C# ADO.NET的SqlDataReader对象,判断是否包含指定字段
在使用ado.net的SqlDataReader对象时,如果SqlDataReader实例对象中没有对应的字段,则会在那一行报错.而SqlDataReader类又没有判断是否存在指定字段的方法,怎么办呢?
我们只好自己写个判断是否包含指定字段的方法了...
上代码:
1 /// <summary> 2 /// SqlDataReader对象是否包含此字段 3 /// </summary> 4 /// <param name="dataReader">SqlDataReader实例对象</param> 5 /// <param name="columnName">字段名称</param> 6 /// <returns></returns> 7 private static bool readerExists(SqlDataReader dataReader, string columnName) 8 { 9 for (int i = 0; i < dataReader.FieldCount; i++) 10 { 11 if (dataReader.GetName(i).Equals(columnName)) 12 { 13 return true; 14 }; 15 } 16 return false; 17 }
本人在网上搜索了很多方法,个人认为这个方法效率相较于其他方法比较理想,而且一目了然,比较好理解.当然,如果有更好的方法,欢迎大家在评论区分享出来.
方法一,使用a标签添加
- 通过设置a标签的href属性,跳转到页面中指定id标签的位置
- a标签的href属性值前要增加#来作为标识,表示是在当前页面的内部跳转
简单的案例:
1 <html> 2 <head></head> 3 <body> 4 <!--设置锚点的a标签--> 5 <a href='#miao'>跳一跳</a> 6 <br /> 7 <!--跳转到的锚点位置--> 8 <h3 id='miao'>跳到这里..</h3> 9 </body> 10 </html>
此方法的弊端有很多,比如会改变地址栏参数,跳转比较突兀,对用户不友好等...
所以,如果你比较注重细节,有这方面的强迫症,建议使用下面这种方法.
方法二,使用jQuery的animate动画跳转
废话不说,先上代码:
1 <html> 2 <head> 3 <title></title> 4 <script> 5 $(document).ready(function () { 6 //点击触发事件 7 $("#jumpNow").click(function () { 8 $("html,body").animate({ 9 scrollTop: $("#imhere").offset().top//跳转到的位置 10 }, { 11 duration: 400,//预定速度 12 easing: "swing",//动画效果.swing:在开头/结尾移动慢,在中间移动快;"linear": 匀速移动 13 }); 14 }); 15 16 }); 17 </script> 18 </head> 19 20 <body> 21 <!--设置锚点的标签--> 22 <span id='jumpNow'>跳一跳</span> 23 <br /> 24 <!--跳转到的锚点位置--> 25 <h3 id='imhere'>跳到这里...</h3> 26 </body> 27 28 </html>
jQuery的animate是实现页面动画的函数,功能比较强大,实现一个锚点跳转绰绰有余.想学习animate函数的小伙伴可点击参考此文档:https://www.cnblogs.com/yixiaoheng/p/3505638.html
此方法可以控制动画跳转的速度和方式,并且不会改变地址栏的参数,相对来说比较优雅.墙裂建议使用此方法!over...
.net 简单实用Log4net(多个日志配置文件)
开发工具vs2017
【注意项目名称不要为log4net,否者在之后配置log4net出错】
1】NuGet中引用Log4net包
2】添加web配置文件命名为log4new.config并在中写入以下代码
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> </system.web> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net-net-1.2" /> </configSections> <!--配置log4net--> <log4net> <root> </root> <logger name="RollingLogFileAppender"> <level value="ALL" /> <appender-ref ref="RollingFileDebug" /> <appender-ref ref="RollingFileInfo" /> <appender-ref ref="RollingFileWarn" /> <appender-ref ref="RollingFileError" /> <appender-ref ref="RollingFileFatal" /> </logger> <appender name="RollingFileDebug" type="log4net.Appender.RollingFileAppender"> <!--文件路径 如果不设置(去掉 value="Log/Debug/")会默认保存到[App_Data]文件夹中--> <param name="File" value="Log/Debug/"/> <!--追加到文件--> <param name="AppendToFile" value="true"/> <!--最多保留的文件数,设为"-1"则不限--> <param name="MaxSizeRollBackups" value="10"/> <!--写到一个文件--> <param name="StaticLogFileName" value="false"/> <!--文件名,按日期命名--> <param name="DatePattern" value="yyyyMMdd".log""/> <!--创建日志文件的方式,可选值:Date[日期],文件大小[Size],混合[Composite]--> <param name="RollingStyle" value="Date"/> <!--日志格式--> <layout type="log4net.Layout.PatternLayout"> <!--%newline输出的日志会换行 [%date{HH:mm:ss fff}]表示记录的时间 --> <conversionPattern value="[%date{HH:mm:ss fff}] %- %message%newline" /> <!--如果想自己设置格式就只需要--> <!--<conversionPattern value="%message"/>--> </layout> <lockingmodel type="log4net.appender.fileappender+minimallock" /> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="Debug" /> <param name="LevelMax" value="Debug" /> </filter> </appender> <appender name="RollingFileInfo" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log/Info/"/> <param name="AppendToFile" value="true"/> <param name="MaxSizeRollBackups" value="10"/> <param name="StaticLogFileName" value="false"/> <param name="DatePattern" value="yyyyMMdd".log""/> <param name="RollingStyle" value="Date"/> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="[%date{HH:mm:ss fff}] %- %message%newline" /> </layout> <lockingmodel type="log4net.appender.fileappender+minimallock" /> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="INFO" /> <param name="LevelMax" value="INFO" /> </filter> </appender> <appender name="RollingFileWarn" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log/Warn/"/> <param name="AppendToFile" value="true"/> <param name="MaxSizeRollBackups" value="10"/> <param name="StaticLogFileName" value="false"/> <param name="DatePattern" value="yyyyMMdd".log""/> <param name="RollingStyle" value="Date"/> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="[%date{HH:mm:ss fff}] %- %message%newline" /> </layout> <lockingmodel type="log4net.appender.fileappender+minimallock" /> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="WARN" /> <param name="LevelMax" value="WARN" /> </filter> </appender> <appender name="RollingFileError" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log/Error/"/> <param name="AppendToFile" value="true"/> <param name="MaxSizeRollBackups" value="10"/> <param name="StaticLogFileName" value="false"/> <param name="DatePattern" value="yyyyMMdd".log""/> <param name="RollingStyle" value="Date"/> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="[%date{HH:mm:ss fff}] %- %message%newline" /> </layout> <lockingmodel type="log4net.appender.fileappender+minimallock" /> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="ERROR" /> <param name="LevelMax" value="ERROR" /> </filter> </appender> <appender name="RollingFileFatal" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log/Fatal/"/> <param name="AppendToFile" value="true"/> <param name="MaxSizeRollBackups" value="10"/> <param name="StaticLogFileName" value="false"/> <param name="DatePattern" value="yyyyMMdd".log""/> <param name="RollingStyle" value="Date"/> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="[%date{HH:mm:ss fff}] %- %message%newline" /> </layout> <lockingmodel type="log4net.appender.fileappender+minimallock" /> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="FATAL" /> <param name="LevelMax" value="FATAL" /> </filter> </appender> </log4net> </configuration>
4】在Global.asax中添加代码
//配置log4 log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(Server.MapPath("~/Web.config")));
5】写一个 LogHelper.cs但是注意在这个.cs文件的命名空间写上 [assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]
注意ConfigFile 后面跟的是配置文件的相对路径
LogHelper代码
public class LogHelper { public static readonly ILog Log = LogManager.GetLogger("RollingLogFileAppender"); // log4j定义了8个级别的log(除去OFF和ALL,可以说分为6个级别),优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。 //ALL 最低等级的,用于打开所有日志记录。 //DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。 //INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息, //WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。 //ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。 //FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。 //TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志级别,一般不会使用。 //OFF 最高等级的,用于关闭所有日志记录。 //如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。例如,如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出,而INFO、DEBUG、TRACE、 ALL级别的log则会被忽略。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。 #region DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。 public static void debug(string write) { Log.Debug("日志记录:" + write); } public static void debug(string write, Exception ex) { Log.Debug("日志记录:" + write + "。错误记载:" + ex.ToString()); } #endregion #region INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息, /// <summary> /// 1 /// </summary> /// <param name="write"></param> public static void Info(string write) { Log.Info("日志记录:" + write); } public static void Info(string write, Exception ex) { Log.Info("日志记录:" + write + "。错误记载:" + ex.ToString()); } #endregion #region WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。,可以使用这个级别。 public static void warn(string write) { Log.Warn("日志记录:" + write); } public static void warn(string write, Exception ex) { Log.Warn("日志记录:" + write + "。错误记载:" + ex.ToString()); } #endregion #region ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。 public static void error(string write) { Log.Error("日志记录:" + write); } public static void error(string write, Exception ex) { Log.Error("日志记录:" + write + "。错误记载:" + ex.ToString()); } #endregion #region FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。 public static void fatal(string write) { Log.Fatal("日志记录:" + write); } public static void fatal(string write, Exception ex) { Log.Fatal("日志记录:" + write + "。错误记载:" + ex.ToString()); } //#endregion #endregion //定义输出的日志内容 public static string logMessage(SysLogMsg logMessage) { StringBuilder strInfo = new StringBuilder(); strInfo.Append("\r\n1. 错误: >> 操作时间: " + logMessage.OperationTime + " 操作人: " + logMessage.UserName + " \r\n"); strInfo.Append("2. 类名: " + logMessage.Class + " \r\n"); strInfo.Append("3. 内容: " + logMessage.Content + "\r\n"); strInfo.Append("-----------------------------------------------------------------------------------------------------------------------------\r\n"); return strInfo.ToString(); } }
6】最后可以开始写日志 ,可以自己定义一种日志格式【自己写一个model,定义几个参数 时间、操作人、类名等等】
最后根据log4new.config设置的路径找到日志所在【未定义的话一般在项目的bin/Debug 或者 bin/Release目录下】 第一个是默认格式日志,第二个是自己定的格式日志【都是string类型,只不过第二个使用\t\n换行,稍微美化一下输出格式】
<param name="File" value="Log/Debug/"/> 表示在当前文件夹Log/Debug中
C# 常用小点
1】创建文件夹
//相对路径 string FilePath = Server.MapPath("./") + "ImageFile/Images/" + DateTime.Now.Year.ToString() + "Year/" + DateTime.Now.Month.ToString() + "Month/"; DirectoryInfo di = new DirectoryInfo(FilePath); if (!di.Exists)//如果没有就创建 {di.Create();} //绝对路径 string receivePath = @"C:/ImageFile/Images/" + DateTime.Now.Year.ToString() + "Year/" + DateTime.Now.Month.ToString() + "Month/"; DirectoryInfo di = new DirectoryInfo(receivePath); if (!di.Exists) { di.Create(); }