[转载]尝鲜Jumony for MVC,体验插件化网站开发

mikel阅读(1021)

[转载]尝鲜Jumony for MVC,体验插件化网站开发 – Ivony… – 博客园.

Jumony for MVC是这一年来Jumony项目的重头戏,Jumony for MVC是Jumony技术与ASP.NET MVC的结合,尽管现在Jumony for MVC还未发布稳定版本,但确实是非常值得一试的一个视图引擎。希望这篇文章能给大家带来眼前一亮的感觉。

在“用网站(WebSite而不是WebProject)项目构建ASP.NET MVC网站”文章中,我已经说明了如何在网站项目上搭建MVC框架,那么这一次,我同样在网站项目上搭建Jumony for MVC。如果您已经安装了NuGet,这一切将变得非常简单,如果还没有,那么可以到NuGet的官方网站(http://www.nuget.org/) 上下载,或者通过VS2010的扩展管理器直接搜索安装。由于Jumony for MVC要求.NET 4.0的环境,所以这一篇文章将基于VS2010 + .NET Framework 4.0 + NuGet等这些高科技武器来介绍,如果你缺乏其中的任何一样,说明你该升级你的装备了。

关于NuGet的使用,博客园也有很多文章介绍,例如dudu的这篇:http://www.cnblogs.com/dudu/archive/2011/07/15/nuget.html。在这篇文章中,我假定大家了解NuGet的基本用法,其实NuGet用起来很简单的。

一、安装Jumony for MVC

废话不多说,首先,当然还是要建立一个解决方案。然后,新建一个空白的网站:

image

掏空web.config中多余的设置:

image

然后通过NuGet安装Jumony for MVC的预览版。首先在网站上点击右键,选择Manage NuGet Packages

image

在搜索框中输入Jumony,然后选择Jumony for MVC,点击旁边的Install按钮

image

当跳出许可协议时,选择Accept(Jumony采用LGPL协议发布)。然后NuGet就会开始下载Jumony for MVC的预览版,同时安装到网站中。

安装完毕后网站应该是这样的:

image

同时web.config文件会被修改,添加大量配置,这些配置已经足够在网站项目运行MVC框架了。

二、准备Controller

然后我们开始来构建我们的第一个Controller,这一次我们尝试用一个独立的项目来承载Controller,这是插件化的第一步。那么新建一个项目,我命名为Demo.Test,这个项目仅仅用来测试我们的环境:

image

为这个项目添加System.Web.Mvc的引用,这里可能要特别注意版本,Jumony for MVC默认是使用MVC2的,所以这里也添加MVC2的引用。

当然你也可以修改web.config中的MVC程序集的版本。要确保你的Controller项目所引用MVC的版本和网站所引用的一致,否则就要添加MVC版本兼容的配置了,我在这里不详述。就用MVC2来做示范。

添加引用后,就可以删除默认的cs文件,添加一个Controller,我命名为TestController,然后往里面随便塞一个Action,像这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;

namespace Demo.Test
{
   public class TestController : Controller
   {
     public ActionResult Index()
     {
        //这个Action就是输出一个视图
      return View();
     }
   }
}

OK,接下来是配置路由,如果路由都在global.asax中配置,那是多么没有创意的事情,而且,这会造成所有的功能都要到那边去配置一下,不 利于我们所追求的插件化。所以,这一次我在Controller程序集里面配置路由,这样,只要这个程序集的DLL放在了网站的bin文件夹,那么路由就 自动被配置了,是不是很酷呢?这么酷的功能利用ASP.NET 4的新特性其实非常好实现。首先我们要添加System.Web程序集的引用,然后添加如下代码:

[assembly: System.Web.PreApplicationStartMethod( typeof( Demo.Test.RoutingRegister ), "RegisterRouting" )]
namespace Demo.Test
{
   public static class RoutingRegister
   {
     public static void RegisterRouting()
     {
     }
   }

注意PreApplicationStartMethod这个特性要注册在程序集范畴(assembly),所以除了在前面要加上assembly:之外,还需要放到namespace块的外面去。

通过这样的代码,我们就能确保在网站应用程序启动前执行RegisterRouting这个方法了,所以,我们可以在这里添加路由注册的代码。不过 在这里,我要向大家介绍一下Jumony for MVC提供的一个MVC框架改善,简单路由表(SimpleRoutingTable)。

首先,通过同样的方法,利用NuGet为这个项目添加Jumony for MVC的引用。然后引用Jumony for MVC的命名空间Ivony.Html.Web.Mvc,最后在RegisterRouting方法中注册一个路由:

    public static void RegisterRouting()
    {
      MvcEnvironment.SimpleRoutingTable.MapAction( "~/", "Test", "Index" );
    }

简单路由表的实例总是可以通过MvcEnvironment静态类型来获取,通过同样的手法,Jumony for MVC会自动为系统注册简单路由表为一个路由服务。在这里简单说一下简单路由表相对于ASP.NET Routing默认实现的一部分改善。

  1. 首先是简单路由表没有RouteTable的先到先得的原则,在简单路由表中的路由规则遵循的是谁更适合就选谁的原则,这更符合使用者的直觉。也使得我们可以把路由规则注册分散到各个项目,即使这些路由规则被注册的时间不可控。(这可是插件化的关键问题之一)
  2. 由于简单路由表遵循的是谁更适合的原则,所以简单路由表不允许注册存在冲突的路由规则,这样更不容易犯错。
  3. 简单路由表取消了复杂的概念,像上面那个路由规则,其实非常简单,就是在”~/”这个虚拟路径和{ controller: Test, action: Test }之间建立一个对应的关系。取消了默认值和有效值的概念,尽管牺牲了灵活性,但提高了对URL的可控性。不会产生令人惊讶的结果。

简单路由表事实上只是RouteBase的一个实现(也就是一个路由),所以尽管通过简单路由表注册的路由规则没有先到先得的规则,但简单路由表与 其他RouteBase实现一起在RouteTable中仍然遵循ASP.NET框架所设定的先到先得规则。关于这一点,我在后面的系列文章中再详细的讨 论简单路由表。

最终的代码像这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Ivony.Html.Web.Mvc;

[assembly: System.Web.PreApplicationStartMethod( typeof( Demo.Test.RoutingRegister ), "RegisterRouting" )]
namespace Demo.Test
{
   public static class RoutingRegister
   {
     public static void RegisterRouting()
     {
       MvcEnvironment.SimpleRoutingTable.MapAction( "~/", "Test", "Index" );
     }
   }
   public class TestController : Controller
   {
     public ActionResult Index()
     {
       //这个Action就是输出一个视图
       return View();
     }
   }
}

三、配置视图

Controller和路由都已经配置完毕,现在回到网站,我们为网站添加刚刚Demo.Test项目的引用:

image

然后生成网站,运行之,检查我们的路由和Controller是否正常运行。(记得一定要先生成网站,这样我们的Demo.Test才会被编译为DLL并放到bin文件夹)。

image

如果您看到的是这个界面,就说明已经成功了。细心的您可能已经发现,查找路径中已然包含了html文件的查找,这就是JumonyViewEngine被注册的结果。这一切都是通过同样的手法(PreApplicationStartMethod特性)在悄声无息中完成的。

那么接下来就是弄一个视图出来,我比较习惯把所有视图页面都放在Views目录下,不要带上Controller名字的文件夹,所以我弄了一个index.html文件放在Views文件夹下:

image

内容就像这样子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Hello Jumony</title>
</head>
<body>
  Jumony for MVC
</body>
</html>

因为不是放在指定的位置,所以我们需要修改一下JumonyViewEngine的设置,打开global.asax文件,修改为下面这样:

  void Application_Start( object sender, EventArgs e )
  {
    MvcEnvironment.JumonyViewEngine.ViewLocationFormats = new[] { "~/Views/{0}.html" };
  }

要搜索多个路径,可以在数组中多添加几个路径模版。当修改完了后,再次打开网站应该是这样了:

image

四、留言板

当然这个网站毫无意义,我们做个有意义的东西,留言板,只包含两个功能添加留言和浏览留言的留言板(尽管还是没啥意义,但是Demo,大家总不能让我弄个论坛来做示范)。

以前做网站都是需要先出来页面,程序才开始动手,那么这一次,我们可以先不要管页面,先构建留言板的功能实现,即Controller实现。

同样的,我新建了一个独立的项目:Demo.MessageController,添加引用创建Controller的过程和之前一样,在此不再赘述,最终实现的Controller像这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web;
using System.Web.Hosting;
using System.IO;
using Ivony.Html.Web.Mvc;

[assembly: System.Web.PreApplicationStartMethod( typeof( Demo.MessageBoard.RoutingRegister ), "RegisterRouting" )]
namespace Demo.MessageBoard
{
   public static class RoutingRegister
  {
    public static void RegisterRouting()
    {
      MvcEnvironment.SimpleRoutingTable.MapAction( "~/messages", "MessageBoard", "List" );
      MvcEnvironment.SimpleRoutingTable.MapAction( "~/messages/add", "MessageBoard", "add" );
    }
  }
 
  public class MessageBoardController : Controller
  {
 
    private const string dataPath = "~/Data";
 
    private string physicalPath;
 
    protected override void OnActionExecuting( ActionExecutingContext filterContext )
    {
      physicalPath = HostingEnvironment.MapPath( dataPath );
      Directory.CreateDirectory( physicalPath );
 
      base.OnActionExecuting( filterContext );
    }
 
    public ActionResult List()
    {
 
      var messages = new DirectoryInfo( physicalPath )
        .EnumerateFiles()
        .OrderBy( file => file.CreationTime )
        .Select( file => System.IO.File.ReadAllText( file.FullName ) );
 
      return View( "Message_List", messages );
    }
 
    public ActionResult Add( string message )
    {
      var filepath = Path.Combine( physicalPath, Guid.NewGuid().ToString() + ".txt" );
 
      System.IO.File.WriteAllText( filepath, message );
 
      return RedirectToAction( "List" );
 
    }
 
  }
 
}

两个Action,简单明了,为了避免配置数据库等麻烦事儿,这里直接用文件系统担任了数据库,所有的留言都以文本文件保存了下来。然后我们为网站添加这个功能,也就是让网站项目引用这个 Controller所在的项目。

然后要为这个留言板提供一个视图,在List这个Action中,已经显示指定了视图的名称,Message_List。所以我在Views文件夹添加了这样一个HTML文件:

image

内容如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>留言板</title>
  <style>
    body
    {
      font-size: 12px;
      margin: 10px;
    }
    .message
    {
      margin: 15px 0px;
      border: dashed 1px;
      padding: 10px;
    }
  </style>
</head>
<body>
  <div class="message">
  </div>
  <form method="post" action="Add" controller>
  <div>
    <textarea id="message" name="message" style="width: 500px; height: 100px;"></textarea>
  </div>

  <div>
    <input type="submit" value="提交留言" />
  </div>
  </form>
</body>
</html>

注意form标签里面的属性,这样写的意思就是,提交到相同Controller的名为Add的Action去。这就是Jumony视图引擎,自然 流畅。由于action本来就是标准form元素的一个属性,所以加一个controller强调不是提交到Add这个URL,controller不设 置值,则会从当前上下文继承。

接下来我们在那个测试首页添加一个链接,让我们可以访问到留言板的页面:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Hello Jumony</title>
</head>
<body>
  <a action="List" controller="MessageBoard">MessageBoard</a>
</body>
</html>

<a>标签中的action和controller就不解释了,这时候再打开网站,看到的应该是这样的效果:

image

点击链接就可以到达留言板:

image

我们可以随便留言,但提交后,页面一闪而过,还是老样子。这是因为我们还没有为留言板页面绑定留言数据。

Jumony现阶段对这种简单重复N次的工作并不比WebForm或是Razor好,所以我决定将绑定数据外包给WebForm来做,顺便也可以看看Jumony for MVC是如何与其他视图引擎无缝兼容的。

WebFormViewEngine的视图查找逻辑也是可以修改的,但比较麻烦,所以这一次我决定按照他的规则,新建一个Shared文件夹,在下面创建一个部分视图。

image

如何在网站创建WebFormViewEngine的视图已经在上一篇文章中探讨过,主要就是设置Inherits属性,作为用户控件(部分视图),应从ViewUserControl继承,以下是部分视图完整代码:

<%@ Control Language="C#" ClassName="MessageListPartial" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
  foreach ( var message in (IEnumerable<string>) Model )
  {
%>
<div class="message">
  <%=message %>
</div>
<% }%>

当然,如果用Razor,这里的代码能够更为简洁和优雅。

接下来是如何将这个部分视图拼合到Jumony视图引擎的html视图中去,其实也非常简单,使用partial标签,将原来的<div class=”message”></div>修改为这样:

  <partial view="MessageListPartial"></partial>

重新打开留言板看看,应该可以看到留言了:

image

Jumony for MVC,值得您关注。。。。。

http://jumony.codeplex.com/

[原创]自定义ASP.NET MVC JsonResult序列化结果

mikel阅读(1860)

自定义ASP.NET MVC JsonResult序列化结果

自定义ASP.NET MVC 控制器和JsonResult

最近项目中前台页面使用EasyUIJQuery插件开发中遇到,EasyUI Form中的Datebox组件绑定ASP.NET MVC返回的DateTime类型的数据错误,因为ASP.NET MVC返回的DateTime类型的JsonResult的结果中的值是“\/Date(277630788015)\/”,于是EasyUI显示的就是返回的值,没有将日期转换,直接显示在DateBox组件中,解决这个问题其实有两种办法:

1. 扩展EasyUIdatebox组件的parser函数自定义格式化日期格式,不过存在一个问题是如果使用form.load数据是先将值赋给datebox不会调用dateboxparser方法,只有在加载完form后再改变datebox的值为”2011-11-3”格式的值;

2. 第二种方式就是本文要讲得修改ASP.NET MVCJson序列化方法,也就是修改JsonResult的序列化方法,下面就来详细说下这种方法。

首先看下ASP.NET MVC中的Controller Json方法的源码:

protected internal JsonResult Json(object data) {

return Json(data, null /* contentType */);

}

protected internal JsonResult Json(object data, string contentType) {

return Json(data, contentType, null /* contentEncoding */);

}

protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding) {

return new JsonResult {

Data = data,

ContentType = contentType,

ContentEncoding = contentEncoding

};

}

可以看出关键还是在JsonResult这个结果类中,JsonResult类的源码如下:

[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]

[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]

public class JsonResult : ActionResult {

public Encoding ContentEncoding {

get;

set;

}

public string ContentType {

get;

set;

}

public object Data {

get;

set;

}

public override void ExecuteResult(ControllerContext context) {

if (context == null) {

throw new ArgumentNullException(“context”);

}

HttpResponseBase response = context.HttpContext.Response;

if (!String.IsNullOrEmpty(ContentType)) {

response.ContentType = ContentType;

}

else {

response.ContentType = “application/json”;

}

if (ContentEncoding != null) {

response.ContentEncoding = ContentEncoding;

}

if (Data != null) {

#pragma warning disable 0618

JavaScriptSerializer serializer = new JavaScriptSerializer();

response.Write(serializer.Serialize(Data));

#pragma warning restore 0618

}

}

}

}

看到这里大家应该明确我们的修改目标了,对,就是ExecuteResult这个方法,这个方法是序列化Data对象为Json格式的,可见ASP.NET MVC 使用的是System.Web.Script.Serialization.JavaScriptSerializer

既然明确了目标,那么就开始动手吧。

1. 扩展JsonResult类自定义个CustomJsonResult类,重写ExecuteResult方法代码如下:


public class CustomJsonResult:JsonResult

{

public override void ExecuteResult(ControllerContext context)

{

if (context == null)

{

throw new ArgumentNullException(“context”);

}

HttpResponseBase response = context.HttpContext.Response;

if (!String.IsNullOrEmpty(ContentType))

{

response.ContentType = ContentType;

}

else

{

response.ContentType = “application/json”;

}

if (ContentEncoding != null)

{

response.ContentEncoding = ContentEncoding;

}

if (Data != null)

{

#pragma warning disable 0618

response.Write(JsonConvert.SerializeObject(Data));

#pragma warning restore 0618

}

}

我们使用的是Newtonsoft.Json.JsonConvert类序列化对象为Json的,具体集中.NET中的序列化对比可以参考文章:.NET使用JSON作为数据交换格式

2. 扩展Controller重写Json方法,代码如下:


public class BaseController:Controller

{

protected override JsonResult Json(object data, string contentType, Encoding contentEncoding)

{

return new CustomJsonResult

{

Data = data,

ContentType = contentType,

ContentEncoding = contentEncoding

};

}

}

下面就是我们实际使用方法了,因为Newtonsoft.Json.JsonConvertDateTime类型可以指定序列化日期的类型为: [JsonConverter(typeof(IsoDateTimeConverter))] [JsonConverter(typeof(JavaScriptDateTimeConverter))]

[JsonConverter(typeof(IsoDateTimeConverter))]序列化后的格式为:1981-03-16T00:20:12.1875+08:00

[JsonConverter(typeof(JavaScriptDateTimeConverter))]序列化后的格式为:new Date(-277630787812)

于是我们指定实体类的DateTime属性为IsoDateTimeConverter,代码如下:

[Field(“P_Date”, “更新日期“)]

[JsonConverter(typeof(IsoDateTimeConverter))]

public DateTime P_Date { get; set; }

控制器继承自BaseControllerAction的返回结果还是JsonResult格式,代码如下:

public class GoodsController:BaseController

{

public JsonResult List(string page, string rows)

{

Page thepage = new Page() { PageSize = 20, CurrentPage = 1 };

if (!String.IsNullOrEmpty(rows))

{

thepage.PageSize = Convert.ToInt32(rows);

}

if (!String.IsNullOrEmpty(page))

{

thepage.CurrentPage = Convert.ToInt32(page);

}

Dictionary<string, object> result = new Dictionary<string, object>();

result.Add(“rows”, new BusinessLogic().SelectByPage<GoodsList>(ref thepage));

result.Add(“total”, thepage.SumCount);

return Json(result);

}

}

[转载]在.NET使用JSON作为数据交换格式

mikel阅读(1568)

[转载]在.NET使用JSON作为数据交换格式_知识库_博客园.

我们知道在.NET中我们有多种对象序列化的方式,如XML方式序列化、Binary序列化,其中XML序列化是一种比较通用的在各语言之间传递数据的方式。除了这两种序列化方式之外,在.NET中还可以使用JSON序列化。
JSON(JavaScript Object Notation)是一种轻量级轻量级的数据交换格式,并且它独立于编程语言,与XML序列化相比,JSON序列化后产生的数据一般要比XML序列化后数 据体积小,所以在Facebook等知名网站中都采用了JSON作为数据交换方式。在.NET中有三种常用的JSON序列化的类,分别是   System.Web.Script.Serialization.JavaScriptSerializer类、 System.Runtime.Serialization.Json.DataContractJsonSerializer类和 Newtonsoft.Json.JsonConvert类。
为了便于下面的演示,下面提供一个类的代码:

[DataContract]
publicclass User
{
///<summary>
/// 编号
///</summary>
[DataMember]
publicint UserId { get; set; }
///<summary>
/// 用户名
///</summary>
[DataMember]
publicstring UserName { get; set; }
///<summary>
/// 创建时间
///</summary>
[DataMember]
[JsonConverter(
typeof(IsoDateTimeConverter))]
public DateTime CreateDate { get; set; }
///<summary>
/// 生日
///</summary>
[DataMember]
[JsonConverter(
typeof(JavaScriptDateTimeConverter))]
public DateTime Birthday { get; set; }
///<summary>
/// 相关URL
///</summary>
[DataMember]
public List<string> Urls { get; set; }
///<summary>
/// 薪水
///</summary>
//[ScriptIgnore]//使用JavaScriptSerializer序列化时不序列化此字段
//[IgnoreDataMember]//使用DataContractJsonSerializer序列化时不序列化此字段
//[JsonIgnore]//使用JsonConvert序列化时不序列化此字段
publicint Salary { get; set; }
///<summary>
/// 权利级别
///</summary>
[DataMember]
public Priority Priority { get; set; }

public User()
{
Urls
=new List<string>();
}
}
///<summary>
/// 权利级别
///</summary>
publicenum Priority:byte
{
Lowest
=0x1,
BelowNormal
=0x2,
Normal
=0x4,
AboveNormal
=0x8,
Highest
=0x16
}

使用System.Web.Script.Serialization.JavaScriptSerializer类
System.Web.Script.Serialization.JavaScriptSerializer类是.NET类库中自带的一种JSON序 列化实现,在.NET Framework3.5及以后版本中可以使用这个类,这个类位于System.Web.Extensions.dll中,使用这个类是必须添加对这个 dll的引用。
下面的代码是使用JavaScriptSerializer进行序列化和反序列化的例子:

publicstaticvoid JavaScriptSerializerDemo()
{
User user
=new User { UserId =1, UserName =李刚, CreateDate = DateTime.Now.AddYears(30),Birthday=DateTime.Now.AddYears(50), Priority = Priority.Highest, Salary =500000 };
//JavaScriptSerializer类在System.Web.Extensions.dll中,注意添加这个引用
JavaScriptSerializer serializer =new JavaScriptSerializer();
//JSON序列化
string result=serializer.Serialize(user);
Console.WriteLine(
使用JavaScriptSerializer序列化后的结果:{0},长度:{1}, result, result.Length);
//JSON反序列化
user = serializer.Deserialize<User>(result);
Console.WriteLine(
使用JavaScriptSerializer反序列化后的结果:UserId:{0},UserName: {1},CreateDate:{2},Priority:{3}, user.UserId, user.UserName, user.CreateDate, user.Priority);

}

说明:如果不想序列化某个字段,可以在字段前面加[JsonIgnore]标记。
使用System.Runtime.Serialization.Json.DataContractJsonSerializer类
System.Runtime.Serialization.Json.DataContractJsonSerializer类位于 System.ServiceModel.Web.dll中,使用这个类时除了需要添加对System.ServiceModel.Web.dll的引用 之外,还需要添加System.Runtime.Serialization.dll的引用,注意这个类也是在.NET Framework3.5及以后版本中可以使用。
下面是使用DataContractJsonSerializer类的例子:

publicstaticvoid DataContractJsonSerializerDemo()
{
User user
=new User { UserId =1, UserName =李刚, CreateDate = DateTime.Now.AddYears(30), Birthday = DateTime.Now.AddYears(50), Priority = Priority.AboveNormal, Salary =50000 };
string result =string.Empty;
//DataContractJsonSerializer类在System.ServiceModel.Web.dll中,注意添加这个引用
DataContractJsonSerializer serializer =new DataContractJsonSerializer(typeof(User));

using (MemoryStream stream =new MemoryStream())
{
//JSON序列化
serializer.WriteObject(stream, user);
result
= Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(
使用DataContractJsonSerializer序列化后的结果:{0},长度:{1}, result, result.Length);
}

//JSON反序列化
byte[] buffer = Encoding.UTF8.GetBytes(result);
using (MemoryStream stream =new MemoryStream(buffer))
{
user
= serializer.ReadObject(stream) as User;
Console.WriteLine(
使用DataContractJsonSerializer反序列化后的结果:UserId: {0},UserName:{1},CreateDate:{2},Priority:{3}, user.UserId, user.UserName, user.CreateDate, user.Priority);
}
}

注意:要使用DataContractJsonSerializer类进行序列化和反序列化,必须给类加上[DataContract]属性, 对要序列化的字段加上[DataMember]属性,如果不想序列化某个字段或者属性,可以加上[IgnoreDataMember]属性。
使用Newtonsoft.Json.JsonConvert类
Newtonsoft.Json.JsonConvert类是非微软提供的一个JSON序列化和反序列的开源免费的类库(下载网址是:http://www.codeplex.com/json/), 它提供了更灵活的序列化和反序列化控制,并且如果你的开发环境使用的是.NET Framework3.5及以后版本的话,你就可以使用Linq to JSON,这样一来面对一大段的数据不必一一解析,你可以使用Linq to JSON解析出你关心的那部分即可,非常方便。
下面是使用Newtonsoft.Json.JsonConvert类的例子:

publicstaticvoid JsonConvertDemo()
{
User user
=new User { UserId =1, UserName =李刚, CreateDate = DateTime.Now.AddYears(30), Birthday = DateTime.Now.AddYears(50), Priority = Priority.BelowNormal, Salary =5000 };
//JsonConvert类在Newtonsoft.Json.Net35.dll中,注意到http://www.codeplex.com/json/下载这个dll并添加这个引用
//JSON序列化
string result = JsonConvert.SerializeObject(user);
Console.WriteLine(
使用JsonConvert序列化后的结果:{0},长度:{1}, result, result.Length);
//JSON反序列化
user = JsonConvert.DeserializeObject<User>(result);
Console.WriteLine(
使用JsonConvert反序列化后的结果:UserId:{0},UserName: {1},CreateDate:{2},Priority:{3}, user.UserId, user.UserName, user.CreateDate, user.Priority);
}

publicstaticvoid JsonConvertLinqDemo()
{
User user
=new User { UserId =1, UserName =周公, CreateDate = DateTime.Now.AddYears(8), Birthday = DateTime.Now.AddYears(32), Priority = Priority.Lowest, Salary =500, Urls =new List<string> { http://zhoufoxcn.blog.51cto.com, http://blog.csdn.net/zhoufoxcn } };
//JsonConvert类在Newtonsoft.Json.Net35.dll中,注意到http://www.codeplex.com/json/下载这个dll并添加这个引用
//JSON序列化
string result = JsonConvert.SerializeObject(user);
Console.WriteLine(
使用JsonConvert序列化后的结果:{0},长度:{1}, result, result.Length);
//使用Linq to JSON
JObject jobject = JObject.Parse(result);
JToken token
= jobject[Urls];
List
<string> urlList =new List<string>();
foreach (JToken t in token)
{
urlList.Add(t.ToString());
}
Console.Write(
使用Linq to JSON反序列化后的结果:[);
for (int i =0; i < urlList.Count 1;i++ )
{
Console.Write(urlList[i]
+,);
}
Console.WriteLine(urlList[urlList.Count
1] +]);
}

注意:如果有不需要序列化的字段,可以给该字段添加[JsonIgnore]标记。在Newtonsoft这 个类库中对于日期的序列化有多种方式,可以类的DataTime成员添加上对应的标记,这样在进行序列化和反序列化时就会按照指定的方式进行,在本例中 User类的CreateDate属性添加的属性是[JsonConverter(typeof(IsoDateTimeConverter))],而 Birthday属性添加的属性是[JsonConverter(typeof(JavaScriptDateTimeConverter))],从序列 化的结果可以看出来它们最终的表现形式并不一样。
本文中所有的示例代码如下:

1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Web.Script.Serialization;
6. using System.Runtime.Serialization.Json;
7. using System.IO;
8. using System.Runtime.Serialization;
9. using Newtonsoft.Json;
10. using Newtonsoft.Json.Linq;
11. using Newtonsoft.Json.Converters;
12.
13. namespace JSONDemo
14. {
15. class Program
16. {
17. staticvoid Main(string[] args)
18. {
19. JavaScriptSerializerDemo();
20. DataContractJsonSerializerDemo();
21. JsonConvertDemo();
22. JsonConvertLinqDemo();
23. Console.ReadLine();
24. }
25.
26. publicstaticvoid JavaScriptSerializerDemo()
27. {
28. User user =new User { UserId =1, UserName =李刚, CreateDate = DateTime.Now.AddYears(30),Birthday=DateTime.Now.AddYears(50), Priority = Priority.Highest, Salary =500000 };
29. //JavaScriptSerializer类在System.Web.Extensions.dll中,注意添加这个引用
30. JavaScriptSerializer serializer =new JavaScriptSerializer();
31. //JSON序列化
32. string result=serializer.Serialize(user);
33. Console.WriteLine(使用JavaScriptSerializer序列化后的结果:{0},长度:{1}, result, result.Length);
34. //JSON反序列化
35. user = serializer.Deserialize<User>(result);
36. Console.WriteLine(使用JavaScriptSerializer反序列化后的结果:UserId:{0},UserName:{1},CreateDate:{2},Priority:{3}, user.UserId, user.UserName, user.CreateDate, user.Priority);
37.
38. }
39.
40. publicstaticvoid DataContractJsonSerializerDemo()
41. {
42. User user =new User { UserId =1, UserName =李刚, CreateDate = DateTime.Now.AddYears(30), Birthday = DateTime.Now.AddYears(50), Priority = Priority.AboveNormal, Salary =50000 };
43. string result =string.Empty;
44. //DataContractJsonSerializer类在System.ServiceModel.Web.dll中,注意添加这个引用
45. DataContractJsonSerializer serializer =new DataContractJsonSerializer(typeof(User));
46.
47. using (MemoryStream stream =new MemoryStream())
48. {
49. //JSON序列化
50. serializer.WriteObject(stream, user);
51. result = Encoding.UTF8.GetString(stream.ToArray());
52. Console.WriteLine(使用DataContractJsonSerializer序列化后的结果:{0},长度:{1}, result, result.Length);
53. }
54.
55. //JSON反序列化
56. byte[] buffer = Encoding.UTF8.GetBytes(result);
57. using (MemoryStream stream =new MemoryStream(buffer))
58. {
59. user = serializer.ReadObject(stream) as User;
60. Console.WriteLine(使用DataContractJsonSerializer反序列化后的结果:UserId:{0},UserName:{1},CreateDate:{2},Priority:{3}, user.UserId, user.UserName, user.CreateDate, user.Priority);
61. }
62. }
63.
64. publicstaticvoid JsonConvertDemo()
65. {
66. User user =new User { UserId =1, UserName =李刚, CreateDate = DateTime.Now.AddYears(30), Birthday = DateTime.Now.AddYears(50), Priority = Priority.BelowNormal, Salary =5000 };
67. //JsonConvert类在Newtonsoft.Json.Net35.dll中,注意到http://www.codeplex.com/json/下载这个dll并添加这个引用
68. //JSON序列化
69. string result = JsonConvert.SerializeObject(user);
70. Console.WriteLine(使用JsonConvert序列化后的结果:{0},长度:{1}, result, result.Length);
71. //JSON反序列化
72. user = JsonConvert.DeserializeObject<User>(result);
73. Console.WriteLine(使用JsonConvert反序列化后的结果:UserId:{0},UserName:{1},CreateDate:{2},Priority:{3}, user.UserId, user.UserName, user.CreateDate, user.Priority);
74. }
75.
76. publicstaticvoid JsonConvertLinqDemo()
77. {
78. User user =new User { UserId =1, UserName =周公, CreateDate = DateTime.Now.AddYears(8), Birthday = DateTime.Now.AddYears(32), Priority = Priority.Lowest, Salary =500, Urls =new List<string> { http://zhoufoxcn.blog.51cto.com, http://blog.csdn.net/zhoufoxcn } };
79. //JsonConvert类在Newtonsoft.Json.Net35.dll中,注意到http://www.codeplex.com/json/下载这个dll并添加这个引用
80. //JSON序列化
81. string result = JsonConvert.SerializeObject(user);
82. Console.WriteLine(使用JsonConvert序列化后的结果:{0},长度:{1}, result, result.Length);
83. //使用Linq to JSON
84. JObject jobject = JObject.Parse(result);
85. JToken token = jobject[Urls];
86. List<string> urlList =new List<string>();
87. foreach (JToken t in token)
88. {
89. urlList.Add(t.ToString());
90. }
91. Console.Write(使用Linq to JSON反序列化后的结果:[);
92. for (int i =0; i < urlList.Count 1;i++ )
93. {
94. Console.Write(urlList[i] +,);
95. }
96. Console.WriteLine(urlList[urlList.Count 1] +]);
97. }
98. }
99.
100. [DataContract]
101. publicclass User
102. {
103. ///<summary>
104. /// 编号
105. ///</summary>
106. [DataMember]
107. publicint UserId { get; set; }
108. ///<summary>
109. /// 用户名
110. ///</summary>
111. [DataMember]
112. publicstring UserName { get; set; }
113. ///<summary>
114. /// 创建时间
115. ///</summary>
116. [DataMember]
117. [JsonConverter(typeof(IsoDateTimeConverter))]
118. public DateTime CreateDate { get; set; }
119. ///<summary>
120. /// 生日
121. ///</summary>
122. [DataMember]
123. [JsonConverter(typeof(JavaScriptDateTimeConverter))]
124. public DateTime Birthday { get; set; }
125. ///<summary>
126. /// 相关URL
127. ///</summary>
128. [DataMember]
129. public List<string> Urls { get; set; }
130. ///<summary>
131. /// 薪水
132. ///</summary>
133. [ScriptIgnore]//使用JavaScriptSerializer序列化时不序列化此字段
134. [IgnoreDataMember]//使用DataContractJsonSerializer序列化时不序列化此字段
135. [JsonIgnore]//使用JsonConvert序列化时不序列化此字段
136. publicint Salary { get; set; }
137. ///<summary>
138. /// 权利级别
139. ///</summary>
140. [DataMember]
141. public Priority Priority { get; set; }
142.
143. public User()
144. {
145. Urls =new List<string>();
146. }
147. }
148. ///<summary>
149. /// 权利级别
150. ///</summary>
151. publicenum Priority:byte
152. {
153. Lowest=0x1,
154. BelowNormal=0x2,
155. Normal=0x4,
156. AboveNormal=0x8,
157. Highest=0x16
158. }
159. }

程序的运行结果如下:

  1. 使用JavaScriptSerializer序列化后的结果: {“UserId”:1,”UserName”:”李刚”,”CreateDate”:”\/Date(353521211984) \/”,”Birthday”:”\/Date(-277630788015)\/”,”Urls”:[],”Priority”:22},长度:127
  2. 使用JavaScriptSerializer反序列化后的结果:UserId:1,UserName:李刚,CreateDate:1981-3-15 16:20:11,Priority:Highest
  3. 使用DataContractJsonSerializer序列化后的结果: {“Birthday”:”\/Date(-277630787953+0800)\/”,”CreateDate”:” \/Date(353521212046+0800)\/”,”Priority”:8,”Urls”: [],”UserId”:1,”UserName”:”李刚”},长度:136
  4. 使用DataContractJsonSerializer反序列化后的结果:UserId:1,UserName:李刚,CreateDate:1981-3-16 0:20:12,Priority:AboveNormal
  5. 使用JsonConvert序列化后的结果:{“UserId”:1,”UserName”:”李 刚”,”CreateDate”:”1981-03-16T00:20:12.1875+08:00″,”Birthday”:new Date(-277630787812),”Urls”: [],”Priority”:2},长度:132
  6. 使用JsonConvert反序列化后的结果:UserId:1,UserName:李刚,CreateDate:1981-3-16 0:20:12,Priority:BelowNormal
  7. 使用JsonConvert序列化后的结果:{“UserId”:1,”UserName”:”周 公”,”CreateDate”:”2003-03-16T00:20:12.40625+08:00″,”Birthday”:new Date(290362812406),”Urls”: [“http://zhoufoxcn.blog.51cto.com”,”http://blog.csdn.net /zhoufoxcn”],”Priority”:1},长度:198
  8. 使用Linq to JSON反序列化后的结果:[“http://zhoufoxcn.blog.51cto.com”,”http://blog.csdn.net/zhoufoxcn”]

总结:通过上面的例子大家可以看出Newtonsoft类库提供的JSON序列化和反序列的方式更加灵活,在实际开发中周公也一直使用Newtonsoft作为JSON序列化和反序列化的不二选择。

[转载]Json 的日期格式与.Net DateTime类型的转换

mikel阅读(1164)

[转载]Json 的日期格式与.Net DateTime类型的转换 – CoolCode – 博客园.

Json 的日期形式大概是这样:”/Date(1242357713797+0800)/” , 甭管它的格式是多么不友好(因为单从形式看根本不知道何年何月),如果这个Date只是一个属性的话, Json.Net 已经为我们处理好了。但有些很特殊的时候,需要单独把这个Date转换为.Net的DateTime格式,那么下面的代码就可以帮到你了。这个代码我已经找了很多次,终于被我发现了,免去重复造轮子的劳动。

这里跟大家分享一下, 可以保留毫秒,完全与原来结果一致。

      static void Main(string[] args)
        { 
            string [] jsonDates = {"/Date(1242357713797+0800)/" , "/Date(1242357722890+0800)/"};
            foreach (string jsonDate in jsonDates)
            {
                Console.WriteLine("Json : {0}", jsonDate);
                DateTime  dtResult =   JsonToDateTime(jsonDate);
                Console.WriteLine("DateTime: {0}", dtResult.ToString("yyyy-MM-dd hh:mm:ss ffffff"));
            } 
            Console.Read();
        }

        public static DateTime JsonToDateTime(string jsonDate)
        {
            string value = jsonDate.Substring(6, jsonDate.Length - 8);
            DateTimeKind kind = DateTimeKind.Utc;
            int index = value.IndexOf('+', 1);
            if (index == -1)
                index = value.IndexOf('-', 1);
            if (index != -1)
            {
                kind = DateTimeKind.Local;
                value = value.Substring(0, index);
            }
            long javaScriptTicks = long.Parse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture);
            long InitialJavaScriptDateTicks = (new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Ticks;
            DateTime utcDateTime = new DateTime((javaScriptTicks * 10000) + InitialJavaScriptDateTicks, DateTimeKind.Utc);
            DateTime dateTime;
            switch (kind)
            {
                case DateTimeKind.Unspecified:
                    dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified);
                    break;
                case DateTimeKind.Local:
                    dateTime = utcDateTime.ToLocalTime();
                    break;
                default:
                    dateTime = utcDateTime;
                    break;
            }
            return dateTime ;
        }

[转载]JSON中的Date Format转换

mikel阅读(1024)

[转载]JSON Date Format – 经世沉淀 – 博客频道 – CSDN.NET.

JSON 是一种流行地、广泛地应用于Internet的数据格式,相对于XML而言,它更小、解析更快。但可能就是由于“小”,JSON规范并没有定义如何序列化 日期。尽管在社区已经有了很多这方面的讨论,但规范仍然未形成。争论的焦点在于日期序列化是否应直接采用数字或者ISO字符串日期,还是应该采用特殊分隔 符标记的字符串日期。因为如果没有分隔符,就没法自我描述自身是一个日期类型。先搁置争议,就各AJAX库和JSON库封装的日期格式作统一讨论。

(1)直接数字型日期。

因为 JavaScript 的时间类型是从1970年1月1日以来的毫秒数。这有点类似UNIX/Linux的时间是1970年1月1日以来的秒数。当然,为支持国际化,这里的1970年1月1日的毫秒数是UTC(GMT)格林威治时间。

如:

  1. {Date: new Date(1278903921551)}

JavaScript Parser:

  1. var obj = eval(‘(‘ + “{Date: new Date(1278903921551)}” + ‘)’);
  2. var dateValue = obj[“Date”];

C# Parser:

  1. long dateNumber = 1278903921551;
  2. long beginTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
  3. DateTime dateValue = new DateTime(beginTicks + dateNumber * 10000);

(2)直接字符串日期。

字符串日期,常见的是ISO8601 标准。

如:

  1. {Date:“2010-07-12T03:05:21Z”}

JavaScript Parser:

  1. // Parse JSON string value when the string value is ISO Date Format
  2. var obj = eval(‘(‘ + ‘{Date:”2010-07-12T03:05:21Z”}’ + ‘)’);
  3. scanDate(obj, isoDateParser);
  4. var dateValue = obj[“Date”];
  5. function scanDate(obj, dateParser) {
  6. for (var key in obj) {
  7. obj[key] = dateParser(key, obj[key]);
  8. if (typeof (obj[key]) === ‘object’) {
  9. scanDate(obj[key], dateParser);
  10. }
  11. }
  12. }
  13. function isoDateParser(key, value) {
  14. if (typeof value === ‘string’) {
  15. var a = /^(/d{4})-(/d{2})-(/d{2})T(/d{2}):(/d{2}):(/d{2}(?:/./d*)?)(?:([/+-])(/d{2})/:(/d{2}))?Z?$/.exec(value);
  16. if (a) {
  17. Var utcMilliseconds = Date.UTC(+a[1], +a[2] – 1, +a[3], +a[4], +a[5], +a[6]);
  18. return new Date(utcMilliseconds);
  19. }
  20. }
  21. return value;
  22. }

C# Parser:

string dateString = “2010-07-12T03:05:21Z” DateTime dateValue = DateTime.Parse(dateString);

(3)带分隔符的数字日期。

  1. /Date(NUMBER(+/-)TZD)/
  1. “//Date(NUMBER(+/-)TZD)//”

NUMBER是1970年1月1日以来的毫秒数,TZD是4位数时区的。+和-表示东、西时区。如果没有时区,则NUMBER是UTC时间。用 /Date()/分隔。//和//是对/的转义。JSON.NET和Microsoft AJAX Library用的就是这种格式。中间的数字也是格林威治时间1970年1月1日以来的毫秒数。和第一种不同是没有采用JavaScript的日期构造 器,而是采用了一种自定义的分隔符。

如:”//Date(1278903921551)//”。

JavaScript Parser:

  1. var obj = eval(‘(‘ + “{Date: //Date(1278903921551)//}”.replace(///Date/((/d+)/)///gi, “new Date($1)”) + ‘)’);
  2. var dateValue = obj[“Date”];

C# Parser:

  1. string dateString = “///Date(1278903921551)///”;
  2. int startPos = dateString.IndexOf(“(“)+1;
  3. int endPos = dateString.LastIndexOf(“)”)-1;
  4. long dateNumber = long.Parse(dateString.Substring(startPos, endPos – startPos + 1));
  5. long beginTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
  6. DateTime dateValue = new DateTime(beginTicks + dateNumber * 10000);

如:”//Date(1278903921551+0800)//”。

JavaScript Parser:

  1. // Parse JSON string value when the string value is JSON Date Format
  2. var obj = eval(‘(‘ + ‘{Date://Date(1278903921551+0800)//}’ + ‘)’);
  3. scanDate(obj, jsonDateParser);
  4. var dateValue = obj[“Date”];
  5. function scanDate(obj, dateParser) {
  6. for (var key in obj) {
  7. obj[key] = dateParser(key, obj[key]);
  8. if (typeof (obj[key]) === ‘object ‘) {
  9. scanDate(obj[key], dateParser);
  10. }
  11. }
  12. }
  13. function jsonDateParser(key, value) {
  14. if (typeof value === ‘string’ ) {
  15. var a = (/^///Date/((/d+)([/+/-](/d/d)(/d/d))?/)///gi).exec(value);
  16. if (a) {
  17. var utcMilliseconds = parseInt(a[1], 10) + ((a[3] == ‘-‘) ? -1 : 1) * (parseInt(a[4], 10) + (parseInt(a[5], 10) / 60.0)) * 60 * 60 * 1000;
  18. return new Date(utcMilliseconds);
  19. }
  20. }
  21. return value;
  22. }

C# Parser:

  1. string dateString = “///Date(1278903921551+0800)///”;
  2. int startPos = dateString.IndexOf(“(“) + 1;
  3. int endPos = dateString.LastIndexOf(“)”) – 1;
  4. string dateNumberString = dateString.Substring(startPos, endPos – startPos + 1);
  5. int timeZoneMilSeconds = 0;
  6. int? timeZoneIndex = null;
  7. if (dateNumberString.IndexOf(“+”) > -1) {
  8. timeZoneIndex = dateNumberString.IndexOf(“+”);
  9. timeZoneMilSeconds = 1;
  10. }
  11. if (dateNumberString.IndexOf(“-“) > -1) {
  12. timeZoneIndex = dateNumberString.IndexOf(“-“);
  13. timeZoneMilSeconds = -1;
  14. }
  15. long dateNumber = 0;
  16. if (timeZoneIndex.HasValue) {
  17. timeZoneMilSeconds = timeZoneMilSeconds * (int)((int.Parse(dateNumberString.Substring(timeZoneIndex.Value + 1).Substring(0, 2))
  18. int.Parse(dateNumberString.Substring(timeZoneIndex.Value + 1).Substring(2)) / 60.0) * 60 * 60 * 1000);
  19. dateNumber = long.Parse(dateNumberString.Substring(0, timeZoneIndex.Value)) + timeZoneMilSeconds;
  20. else {
  21. dateNumber = long.Parse(dateNumberString);
  22. }
  23. long beginTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
  24. DateTime dateValue = new DateTime(beginTicks + dateNumber * 10000);

以上就是常见的JSON的日期格式了。JSON库或者一些AJAX库已经封装了相应的JavaScript的日期的支持,在解析时应直接使用JSON库自带的解析器。如果需要自定义解析这些日期,可参考上述JavaScript和C#代码。

[转载]Easyui的DateBox日期格式化

mikel阅读(1201)

转载Easyui的DateBox日期格式化 – hotdog – ITeye技术网站.

DateBox 日期显示默认的格式为“dd/mm/yyyy”,如果想自定义成我们的格式需要实现两个函数,formatter和parser。

formatter函数使得选择日期后将其格式化为我们需要的格式,parser函数在选择好日期后告诉控件如何去解析我们自定义的格式。

定义如下:
formatter:A function to format the date, the function take a ‘date’ parameter and return a string value.
parser:A function to parse a date string, the function take a ‘date’ string and return a date value.

如将日期格式化为yyyy-mm-dd的格式:

Java代码  收藏代码
  1. $(‘$dd’).datebox({
  2. formatter: function(date){ return date.getFullYear()+‘-‘+(date.getMonth()+1)+‘-‘+date.getDate(); },
  3. parser: function(date){ return new Date(Date.parse(date.replace(/-/g,“/”))); }
  4. });

[转载]InfoQ: 看板和Scrum——相得益彰

mikel阅读(1680)

[转载]InfoQ: 看板和Scrum——相得益彰.

Scrum和看板是敏捷软件开发中的两股风潮 ── 内容简单,但力量强大。它们之间有什么关系呢?

本书的目的是拨开重重迷雾,让大家明白如何在自己的环境中应用看板和Scrum,进行改进。

全书分成两部分。第一部分讲述的是看板和Scrum之间的异同。这个比较只是为了让读者更好地理解它们,而不是判断孰优孰劣。工具没有好坏之分,只有不恰当的使用场合和使用方式。

第二部分是案例分析,它讲述了在使用Scrum方法的组织中,为运维和支撑团队实施看板的过程。

本书与“硝烟中的Scrum和XP”的风格保持一致,依然使用对话式的风格,伴以大量的实践和图示。

全书内容如下:

  • 看板和Scrum简介
  • 看板、Scrum及其它敏捷方法的比较
  • 实践案例及常见误区
  • 用卡通的方式展示每日工作
  • 在Scrum组织中实施看板的案例分析

免费下载

Henrik Kniberg和Mattias Skarin愿意把这本书放到InfoQ.com上提供免费 免费下载这本书(PDF) ,希望能够让更多的读者从中收益。

免费下载(PDF版本)

22.95美元购买此书

如果你喜欢它的电子版,请购买此书来支持作者和InfoQ图书系列。购买地址在这里

章节目录

序──Mary Poppendieck
序──David Anderson
引子

第一部分──二者对比

1. 究竟什么是Scrum,什么是看板?
2. Scrum和看板有什么关系?
3. Scrum规定了角色
4. Scrum规定了固定时长的迭代
5. 看板按流程状态限制WIP,Scrum按迭代限制WIP
6. 二者都是经验主义的
7. Scrum在迭代内拒绝变化
8. Scrum板在迭代之间重置
9. Scrum规定了跨功能团队
10. Scrum的backlog条目必须能跟Sprint搭配的上
11. Scrum规定了估算和生产率
12. 二者都允许在多个产品上并行工作
13. 二者都是既精益又敏捷
14. 小小差异
15. Scrum板 vs. 看板图──一个不大不小的例子
16. 小结── Scrum vs.看板

第二部分─案例分析

17. 技术支持的现状
18. 到底为什么要改变?
19. 我们从哪里开始?
20. 上路
21. 团队启动
22. 直面相关干系人
23. 做出第一个图
24. 设置第一个WIP上限
25.守住WIP上限
26. 什么任务能放到看板图上?
27. 怎样做估算?
28. 我们是怎么工作的,具体点?
29. 哪种做计划的方法好呢
30. 度量什么呢?
31. 忽然一切都不一样了
32. 经验心得

结束语

关于作者

Henrik Kniberg和Mattias Skarin是两位咨询师,在斯德哥尔摩的Crisp公司工作。他们喜欢从软件开发中技术和人的角度入手,帮助其他公司取得成功。他们已经让许多企业应用 了精益和敏捷软件开发。他们喜欢辅导、教学、写作。Henrik的前一本书“硝烟中的Scrum和XP”已经有了15万读者,在世界范围内,数百家公司以 此作为敏捷软件开发的首要向导。

[转载]SQLServer中的临时表和表变量

mikel阅读(1131)

[转载]临时表 Vs 表变量 – OK_008 – 博客园.

开始


说 临时表和表变量,这是一个古老的话题,我们在网上也找到很多的资料阐述两者的特征,优点与缺点。这里我们在SQL Server 2005\SQL Server 2008版本上通过举例子,说明临时表和表变量两者的一些特征,让我们对临时表和表变量有进一步的认识。在本章中,我们将从下面几个方面去进行描述,对其 中的一些特征举例子说明:

  • 约束(Constraint)
  • 索引(Index)
  • I/0开销
  • 作用域(scope)
  • 存儲位置
  • 其他

例子描述


约束(Constraint)

在临时表和表变量,都可以创建Constraint。针对表变量,只有定义时能加Constraint。

e.g.在Microsoft SQL Server Management Studio(MSSMS)查询中,创建临时表并建Constraint场景,<脚本S1.>

Use tempdb
go
if object_id('Tempdb..#1') Is Not Null 
   Drop Table #1                
Go
Create Table #1
(
 ID int,
 Nr nvarchar(50) not null,
 OperationTime datetime default (getdate()),
 Constraint PK_#1_ID Primary Key (ID)
)

Alter Table #1 Add Constraint CK_#1_Nr Check(Nr Between '10001' And '19999')
Go

< 脚本S1.>中,可以看出在临时表#1的创建时,创建Constraint如“Constraint PK_#1_ID Primary Key(ID)”,也可以在创建临时表#1后创建Constraint,如“Alter Table #1 Add Constraint CK_#1_Nr Check(Nr Between ‘10001’ And’19999′)”,下面我们来看表变量的场景,在定义表变量时不能指定Constraint名,定义表变量后不能对表变量创建 Constraint。

e.g. 在定义表变量时不能指定Constraint名<代码S2.>

Use tempdb
Go
Declare @1 Table
(
 ID int,
 Nr nvarchar(50) not null,
 OperationTime datetime default (getdate()),
 Constraint [PK_@1_ID] Primary Key (ID)
)
image

在定义表变量后不能对表变量创建Constraint,<代码S3.>

use tempdb
go
Declare @1 Table
(
 ID int primary key clustered,
 Nr nvarchar(50),
 OperationTime datetime default (getdate())
)

Alter Table @1 Add Constraint [CK_@1_Nr] Check(Nr Between '10001' And '19999')

image

在<代码S2.>和<代码S3.>中可以发现,在解析T-SQL语法过程就发生错误,也就是SQL Server不支持定义表变量时对Constraint命名,也不支持定义表变量后,对其建Constraint。

这里慎重提示下,在<代码S1.>给临时表建Constraint的时候,特别是在并发场景中,不要指定具体的Constraint名称,不然会发生对象已存在的错误提示。

e.g. 在MSSMS中我们先执行之前<代码S1.>的创建临时表#1,不关闭当前会话的情况下,另建一个查询,执行与<代码S1.>相同的代码,如图

image

左边的查询窗口,是执行原先的<代码S1.>,右边的查询窗口,是后执行相同的<代码S1.>。在这里,我们注意红色圈圈 部分,发现在创建临时表#1的过程,明确给了一个主键名称“PK_#1_ID”,当右边再创建相同临时表#1的时候就发生了对象重复错误问题。我们也可以 通过SQL Server提供的系统视图sys.objects查询约束“PK_#1_ID”的信息,

use tempdb

go

Select * from sys.objects Where name='PK_#1_ID'

image

在系统视图sys.objects,发现“PK_#1_ID”名称后面不加如何的随机数值表述不同会话有不同的对象。根据SQL Server对sys.objects的描述规则,sys.objects中的Name列数据是唯一的。当另一个会话创建相同的对象时就会发生对象重复的 错误。

在Constraint中,Foreign Key是不能应用与表变量,对于临时表,创建Foreign Key是没有意义的。也就是说临时表不受Foreign Key约束。下面我们通过例子来说明临时表的情况,

e.g.< 脚本S4.>

use tempdb
go
if object_id('Tempdb..#1') Is Not Null
    Drop Table #1                
Go
if object_id('Tempdb..#2') Is Not Null
    Drop Table #2                
Go
Create Table #1
(

    ID int,
    Nr nvarchar(50) not null,
    OperationTime datetime default(getdate()),
    Constraint PK_#1_ID Primary Key(ID)
)
Alter Table #1 Add Constraint CK_#1_Nr Check(Nr Between '10001' And '19999')
Create table #2
(
    ID int Primary Key,
    ForeignID int Not null ,foreign Key(ForeignID) References #1(ID)
)
Go

image

可以看出对于临时表不强制Foreign Key约束,我们也可以通过SQL Server系统视图sys.foreign_keys查询

use tempdb
go
Select * from sys.tables Where name like '#[1-2]%'
Select * From sys.foreign_keys
image

右边的查询,只看到在sys.tables表哦中存在刚才创建的临时表#1和#2,在sys.foreign_keys看不到有关Foreign Key约束信息。这也验证了左边SQL Server提示的,在临时表中无法强制使用Foreign Key约束。

索引(Index)

从索引方面看临时表和表变量,与从Constraint上分析有些类似,在临时表中,它与真实表一样可以创建索引。在表变量定义过程中,也可以创建一些类似唯一和聚集索引。

e.g.< 脚本S5.>

use tempdb

go

declare @1 Table(      

                ID int  primary key clustered,

                Nr nvarchar(50) unique Nonclustered

)

Insert into @1 (id,Nr) values(1,'10001')

Insert into @1 (id,Nr) values(2,'10002')

Insert into @1 (id,Nr) values(8,'10003')

Insert into @1 (id,Nr) values(3,'10004')

Insert into @1 (id,Nr) values(7,'10005')

Select top 2 *

                From sys.indexes As a

                                Inner Join sys.tables As b On b.object_id=a.object_id

                Order by b.create_date Desc

Select Nr From @1 Where Nr='10005'

go

image

image

上面截的是两张图,第一张图描述在表变量使聚集Primary Key,创建非聚集的Unique约束,第二张图描述查询语句”Select Nr From @1 Where Nr=’10005’” 应用到在表变量创建的唯一索引“UQ_#……”

是于临时表索引的例子,我们拿一个例子说明,与前边说的Constraint例子有点相似,这里我们对临时表创建索引,并给索引一个具体名称,测试是否会重复。

e.g.在MSSMS新增两个查询,编写下面的SQL语句:

< 脚本S6.>

Use tempdb
Go
if object_id('#1') is not null    
    Drop Table #1 
    
Create Table #1
(
 ID int primary key,
 Nr nvarchar(50) not null,
 OperationTime datetime default (getdate()),
)

create nonclustered index IX_#1_Nr on #1(Nr Asc)
go
Select b.name As TableName,
       a.* 
    from sys.indexes As a
        Inner join sys.tables As b On b.object_id=a.object_id
    Where b.name like '#1[_]%'
    Order by b.create_date Asc

image

从返回的结果,我们看到在系统视图表Sys.Indexes中,创建有两个相同的索引”IX_#1_Nr”,但注意下object_id数据不同。 在SQL Server中是允许不同的表索引名称可以相同的。在并发的环境下,按原理是可以对临时表创建的索引给明确名称的。除非并发的情况会发生重复的表名或重复 的Constraint,或其它系统资源不足的问题,才会导致出错。

I/0开销

临时表与表变量,在I/O开销的描述,我们直接通过一个特殊的例子去描述它们,在MSSMS上新增两个查询,分别输入临时表和表变量的测试代码:

e.g.< 脚本S7.>临时表:

Use tempdb
Go
if object_id('#1') is not null    
    Drop Table #1 
    
Create Table #1
(
 ID int primary key,
 Nr nvarchar(50) not null,
 OperationTime datetime default (getdate())
)

Insert into #1(ID,Nr,OperationTime)
Select top 50000 row_number()over (order by a.object_id),left(a.name+b.name,50) ,a.create_date
    from master.sys.all_objects As a ,sys.all_columns As b
    Where type='S'



Select Nr,count(Nr) As Sum_ 
    From #1 
    Where Nr like 'sysrscolss%'    
    Group by Nr

< 脚本S8.>表变量:

Use tempdb
Go
Declare @1 Table
(
 ID int primary key,
 Nr nvarchar(50) not null,
 OperationTime datetime default (getdate())
)

Insert into @1(ID,Nr,OperationTime)
Select top 50000 row_number()over (order by a.object_id),left(a.name+b.name,50) ,a.create_date
    from master.sys.all_objects As a ,sys.all_columns As b
    Where type='S'
    

Select Nr,count(Nr) As Sum_ 
    From @1 
    Where Nr like 'sysrscolss%'    
    Group by Nr

image

< 脚本S7.>和< 脚本S8.>,主要是看最后的查询语句I/O的开销,两者有何不同。通过上面的运行结果图形描述,可以看出查询开始,不管是临时表还是表变量,都使 用到了聚集索引扫描(Clustered Index Scan),两者虽然返回的数据一致,但I/O的开销不同。临时表的I/O开销是0.324606,而表变量只有0.003125 相差非常大。在临时表的执行计划图形中,我们发现一行“缺少索引(影响 71.9586):CREATE ……)”提示信息。我们对临时表#1,在字段“Nr”上创建一个非聚集索引,再看执行执行结果:

create nonclustered index IX_#1_Nr On #1(Nr)

image

我们在临时表#1上创建完索引“IX_#1_Nr”,运行看上面的图形显示,就感觉非常的有意思了。在临时表#1查询时用了索引搜索(Index Seek),而且I/O开销减少到了0.0053742。虽然开始查询的时候I/O开销还是比表变量开始查询的时候大一些,但执行步骤中比变变量少了一个 “排序(Sort)”开销,后最后的看回Select结果,估计子树的成本比使用表变量的大大减少。

这里的例子只是描述一个特殊的情况,在真实的环境中,要根据实际的数据量来判断是否使用临时表或表变量。倘若在存储过程中,当数据量非常少如只有不到50行记录,数据占的页面也不会超过1个页面,那么使用表变量是一个很好的解决方案。

作用域(scope)

表变量像局部变量(local variable)一样,有着很窄的作用域,只能应用于定义的函数、存储过程或批处理内。如,一个会话里面有几个批处理,那么表变量只能作用在它定义所在的批处理范围内。其他的批处理无法再调用它。

e.g.在MSSMS新增一个查询,编写< 脚本S9.>

use tempdb
Go
Set Nocount on
declare @1 Table(      
                ID int  primary key clustered,
                Nr nvarchar(50) unique Nonclustered
)
Insert into @1 (id,Nr) values(1,'10001')
Insert into @1 (id,Nr) values(2,'10002')
Insert into @1 (id,Nr) values(8,'10003')
Insert into @1 (id,Nr) values(3,'10004')
Insert into @1 (id,Nr) values(7,'10005')

Select * From @1

Go --批处理结束点

Select * From @1

image

< 脚本S9.>所在的查询相当于一个会话,”Go”描述的一个批处理的结束点。在”Go”之前定义的表变量,在”Go”之后调用是发生“必须声明变量@1”的错误提示。

临时表与表变量不同,临时表的作用域是当前会话都有效,一直到会话结束或者临时表被Drop的时候。也就是说可以跨当前会话的几个批处理范围。

e.g.< 脚本S10.>

Use tempdb
go
if object_id('Tempdb..#1') Is Not Null 
   Drop Table #1                
Go
Create Table #1
(
 ID int,
 Nr nvarchar(50) not null,
 OperationTime datetime default (getdate()),
 Constraint PK_#1_ID Primary Key (ID)
)
Select * from #1

go --批处理结束点

Select * from #1

image

< 脚本S10.>中可以看出在”GO”前后都可以查询到临时表#1。

在描述临时表与表变量的作用域时,有个地方要注意的是,当 sp_executesql 或 Execute 语句执行字符串时,字符串将作为它的自包含批处理执行. 如果表变量在sp_executesql 或 Execute 语句之前定义,在sp_executesql 或 Execute 语句的字符串中无法调用外部定义的表变量。

e.g.< 脚本S11.>

use tempdb
go
Set nocount on
declare @1 Table(      
                ID int  primary key clustered,
                Nr nvarchar(50) unique Nonclustered
)
Insert into @1 (id,Nr) values(1,'10001')
Insert into @1 (id,Nr) values(2,'10002')
Insert into @1 (id,Nr) values(8,'10003')
Insert into @1 (id,Nr) values(3,'10004')
Insert into @1 (id,Nr) values(7,'10005')

Select * From @1

Execute(N'Select * From @1')

go

image

< 脚本S11.>中,当执行到”Execute(N’Select * From @1′)”时候,同样发生与< 脚本S9.>一样的错误提示“必须声明变量@1”.

临时表是可以在sp_executesql 或 Execute 语句执行字符串中被调用。这里不再举例子,如果你有所模糊可以参考< 脚本S11.>把表变量转成临时表测试下就能加深理解与记忆。

存儲位置

说到临时表和表变量的存储位置,我们可以看到有很多版本的说法,特别是表变量。有的说表变量数据存储在内存中,有的说存储在数据库tempdb中, 有的说有部分存储在内存,部分存储在数据库tempdb中。根据我查到的官方资料,说的是在SQL Server 2000下:

A table variable is not a memory-only structure. Because a table variable might hold more data than can fit in memory, it has to have a place on disk to store data. Table variables are created in the tempdb database similar to temporary tables. If memory is available, both table variables and temporary tables are created and processed while in memory (data cache).

在SQL Server 2005\SQL2008的版本,表变量存储与临时表有相似,都会在数据库tempdb创建,使用到tempdb存储空间。

e.g.< 脚本S12.>临时表

use tempdb
go
Set nocount on

Exec sp_spaceused /*插入数据之前*/

if object_id('#1') Is not null 
    Drop Table #1 
    
create table #1(ID int ,Nr nvarchar(50))
Insert into #1  (ID,Nr)
    Select top(1) row_number() Over(order By a.object_id),left(a.name+b.name,50)
        From sys.all_objects As a,
            sys.all_columns As b    
                                
Select top(1) name,object_id,type,create_date from sys.tables Order by create_date Desc            

Exec sp_spaceused /*插入数据之后*/
Go

image

在< 脚本S12.>执行后,我们可以看到在数据库tempdb中的表sys.tables创建有表#1。我们接着看空间的使用情况,插入数据之前,数据 库未使用空间(unallocated space)为510.39MB,向临时表#1插入1条数据后,数据库未使用空间为501.38MB,未使用空间变小了。再来看整个数据库的数据 (data)使用的空间变化,从552KB变成560KB,使用了一页的数据空间(8kb)。这说明一点,临时表,即使你只插入一条数据都会使用到数据库 tempdb的空间。也许会有人问,要是我只建临时表#1,不插入数据,会如何。我们可以结果:

image

这里你会发现前后的空间大小不变,不过,不要认为没有使用到数据库tempdb数据空间,当你多用户创建临时表结构的时候,你就会发现其实都会应用到数据库tempdb的空间。我这里创建了10个#1后的效果如:

image

相同的原理,我们使用类似的方法测试表变量的情况,发现结论是与临时表的一致的,会使用到数据库tempdb的空间.

e.g.< 脚本S13.>表变量

use tempdb
go
Set nocount on
Exec sp_spaceused /*插入数据之前*/

Declare @1 table(ID int ,Nr nvarchar(50))
Insert into @1  (ID,Nr)
    Select top(1) row_number() Over(order By a.object_id),left(a.name+b.name,50)
        From sys.all_objects As a,
            sys.all_columns As b            
            
Select top(1) name,object_id,type,create_date from sys.objects Where type='U' Order by create_date Desc            

Exec sp_spaceused /*插入数据之后*/

Go
Exec sp_spaceused /*Go之后*/

image

< 脚本S13.>中,我多写了一个”GO”之后检查空间大小的存储过程sp_spaceused。这样为了了更能体现表变量使用空间变化情况。从插入 数据前和插入数据后的结果图来看,表变量不仅在数据库tempdb创建了表结构#267ABA7A类似的这样表,表变量也应用到了数据库tempdb的空 间。不过这里注意一点就是在”Go”之后,我们发现表变量@1,会马上释放所使用的数据空间。为了更能体现使用空间情况。我们可以向表变量@1插入大量数 据看空间变化情况(测试插入1000万的数据行)。

e.g.< 脚本S14.>

use tempdb
go
Set nocount on
Exec sp_spaceused /*插入数据之前*/

Declare @1 table(ID int ,Nr nvarchar(50))
Insert into @1  (ID,Nr)
    Select top(10000000) row_number() Over(order By a.object_id),left(a.name+b.name,50)
        From sys.all_objects As a,
            sys.all_columns As b            
            
Select top(1) name,object_id,type,create_date from sys.objects Where type='U' Order by create_date Desc            

Exec sp_spaceused /*插入数据之后*/

Go
Exec sp_spaceused /*Go之后*/

image

这里我们可清晰的看到数据库tempdb的大小(database_size)变化情况,从插入数据前的552.75MB变成插入数据之后的 892.75MB。非常有意思的是我们在”Go之后”发现数据库大小保存在892.75MB,但数据使用空间(data)从560KB— >851464KB—>536KB ,说明SQL Server自动释放为使用的数据空间,但不会马上自动释放数据库分配的磁盘空间。我们在实际的环境中,发现临时数据库tempdb使用的磁盘空间越来越 大,这是其中的原因之一。

其他

临时表与表变量,还有其他的特征,如临时表受事务回滚,而表变量不受事务回滚影响。对应事务方面,更为正确的说法是表变量的事务只在表变量更新期间存在。因此减少了表变量对锁定和记录资源的需求。

e.g.< 脚本S15.>

use tempdb
go
Set nocount on

if object_id('#1') Is not null 
    Drop Table #1     
create table #1(ID int ,Nr nvarchar(50))
Declare @1 table(ID int ,Nr nvarchar(50))

begin tran /*开始事务*/

Insert into #1  (ID,Nr)
    Select top(1) row_number() Over(order By a.object_id),left(a.name+b.name,50)
        From sys.all_objects As a,
            sys.all_columns As b    


Insert into @1  (ID,Nr)
    Select top(1) row_number() Over(order By a.object_id),left(a.name+b.name,50)
        From sys.all_objects As a,
            sys.all_columns As b    
            
rollback tran /*回滚事务*/

Select * from #1
Select * from @1

Go

image

这里发现”Rollback Tran”之后,临时表#1没有数据插入,而表变量@1还有一条数据存在。说明表变量不受”Rollback Tran”所影响。它的行为有类似于局部变量一样。

另外SQL Server对表变量不保留任何的统计信息,因为如此,我们在数据量大的时候使用表变量,发现比临时表要慢许多。前面在I/O开销那里我们取有一个特殊的例子,这里不再举例。

小结

无论如何,临时表和表变量有各自的特征,有自己优点和缺点。在不同的场景选择它们灵活应用。本文章是我对临时表和表变量的一些认识理解,可能有些地方说的不够好或者遗漏,你可以留言或Email与我联系,我会继续改进或纠正,我也不希望有些错误的见解会误导别人。正如Phil Factor说的一句” I’d hate to think of anyone being misled by my advice!”.

附参考:

http://support.microsoft.com/kb/305977/en-us

http://stackoverflow.com/questions/27894/whats-the-difference-between-a-temp-table-and-table-variable-in-sql-server

http://msdn.microsoft.com/en-us/library/aa175774(SQL.80).aspx

http://msdn.microsoft.com/en-us/library/cc966545.aspx

http://www.simple-talk.com/sql/t-sql-programming/temporary-tables-in-sql-server/

http://support.microsoft.com/kb/942661/en-us

[转载]Android和iOS自带的人脸检测API

mikel阅读(1198)

[转载]Android和iOS自带的人脸检测API – Range – 博客园.

说说Android和iOS里面自带的人脸检测API。

Android提供了一个人脸检测类 Android.media.FaceDetector,调用findFaces函数,它可以返回图片中的找到的人脸。人脸的属性封装在Face类,包括x, y, z三轴的人脸pose,还有两眼距离eyesDistance()。这个API从Android刚推出时Level 1就已经有了。

iOS以前是没有人脸检测模块的。去年苹果收购了Polar Rose,把他们人脸检测技术融入到新推出的iOS 5当中:CIDetector是个通用的检测器,构造时可以指定感兴趣的目标,目前只能检测人脸CIDetectorTypeFace。返回的人脸信息封装在CIFaceFeature类中,包含了:leftEyePosition, rightEyePosition, mouthPosition 5现在还在Beta测试版中,只对付费developer开放,估计很快就要正式发布了。

从功能上来看两者差不多。至于效果,我只看过WWDC上iOS的Demo,可以做实时Avatar。Android的API从初代Level 1就有了,如果用的人不多的话,猜测可能效果不能保证。

VIA 老杨

[转载]快速构建SVN局域网服务器

mikel阅读(1260)

[转载]快速构建SVN局域网服务器 – 游空 – 博客园.

先说一下初衷。

前一阵子想把自己的代码拷贝一份到女友的电脑上,这样无论在哪台电脑都可以弄代码了,然而却会有两边版本不一致的问题,但苦于没有找到解决的途径。

前两天做那GM回复工具,也需要用到数据共享,同事建议说用SVN局域网服务器来实现,顿时来了兴趣。于是,在同事的协助下,动手把SVN局域网服务器搞起来了。真的很方便,动起来其实也很简单,现在和大家分享一下。

1  先从官网下载SVN服务端,如果是安装包,则自动会设置好环境变量。如果是绿色版,则需要手动设置环境变量: 如:文件所在目录为 C:\Program Files\svn-win32-1.6.5\bin,则设置环境变量如图所示:

2  建立自己的SVN服务器根目录文件夹,如: E:\SvnRoot

3  在命令行在该文件夹下创建SVN的数据目录,如:

4 于是成功在SvnRoot目录下创建了 code 文件夹,现在看看该文件夹下自动生成的内容:

5 打开 conf 文件夹,有三个配置文件如下,svnserve配置指定哪个是账号文件,passwd配置账号和密码,authz 配置已有账号的权限:

6  svnserve具体配置如下,主要是将  password-db 前的#号去掉,即去掉注释使其生效:

7  passwd具体配置如下,主要是新增自己需要的账号和密码,也可以将原有的账号去掉注释使用

8 authz 具体配置如下,主要是设置账号的读写权限,即更新提交权限:

9 构建完毕,即可启动服务器:建一个任意名字的 .bat 文件,内容如下,后面的是刚才新设置的SVN根目录路径:

10 双击即可启动服务器,每次开机后第一次使用SVN服务器前就必须先启动,且不能关闭。

11 后来我嫌这个窗口一直得显示不方便,于是找到一个隐藏其窗口的途径,如:建一个 start.vbs 文件,内容如下:

12 双击该文件即可执行svnroot.bat,且隐藏了cmd窗口:

13 现在,在局域网下的其他电脑或者本机可以用下面的url来checkout数据目录,其中 192.168.0.101为服务器所在电脑的IP地址,如果是服务器本机也可用svn://localhost/data作为checkout地址来操作:

14 按checkout时弹出账号密码输入提示,用刚配置的账号密码即可,同时勾选左下角的框记录账号信息,以后就不用再输入:

15 checkout 成功:

16 现在,就可以在checkout 的code 目录下添加或修改删除文件了。SVN构建局域网服务器兼使用说明讲解完毕。