一、前言:

      最近做一个简单的在线升级Demo,使用了微软较早的.Net Remoting技术来练手。

简单的思路就是在服务器配置一个Remoting对象,然后在客户端来执行Remoting对象中的方法。

过程:

(1) 读取本地dll文件的名称与版本号,与服务器的进行对比

(2) 确认需要升级的文件名称与版本号并告诉服务器,服务器将其复制到一个临时文件夹并压缩成zip

(3) 将服务器的zip下载到本地的临时文件夹,并解压。

定义服务器端为UpdateServer,其配置文件为:

<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown type="UpdateLibrary.RemoteObject, UpdateLibrary" mode="Singleton" objectUri="RemoteObject.rem"/>
</service>
<channels>
<channel ref="http" port="">
</channel>
</channels>
</application>
</system.runtime.remoting>
<appSettings>
<!--<add key="Dir" value="E:\server"/>-->
</appSettings>
</configuration>

定义客户端为UpdateClient,其配置文件为:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ServerUrl" value="127.0.0.1:8989"/>
<add key="Modules" value="BIMCoreDB"/>
<add key="BufferLength" value=""/>
</appSettings>
</configuration>

定义两端共同调用的dll为UpdateLibrary。

二、服务器端代码:

     程序主入口:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Configuration;
using UpdateLibrary; namespace UpdateServer
{
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
LoadConfig();
Application.Run(new FormServer());
} private static void LoadConfig()
{
Config.Dir = System.IO.Path.Combine(Application.StartupPath, "serverfiles"); //更新包所在位置
Config.TempDir = System.IO.Path.Combine(Application.StartupPath, "temp"); //临时文件夹,用于放更新文件的地方。
}
}
}

服务器窗体后台代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Remoting;
namespace UpdateServer
{
public partial class FormServer : Form
{
public FormServer()
{
InitializeComponent();
try
{
//remoting配置
RemotingConfiguration.Configure(string.Format("{0}\\UpdateServer.exe.config", Application.StartupPath), false);
}
catch (Exception e)
{
MessageBox.Show(this, e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void FormServer_Load(object sender, EventArgs e)
{
lbl_time.Text = "当前时间:"+DateTime.Now.ToString("T");
tm_Server = new Timer();
tm_Server.Tick += tm_Server_Tick;
tm_Server.Interval = ;
tm_Server.Enabled = true;
}
void tm_Server_Tick(object sender,EventArgs e)
{
lbl_time.Text = string.Empty;
lbl_time.Text = "当前时间:" + DateTime.Now.ToString("T");
}
}
}

三、UpdateLibrary:

UpdateLibrary类库包含三个类:
        (1)Config类:用于提取配置文件中的信息。

(2)ZipHelper类:第三方库,用于文件压缩与解压缩。

(3)RemoteObject类:remoting对象,实现两端之间所需要的方法。

Config代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq; namespace UpdateLibrary
{
/// <summary>
/// 将配置文件中的信息传给Config对象
/// </summary>
public static class Config
{
public static string Dir { get; set; }
public static string TempDir { get; set; }
public static string[] Modules { get; set; }
public static int BufferLength { get; set; }
public static string ServerUrl { get; set; }
}
}

        ZipHelper代码:(比较实用的压缩与解压缩方法)

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Checksums;
using System.IO; namespace UpdateLibrary
{
public class ZipHelper
{
#region 压缩
/// <summary>
/// 压缩文件
/// </summary>
/// <param name="sourceFilePath"></param>
/// <param name="destinationZipFilePath"></param>
public static void CreateZip(string sourceFilePath, string destinationZipFilePath)
{
if (sourceFilePath[sourceFilePath.Length - ] != System.IO.Path.DirectorySeparatorChar)
sourceFilePath += System.IO.Path.DirectorySeparatorChar;
ZipOutputStream zipStream = new ZipOutputStream(File.Create(destinationZipFilePath));
zipStream.SetLevel(); // 压缩级别 0-9
CreateZipFiles(sourceFilePath, zipStream);
zipStream.Finish();
zipStream.Close();
}
/// <summary>
/// 递归压缩文件
/// </summary>
/// <param name="sourceFilePath">待压缩的文件或文件夹路径</param>
/// <param name="zipStream">打包结果的zip文件路径(类似 D:\WorkSpace\a.zip),全路径包括文件名和.zip扩展名</param>
/// <param name="staticFile"></param>
private static void CreateZipFiles(string sourceFilePath, ZipOutputStream zipStream)
{
Crc32 crc = new Crc32();
string[] filesArray = Directory.GetFileSystemEntries(sourceFilePath);
foreach (string file in filesArray)
{
if (Directory.Exists(file)) //如果当前是文件夹,递归
{
CreateZipFiles(file, zipStream);
}
else //如果是文件,开始压缩
{
FileStream fileStream = File.OpenRead(file);
byte[] buffer = new byte[fileStream.Length];
fileStream.Read(buffer, , buffer.Length);
string tempFile = file.Substring(sourceFilePath.LastIndexOf("\\") + );
ZipEntry entry = new ZipEntry(tempFile);
entry.DateTime = DateTime.Now;
entry.Size = fileStream.Length;
fileStream.Close();
crc.Reset();
crc.Update(buffer);
entry.Crc = crc.Value;
zipStream.PutNextEntry(entry);
zipStream.Write(buffer, , buffer.Length);
}
}
}
#endregion #region 解压缩 public static void UnZip(Stream stream, string targetPath)
{
using (ZipInputStream zipInStream = new ZipInputStream(stream))
{
ZipEntry entry;
while ((entry = zipInStream.GetNextEntry()) != null)
{
string directorName = Path.Combine(targetPath, Path.GetDirectoryName(entry.Name));
string fileName = Path.Combine(directorName, Path.GetFileName(entry.Name));
// 创建目录
if (directorName.Length > )
{
Directory.CreateDirectory(directorName);
}
if (fileName != string.Empty && !entry.IsDirectory)
{
var ext = System.IO.Path.GetExtension(fileName);
using (FileStream streamWriter = File.Create(fileName))
{
int size = ;
byte[] data = new byte[ * ];
while (true)
{
size = zipInStream.Read(data, , data.Length);
if (size > )
{
streamWriter.Write(data, , size);
}
else break;
}
}
}
}
}
}
#endregion
}
}

        RemoteObject代码:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Collections;
using Newtonsoft.Json; namespace UpdateLibrary
{
public class RemoteObject : MarshalByRefObject
{
public string GetUpdateFileVersion()
{
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(Config.Dir);
System.IO.FileInfo[] files = dir.GetFiles("*.dll"); //获取服务器端所有dll文件 List<string> fileinfo = new List<string>();//记录文件名与文件版本
foreach(var file in files)
{
string filename = System.IO.Path.GetFileNameWithoutExtension(file.ToString());//获取文件名称
fileinfo.Add(filename);
FileVersionInfo ver = FileVersionInfo.GetVersionInfo(System.IO.Path.Combine(Config.Dir, file.ToString()));
string fileversion = ver.FileVersion; //获取文件的版本
fileinfo.Add(fileversion);
}
string SendData = JsonConvert.SerializeObject(fileinfo);//转Json
return SendData;
} public IList CreateZipfile(string str_r)
{
List<string> templist = JsonConvert.DeserializeObject<List<string>>(str_r);//接收到确认更新的文件名 foreach (string filename in templist) //把需要更新的文件都复制到临时文件夹中
{
string updatefile = Path.Combine(Config.Dir, filename + ".dll");
File.Copy(updatefile, Path.Combine(Config.TempDir, filename + ".dll"), true);
System.IO.File.SetAttributes(Path.Combine(Config.TempDir, filename + ".dll"), System.IO.FileAttributes.Normal);//去掉文件只读属性
} string tempzippath=Path.Combine(Config.Dir,"tempzip");//临时压缩包路径,默认更新文件夹下的tempzip文件夹
if(Directory.Exists(tempzippath)==false) //判断是否有安放压缩包的地方
{
Directory.CreateDirectory(tempzippath);
} ZipHelper.CreateZip(Config.TempDir, Path.Combine(tempzippath, "Update.zip"));//将临时文件夹内的文件都压缩到tempzip文件夹下的update.zip
System.IO.FileInfo f = new FileInfo(Path.Combine(tempzippath,"Update.zip"));//获得该压缩包的大小
IList SendData = new ArrayList();
SendData.Add(Path.Combine(tempzippath, "Update.zip")); //得到压缩包名称
SendData.Add(f.Length); //得到压缩包文件大小
return SendData;
}
public byte[] GetFile(string name, int start, int length)
{
using (System.IO.FileStream fs = new System.IO.FileStream(System.IO.Path.Combine(Config.TempDir, name), System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite))
{
byte[] buffer = new byte[length];
fs.Position = start;
fs.Read(buffer, , length);
return buffer;
}
} public void Finish(string name)
{
// File.Delete(System.IO.Path.Combine(Config.TempDir, name)); //删除压缩包文件夹
}
}
}

四、客户端端代码:

程序主入口:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Configuration;
using UpdateLibrary; namespace UpdateClient
{
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
LoadConfig(args);
Application.Run(new FormClient());
}
private static void LoadConfig(string[] args)
{
Config.Dir = System.IO.Path.Combine(Application.StartupPath,"localfiles");//本地文件位置
Config.TempDir = System.IO.Path.Combine(Application.StartupPath, "temp");//本地放更新文件的位置
Config.ServerUrl = ConfigurationManager.AppSettings["ServerUrl"].ToString();//设置服务器Url
Config.Modules =ConfigurationManager.AppSettings["Modules"].ToString().Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);//更新文件的名称
Config.BufferLength = int.Parse(ConfigurationManager.AppSettings["BufferLength"].ToString()); //缓存大小
}
}
}

第一个窗体FormClient,用于比对文件,如果有更新则提供按钮进入更新窗体FormUpdate

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using UpdateLibrary;
using System.IO;
using Newtonsoft.Json; namespace UpdateClient
{
public partial class FormClient : Form
{
Dictionary<string, string> Localupdate = new Dictionary<string, string>(); //确认本地需要文件
Dictionary<string, string> ConfirmUpdate = new Dictionary<string, string>(); //确认需要更新的文件并告诉服务器的
Dictionary<string, string> ConfirmAdd = new Dictionary<string, string>(); //确认需要新增的文件
public FormClient()
{
InitializeComponent();
btn_update.Enabled = false;
int ScreenWidth = Screen.PrimaryScreen.WorkingArea.Width;
int ScreenHeight = Screen.PrimaryScreen.WorkingArea.Height;
int x = ScreenWidth - this.Width - ;
int y = ScreenHeight - this.Height - ;
this.Location = new Point(x, y);
} private void btn_update_Click(object sender, EventArgs e)
{
Form updatewindow = new FormUpdate(ConfirmUpdate);
updatewindow.Show();
this.Hide();
} private void FormClient_Load(object sender, EventArgs e)
{
Dictionary<string, string> localfileversion = new Dictionary<string, string>();
foreach (string module in Config.Modules)
{
string filepath = System.IO.Path.Combine(Config.Dir, module + ".dll");
FileVersionInfo ver = FileVersionInfo.GetVersionInfo(filepath);
string dllVersion = ver.FileVersion;
localfileversion.Add(module, dllVersion); //文件名-版本
} //文件对比
try
{
RemoteObject remoteObject = (RemoteObject)Activator.GetObject(typeof(RemoteObject), string.Format("http://{0}/RemoteObject.rem", Config.ServerUrl));
//获取服务器更新包的版本号 string SFVersion = remoteObject.GetUpdateFileVersion();//获取服务器端更新文件的名称与版本号
List<string> Recieve = new List<string>();
Recieve = JsonConvert.DeserializeObject<List<string>>(SFVersion);//转成泛型
Dictionary<string, string> serverfileversion = new Dictionary<string, string>();//转成字典型
for (int i = ; i < Recieve.Count; i += )
{
serverfileversion.Add(Recieve[i], Recieve[i + ]);
} if (serverfileversion.Count > ) //是否有更新文件
{
foreach (var serverkey in serverfileversion.Keys)
{
if (localfileversion.ContainsKey(serverkey)) //本地是否有更新文件的名称,没有说明是新增的
{
if (localfileversion[serverkey] == serverfileversion[serverkey]) //版本号相同?
{
serverfileversion.Remove(serverkey);//不需要更新
}
else
{
ConfirmUpdate.Add(serverkey, serverfileversion[serverkey]); //确认更新的
Localupdate.Add(serverkey, localfileversion[serverkey]); //本地的版本
}
}
else
{
ConfirmAdd.Add(serverkey, serverfileversion[serverkey]);//确认新增文件,用于提示
}
}
}
else
{
lblmessage.Text = "暂无更新文件";
btn_update.Visible = false;
}
}
catch(Exception ex)
{
lblmessage.Text = ex.ToString();
btn_update.Visible = false;
} if(ConfirmAdd.Count== && ConfirmUpdate.Count==)
{
lblmessage.Text = "没有需要更新的模块";
btn_update.Visible = false;
}
else
{
string upinfo = string.Empty;
lblmessage.Text = "检测完成,需要更新";
btn_update.Enabled = true;
//显示更新的
if (ConfirmUpdate.Count>)
{
upinfo = "更新文件信息:\r\n\r\n";
foreach(var key in ConfirmUpdate.Keys)
{
upinfo += "文件名为:" + key + "\r\n" + "旧版本号:" + Localupdate[key] + "\r\n" + "新版本号:" + ConfirmUpdate[key] + "\r\n";
}
} //显示新增的
if (ConfirmAdd.Count > )
{
upinfo += "\r\n";
upinfo += "新增文件\r\n";
foreach (var key in ConfirmAdd.Keys)
{
upinfo += "文件名为:" + key + "\r\n" + "版本号为:" + ConfirmAdd[key] + "\r\n";
ConfirmUpdate.Add(key, ConfirmAdd[key]);
}
}
txt_UpdateMessage.Text = upinfo;
} }
private void FormClient_FormClosed(object sender, FormClosedEventArgs e)
{
Environment.Exit();
} }
}

第二个窗体FormUpdate,用于更新文件,这里同样使用了backgroundWorker控件来进行异步操作。

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using UpdateLibrary;
using Newtonsoft.Json;
using System.Collections;
using System.IO; namespace UpdateClient
{
public partial class FormUpdate : Form
{
Dictionary<string, string> serverupdatefiles = new Dictionary<string, string>();
public FormUpdate(Dictionary<string, string> confirmupdate)
{
InitializeComponent();
int ScreenWidth = Screen.PrimaryScreen.WorkingArea.Width;
int ScreenHeight = Screen.PrimaryScreen.WorkingArea.Height;
int x = ScreenWidth - this.Width - ;
int y = ScreenHeight - this.Height - ;
this.Location = new Point(x, y);
serverupdatefiles = confirmupdate; //获得需要更新的列表
} private void FormUpdate_Load(object sender, EventArgs e)
{
bgk_Update.RunWorkerAsync() ;
} private void FormUpdate_FormClosed(object sender, FormClosedEventArgs e)
{
Environment.Exit(); //终止该进程
} private void bgk_Update_DoWork(object sender, DoWorkEventArgs e)
{
try
{
RemoteObject remoteObject = (RemoteObject)Activator.GetObject(typeof(RemoteObject), string.Format("http://{0}/RemoteObject.rem", Config.ServerUrl));
bgk_Update.ReportProgress(, "准备更新..."); List<string> list_temp = new List<string>();
list_temp = DictToList(serverupdatefiles);//将确认更新的文件名转成泛型
string str_confirmupdate = JsonConvert.SerializeObject(list_temp);//转成Json //将确认的文件列表返回给server,使其压缩并放置在temp文件夹下
IList RecieveData = new ArrayList();
RecieveData = remoteObject.CreateZipfile(str_confirmupdate); string fileName = RecieveData[].ToString(); //获得压缩包的名称
int fileLength = Convert.ToInt32(RecieveData[]);//获得压缩包的大小 string filePath = Path.Combine(Config.TempDir,"Update.zip");//解压到本地临时文件夹下的Update.zip using (System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
{
for (int i = ; i < fileLength; i += Config.BufferLength * )
{
var percent = (int)((double)i / (double)fileLength * );
bgk_Update.ReportProgress(percent, "正在下载更新包...");
int length = (int)Math.Min(Config.BufferLength * , fileLength - i);
var bytes = remoteObject.GetFile(fileName, i, length);
stream.Write(bytes, , length);
}
stream.Flush();
}
remoteObject.Finish(fileName);
bgk_Update.ReportProgress(, "正在解压");
using (System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
ZipHelper.UnZip(stream, Config.TempDir);//解压获得更新文件
}
System.IO.File.Delete(filePath);//删除解压包
e.Result = "更新完成";
}
catch (Exception ex)
{
e.Result = ex;
}
} private void bgk_Update_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lbl_message.Text = (string)e.UserState;
pbr_Update.Value = e.ProgressPercentage;
} private void bgk_Update_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Result is Exception)
{
lbl_message.Text = (e.Result as Exception).Message;
}
else
{
lbl_message.Text = (string)e.Result;
}
} public List<string> DictToList(Dictionary<string,string> dict)
{
List<string> templist = new List<string>();
foreach(var key in dict.Keys)
{
templist.Add(key);
}
return templist;
}
}
}

五、小结:

(1) 运用了Remoting技术,简单来说就是服务器通过config文件配置remoting服务;客户端调用这个remoting服务;

(2) 使用了BackgroundWorker控件,其主要是三种方法Dowork();ProgressChanged();RunWorkerCompleted()

其中控件的ReportProgress()方法可以通过ProgressChanged中的label与ProgressBar来显示升级状况与进度。

(3) 上篇以remoting方式叙述如何从服务器端下载文件到本地,下篇将介绍得到更新的dll后如何将调用旧dll的exe程序关闭再重启加载新dll的方法,从而实现更新。

【Remoting】.Net remoting方法实现简单的在线升级(上篇:更新文件)的更多相关文章

  1. 【Remoting】.Net remoting方法实现简单的在线升级(下篇:重启exe)

    一.前言      上篇运用了.Net Remoting技术解决了本地与服务器版本对比,并下载更新包的过程. 本篇主要是应用Process,来实现重启程序的过程. 情景假设:       Revit2 ...

  2. 【uniapp 开发】uni-app 资源在线升级/热更新

    注:本文为前端代码资源热更新.如果是整包升级,另见文档 https://ask.dcloud.net.cn/article/34972 HBuilderX 1.6.5 起,uni-app 支持生成 A ...

  3. 【转】Microsoft .Net Remoting之Remoting事件处理全接触

    Remoting事件处理全接触 前言:在Remoting中处理事件其实并不复杂,但其中有些技巧需要你去挖掘出来.正是这些技巧,仿佛森严的壁垒,让许多人望而生畏,或者是不知所谓,最后放弃了事件在Remo ...

  4. 基于PHP实现一个简单的在线聊天功能(轮询ajax )

    基于PHP实现一个简单的在线聊天功能(轮询ajax ) 一.总结 1.用的轮询ajax 二.基于PHP实现一个简单的在线聊天功能 一直很想试着做一做这个有意思的功能,感觉复杂的不是数据交互和表结构,麻 ...

  5. ListView与.FindControl()方法的简单练习 #2 -- ItemUpdting事件中抓取「修改后」的值

    原文出處  http://www.dotblogs.com.tw/mis2000lab/archive/2013/06/24/listview_itemupdating_findcontrol_201 ...

  6. PHP基础示例:简单的在线文件管理

    先截个图: 下面为代码部分,由于只有一个文件,所以就不折叠了. <?php //简单的在线文件管理 $path = "./"; $filelist=array("f ...

  7. 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。

    基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...

  8. js实用方法记录-简单cookie操作

    js实用方法记录-简单cookie操作 设置cookie:setCookie(名称,值,保存时间,保存域); 获取cookie:setCookie(名称); 移除cookie:setCookie(名称 ...

  9. 关于js的对象创建方法(简单工厂模式,构造函数模式,原型模式,混合模式,动态模式)

    // 1.工厂方式创建对象:面向对象中的封装函数(内置对象) 简单来说就是封装后的代码,简单的工厂模式是很好理解的,关于它的作用,就是利用面向对象的方法,把一些对象封装,使一些占用空间多的,重复的代码 ...

随机推荐

  1. WIX 安装部署教程(六) 为你收集的七个知识点

    前段时间整理5篇WIX(Windows Installer XML)的安装教程,但还不够完善,这里继续整理了七个知识点分享给大家.WIX最新版本3.8,点击下载 WIX安装部署(一)同MSBuild自 ...

  2. Fatal error: Maximum function nesting level of '100' reached, aborting!

    这个问题是由于启用了xdebug,而xdebug默认设置了函数最大嵌套数为100 解决办法: 找到php.ini文件,找到xdebug在最后加上xdebug.max_nesting_level = 5 ...

  3. C#高级二

    编程小虾米大侠之梦 软件环境:win7 开发工具:vs 2010 平台:.NET 语言:C# 类库版本:.NET Framework 2.0 语言特点:强类型语言 规划知识点: 1..net fram ...

  4. sonne_game网站开发02spring+mybatis框架搭建

    从最开始搭框架谈起,而且,我不仅仅会讲how,还会努力讲why.因为对于web开发,由于有太多好的框架.组件.工具,使得how往往不是那么深刻,背后的why更值得专研.如果有初学者关注我这个系列,也一 ...

  5. 字符串正则替换replace第二个参数是函数的问题

    按照JS高程的说法,如下 replace()方法的第二个参数也可以是一个函数.在只有一个匹配项(即与模式匹配的字符串)的情况下,会向这个函数传递3个参数:模式的匹配项.模式匹配项在字符串中的位置和原始 ...

  6. 每天一个linux命令(52):ifconfig命令

    许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改.Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config).通常需 ...

  7. MVVM架构~knockoutjs系列之扩展ajax验证~验证输入数据是否与后台数据相等

    返回目录 在看这篇文章之前,你有必要先看我之前的文章,之前文章是将一个方法以参数的形式传给KO,然后返回一个真假值,去做验证,这类似于面向对象语言里的委托,在JS里我们叫它回调方法,本篇文章与前一文章 ...

  8. 爱上MVC系列~过滤器实现对响应流的处理

    回到目录 MVC的过滤器相信大家都用过,一般用来作权限控制,因为它可以监视你的Action从进入到最后View的渲染,整个过程ActionFilter这个过滤器都参与了,而这给我们的开发带来了更多的好 ...

  9. Atitit 电子商务订单号码算法(java c# php js 微信

    Atitit 电子商务订单号码算法(java c# php js  微信 1.1. Js版本的居然钱三爷里面没有..只好自己实现了. 1.2. 订单号标准化...长度16位 1.3. 订单号的结构 前 ...

  10. 跨终端 Web

    跨终端 Web(移动优先|响应式|HTML5|Hybrid|桌面+移动应用|一线前端负责人联袂推荐) 徐凯  著   ISBN 978-7-121-23345-6 2014年6月出版 定价:55.00 ...