一、前言:

      最近做一个简单的在线升级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. 用“MEAN”技术栈开发web应用(一)AngularJs前端架构

    前言 不知何时突然冒出“MEAN技术栈”这个新词,听起来很牛逼的样子,其实就是我们已经熟悉了的近两年在前端比较流行的技术,mongodb.express.angularjs.nodejs,由于这几项技 ...

  2. Java提高配(三七)-----Java集合细节(三):subList的缺陷

    我们经常使用subString方法来对String对象进行分割处理,同时我们也可以使用subList.subMap.subSet来对List.Map.Set进行分割处理,但是这个分割存在某些瑕疵. 一 ...

  3. Linux Shell函数

    200 ? "200px" : this.width)!important;} --> 介绍 正文 $? $?是shell变量,表示"最后一次执行命令"的 ...

  4. Unity3D热更新全书-何谓热更新,为何热更新,如何热更新

    首先来赞叹一下中文,何谓为何如何,写完才发现这三个词是如此的有规律. 为何赞叹中文?因为这是一篇针对新手程序员的文字,是一节语文课. 然后来做一下说文解字,也就是 何谓热更新 热更新,每个程序员一听就 ...

  5. MYSQL-用户操作

    说明:本文主要写了,MYSQL对于用户的一些操作,有:查看用户,创建用户,权限的赋予,收回,用户的密码修改和删除. MySql的用户管理是通过 User表来实现的,添加新用户常用的方法有两个,一是在U ...

  6. Redis操作命令总结

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/118.html?1455860089 一.key pattern 查询相应 ...

  7. jQuery对 动态添加 的元素 绑定事件(on()的用法)

    从jQuery 版本 1.7 起,on() 方法是向被选元素添加事件处理程序的(官方推荐)首选方法. 当浏览器下载完一个页面的时候就开始渲染(翻译)HTML标签,然后执行css.js代码,在执行js代 ...

  8. 学习ASP.NET MVC(九)——“Code First Migrations ”工具使用示例

    在上一篇文章中,我们学习了如何使用实体框架的“Code First Migrations ”工具,使用其中的“迁移”功能对模型类进行一些修改,同时同步更新对应数据库的表结构. 在本文章中,我们将使用“ ...

  9. 初了解NodeJS

    发现了NodeJS以后让我有一种很激动的心情,因为它能做我以前一直想写的东西,而如果没有NodeJS我还需要去学习别的语言,但是有了NodeJS以后就可以通过Javascript来写我的程序了,不得不 ...

  10. 如何下载android官网Lib包

    例如:https://dl-ssl.google.com/android/repository/sources-23_r01.zip