使用WebBrower修改网页iFrame中的内容

由于很变态的中国式需求,我们需要在自己的网站中,嵌入第三方厂商的网站。
而这个第三方的网站中,使用了几层的iFrame,同时使用了基于IE的Activex控件。
由于双方的控件风格差距太大,所以需要替换CSS样式,同时要删掉部分与我们网站冲突的内容。

想了几种方法:
1、说服对方进行调整,但经过接触,了解到其超级不靠谱,只能靠自己了。
2、直接通过iFrame嵌入对方网站,用JS进行修改。可是跨站修改,浏览器不同意啊。
3、自己写个控件,嵌入到自己网页中,在控件中调用对方URL,并通过WebBrowser控件修改CSS及HTML。问题是,IE中,不能再直接嵌入一个WebBrowser控件,主要出于安全考虑。(如果IE允许这样操作的话,我只需要在WebBrowser中指向同一个URL,就可以无限调用,耗光资源,其他的事情,有太多可以做了)自己用很挫的方法实现了这个功能,后来考虑到稳定性,放弃了。
4、使用非基于IE的Web插件,在控件中调用对方URL,并通过控件修改CSS及HTML。比如Flash或者CefSharp,但问题是,非IE核心如何调用Activex控件啊。
5、限制入口,基于WebBrowser定制IE浏览器

最后使用了方法5,说实话,真烦。

这里把通过WebBrowser修改CSS及HTML的方法说一下,下面是我写的一段测试用代码,请按自己的实际情况修改:

        //替换样式
        //webMain是WebBrowser控件的名称
        //NewStyle.css中是要替换的样式
        private void modifyStyle()
        {
            int frameNum = webMain.Document.Window.Frames.Count;
            if (frameNum == 2)
            {
                IHTMLDocument2 leftIFrame = webMain.Document.Window.Frames[0].Document.DomDocument as IHTMLDocument2;

                int length = leftIFrame.styleSheets.length;
                IHTMLStyleSheet styleSheet = leftIFrame.createStyleSheet(@"", length + 1);

                TextReader reader = new StreamReader(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "NewStyle.css"));
                string style = reader.ReadToEnd();
                styleSheet.cssText = style;
            }
        }

        //删除tree节点下的div,只保留特定的一个节点
        private void delDiv(String flowid)
        {
            int frameNum = webMain.Document.Window.Frames.Count;
            if (frameNum == 2)
            {
                IHTMLDocument3 leftIFrame = webMain.Document.Window.Frames[0].Document.DomDocument as IHTMLDocument3;
                foreach (dynamic e in leftIFrame.getElementById("tree").children)
                {
                    String ret = e.InnerText;
                    if (!ret.Contains(flowid))
                    {
                        e.parentNode.removeChild(e);
                    }
                }
            }
        }

如何在WinForm程序中测试C#写的COM控件

在网上查了一下,应该是不能直接在WinForm中使用C#写的COM控件。
但可以用变通的方法测试COM控件中的方法,比如,下面的例子中,就可以测试addTest方法:

Type comType = null;
object comObject = null;

//这个无效,不知道为什么
//comType = Type.GetTypeFromProgID("XX.CSControl.TestControl");
if (comType == null)
{
    Guid guid = new Guid("4B4D1D4C-16CA-48E0-87A4-AFF3C6CB6E26");
    comType = Type.GetTypeFromCLSID(guid);
}

try
{
    comObject = Activator.CreateInstance(comType);
}
catch(Exception e)
{
    MessageBox.Show(e.Message);
    return;
}

object[] args = new object[2];
args[0] = 1;
args[1] = 2;

int ret = (int)comType.InvokeMember("addTest", BindingFlags.InvokeMethod, null, comObject, args);
if(ret==3)
{
    MessageBox.Show("TestOK");
}

C#开发COM组件(Web)

这两天由于一个很挫的需求,准备开发一个COM组件,由于所有用户的客户端都安装有.Net Framework 4.0,加上工期紧的很,就准备用C#上了。

1、首先,建立一个Windows Forms Control Library项目

2、项目属性,Build,选中,Register for COM interop

3、项目属性,Signing,选中,Sign the assembly,并生成一个签名文件

4、编辑AssemblyInfo.cs,查看是否有下面两行,没有要添加

using System.Security;

[assembly: AllowPartiallyTrustedCallers()] 
[assembly: ComVisible(true)]

5、添加接口文件IObjectSafety.cs

using System;
using System.Runtime.InteropServices;

namespace CSControl
{
    //不要修改GUID
    [ComImport]
    [Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IObjectSafety
    {
        [PreserveSig]
        int GetInterfaceSafetyOptions(ref Guid riid,
                      [MarshalAs(UnmanagedType.U4)] ref int pdwSupportedOptions,
                      [MarshalAs(UnmanagedType.U4)] ref int pdwEnabledOptions);

        [PreserveSig()]
        int SetInterfaceSafetyOptions(ref Guid riid,
                      [MarshalAs(UnmanagedType.U4)] int dwOptionSetMask,
                      [MarshalAs(UnmanagedType.U4)] int dwEnabledOptions);
    }
}

6、控件类要同时实现UserControl, IObjectSafety两个接口

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Security;

namespace CSControl
{
    //更改为你自己的UUID
    [ComVisible(true)]
    [Guid("4B4D1D4C-16CA-48E0-87A4-AFF3C6CB6E26")]
    [ProgId("XX.CSControl.TestControl")]
    public partial class TestControl : UserControl, IObjectSafety
    {
        //===================================================
        //实现IObjectSafety接口
        //不要修改下面的GUID
        private const string _IID_IDispatch = "{00020400-0000-0000-C000-000000000046}";
        private const string _IID_IDispatchEx = "{a6ef9860-c720-11d0-9337-00a0c90dcaa9}";
        private const string _IID_IPersistStorage = "{0000010A-0000-0000-C000-000000000046}";
        private const string _IID_IPersistStream = "{00000109-0000-0000-C000-000000000046}";
        private const string _IID_IPersistPropertyBag = "{37D84F60-42CB-11CE-8135-00AA004BB851}";

        private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;
        private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;
        private const int S_OK = 0;
        private const int E_FAIL = unchecked((int)0x80004005);
        private const int E_NOINTERFACE = unchecked((int)0x80004002);

        private bool _fSafeForScripting = true;
        private bool _fSafeForInitializing = true;

        public int GetInterfaceSafetyOptions(ref Guid riid,
                             ref int pdwSupportedOptions,
                             ref int pdwEnabledOptions)
        {
            int Rslt = E_FAIL;

            string strGUID = riid.ToString("B");
            pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;
            switch (strGUID)
            {
                case _IID_IDispatch:
                case _IID_IDispatchEx:
                    Rslt = S_OK;
                    pdwEnabledOptions = 0;
                    if (_fSafeForScripting == true)
                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER;
                    break;
                case _IID_IPersistStorage:
                case _IID_IPersistStream:
                case _IID_IPersistPropertyBag:
                    Rslt = S_OK;
                    pdwEnabledOptions = 0;
                    if (_fSafeForInitializing == true)
                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA;
                    break;
                default:
                    Rslt = E_NOINTERFACE;
                    break;
            }

            return Rslt;
        }

        public int SetInterfaceSafetyOptions(ref Guid riid,
                             int dwOptionSetMask,
                             int dwEnabledOptions)
        {
            int Rslt = E_FAIL;

            string strGUID = riid.ToString("B");
            switch (strGUID)
            {
                case _IID_IDispatch:
                case _IID_IDispatchEx:
                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_CALLER) &&
                         (_fSafeForScripting == true))
                        Rslt = S_OK;
                    break;
                case _IID_IPersistStorage:
                case _IID_IPersistStream:
                case _IID_IPersistPropertyBag:
                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_DATA) &&
                         (_fSafeForInitializing == true))
                        Rslt = S_OK;
                    break;
                default:
                    Rslt = E_NOINTERFACE;
                    break;
            }

            return Rslt;
        }

        //===================================================
        //实现UserControl接口
        ......
    }
}

7、编译,控件会自动注册

8、写个网页,测试一下

<!--CSControl-->
<object id="testControl" width="500" height="500" classid ="clsid:4B4D1D4C-16CA-48E0-87A4-AFF3C6CB6E26"&#93;</object>

9、打CAB包,发布到网站
由于是自己生产的签名文件,所以会有安全提示。最简单的方法是从靠谱的CA那里买一个证书,还有办法就是将证书导入到IE的信任列表中,再就是将IE的安全级别降为最低。

10、当然也可以通过命令行本地注册

rem 注册
regtlibv12 %PATH_TO_DLL_AND_TLB_FOLDER%\CSControl.tlb

rem 反注册
regtlibv12 -u %PATH_TO_DLL_AND_TLB_FOLDER%\CSControl.tlb

C#开发COM组件(本地)

本篇文章介绍了如何用C#写一个简单的COM组件(Dll)。

1、新建一个C# Class Library项目“CSCOMTest”

2、项目中,新增一个接口文件IJustATestCOM.cs,Guid要自己生成

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace CSCOMTest
{
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [Guid("A5377871-3334-4679-A3CD-84312B1DCD3E")]
    public interface IJustATestCOM
    {
        int Add(int a, int b);
        String SayHiTo(String someOne);
    }
}

3、项目中,新增一个类文件JustATestCOM.cs,Guid要自己生成

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace CSCOMTest
{
    [ComVisible(true)]
    [Guid("A6E4F456-32C1-4C8E-9171-D616B5DA1E20")]
    [ProgId("CSCOMTest.JustATestCOM")]
    public class JustATestCOM : IJustATestCOM
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public String SayHiTo(String someOne)
        {
            return "Hi " + someOne + "!";
        }
    }
}

4、项目属性->Application->Assembly Infomation->Make Assembly COM-Visible->打勾

5、项目属性->Signing->Sign the assembly->打勾
项目属性->Signing->Choose a strong name key file->New

6、编译

7、注册

#这里一定要用正确版本的gacutil及RegAsm
C:\VBS\DNFW4\gacutil.exe /i CSCOMTest.dll
C:\VBS\DNFW4\RegAsm.exe CSCOMTest.dll

8、反注册

#这里一定要用正确版本的gacutil及RegAsm
C:\VBS\DNFW4\gacutil.exe /u CSCOMTest
C:\VBS\DNFW4\RegAsm.exe /u CSCOMTest.dll

9、调用测试

'发生错误时,继续运行
On Error Resume Next

'清除错误状态
Err.Clear

Set Obj=CreateObject("CSCOMTest.JustATestCOM")

'输出错误信息
If Err.Number <> 0 Then
    WScript.Echo "Error: " & Err.Number
    WScript.Echo "Error (Hex): " & Hex(Err.Number)
    WScript.Echo "Source: " &  Err.Source
    WScript.Echo "Description: " &  Err.Description
    'Err.Clear
    '退出程序
    WScript.Quit(Err.Number)
End If

'On Error Goto 0

WScript.Echo obj.Add(1,2)
WScript.Echo obj.SayHiTo("dcom")


set obj=Nothing

10、生成tlb文件

regasm CSCOMTest.dll /tlb:CSCOMTest.tlb

VS2012格式化全部代码

只需要在VS2012中运行下面代码即可:
Tools->Library Package Manager->Package Manager Console

function FormatItems($projectItems) {
    $projectItems |
    % {
        # Write-Host "    Examining item: $($_.Name)";

        if ($_.Name -and $_.Name.ToLower().EndsWith(".cs") `
            -and (-not $_.Name.ToLower().Contains(".designer."))) {

            $win = $_.Open('{7651A701-06E5-11D1-8EBD-00A0C90F26EA}');
            $win.Activate();

            $dte.ExecuteCommand('Edit.FormatDocument');

            if (!$_.Saved) {
                Write-Host "    Saving modified file: $($_.Name)";
                $dte.ExecuteCommand('File.SaveSelectedItems');
            }

            $dte.ExecuteCommand('Window.CloseDocumentWindow');
        }

        if ($_.ProjectItems -and ($_.ProjectItems.Count -gt 0)) {
            # Write-Host "    Opening sub-items of $($_.Name)";

            FormatItems($_.ProjectItems);
        }
    };
}

$dte.Solution.Projects | % {
    Write-Host "-- Project: $($_.Name)";

    FormatItems($_.ProjectItems)
}
;

注意:上面代码会遍历文件,打开,格式化,保存再关闭。运行前请保存并备份!

参考:How to format all files in Visual Studio 2012?

.Net4的WinForm程序使用.Net2控件

在.Net4的WinForm程序中,混用.Net2控件会报下面的问题

Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.

解决方法为在App.config文件中,增加以下配置

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

C# Https Soap Client

1、Soap Https Soap Client

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;

namespace IISSoapClientTest
{
    class Program
    {
        public static void HelloHttp(string url)
        {
            Hello h = new Hello(url);
            string ans = h.HelloWorld("C# http client");
            Console.WriteLine(ans);
            Console.WriteLine();
        }

        //同样的证书,IIS可以过,Tomcat过不去
        public static void HelloHttps(string url,String certPath)
        {
            X509CertificateCollection certs = new X509CertificateCollection();
            X509Certificate cert = X509Certificate.CreateFromCertFile(certPath);

            Hello h = new Hello(url);
            h.ClientCertificates.Add(cert);
            string ans = h.HelloWorld("C# https client");
            Console.WriteLine(ans);
            Console.WriteLine();
        }

        //绕过证书检查
        public static void HelloHttpsWithRemoteCertificateValidationCallback(string url)
        {
            //ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
            ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(RemoteCertificateValidationCallback);

            Hello h = new Hello(url);
            string ans = h.HelloWorld("C# https client");
            Console.WriteLine(ans);
            Console.WriteLine();
        }

        private static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, 
            X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }  

        static void Main(string[] args)
        {
            //HelloHttp("http://127.0.0.1:80/Hello.asmx");
            //HelloHttps("https://127.0.0.1:443/Hello.asmx");
            //HelloHttpsWithRemoteCertificateValidationCallback("https://127.0.0.1:443/Hello.asmx");

            //HelloHttp("http://127.0.0.1:8080/SoapTest/services/HelloService");
            HelloHttps("https://127.0.0.1:8443/SoapTest/services/HelloService", @"D:\DiskE\Projects\VS2010\TestProjects\SSLSocket\myKeyStore.cer");
            //HelloHttpsWithRemoteCertificateValidationCallback("https://127.0.0.1:8443/SoapTest/services/HelloService");
        }
    }
}

C# Https Soap Server(IIS7)

1、首先准备一个p12格式的服务端证书
无论是购买,还是用openssl或java keytool生成自签名证书都可以

2、在IIS7的根目录,选中“安全性->根目录证书”,选择“导入”即可

3、如果显示证书链有问题,则在IE中导入CA证书就好了

4、在需要HTTPS的网站上,选择“绑定”,绑定类型为https,选择需要的证书

5、在客户端的IE中,导入CA证书就好了