FineUI 官方论坛

标题: FineUIPro+CEFSharp的集成案例 [打印本页]

作者: 一尺野光    时间: 2018-8-17 11:51
标题: FineUIPro+CEFSharp的集成案例
FineUIPro+CEFSharp的集成案例
一、        起源
   经过N个月的努力,以FineUIPro帮助客户开发并部署企业应用系统。
在进行试运行阶段之时,突然,客户提出一个需求:
甲:你们的系统怎么在浏览器中进去的?这样,没有一点企业形象,你看看还360、IE的,太Low了!这样不行滴,要在桌面上有个公司LOG的快捷键,这样才能体现企业光辉形象。
   乙:哦,那我们做一个浏览器的快捷键,怎么样?
   甲:不行的,那还是在浏览器中,还是LOW。
   甲:你们这种是B/S、我们要的是C/S!
   乙:……(狂吐)
   甲、乙:……(N个回合沟通,讨论B/S、C/S的利弊)
   结论:甲胜、乙输。做C/S。
二、        对策
   不可能重码代码,只能想变通之道。
   乙问度娘:度娘啊,B/S转C/S的方案有没有?
度娘答:没有!这里有B/S转SB倒有,要不一试?
只能再找方案。(N时……)
乙准备上吊之时,119出场:CEFSharp
方案:
   做一个集成CEFSharp的WinForm程序,实现内嵌B/S。
   效果:
      B/S成果仍然保留,留住心血;
      不依赖各种浏览器,可以在桌面上做快捷键,体现企业形象。
   CEFSharp的解决之道与优势:
      度娘:
CEFSharp支撑与JS相互调用;
另外我可以告诉你很多哦!
乙:滚,早干嘛去了……
三、        意外收获
   由于之前系统中需要用到多个ActiveX插件(例:单据插件、FastReport报表插件),因此,系统只能在IE浏览器中使用,无法使用Chrome浏览器,存在IE的各种性能问题,现在借助CEFSharp,则可以抛弃IE浏览器,采用Chrome内核,一方面:提升性能;另一方面:通过UserControl集成ActiveX,并且为后续可以做更多的扩展支撑。
四、        成果展示

(一)     项目方案


(二)     桌面快捷键



(三)     
系统界面

1.   首页

2.   业务页


3.   调用单据插件ActiveX

4.   报表设计页

5.   报表预览页

杭州睿弈科技有限公司
13857101985


作者: sanshi    时间: 2018-8-17 17:08
不错,有想法。那个ActiveX没看懂:
UserControl集成ActiveX
是啥意思
作者: leetle    时间: 2018-8-17 18:41
导出表格时,无响应。有遇到没?
作者: 一尺野光    时间: 2018-8-18 09:15
leetle 发表于 2018-8-17 18:41
导出表格时,无响应。有遇到没?

   该问题归根到底是CefSharp对文件下载的控制问题,网上有介绍需要重构CefSharp的接口来支持。而我的思路则是比较简单,通过CefSharp与Js的互调控制,来变通实现文件的下载。
需要做几处调整:
1、通过CefSharp打开网站时,需要标注该项目是运行在CefSharp中,我的方式是在打开网站的网址中,增加一个参数,保存到Session中
if (Request.QueryString["Container"]!=null)
        {
          Session["Container"] = Request.QueryString["Container"].ToString();
        }
2、针对导出的处理
2.1、在CefSharp的WinForm中,增加一个类,用于响应网站的Js调用,里面有一个下载保存的处理
2.1.1、定义一个ryCefExtendFunCls的类,里面放置针对文件Url,通过Http下载的控制
/// <summary>
    /// Http方式下载文件
    /// </summary>
    /// <param name="AUrl">http地址</param>
    /// <param name="ALocalFile">本地文件</param>
    /// <returns></returns>
    public bool CallDownloadFromHttp(string AUrl, string ALocalFile)
    {
      bool flag = false;
      long startPosition = 0; // 上次下载的文件起始位置
      FileStream writeStream; // 写入本地文件流对象

      // 判断要下载的文件夹是否存在
      if (File.Exists(ALocalFile))
      {

        writeStream = File.OpenWrite(ALocalFile);             // 存在则打开要下载的文件
        startPosition = writeStream.Length;                  // 获取已经下载的长度
        writeStream.Seek(startPosition, SeekOrigin.Current); // 本地文件写入位置定位
      }
      else
      {
        writeStream = new FileStream(ALocalFile, FileMode.Create);// 文件不保存创建一个文件
        startPosition = 0;
      }


      try
      {
        HttpWebRequest myRequest = (HttpWebRequest)HttpWebRequest.Create(AUrl);// 打开网络连接

        if (startPosition > 0)
        {
          myRequest.AddRange((int)startPosition);// 设置Range值,与上面的writeStream.Seek用意相同,是为了定义远程文件读取位置
        }


        Stream readStream = myRequest.GetResponse().GetResponseStream();// 向服务器请求,获得服务器的回应数据流


        byte[] btArray = new byte[512];// 定义一个字节数据,用来向readStream读取内容和向writeStream写入内容
        int contentSize = readStream.Read(btArray, 0, btArray.Length);// 向远程文件读第一次

        while (contentSize > 0)// 如果读取长度大于零则继续读
        {
          writeStream.Write(btArray, 0, contentSize);// 写入本地文件
          contentSize = readStream.Read(btArray, 0, btArray.Length);// 继续向远程文件读取
        }

        //关闭流
        writeStream.Close();
        readStream.Close();

        flag = true;        //返回true下载成功
      }
      catch (Exception)
      {
        writeStream.Close();
        flag = false;       //返回false下载失败
      }

      return flag;
    }

    /// <summary>
    /// 调用保存文件对话框
    /// </summary>
    /// <param name="ADefaultFileName"></param>
    /// <returns></returns>
    private void CallShowSaveDialog()
    {
      SaveFileDialog mySaveFileDialog = new SaveFileDialog();

      //设置文件类型
      mySaveFileDialog.Filter = "全部文件(*.*)|*.*";
            
      //保存对话框是否记忆上次打开的目录
      mySaveFileDialog.RestoreDirectory = true;

      //设置默认的文件名

      mySaveFileDialog.FileName = _SaveFileDefaultName;

      //点了保存按钮进入
      if (mySaveFileDialog.ShowDialog() == DialogResult.OK)
      {
        _SaveFileName=mySaveFileDialog.FileName;
      }
      else
      {
        _SaveFileName="";
      }
    }

    /// <summary>
    /// 扩展:下载文件
    /// </summary>
    /// <param name="AUrl"></param>
    public void downLoadFile(string AUrl)
    {
      ryAppConfigOperatorCls myAppConfigOperatorCls = new ryAppConfigOperatorCls();
      AUrl = myAppConfigOperatorCls.GetAppRootUrl() + AUrl.Replace("_RYLJFG_", "/");

      string sFileName = Path.GetFileName(AUrl);
      _SaveFileDefaultName = sFileName;
      Thread InvokeThread = new Thread(new ThreadStart(CallShowSaveDialog));
      InvokeThread.SetApartmentState(ApartmentState.STA);
      InvokeThread.Start();
      InvokeThread.Join();

      if (_SaveFileName != "" && CallDownloadFromHttp(AUrl, _SaveFileName))
      {
        MessageBox.Show("文件已成功下载!");
      }
    }
2.1.2、再在CefSharp的ChromiumWebBrowser中对其进行注册
public void InitiContainer(string AUrl)
    {
      var Settings = new CefSettings
      {
        Locale = "zh-CN",
        AcceptLanguageList = "zh-CN",
        CachePath = Directory.GetCurrentDirectory() + @"\Cache",
        PersistSessionCookies = true,
        MultiThreadedMessageLoop = true
      };
      CefSharpSettings.LegacyJavascriptBindingEnabled = true;
      Cef.Initialize(Settings);
      _Browser = new ChromiumWebBrowser(AUrl);
      GloableDefine._CallWebBrowseExecJs = DoExecWebBrowseScript;

      pnlContainer.Controls.Add(_Browser);
      _Browser.Dock = DockStyle.Fill;

      //注册ryCefExtendFunCls JS对象
      _Browser.RegisterJsObject("ryCefExtend", new ryCefExtendFunCls());
      
    }

2.2、在网站的项目aspx页面中,增加一个JS,用于调用CefSharp中的下载保存方法

<script type="text/javascript">
  function Call_RYCEF_Download(AUrl) {
    //注意在js函数里面只认小写开头
    ryCefExtend.downLoadFile(AUrl);
  }
</script>

2.3、在网站的项目aspx页面后台代码中,根据Session的判断,去分别控制以哪种方式下载
public void btnDownload_Click(object sender, EventArgs e)
    {
      if (gcgzwjGrid.SelectedRowIndex < 0)
        return;

      string sPsflID = gcgzwjGrid.Rows[gcgzwjGrid.SelectedRowIndex].DataKeys[0].ToString();
      string sTempFile = GetFile(sPsflID);
      if (File.Exists(sTempFile))
      {
        if (Session["Container"] != null && Session["Container"].ToString() == "RYCEFCONTAINER")
        {
          //通过CefSharp接口下载
          sTempFile = sTempFile.Substring(sTempFile.IndexOf("workfiles")).Replace("\\", "_RYLJFG_");
          PageContext.RegisterStartupScript("Call_RYCEF_Download('" + sTempFile + "');");
        }
        else
        {
          //采用浏览器方式下载
          Response.ContentType = "application/octet-stream";
          Response.AddHeader("Content-Disposition", "attachment;filename=" +
            Server.UrlEncode(Path.GetFileName(sTempFile)));
          Response.TransmitFile(sTempFile);
        }
      }
    }

2.4、由于在FineUiPro中,对于调用下载的按钮,需要控制Button参数(EnableAjax与DisableControlBeforePostBack),因此,在页面上的下载控件,需要有两个,一个用于常规浏览器使用,另一个特供CefSharp使用,前后端如下:
<f:MenuButton ID="btnDownload" runat="server" Text="附件下载" OnClick="btnDownload_Click" EnableAjax="false" DisableControlBeforePostBack="false">
              </f:MenuButton>
              <f:MenuButton ID="btnDownloadByCef" runat="server" Text="附件下载" OnClick="btnDownload_Click" Hidden="true">
              </f:MenuButton>

protected void Page_Load(object sender, EventArgs e)
    {
      if (!IsPostBack)
      {
        if (Session["Container"] != null && Session["Container"].ToString() == "RYCEFCONTAINER")
        {
          btnDownloadByCef.Hidden = false;
          btnDownload.Hidden = true;
        }
3、最终效果
图片传不上来
作者: leetle    时间: 2018-8-18 11:31
我项目中不用Session来解决问题。谢谢你的方案。
作者: 一尺野光    时间: 2018-8-18 11:36
leetle 发表于 2018-8-18 11:31
我项目中不用Session来解决问题。谢谢你的方案。

你是在CefSharp中实现自己的Session,可以避免Session过期的问题
作者: Stark11    时间: 2019-7-27 23:42
这种技术方案是典型的逆势而下的,佩服你们的老板同意这个方案,果然有钱能使鬼推磨
作者: 刘军    时间: 2019-8-9 14:59
Stark11 发表于 2019-7-27 23:42
这种技术方案是典型的逆势而下的,佩服你们的老板同意这个方案,果然有钱能使鬼推磨 ...

这种模式唯一的缺点是需要安装客户端及.net framework,其他方面完爆普通浏览器,可以使js有调用设备的能力,事实上很多项目都采用这种方案了,楼主这套系统采用ActiveX导致必须依赖IE的问题,反而一开始就应该采用这种方案




欢迎光临 FineUI 官方论坛 (https://fineui.com/BBS/) Powered by Discuz! X3.4