详解C#泛型(二)

 

  一、自定义泛型方法(Generic Method),将类型参数用作参数列表或返回值的类型:

void MyFunc<T>() //声明具有一个类型参数的泛型方法
{
    Type genericType = typeof(T); //在泛型方法体内部获取类型参数的类型信息
    //do…
}
//调用泛型方法
MyFunc<int>();

  1.声明泛型方法时,可以在参数列表中使用这个类型参数:void MyFunc<T>(T obj) { };此时在调用该泛型方法时可以省略类型参数的指定由编译器推断其类型,例如:MyFunc<int>(myNum)与MyFunc(myNum)完全等效;

  2.声明泛型方法时,可以在返回值类型中使用这个类型参数:T MyFunc<T>() { return default(T); };编译器的类型推断功能不适用于仅在返回值类型中使用类型参数的情况,此种情况在调用时必须显式指定类型参数;

  3.声明泛型方法时,方法的参数列表和返回值可以指定相同或不相同的类型参数,如果不相同则一般参数列表的类型参数在前,返回值的类型参数在后:U MyFunc<T, U>(T obj) { return default(U); },此时依然不能使用编译器的类型推断功能;如果相同,则在调用时可以使用编译器的类型推断功能省略类型参数的显式指定;
类型参数数量的不同,可以构成重载方法:

void MyFunc() { }
void MyFunc<T>() { }
void MyFunc<T, U>() { }

  4.泛型方法中的类型参数也可以指定约束;

  5.在泛型类中声明的方法,方法的参数列表和返回值可以使用泛型类的类型参数作为类型;泛型方法可出现在泛型或具体类型中,只有当方法有属于自己的类型参数时才是泛型方法,在泛型类中声明泛型方法时,二者类型参数的占位符不可以相同:

class MyClass<T> //声明一个泛型类,类型参数占位符为T
{
    void MyFunc(T obj) //声明一个非泛型方法,使用泛型类的类型参数T作为参数类型
    {
        //do…
    }
    //不能声明泛型方法void MyFunc<T>,泛型方法的类型参数占位符不能与类的类型参数占位符相同
    void MyFunc<U>(T obj1, U obj2) //声明一个泛型方法,类型参数占位符为U
    {
        //do…
    }
}
class MyClass //定义一个具体类
{
    void MyFunc<T>(T obj) //声明一个泛型方法
    {
        //do…
    }
}            

  二、自定义泛型接口(Generic Interface),将类型参数用作参数列表或返回值的类型:

interface IMyInterface<T> //定义具有一个类型参数的泛型接口
{
    void MyFunc(T obj); //声明参数为T的方法
}
//声明泛型类继承自泛型接口
public class MyClass<T> : IMyInterface<T>
{
    public void MyFunc(T obj)
    {
        //do…
    }
}
//指定类型参数为string类型,创角泛型类的实例赋值给泛型接口的变量
IMyInterface<string> iMyInterface = new MyClass<string>();

  1.适用于泛型类的规则基本也适用于泛型接口;

  三、自定义泛型委托(Generic Delegate),将类型参数用作参数列表或返回值的类型:

delegate void MyDelegate<T>(T obj); //定义具有一个类型参数的泛型委托,参数列表中有一个参数
void MyGenericFunc<T>(T obj) //声明一个泛型方法,参数列表中有一个参数
{
    //do…
}
void MyFunc(string str)
{
    //do…
}
//声明泛型委托的实例,指定类型参数为string类型,此时可匹配的方法签名为void myFunc(string str)
MyDelegate<string> myDelegate;
//赋值一个指定类型参数为string的泛型方法
myDelegate = MyGenericFunc<string>;
//添加一个参数列表为string类型的具体方法
myDelegate += MyFunc;

  1.泛型委托同泛型类一样,需要在实例化时指定类型参数的类型;

  2.泛型委托的实例同具体委托的实例一样,只需要方法的参数列表和返回值类型相同即可进行匹配,因此不管目标方法是指定了符合要求类型的泛型方法还是具体方法都可以进行匹配; 

  四、反射中的泛型:

Type myType = typeof(MyClass<>); //获取未指定任何类型参数的开放式构造类的类型信息,多个类型参数时添加,:typeof(MyClass<,>)
myType = myType.MakeGenericType(typeof(int)); //通过类型信息的实例方法MakeGenericType()构建指定所有类型参数的封闭式构造类的类型信息,如未指定所有类型参数会抛出异常ArgumentException
//也可以直接获取封闭式构造类的类型信息,当类型参数在一开始就确定时推荐使用此种方式
//myType = typeof(MyClass<int>); //多个类型参数时需要同时指定:typeof(MyClass<int, string>)

  1.通过反射只可以获取未指定任何类型参数的开放式构造类的类型信息和指定所有类型参数的封闭式构造类的类型信息,即无法获取MyClass<int, >的类型信息;

获取C#中方法的执行时间及其代码注入

 

  在优化C#代码或对比某些API的效率时,通常需要测试某个方法的运行时间,可以通过DateTime来统计指定方法的执行时间,也可以使用命名空间System.Diagnostics中封装了高精度计时器QueryPerformanceCounter方法的Stopwatch类来统计指定方法的执行时间:

  1.使用DateTime方法:

DateTime dateTime = DateTime.Now;
MyFunc();
Console.WriteLine((DateTime.Now - dateTime).TotalMilliseconds);

  2.使用Stopwatch方式:

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
MyFunc();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds); //本次MyFunc()方法的运行毫秒数
//重置计时器
stopwatch.Restart(); //此处可以使用stopwatch.Reset(); stopwatch.Start();组合代替
MyFunc();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds); //本次MyFunc()方法的运行毫秒数

  以上两种办法都可以达到获取方法执行时间的目的,但是在需要对整个项目中的方法都进行监测用时时,除了使用性能分析工具,我们还可以通过代码注入的方式给程序集中每一个方法加入计时器;

  通过命名空间System.Reflection.Emit中的类可以动态的创建程序集、类型和成员,通常类库Mono.Cecil可以动态读取并修改已经生成的IL文件,这种在不修改源代码的情况下给程序集动态添加功能的技术称为面向切面编程(AOP);

  这里给出了一个注入使用Stopwatch来检测方法执行时间的代码,这里的Mono.Cecil类库可以通过nuget进行安装:

using System;
using System.IO;
using System.Linq;
using System.Diagnostics;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
    static void Main(string[] args)
    {
        for (int i = 0; i < args.Length; i++)
        {
            FileStream fileStream = new FileStream(args[i], FileMode.Open);
            if (fileStream != null)
            {
                AssemblyDefinition aD = AssemblyDefinition.ReadAssembly(fileStream);
                ModuleDefinition mD = aD.MainModule;
                Collection<TypeDefinition> typeDefinition = mD.Types;
                foreach (TypeDefinition type in typeDefinition)
                {
                    if (type.IsClass)
                    {
                        foreach (MethodDefinition method in type.Methods)
                        {
                            if (method.IsPublic && !method.IsConstructor)
                            {
                                ILProcessor il = method.Body.GetILProcessor();
                                TypeReference stT = mD.ImportReference(typeof(Stopwatch));
                                VariableDefinition stV = new VariableDefinition(stT);
                                method.Body.Variables.Add(stV);
                                Instruction first = method.Body.Instructions.First();
                                il.InsertBefore(first, il.Create(OpCodes.Newobj,                       mD.ImportReference(typeof(Stopwatch).GetConstructor(new Type[] { }))));
                                il.InsertBefore(first, il.Create(OpCodes.Stloc_S, stV));
                                il.InsertBefore(first, il.Create(OpCodes.Ldloc_S, stV));
                                il.InsertBefore(first, il.Create(OpCodes.Callvirt,                      mD.ImportReference(typeof(Stopwatch).GetMethod("Start"))));

                                Instruction @return = method.Body.Instructions.Last();
                                il.InsertBefore(@return, il.Create(OpCodes.Ldloc_S, stV));
                                il.InsertBefore(@return, il.Create(OpCodes.Callvirt,                       mD.ImportReference(typeof(Stopwatch).GetMethod("Stop"))));

                                il.InsertBefore(@return, il.Create(OpCodes.Ldstr, $"{method.FullName} run time: "));
                                il.InsertBefore(@return, il.Create(OpCodes.Ldloc_S, stV));
                                il.InsertBefore(@return, il.Create(OpCodes.Callvirt,                       mD.ImportReference(typeof(Stopwatch).GetMethod("get_ElapsedMilliseconds"))));
                                il.InsertBefore(@return, il.Create(OpCodes.Box, mD.ImportReference(typeof(long))));
                                il.InsertBefore(@return, il.Create(OpCodes.Call,                       mD.ImportReference(typeof(string).GetMethod("Concat", new Type[] { typeof(object), typeof(object) }))));
                                il.InsertBefore(@return, il.Create(OpCodes.Call,                       mD.ImportReference(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }))));
                            }
                        }
                    }
                }
                FileInfo fileInfo = new FileInfo(args[i]);
                string fileName = fileInfo.Name;
                int pointIndex = fileName.LastIndexOf('.');
                string frontName = fileName.Substring(0, pointIndex);
                string backName = fileName.Substring(pointIndex, fileName.Length - pointIndex);
                string writeFilePath = Path.Combine(fileInfo.Directory.FullName, frontName + "_inject" + backName);
                aD.Write(writeFilePath);
                Console.WriteLine($"Success! Output path: {writeFilePath}");
                fileStream.Dispose();
            }
        }
        Console.Read();
    }

  完整的项目传到了Github上=>InjectionStopwatchCode,下载项目后,通过dotnet build命令即可编译出可执行程序,将目标程序集文件拖入到该应用程序即可在程序集目录导出注入代码后的程序集文件,经过测试,包括方法拥有返回值和方法的参数列表中包含out和ref参数等情况都不会对运行结果产生影响;

  示例:

using System;

public class MyClass
{
    public void MyFunc()
    {
        int num = 1;
        for (int i = 0; i < int.MaxValue; i++)
        {
            num++;
        }
    }
}
public class Program
{
    public static void Main(string[] args)
    {
        MyClass myObj = new MyClass();
        myObj.MyFunc();
        Console.Read();
    }
}

  原始IL代码:

  代码注入后IL代码:

  代码注入后运行结果:

详解C#泛型(一)

 

  一、C#中的泛型引入了类型参数的概念,类似于C++中的模板,类型参数可以使类型或方法中的一个或多个类型的指定推迟到实例化或调用时,使用泛型可以更大程度的重用代码、保护类型安全性并提高性能;可以创建自定义的泛型类型(类、结构、接口、委托)和泛型方法;

  1.在泛型类型的定义或泛型方法的声明中,类型参数是类型的占位符,这些占位符指代的类型需要在实例化泛型类型或调用泛型方法时进行指定;

  ※类型参数一般以T命名,如果是多个,使用T、U、V等,如果有指定约束,可以结合约束命名,例如需要继承自MyClass的类型参数命名为TMyClass;任何命名都并不会给类型参数增加额外作用;

  2.泛型是运行时起作用的一套机制,根据运行时类型参数被指定为值类型还是引用类型其使用方式有所不同:

  ※当类型参数被指定为值类型时,会在第一次指定该特定值类型的类型时创建该类型唯一的专用化泛型类型,泛型类型中的类型参数会被替换为相应的值类型;

  ※当类型参数被指定为引用类型时,会在第一次指定任意引用类型时创建一个通用化泛型类型,泛型类型中的类型参数会被替换为该引用类型,并在之后每次指定为引用类型时重用该泛型类型并修改其中类型参数的类型;造成这种差异的原因可能在于所有的引用大小相同;

  二、这篇我们先了解下泛型类,泛型类的定义中可以将类型参数用作成员变量的类型或方法中参数列表、返回值的类型:

class MyClass<T> //声明具有一个类型参数的泛型类,可以有多个类型参数,用,隔开:<T, U>
{
    public T MyObj; //声明T类型的字段
    private Type myGenericField = typeof(T); //在泛型类内部可以获取类型参数的类型信息
    //do…
}
//声明泛型类的实例,指定类型参数为int类型
MyClass<int> myObj = new MyClass<int>();

  1.在定义泛型类型时,可以对类型参数的种类添加限制,这些限制称为约束(Constraint),使用约束可以增加类型参数所能进行操作和调用方法的数量;约束使用上下文关键字where指定,位于基类和接口之后,例如:

class MyClass<T> : MyBaseClass where T : MyType //指定基类约束,T需要是指定的类MyType或继承自类MyType,基类约束需要在所有约束之前,基类约束本身也可以是泛型类型,例如MyType<T>

  ※其它特殊的约束:

  where T : IMyInterface //指定接口约束,T需要是指定的接口IMyInterface或实现接口IMyInterface,可以同时指定多个接口约束,接口约束本身也可以是泛型类型,例如IMyInterface<T>
  where T : class //指定类型约束,T需要是类类型
  where T : struct //指定类型约束,T需要是值类型,但不可以是可空类型
  where T : new() //类型参数必须有公共的无参数构造函数,与其他约束一起使用时,new()约束必须最后指定;由于结构的定义中一定包含无参数构造函数,所以struct约束包含new()约束,二者不可同时使用,通常与class约束一起使用:class, new()
  where T : struct where U : class //给多个类型参数指定约束
  where U : T //类型参数作为约束,类型参数U继承自类型参数T

  ※可以对一个类型参数应用多个约束,多个约束使用,隔开:where T : MyType, IMyInterface;

  ※没有约束的类型参数称为未绑定类型参数(Unbounded Type Parameter),这些类型参数的变量在使用时不可以使用==和!=运算符,因为无法保证运行时指定的类型支持这些运算符;

  ※对于使用类型约束class的类型参数,应避免对其变量使用==和!=运算符,因为在泛型中这些运算符仅会根据引用来判断是否相等,即使类型对==和!=运算符进行了重载也不行;如果必须根据值进行判断,应给类型参数加入基类约束IEquatable<T>或IComparable<T>并在类型中实现它们,比较时使用Equals或CompareTo;

  ※从C#7.3开始,可以使用特殊类型System.Delegate、System.MulticastDelegate和System.Enum作为基类约束中的基类,还可以使用非托管类型unmanaged作为类型约束中的类型;

  2.非泛型类(即具体类,Concrete Class)只可以继承自具体类或封闭式构造类(即指定了所有类型参数的泛型类,Closed Constructed Class),不可以继承自开放式构造类(即没有完全指定所有类型参数的泛型类,Open Constructed Class);泛型类可以继承自具体类和封闭式构造类,也可以继承自开放式构造类,继承自开放式构造类时,派生类的类型参数中必须包含基类中未指定类型的类型参数,同时,派生类中这些类型参数的约束必须为基类中对应类型参数约束的超集;可以总结为以下几种情况:

//对于基类为具体类或仅有一个类型参数的情况
class BaseClass { }
class BaseGenericClass<T> { }
//定义一个泛型类,继承具体类BaseClass
class MyClass<T> : BaseClass { }
//定义一个泛型类,继承自指封闭式构造类BaseGenericClass<int>
class MyClass<T> : BaseGenericClass<int> { }
//定义一个泛型类,继承自开放式构造类BaseGenericClass<T>
class MyClass<T> : BaseGenericClass<T> { }
//定义一个非泛型类,继承自封闭式构造类BaseGenericClass<int>
class MyClass : BaseGenericClass<int> { }
//对于基类有多个类型参数的情况
class MultipleBaseGenericClass<T, U> { }
//定义一个泛型类,继承自开放式构造类MultipleBaseGenericClass<T, int>
class MyClass<T> : MultipleBaseGenericClass<T, int> { }
//定义一个泛型类,继承自开放式构造类MultipleBaseGenericClass<T, U>
class MyClass<T, U> : MultipleBaseGenericClass<T, U> { }
//定义一个非泛型类,继承自封闭式构造类MultipleBaseGenericClass<int, string>
class MyClass : MultipleBaseGenericClass<int, string> { }

  ※在继承中,派生类类型的对象可以通过隐式转换赋值给基类类型的变量,这同样适用于泛型类型的对象,例如泛型类List<T>继承自泛型接口IList<T>,那么可以把List<int>类型的对象隐式转换为IList<int>类型的变量:

IList<int> iList = new List<int>();

  3.泛型类为不可变量,泛型类型相同但类型参数指定类型不同的泛型类型即是不同的类型,它们之间不能进行类型转换(即使类型参数指定的类型之间存在类型转换关系),例如不能把List<DerivedClass>类型的对象赋值给一个List<BaseClass>类型的变量;

  4.泛型类最常见的用途是创建泛型集合类,在命名空间System.Collections.Generic中包含系统定义的各种泛型集合类,应尽可能的使用这些泛型集合来代替普通的集合;

详解C#委托和事件(二)

 

  一、当我们使用关键字delegate声明一个自定义委托类型时,实际上是声明了一个该名称的类类型,继承自抽象类System.MulticastDelegate,还包含实例方法Invoke、BeginInvoke、EndInvoke:

  public delegate void MyDelegate();

  

  其中的构造函数中第二个参数是native int类型的,这个是什么呢?我们接着看:

  我们知道在C#中任何方法都可以直接赋值给签名一致的委托实例,这个过程看似并不合理,按理来说C#中不支持直接获取函数的指针,其实这里是由编译器进行了取址操作,查看IL代码可知:

  MyDelegate myDelegate = myObj.MyFunc;

  

  可以看到由编译器为我们进行了构建委托实例的过程,而且这里调用了ldftn命令将实例方法MyFunc()的native int类型的非托管指针推到栈中,从而将该方法的指针传到委托的构造函数中;

  由于上面的构造函数存在C#中不支持的函数指针类型void(),所以不能在运行时使用Activator类中的方法创建委托实例,但在委托基类Delegate中存在静态方法CreateDelegate()调用非托管代码用于动态创建委托实例,命名空间System.Reflection中的方法信息类MethodInfo的实例方法CreateDelegate()也提供了类似的方式以在运行时动态构建委托实例:

    Type delegateType = typeof(MyDelegate);  //这里以可访问到的委托类型举例
    Delegate @delegate = Delegate.CreateDelegate(delegateType, myObj, "MyFunc");
    //@delegate = typeof(MyClass).GetMethod("MyFunc").CreateDelegate(delegateType, myObj);
    //添加其它委托实例
    @delegate = Delegate.Combine(@delegate, otherDelegate);
    //调用委托
    @delegate.DynamicInvoke();
    //当指定的委托类型可访问时,可以将委托实例显式转换为指定的委托类型后使用()或Invoke()正常调用
    //MyDelegate myDelegate = @delegate as MyDelegate;
    //myDelegate();

  对委托实例或方法的+、+=操作实际上也是调用基类Delegate中的静态方法Combine()并将合成后的委托强制转换为原类型后返回,-、-=操作则是调用静态方法Remove();

   

  二、委托的异步调用:通过委托类型的实例方法BeginInvoke开启子线程并在该子线程中执行委托实例中的方法,以此种方式调用的委托实例中有且只能有一个方法,如果包含多个方法,会抛出异常ArgumentException:

  myDelegate.BeginInvoke(null, null); //其中第一个参数为AsyncCallback类型的回调函数

  如果需要异步调用一个委托实例中方法列表中的所有方法,需要先获取方法列表,再依次进行异步调用:

  Delegate[] delegates = myDelegate.GetInvocationList();
  for (int i = 0; i < delegates.Length; i++)
  {
    (delegates[i] as MyDelegate).BeginInvoke(null, null);
  }

  三、当调用委托时,如果方法列表中某个方法内引发异常且未在该方法体内捕获时,该异常将传递给委托的调用方,并且不再调用方法列表中的后面的方法,因此在方法体内捕获异常显得尤为重要;

  四、泛型中的委托:自定义泛型委托(Generic Delegate),将类型参数用作参数列表或返回值的类型:

  delegate void MyDelegate<T>(T obj); //声明具有一个类型参数的泛型委托,参数列表中有一个参数
  void MyGenericFunc<T>(T obj) //声明一个泛型方法,参数列表中有一个参数
  {
    //do…
  }
  void MyFunc(string str)
  {
    //do…
  }
  //声明泛型委托的实例,指定类型参数为string类型,此时可匹配的方法签名为void myFunc(string str)
  MyDelegate<string> myDelegate;
  //赋值一个指定类型参数为string的泛型方法
  myDelegate = MyGenericFunc<string>;
  //添加一个参数列表为string类型的具体方法
  myDelegate += MyFunc;

  ※泛型委托同泛型类一样,需要在实例化时指定类型参数的类型;

  ※泛型委托的实例同具体委托的实例一样,只需要方法的参数列表和返回值类型相同即可进行匹配,因此不管目标方法是指定了符合要求类型的泛型方法还是具体方法都可以进行匹配;

详解C#特性和反射(四)

 

  本篇内容是特性和反射的最后一篇内容,前面三篇文章:

详解C#特性和反射(一)

详解C#特性和反射(二)

详解C#特性和反射(三)

  一、晚期绑定(Late Binding)是一种在编译时不知道类型及其成员,而在运行时创建指定类型的实例并调用其成员的技术,使用命名空间System中的Activator类来实现晚期绑定,例如:

    Type myType = Type.GetType(myClassName);  //首先获取类型信息
    object myObj = Activator.CreateInstance(myType);  //通过Activator根据类型信息创建对象
    //如果获取的类型可以获取到,即位于当前程序集,可以使用强制转换或as运算符得到该类型的变量,然后直接调用该对象的成员:
    //MyClass myClass = (MyClass)myObj;
    //也可以使用CreateInstance的泛型重载方法直接获取该类型的变量:
    //MyClass myClass = Activator.CreateInstance<MyClass>();
    //如果获取的类型不确定或当前程序编译时并没有引入该类型所在的命名空间,则需要通过反射访问该类型的成员:
    MethodInfo methodInfo = myType.GetMethod(methodName);  //获取类型中的方法信息
    methodInfo.Invoke(myObj, null);  //调用类型中的无参实例方法

  ※静态方法CreateInstance会调用匹配度最高的构造函数构建对象,例子中的CreateInstance(Type type)和CreateInstance<T>()只会调用目标类型中无参的公有构造函数,当目标类型中没有无参的公有构造函数时会抛出异常MissingMethodException;Activator类中提供多个CreateInstance的重载方法以调用不同的构造函数并传入参数,这里不再一一介绍,用到时查看定义即可看到所有的方法声明;

  二、使用晚期绑定技术也会打破单例模式中的对象唯一性:

    public class MyClass
    {
        public static MyClass Instace = new MyClass();
        private MyClass()
        {
            Console.WriteLine("MyClass's Constructor Execute.");
        }
        public int MyFiled;
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Access the static member Instace of MyClass.");
            MyClass.Instace.MyFiled = 10;
            Console.WriteLine("Use the Activator class to create instance of MyClass.");       //通过CreateInstance的重载方法调用目标类型的私有构造函数
            MyClass myObj = (MyClass)Activator.CreateInstance(typeof(MyClass), true);
            Console.WriteLine($"Instace of MyClass's MyFiled is {MyClass.Instace.MyFiled}, myObj's MyFiled is {myObj.MyFiled}.");
            Console.Read();
        }
    }

  输出结果为(在.Net Core下运行,不同平台下静态成员的初始化时机略有不同,会导致这里第一行和第二行输出顺序不一致):

Access the static member Instace of MyClass.
MyClass's Constructor Execute.
Use the Activator class to create instance of MyClass.
MyClass's Constructor Execute.
Instace of MyClass's MyFiled is 10, myObj's MyFiled is 0.

因此在使用单例模式时,应当避免使用晚期绑定创建对象,而应使用反射获取单例中的静态成员Instance;

记一次.net core调用SOAP接口遇到的问题

 

背景

最近需要将一些外部的Web Service及其他SOAP接口的调用移到一个独立的WebAPI项目中,然后供其他.Net Core项目调用。之前的几个Web Service已经成功迁移,但是在迁移一个需要用户名密码认证的SOAP接口的时候却始终调用不成功。下面直接上代码。

示例代码

.net framework中通过添加服务引用会自动在web.config(或者app.config)中生成类似以下绑定配置:

<system.serviceModel>
  <bindings>
    <customBinding>
      <binding name="binding">
        <mtomMessageEncoding ="Soap11WSAddressing10" />
        <httpTransport authenticationScheme="Basic"/>
      </binding>
    </customBinding>
  </bindings>
  <client>
    <endpoint address="http://sampl.url" binding="customBinding" bindingConfiguration="binding" contract="TestClient" name="binding" />
  </client>
</system.serviceModel>

可以直接调用client的无参构造函数(会从配置中读取相应的配置)来获取客户端实例,但是由于.net core中已经不支持web.config(或者app.config),因此需要自己通过代码来创建bindingEndpointAddress来获取客户端实例

var binding = new CustomBinding(new HttpTransportBindingElement
{
    AuthenticationScheme = AuthenticationSchemes.Basic
});

var client = new TestClient(binding, new EndpointAddress(new Uri("http://sample.url")));
client.ClientCredentials.UserName.UserName = "admin";
client.ClientCredentials.UserName.Password = "123456";

var response = client.Add(new Request()).Result

发现问题

在测试过程中始终抛异常The server returned an invalid or unrecognized response.。使用上面相同的代码在.net framework里测试却能正常获取响应,初步判断应该是.net core中的问题。通过wireshark工具抓包比对,我发现了差别。

请求正常的抓包截图

请求失败的抓包截图

通过两个请求的比对发现,失败的请求头部信息中没有Authorization信息。接着我使用HttpClient发送post请求来模拟对SOAP接口的调用。


httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "YWRtaW46MTIzNDU2==");

在设置和不设置Authorization的分别测试中发现设置了Authorization能够成功请求并获得响应,不设置Authorization的请求获得了一段html格式的文本响应,其中有一段很说明问题HTTP 401 - Unauthorized。在这两个不同的请求抓包中也发现成功的请求头部中是包含Authorization信息的,另一个则没有。因此基本断定问题就出现在这里。

如何解决

问题找到了,是因为请求头部中缺少Authorization信息,但是如何解决我却始终没有找到好的办法,SOAP接口的调用不像使用HttpClient发送post请求可以对header进行修改。直接使用HttpClient发送post请求来调用SOAP接口对XML的序列化和反序列化又很是麻烦,也不想使用这种过于牵强的做法。翻遍了博客园和stackoverfolw也始终没有找到解决办法。

终于,在我不懈的努力中看到了曙光,在github上翻dotnet/wcfIssues的时候找到一些相关东西,特别是这一条https://github.com/dotnet/wcf/issues/3008。其中提到System.Private.ServiceModel版本高于或等于4.5.0的时候会抛异常The server returned an invalid or unrecognized response.,虽然他的使用跟我的不太一样,但是异常信息却相同。我当即将版本降到4.4.4再次测试,结果令人惊喜,请求成功了,通过抓包分析Authorization信息在请求头部中。这证实我之前的判断,就是因为没有Authorization信息导致请求失败。

结语

问题解决了,System.Private.ServiceModel4.5.0及以上版本会存在此问题,降级到4.4.4方可解决,希望微软早日修复此问题,在NuGet包管理中看到有更新但又不能更新的包是一件很不爽的事情!

C# WebRequest.Create 锚点“#”字符问题

 

背景

在与后台API接口对接时,如将网页Url作为参数请求数据时,如果是锚点参数,则会丢失。

锚点参数

请求通过WebRequest.Create创建一个WebRequest:

1 var uri = "https://id.test.xxx.com/api/v1/auth/sso/url?redirectUrl=https://test.xxx.com/extend/userhelp#120";
2 var webRequest = WebRequest.Create(uri);

得到的webRequest,发现后面的锚点参数没有了。如下图:

获取的webRequest,Address和RequestUri中的Query参数,不包含锚点参数“#120”

正常的查询参数

我们换正常的查询参数,再试验如上步骤

通过WebRequest.Create创建一个WebRequest:

1 var uri = "https://id.test.xxx.com/api/v1/auth/sso/url?redirectUrl=https://test.xxx.com/extend/userhelp?id=120";
2 var webRequest = WebRequest.Create(uri);

得到的webRequest,查询Address和RequestUri中的Query参数。如下图:

后面的查询参数"?id=120",数据正常。

总结

原因:WebRequest.Create方法中,锚点参数会在另一属性中出现,

如果有锚点参数,并调用WebRequest.Create,临时解决方案:

  1. 在调用WebRequest.Create时Uri不添加锚点参数,调用完后再自行拼接锚点参数。
  2. 从WebRequest.RequestUri.Fragment中获取锚点参数,重新组合成Uri.

值得注意的是,生成Request后,请求服务器API接口,默认后台是不会处理锚点信息的。所以如果需要支持锚点,需要API接口支持。

根据内容来产生一个二维码

 

实现的功能以及效果如下:

样式代码:

.divtable {
    display: table;
    border-collapse: collapse;
    border-spacing: 0;
    margin-right: auto;
    margin-left: auto;
    width: 100%;
}

.divth, .divtr {
    display: table-row;
    vertical-align: middle;
    height: 20px;
    padding-left: 2px;
    padding-right: 2px;
}

.divth {
    background-color: #efebde;
    text-align: center;
}

.divtd {
    display: table-cell;
    border: 1px solid #c0c0c0;
}

前端ASP.NET 代码:

<div class="divtable" >
                    <div class="divtr">
                        <div class="divtd">
                            内容
                        </div>
                        <div class="divtd" >
                            <asp:TextBox ID="TextBoxQRCode" runat="server" Width="500" TextMode="MultiLine" Rows="20"></asp:TextBox>
                        </div>
                    </div>
                    <div class="divtr"  style="line-height:40px;" >
                        <div class="divtd">
                           尺寸
                        </div>
                        <div class="divtd">
                            宽度:<asp:TextBox ID="TextBoxWidth" runat="server" Text="300"></asp:TextBox>高度:<asp:TextBox ID="TextBoxHeight" runat="server" Text="300"></asp:TextBox>
                        </div>
                    </div>
                    <div class="divtr"  style="line-height:40px;" >
                        <div class="divtd">

                        </div>
                        <div class="divtd">
                            <asp:Button ID="ButtonGenerate" runat="server" Text="生成" OnClick="ButtonGenerate_Click" />
                        </div>
                    </div>
                    <div class="divtr"  style="line-height:120px;" >
                        <div class="divtd">
                           二维码
                        </div>
                        <div class="divtd">
                            <asp:PlaceHolder ID="PlaceHolderBarCode" runat="server" />
                        </div>
                    </div>
                </div>

把QRCoder.dll类库引入Bin目录中去:

然后在ASPX.CS引用命名空间:

using QRCoder;
using System.IO;
using System.Drawing;

OnClick事件:

protected void ButtonGenerate_Click(object sender, EventArgs e)
    {
        string code = this.TextBoxQRCode.Text;
        QRCodeGenerator qrGenerator = new QRCodeGenerator();
        QRCodeGenerator.QRCode qrCode = qrGenerator.CreateQrCode(code, QRCodeGenerator.ECCLevel.Q);
        System.Web.UI.WebControls.Image imageBarCode = new System.Web.UI.WebControls.Image();
        imageBarCode.Height = string.IsNullOrEmpty(this.TextBoxHeight.Text.Trim()) ? 300 : Convert.ToInt32(this.TextBoxHeight.Text.Trim());
        imageBarCode.Width = string.IsNullOrEmpty(this.TextBoxWidth.Text.Trim()) ? 300 : Convert.ToInt32(this.TextBoxWidth.Text.Trim());

        using (Bitmap bitMap = qrCode.GetGraphic(20))
        {
            using (MemoryStream ms = new MemoryStream())
            {
                bitMap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                byte[] byteImage = ms.ToArray();
                imageBarCode.ImageUrl = "data:image/png;base64," + Convert.ToBase64String(byteImage);
            }
            this.PlaceHolderBarCode.Controls.Add(imageBarCode);
        }
    }