MVC中使用SignalR打造酷炫实用的即时通讯功能附源码 - Kovin - 博客园

mikel阅读(962)

来源: MVC中使用SignalR打造酷炫实用的即时通讯功能附源码 – Kovin – 博客园

前言,现在这世道写篇帖子没个前言真不好意思发出来。本贴的主要内容来自于本人在之前项目中所开发的一个小功能,用于OA中的即时通讯。由于当时走的太急,忘记把代码拿出来。想想这已经是大半年前的事情了,时间过了这么久,在当时最新的SignalR2.0.1到现在已经变成了2.2。昨天晚上特地熬了个夜,重新又把它写出来做了一个小小的Demo。当然我只是大自然的搬运工,这个SignalR即时通讯功能里面有一些前端的类库不是我自己写的。我只是改吧改吧~~在此鸣谢 @贤心,是他的几条库才使得我的这个功能如此酷炫。前言猝!

最终效果演示

没个GIF的演示我会拿出来秀?

看上去是不是感觉还可以? 那下面我讲解一下开发步骤。

创建MVC项目

首先我们打开VS2015(当然其它的版本也可以。我只是赶了个时髦,有天心血来潮就给安装了),再依次点击[文件]-[新建]-[项目]后弹出如下界面:

我们选择ASP.NET Web应用程序,并且将项目名称完善好,选择好项目保存路径。再点击确定:

这里为了让等会的操作更加简单我直接选择了一个ASP.NET 4.5的 Empty 模板。并把下方的“为以下项目添加文件夹和核心引用”选择MVC。再点击确定:

好了到这里风云突变狂风大作…一个活生生拥有着MVC核心引用和文件夹的项目已经展现在眼前。下面我们就往项目中加入等会要使用到的SignalR。

为MVC项目在NuGet中引用SignalR

这里用到了NuGet,网上也有很多资源讲解怎么使用这个。我这里只大概讲解一下。首先打开[工具]-[NuGet 程序包管理器]-[管理解决方案的 NuGet 程序包]

接下来在出现的界面中将程序包源改成:联机,然后搜索SignalR。接下来自行解决~.~

使用SignalR

由于此时的项目还是一个Empty的项目,需要通过Startup类来配置OWIN程序,所以要在项目中加入一个OWIN Startup类

创建好之后,再在Configuration函数中加入app.MapSignalR();

好了下面,我们再为SignalR创建一个集线器Hubs,我的习惯是在项目中创建一个Hubs目录,然后把需要创建的HubClass放到里面。下面先在项目中创建一个Hubs目录,再在目录上单击右键选择[添加]-[新建项]选择[SignalR 集线器类]

点击确定,再把新建的HubClass中的Hello函数干掉。然后在类上增加一个特性:[HubName("systemHub")]。既然是要聊天那么自然离不开用户,为了方便管理我建立了一个用户的实体类UserDetail

复制代码
 1   /// <summary>
 2  /// 用户细节
 3  /// </summary>
 4  public class UserDetail
 5  {
 6  /// <summary>
 7  /// 连接ID
 8  /// </summary>
 9  public string ConnectionId { get; set; }
10  /// <summary>
11  /// 用户ID
12  /// </summary>
13  public string UserID { get; set; }
14  /// <summary>
15  /// 用户名
16  /// </summary>
17  public string UserName { get; set; }
18  /// <summary>
19  /// 用户部门
20  /// </summary>
21  public string DeptName { get; set; }
22  /// <summary>
23  /// 登录时间
24  /// </summary>
25  public DateTime LoginTime { get; set; }
26  }
复制代码

既然用户类有了,那么我们可以在Hub里面创建一个用户池,用来管理在线用户。

1 public static List ConnectedUsers = new List();

现在用户池有了,下面需要实现三个功能就能进行登录、上线、下线、私聊操作了。这是下面的逻辑处理代码:

复制代码
 1 /// <summary>
 2 /// 登录连线
 3 /// </summary>
 4 /// <param name="userID">用户ID</param>
 5 /// <param name="userName">用户名</param>
 6 /// <param name="deptName">部门名</param>
 7 public void Connect(string userID, string userName, string deptName)
 8 {
 9     var id = Context.ConnectionId;
10 
11     if (ConnectedUsers.Count(x => x.ConnectionId == id) == 0)
12     {
13         if (ConnectedUsers.Count(x => x.UserID == userID) > 0)
14         {
15             var items = ConnectedUsers.Where(x => x.UserID == userID).ToList();
16             foreach (var item in items)
17             {
18                 Clients.AllExcept(id).onUserDisconnected(item.ConnectionId, item.UserName);
19             }
20             ConnectedUsers.RemoveAll(x => x.UserID == userID);
21         }
22         //添加在线人员
23         ConnectedUsers.Add(new UserDetail { ConnectionId = id, UserID = userID, UserName = userName, DeptName = deptName, LoginTime = DateTime.Now });
24 
25         // 反馈信息给登录者
26         Clients.Caller.onConnected(id, userName, ConnectedUsers);
27 
28         // 通知所有用户,有新用户连接
29         Clients.AllExcept(id).onNewUserConnected(id, userID, userName, deptName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
30 
31     }
32     else
33     {
34 
35     }
36 }
37 
38 /// <summary>
39 /// 发送私聊
40 /// </summary>
41 /// <param name="toUserId">接收方用户连接ID</param>
42 /// <param name="message">内容</param>
43 public void SendPrivateMessage(string toUserId, string message)
44 {
45     string fromUserId = Context.ConnectionId;
46     var toUser = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == toUserId);
47     var fromUser = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == fromUserId);
48 
49     if (toUser != null && fromUser != null)
50     {
51         // send to 
52         Clients.Client(toUserId).receivePrivateMessage(fromUserId, fromUser.UserName, message);
53 
54         // send to caller user
55         //Clients.Caller.sendPrivateMessage(toUserId, fromUser.UserName, message);
56     }
57     else
58     {
59         //表示对方不在线
60         Clients.Caller.absentSubscriber();
61     }
62 }
63 
64 /// <summary>
65 /// 离线
66 /// </summary>
67 public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
68 {
69         var item = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
70         if (item != null)
71         {
72             Clients.All.onUserDisconnected(item.ConnectionId, item.UserName); //调用客户端用户离线通知
73             ConnectedUsers.Remove(item);
74         }
75         return base.OnDisconnected(stopCalled);
76 }
复制代码

我这里写的逻辑只是一个简单的示例,这个可以根据自己的想法和需求任意发挥。发挥时遇到问题也欢迎在留言一起交流。服务端代码完了,下面开始建立客户端代码。

客户端调用SignalR

到了这里我们貌似还没有新建页面,访问会报404! 那么我们先在Controllers目录上单击右键选择[添加]-[控制器]在弹出的界面中选择[MVC5控制器]

名字取成Home。然后把@贤心大神的弹层库等等库引用进来。把JS也引用进来。 在这时也能看到在Scripts目录下面有了几个SignalR的js文件。那么既然我们建好Controller了下面就在HomeController的IndexAction上面单击右键选择[添加视图]在调用的时候需要注意的是要引用SignalR的内容:

1 <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
2 <script src="~/signalr/hubs"></script>

至于调用的方法也是比较简单的啦,下面附上简单的示例。更多详细的处理可以下载我的DEMO源码进行参考。

复制代码
 1 //实例SystemHub,首字母必须小写才能调用
 2 var systemHub = $.connection.systemHub;
 3 //开始链接到集线器
 4 $.connection.hub.start().done(function () {
 5     //调用服务端函数Connect(首字母小写)以及传递客户端参数进行上线操作
 6     systemHub.server.connect(userid, username, deptname);
 7 });
 8 //新用户上线
 9 systemHub.client.onNewUserConnected = function (id, userID, userName, deptName, loginTime) {
10     //定义onNewUserConnected客户端函数供服务端调用
11 };
12 //用户离线
13 systemHub.client.onUserDisconnected = function (id, userName) {
14     //定义onUserDisconnected客户端函数供服务端调用
15 };
16 //发送消息时,对方已不在线
17 systemHub.client.absentSubscriber = function () {
18     //定义absentSubscriber客户端函数供服务端调用
19 };
20 //接收消息
21 systemHub.client.receivePrivateMessage = function (fromUserId, userName, message) {
22     //定义receivePrivateMessage客户端函数供服务端调用
23 };
24 //发送消息
25 systemHub.server.sendPrivateMessage(ChatCore.nowchat.id, data.content);
复制代码

好了,关于MVC中使用SignalR的介绍也详细描述了,我只是抛砖引玉把自己以前开发的功能进行分享以及自己的温习。具体的源码在下方进行下载,如果觉得内容不错,欢迎留言献花以示鼓励~~

源码下载:FangsiChat.zip

SignalR QuickStart - 张善友 - 博客园

mikel阅读(1097)

来源: SignalR QuickStart – 张善友 – 博客园

SignalR 是一个集成的客户端与服务器库,基于浏览器的客户端和基于 ASP.NET 的服务器组件可以借助它来进行双向多步对话。 换句话说,该对话可不受限制地进行单个无状态请求/响应数据交换;它将继续,直到明确关闭。 对话通过永久连接进行,允许客户端向服务器发送多个消息,并允许服务器做出相应答复,值得注意的是,还允许服务器向客户端发送异步消息。它和AJax类似,都是基于现有的技术。本身是一个复合体。一般情况下,SignalR会使用JavaScript的长轮询( long polling),实现客户端和服务端通信。在WebSockets出现以后,SignalR也支持WebSockets通信。当然SignalR也使用了服务端的任务并行处理技术以提高服务器的扩展性。它的目标整个 .NET Framework 平台,它也不限 Hosting 的应用程序,而且还是跨平台的开源项目,支持Mono 2.10+,觉得它变成是 Web API 的另一种实作选择,但是它在服务端处理联机的功能上比 ASP.NET MVC 的 Web API 要强多了,更重要的是,它可以在 Web Form 上使用。

SignalR 内的客户端库 (.NET/JavaScript) 提供了自动管理的能力,开发人员只需要直接使用 SignalR 的 Client Library 即可,同时它的 JavaScript 库可和 JQuery 完美整合,因此能直接与像 JQuery 或 Knockout.js 一起使用。

SignalR内部有两类对象:

· Persistent Connection(HTTP持久链接):持久性连接,用来解决长时间连接的能力,而且还可以由客户端主动向服务器要求数据,而服务器端也不需要实现太多细节,只需要处理 PersistentConnection 内所提供的五个事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 即可。

· Hub:信息交换器,用来解决 realtime 信息交换的功能,服务器端可以利用 URL 来注册一个或多个 Hub,只要连接到这个 Hub,就能与所有的客户端共享发送到服务器上的信息,同时服务器端可以调用客户端的脚本,不过它背后还是不离 HTTP 的标准,所以它看起来神奇,但它并没有那么神奇,只是 JavaScript 更强,强到可以用像 eval() 或是动态解释执行的方式,允许 JavaScript 能够动态的加载与执行方法调用而己。

SignalR 将整个交换信息的行为封装得非常漂亮,客户端和服务器全部都使用 JSON 来沟通,在服务器端声明的所有 hub 的信息,都会一般生成 JavaScript 输出到客户端,.NET 则是依赖 Proxy 来生成代理对象,这点就和 WCF/.NET Remoting 十分类似,而 Proxy 的内部则是将 JSON 转换成对象,以让客户端可以看到对象。

下面我们来针对Persistent Connection和Hub 做个Demo试试:

新建一个ASP.NET MVC项目MvcApplicationSignalR,通过Nuget添加SignalR的包。

新建一个类MyConnection 继承自 PersistentConnection ,引用SignalR命名空间,重写OnReceivedAsync 的方法,并要求 SignalR 对传入的信息做广播

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SignalR;
using System.Threading.Tasks;

namespace MvcApplicationSignalR
{
public  class MyConnection : PersistentConnection

{
protected override Task OnReceivedAsync(IRequest request, string connectionId, string data)
{
// Broadcast data to all clients
data = string.Format(“数据是:{0} 时间是:{1}”, data, DateTime.Now.ToString());
return Connection.Send(connectionId, data);
}

}
}

接着在 Global.asax 中加入对应路由信息,这会由 SignalR 的路由表来处理 Metadata 的输出工作,红色部分代码:

protected void Application_Start()
{
RouteTable.Routes.MapConnection<MyConnection>(“echo”, “echo/{*operation}”);

 

这样服务器端就完成了。现在我们在项目中Master、View (Home/Index),然后加入必要的代码:

<head>
<meta charset=”utf-8″ />
<title>@ViewBag.Title</title>
<link href=”@Url.Content(“~/Content/Site.css”)” rel=”stylesheet” type=”text/css” />
<script src=”@Url.Content(“~/Scripts/jQuery-1.6.4.min.js”)” type=”text/javascript”></script>
<script src=”@Url.Content(“~/Scripts/modernizr-1.7.min.js”)” type=”text/javascript”></script>
  <script src=”@Url.Content(“~/Scripts/jQuery.signalR-0.5.2.min.js”)” type=”text/javascript”></script>
</head>

@{
ViewBag.Title = “Home Page”;
}
<script type=”text/javascript”>
$(function () {
var connection = $.connection(‘echo’);

connection.received(function (data) {
$(‘#messages’).append(‘<li>’ + data + ‘</li>’);
});

connection.start();

$(“#broadcast”).click(function () {
connection.send($(‘#msg’).val());
});
$(“#btnStop”).click(function () {
connection.stop();
});
});

</script>
<h2>@ViewBag.Message</h2>
<input type=”text” id=”msg” />
<input type=”button” id=”broadcast” value=”发送” />
<input type=”button” id=”btnStop” value=”停止” />
<ul id=”messages”>
</ul>

运行起来就是这个效果:

image

下面我们来展示 SignalR 的另一个功能:由服务器端调用客户端的 JavaScript 脚本的功能,而这个功能的要求必须是要实现成 Hub 的模式,因此我们可以顺便看到如何实现一个 Hub 类型的 SignalR 应用程序。

向项目中加入一个类Chat继承自 Hub 类 (这是 Hub 应用程序的要求) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using SignalR.Hubs;
using System.Threading.Tasks;
using System.Threading;

namespace MvcApplicationSignalR
{
[HubName(“geffChat”)]
public class Chat : Hub
{
public void SendMessage(string message)
{
Clients.sendMessage(message);
}
}
}

这段程序代码的用意是,在连接进到 Hub 时,将连接代码加到联机用户的集合中,等会就会使用到,因为我们会依照客户端的 ID 来调用客户端脚本。

1. HubName:这个 atttibute 代表 client 端要如何建立对应 server 端对象的 proxy object。通过 HubName , server 端的 class name才不会被 client 绑死。如果没有设定,则会以 server 端 class name 为 HubName 默认值。

2. 继承 Hub:继承 Hub 之后,很多对应的设计就都不用写了,我们只需要把注意力放在 client 如何送 request 给 server的 hub , server 如何通知 client 即可。

3. public void SendMessage(string message) ,就像 WebService Method 或 PageMethod 一般, client 端通过 proxy object ,可以直接调用 server 端这个方法。后续会介绍到如何在页面上使用。

4. Clients 属性:代表所有有使用 Chat 的页面。而 Clients 的型别是 dynamic ,因为要直接对应到 JavaScript 的对象。

5. Clients.sendMessage(message):代表 server 端调用 Clients 上的 sendMessage 方法,也就是 JavaScript 的方法。

6. 总结: Chat 对象职责就是当 client 端调用SendMessage() 方法后,要把这个 message ,送给所有 client 页面上呈现。以达到聊天室的功能。

服务端的做完了,开始制作客户端,同样在Home/Index页面上增加以下html代码

<%–很重要的一个参考,一定要加,且在这一行之前,一定要先参考jQuery.js与signalR.js–%>
<script src=”@Url.Content(“~/signalr/hubs”)” type=”text/javascript”></script>

@{
ViewBag.Title = “Home Page”;
}
<script type=”text/javascript”>
$(function () {
var connection = $.connection(‘/echo’);

connection.received(function (data) {
$(‘#messages’).append(‘<li>’ + data + ‘</li>’);
});

connection.start();

$(“#broadcast”).click(function () {
connection.send($(‘#msg’).val());
});
$(“#btnStop”).click(function () {
connection.stop();
});

  // 建立对应server端Hub class的对象,请注意geffChat的第一个字母要改成小写
var chat = $.connection.geffChat;

        // 定义client端的javascript function,供server端hub,通过dynamic的方式,调用所有Clients的javascript function
chat.sendMessage = function (message) {
//当server端调用sendMessage时,将server push的message数据,呈现在wholeMessage中
$(‘#wholeMessages’).append(‘<li>’ + message + ‘</li>’);
};

        $(“#send”).click(function () {
//调用叫server端的Hub对象,将#message数据传给server
chat.sendMessage($(‘#message’).val());
$(‘#message’).val(“”);
});

        //把connection打开
$.connection.hub.start();

});

</script>
<h2>@ViewBag.Message</h2>
<input type=”text” id=”msg” />
<input type=”button” id=”broadcast” value=”发送” />
<input type=”button” id=”btnStop” value=”停止” />
<ul id=”messages”>
</ul>

<div>
<input type=”text” id=”message” />
<input type=”button” id=”send” value=”发送” />
<div>
聊天室内容:
<br />
<ul id=”wholeMessages”>
</ul>
</div>
</div>

1. 先引用 jQuery 与 signalR 的 js 文件。

2. 很重要的一个步骤:加入一个 js 引用,其路径为「根目录/signalr/hubs」。 SignalR 会建立相关的 JavaScript,放置于此。

3. 通过 $.connection.『server 端的 HubName』,即可建立对应该 hub 的 proxy object。要注意,首字母需小写。

4. 定义 client 端上,供 server 端通知的 JavaScript function,这边的例子是 sendMessage。

5. 当按下发送按钮时,调用 server 端的 SendMessage() 方法,只需要直接通过 proxy object 即可。要注意,首字母需小写。

6. 记得透过 $.connection.hub.start() ,把 connection 打开。

image

注意:SingalR 会自动生成一个siganlr/hub 的桥接js..,在本机使用localhost测试都不会有问题。当部署到IIS的时候会发生404错误,是由于被IIS误判可能是虚拟目录…,解决方法是在web.config加入一段:

<!– 加入下面这一段–>

<system.webServer>

<validation validateIntegratedModeConfiguration=”false” />

<modules runAllManagedModulesForAllRequests=”true”>

</modules>

</system.webServer>

参考:

实例代码:MvcApplicationSignalR

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。 - 丶Pz - 博客园

mikel阅读(899)

来源: ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。 – 丶Pz – 博客园

前面几篇基本已经实现了大部分即时通讯功能:聊天,群聊,发送文件,图片,消息。不过这些业务都是比较粗犷的。下面我们就把业务细化,之前用的是死数据,那我们就从加好友开始吧。加好友,首先你得知道你要加谁。Layim界面右下角有个+号,点击它之后就会弹出查找好友的界面,不过那个界面需要自定义。由于前端不是我的强项,勉强凑了个页面。不过不要在意这些细节。这些都不重要,今天主要介绍一下ElasticSearch搜索解决方案。它是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。不熟悉Elastic的也不要紧,本篇的搜索查找好友用关系数据库一样实现。(不想对ES做深入研究的可以简单看一下思路即可)

首先安装好ES环境。安装步骤可以参考 http://www.cnblogs.com/panzi/p/5659697.html

OK,先看一下成型效果图,第一张是用户,第二个是群。

 

好的,界面就是这样。当然要绑定什么数据可以自己定义,比如绑定签名,男女或者其他信息等,都可以,在高级一点,把是否在线信息加上。既然用了layui,那顺带着绑定插件我就直接用laytpl了。前端模板如下:(一个用户模板,一个群模板)。

再说数据源,既然我用到了ElasticSearch(下文用ES代替),但是数据库(MSSQL)也保存了相应的信息。这就需要,当一个用户注册进来之后,我们还需要将该搜索信息保存到ES中。伪代码如下:

 

    var dt = UserRegister();
    if(dt){
      //如果用户注册成功,返回了相应的信息,我们就把他在加到ES中去。
         AdduserInfoToElastic(dt);   
   }

下图就是ES中保存的用户信息,这些用户信息是和数据库中的数据同步的,因此,修改用户信息的时候,此表中的数据也要同步。当然有延迟也没问题。

用ES的一个好处呢就是快,他能帮你做缓存,帮你分词查询。我们做练习用数据库搜索几十条没问题。如果数据量大了,搜索条件复杂了,ES就能体现出他的优势了。现在我用的搜索条件很简单,一个是IM号,类似QQ,一个就是昵称。对接ES的客户端是PlainElastic.Net,用nuget安装即可。 install-package PlainElastic.Net.

ES有自己的查询语法,又复杂的也有简单点的,我就用一些简单的,毕竟我也没太深入研究。正如前文所说,它提供了Restful api。我们查询的路径就是:127.0.0.1:9200/layim/layim_user/_search POST方法,POST的参数如下:

 

复制代码
{
  "query": {
    "match_all": {}//没有条件的时候就是match_all相当于查询所有
  },
  "from": 0,//分页开始
  "size": 50,//每页条数
  "sort": {  //排序,根据 province 正序排序
    "province": { 
      "order": "asc"
    }
  }
}
复制代码

 

上边的是查询所有的情况,当用户输入了查询条件时候,比如精确查询IM号为  288186 的用户,搜索条件如下:

复制代码
{
  "query": {
    "filtered": {
      "filter": {
        "or": [  //or 查询,下面的条件符合一个即可
          {
            "term": {
              "im": 288186    //im =288186
            }
          },
          {
            "query": {
              "match_phrase": { //短语查询
                "nickname": {
                  "query": "288186", //或者昵称中有288186
                  "slop": 0
                }
              }
            }
          }
        ]
      }
    }
  },
  "from": 0,
  "size": 50
}
复制代码

不熟悉语法的同学可能看不太懂,总之上边这些参数的意思就是  select  top 50 * from layim_user where im=288186 or nickname like ‘%288186%’

查询结果他会把查询到的总数和耗时(ms)返回,像几万条数据的话大部分就是几十毫秒甚至不到十毫秒。相对于从数据用查,速度还是可以的。

基于PlainElastic.NET我又封装了一层查询的核心方法:

复制代码
public BaseQueryEntity<T> QueryBayConditions(string query)
        {
            try
            {
                string cmd = CreateSearchCommand();//构造查询命令
                OperationResult result = Client.Post(cmd, query);//Post query参数查询
                var data = serializer.ToSearchResult<T>(result.Result); //返回结果转换
                return GetResults(data);
            }
            catch (Exception ex)
            {
                ESLog.WriteLogException(MethodBase.GetCurrentMethod(), "查询条件:" + query);
                ESLog.WriteLogException(MethodBase.GetCurrentMethod(), ex);
                return new BaseQueryEntity<T>();
            }
        }
复制代码

 

复制代码
public class BaseQueryEntity<T> where T :BaseEntity
    {
        /// <summary>
        /// 命中条数
        /// </summary>
        public long hits { get; set; }
        /// <summary>
        /// 花费时间(单位ms)
        /// </summary>
        public long took { get; set; }
        public IEnumerable<T> list { get; set; }
    }
复制代码

查询结果json:

总共有3883个用户,ES查询用了5ms,加上http请求耗时,总共耗时为45毫秒。

写到这里呢,就暂时对ES做个简单的总结,其实如果做练习的话也没必要用他来查,直接查数据库就可以了,如果再配合缓存的话速度也不慢,而且今天讲的业务逻辑也不复杂。说白了,本篇就讲了个CRUD的 Retrieve。(R)还有聊天记录顺便说一下,也是用ES查的,不过到时候会增加一些小的效果。我也不卖关子了,就是模糊查询的关键字高亮效果。

下面继续说加好友流程,首先,如果你想吧一个用户加为好友首先,得发送加好友请求吧,类似QQ。当然,如果那个人设置了任何人都可以加好友,是不是就可以直接加上了。如果那个人设置了不允许任何人加好友,那么你也没法加他的,所以,简单的一个加好友也可以设计的很复杂。本篇就介绍普通的申请流程。点击加好友之后,弹出框,填写附加消息:(我们搜索出288186的用户添加)

当我们点击发送之后要考虑什么呢?先不要看下文,仔细思考一下。5          ,4,           3             ,2,               1。。。

 

没错,就是如果对方恰好在线的处理,和对方不在线的处理、如果对方在线,保存到数据库并且即时提醒。不在线,保存到数据库,等下次登录提醒用户。详细做法下一篇再写吧。先贴一个预告图:

本篇贴的图比较多,讲了一些业务上的东西和ES的简单使用,场景和使用方式等。本篇就到这里吧。

 

      下篇预告【中级】ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(五) 之 加好友,加群消息提示,Hub中的User用法。

 

   想要学习的小伙伴,可以关注我的博客哦,我的QQ:645857874,Email:fanpan26@126.com

GitHub:https://github.com/fanpan26/LayIM_NetClient/

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(三) 之 实现单聊,群聊,发送图片,文件。 - 丶Pz - 博客园

mikel阅读(738)

来源: ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(三) 之 实现单聊,群聊,发送图片,文件。 – 丶Pz – 博客园

上篇讲解了如何搭建聊天服务器,以及客户端js怎么和layui的语法配合。服务器已经连接上了,那么聊天还会远吗?

进入正题,正如上一篇提到的我们用 Client.Group(groupId)的方法向客户端推送消息。本篇就先不把业务搞复杂了,就默认现在两个用户都各自打开了对方的聊天窗口,那么聊天过程是这样的。

同理,B给A发消息也是这个流程,因为无论如何,A(ID)和B(ID)都会按照规则生成同一个组名。其中由于LayIM已经帮我们在客户端做好了发送消息并且将消息展示在面板上,所以我们要做的就是当接收到消息后的处理,同样对接LayIM的接口就可以了。代码如下:首先要监听layim的sendMessage事件,

然后我们调用自己的send方法。

看到上述两个方法是不是很熟悉,没错,就是代理帮我们生成的,这是和我们在ChatServer的Hub里面写的方法是对应的。至于sendObj我们只要在后台写好对应的 Model即可。上次的截图只给大家截了主要的方法,下图是单聊的核心方法

可能除了文字就是代码,感觉很枯燥,那我就直接把效果图上几张,首先,我打开谷歌浏览器,模拟用户A,打开腾讯浏览器,模拟用户B

看看实际效果:

 

哦了,单聊就到此结束,群聊呢,其实是一个道理,不过有一个坑你要记住

否则,如果那个id传错了,你会发现意想不到的效果~~当然,只有自己实战才可以。群聊的我就不截图了。下面我在简单介绍一下,如何对接Layim的发送文件和图片接口。首先修改layim.config的配置:

 

复制代码
 ,
            uploadImage: {
                url: '/layimapi/upload_img'
                ,
                type: '' //默认post
            }

            ,
            uploadFile: {
                url: '/layimapi/upload_file'
                ,
                type: '' //默认post
            }
复制代码

官网已经说明了返回格式:(官方文档:http://www.layui.com/doc/layim.html)

 

里面的图片和文件显示效果内部已经封装的很好了,所以我们要操心的就是怎么接收和保存文件。我发一下我之前写过的代码仅供参考:

 

复制代码
        [HttpPost]
        [ActionName("upload_img")]
        public JsonResult UploadImg(HttpPostedFileBase file)
        {
            return Json(FileUploadHelper.Upload(file, Server.MapPath("/upload/"), true), JsonRequestBehavior.DenyGet);
        }
        [HttpPost]
        [ActionName("upload_file")]
        public JsonResult UploadFile(HttpPostedFileBase file)
        {
            return Json(FileUploadHelper.Upload(file, Server.MapPath("/upload/"), false), JsonRequestBehavior.DenyGet);
        }
复制代码
复制代码
public static JsonResultModel Upload(HttpPostedFileBase file,string fullPath,bool isImg=true) {
            try {
                if (file != null && file.ContentLength > 0)
                {
                    string fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
                    string fileName = Guid.NewGuid().ToString();
                    string fullFileName = string.Format("{0}{1}", fileName, fileExtension);
                    string oldFileName = Path.GetFileName(file.FileName);

                    string fileSavePath = string.Format("{0}{1}", fullPath, fullFileName);
                    string url = "/upload/" + fullFileName;
                    file.SaveAs(fileSavePath);
                    object imgObj = new { src = url };
                    object fileObj = new { src = url, name = oldFileName };
                    return JsonResultHelper.CreateJson(isImg ? imgObj : fileObj);
                }
                return JsonResultHelper.CreateJson(null, false, "请添加一个文件");
            }
            catch (Exception ex) {
                //记录日志
                return JsonResultHelper.CreateJson(null, false, "添加文件出错,请联系平台管理员");
            }
        }
复制代码

只要接口对接好了,剩下的就交给Layim吧。看一下效果:

 

对接图片和文件是不是很简单呢?不过,目前来看,如果网络不好或者文件太大的话,上传太慢,导致界面感觉没反应一样,其实解决也很简单,加一个loading就可以了,至于在哪里加,先想想吧,下篇开头我会把我自己做的方法介绍一下。整体来说,本来Layim就是实现聊天,互动就可以了,但是还有很多小的细节。接下来,我们要进行中级篇了。保证更好玩哦

PS:又浏览了一遍,感觉就是截了好几张图,因为在博客里面类似这个功能已经写过很多了,有不明白的同学留言或者给我发消息吧。

 

      下篇预告【中级】ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。

 

   想要学习的小伙伴,可以关注我的博客哦,我的QQ:645857874,Email:fanpan26@126.com

GitHub:https://github.com/fanpan26/LayIM_NetClient/

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(二) 之 ChatServer搭建,连接服务器,以及注意事项。 - 丶Pz - 博客园

mikel阅读(1123)

来源: ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(二) 之 ChatServer搭建,连接服务器,以及注意事项。 – 丶Pz – 博客园

上一篇我们已经完成了初步界面的搭建工作,本篇将介绍IM的核心内容了,就是SignalR的Hub类。整个即时通讯机制都是以它为基础的。至于原理我也不再讲解,讲了也不如专业的文章讲得好。所以我们直接看业务,上代码。有一部分原理 在文章 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(二) 实现聊天室连接 (当时是LayIM1.0版本)。原理是一样的,不过这次我把Server端单独提取出来,为了防止 Server端和UI端过度耦合,拆分不方便。进入正题:

打开项目 LayIM.ChatServer,新建LayIMHub类继承自Hub。(这里要添加Microsoft.AspNet.SignalR.Core,Microsoft.AspNet.SignalR.SystemWeb的引用)核心方法如下:

上边三个重写的方法一看就很明白,通过这几个方法,客户端很容易知道自己的连接状态。我们主要看下边四个方法。

  • 单聊连接(ClientToClient):这个方法就是当用户点击某个人的头像打开聊天界面的时候,要调用的方法,目的是让当前聊天的两个人分配到一个组里。(当然:Clients.Client(connectionId)才是真正的给某个Client发送消息的方法),我这里用的原理就是群组,就算是两个人聊天,相当于一个群里面只有两个人,所以,两个人和多个人聊天原理是相同的。文字有些抽象,画个图大家应该就明白了

上图就是用户A想和用户B聊天打开聊天窗口时的流程,同理如果B恰好也打开了A的窗口,那么组 FRIEND_10001_10000 里面就是存在A和B两个客户端,那么他们俩发的消息就是他们两个人能够收到了。之所以把自己发送的消息还返回给自己,那是因为,这样客户端能够知道我的消息是否发送成功,因为layim在客户端发送消息时已经做了处理,直接将消息加入到聊天框的,但是服务端可能没有收到。所以可能会出现客户端疯狂发消息,但是服务端(对方)一条也没有接收到的情况。还有另外一种情况,A,B都在线,但是B没有打开A的聊天窗口怎么办呢?这里先卖个关子,涉及到后边的在线人员统计机制了。用这个可以做好多东西,只要是客户端涉及是否在线的都可以用到。

然后我们在UI端引用ChatServer.dll ,新建Startup(如果没有的话),代码如下:

 

复制代码
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartupAttribute(typeof(LayIM_SignalR_Chat.V1._0.Startup))]
namespace LayIM_SignalR_Chat.V1._0
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            //ConfigureAuth(app);
            app.Map("/layim", map =>
            {
                var hubConfiguration = new HubConfiguration()
                {
                    EnableJSONP = true
                };
                map.RunSignalR(hubConfiguration);
            });
        }
    }
}
复制代码

页面上需要引用 JQuery.js,signalr.js,/layim/hubs (这个是客户端代理自动生成的),另外,我自己写了个简单的服务调用封装 hub.js,先运行项目,看看自动生成的代码有没有:(http://localhost:8496/layim/hubs)

 

复制代码
/*!
 * ASP.NET SignalR JavaScript Library v2.1.2
 * http://signalr.net/
 *
 * Copyright Microsoft Open Technologies, Inc. All rights reserved.
 * Licensed under the Apache 2.0
 * https://github.com/SignalR/SignalR/blob/master/LICENSE.md
 *
 */

/// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" />
/// <reference path="jquery.signalR.js" />
(function ($, window, undefined) {
    /// <param name="$" type="jQuery" />
    "use strict";

    if (typeof ($.signalR) !== "function") {
        throw new Error("SignalR: SignalR is not loaded. Please ensure jquery.signalR-x.js is referenced before ~/signalr/js.");
    }

    var signalR = $.signalR;

    function makeProxyCallback(hub, callback) {
        return function () {
            // Call the client hub method
            callback.apply(hub, $.makeArray(arguments));
        };
    }

    function registerHubProxies(instance, shouldSubscribe) {
        var key, hub, memberKey, memberValue, subscriptionMethod;

        for (key in instance) {
            if (instance.hasOwnProperty(key)) {
                hub = instance[key];

                if (!(hub.hubName)) {
                    // Not a client hub
                    continue;
                }

                if (shouldSubscribe) {
                    // We want to subscribe to the hub events
                    subscriptionMethod = hub.on;
                } else {
                    // We want to unsubscribe from the hub events
                    subscriptionMethod = hub.off;
                }

                // Loop through all members on the hub and find client hub functions to subscribe/unsubscribe
                for (memberKey in hub.client) {
                    if (hub.client.hasOwnProperty(memberKey)) {
                        memberValue = hub.client[memberKey];

                        if (!$.isFunction(memberValue)) {
                            // Not a client hub function
                            continue;
                        }

                        subscriptionMethod.call(hub, memberKey, makeProxyCallback(hub, memberValue));
                    }
                }
            }
        }
    }

    $.hubConnection.prototype.createHubProxies = function () {
        var proxies = {};
        this.starting(function () {
            // Register the hub proxies as subscribed
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, true);

            this._registerSubscribedHubs();
        }).disconnected(function () {
            // Unsubscribe all hub proxies when we "disconnect".  This is to ensure that we do not re-add functional call backs.
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, false);
        });

        proxies['layimHub'] = this.createHubProxy('layimHub'); 
        proxies['layimHub'].client = { };
        proxies['layimHub'].server = {
            clientSendMsgToClient: function (message) {
                return proxies['layimHub'].invoke.apply(proxies['layimHub'], $.merge(["ClientSendMsgToClient"], $.makeArray(arguments)));
             },

            clientSendMsgToGroup: function (message) {
                return proxies['layimHub'].invoke.apply(proxies['layimHub'], $.merge(["ClientSendMsgToGroup"], $.makeArray(arguments)));
             },

            clientToClient: function (fromUserId, toUserId) {
                return proxies['layimHub'].invoke.apply(proxies['layimHub'], $.merge(["ClientToClient"], $.makeArray(arguments)));
             },

            clientToGroup: function (fromUserId, toGroupId) {
                return proxies['layimHub'].invoke.apply(proxies['layimHub'], $.merge(["ClientToGroup"], $.makeArray(arguments)));
             }
        };

        return proxies;
    };

    signalR.hub = $.hubConnection("/layim", { useDefaultPath: false });
    $.extend(signalR, signalR.hub.createHubProxies());

}(window.jQuery, window));
复制代码

可以看到,上述代码server已经对应了服务端我们自定义的方法。(这里注意,当我们调用server端的方法的时候,首字母都是小写的。)

客户端连接服务器核心代码:

 

复制代码
//连接服务器
                connect: function () {
                    $.connection.hub.url = _this.option.serverUrl;
                    _this.proxy.proxyCS = $.connection.layimHub;
                    $.connection.hub.start({ jsonp: true }).done(function () {
                        //连接服务器
                        //TODO处理聊天界面之前的逻辑
                        //console.log('自定义连接服务器成功方法:' + success);
                        if (call.ready) {
                            for (var i = 0; i < call.ready.length; i++) {
                                call.ready[i]({ connected: true });
                            }
                        }
                        console.log('连接服务器成功');
                    }).fail(function () {
                        //console.log('自定义连接服务器成功方法:' + success);
                        if (call.failed) {
                            for (var i = 0; i < call.failed.length; i++) {
                                call.failed[i]({ connected: false });
                            }
                        }
                    });
                },
复制代码

不知不觉竟然写成了倒叙,给大家解释一下,上文中A和B能够连接的前提条件是 初始化连接服务器能够成功,也就是说客户端调用connect方法然后能够打印 ‘连接服务器成功’。

最后呢,跟Layim1.0不同的是Layim2.0引用js的方法有所变化,类似seajs的语法。所以,我们的hub代码要改成如下形式:

autohub.js

signalr.2.1.2.min.js

hub.js

我们都以这种形式定义成模块之后,需要在页面中添加模块:

 

复制代码
 //自定义模块
    layui.extend({
        signalr: '/scripts/signalr/signalr',
        autohub: '/scripts/signalr/autohub',//自动生成的
        hub: '/Scripts/signalr/hub',
           
    });
复制代码

最后,我们以use的形式开启服务端:

运行项目看一下,打印输出:

到此为止,连接服务器工作已经完成了,总结一下:

1.创建Hub文件

2.修改相应config(startup)

3.修改相应的js代码规范(layui.use)

4.界面初始化之后调用connect方法。

 

本篇就到这里了,由于项目还在进行中,暂时不能提供源代码,不过思路应该没问题吧,如果对博客中有什么疑问或者看法的话,欢迎在底下评论交流。

      下篇预告【初级】ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(三) 之 实现单聊,群聊,发送图片,文件。

 

   想要学习的小伙伴,可以关注我的博客哦,我的QQ:645857874,Email:fanpan26@126.com

GitHub:https://github.com/fanpan26/LayIM_NetClient/

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(一) 之 基层数据搭建,让数据活起来(数据获取) - 丶Pz - 博客园

mikel阅读(625)

来源: ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(一) 之 基层数据搭建,让数据活起来(数据获取) – 丶Pz – 博客园

大家好,本篇是接上一篇 ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(零) 前言  ASP.NET SignalR WebIM系列第二篇。本篇会带领大家将 LayIM界面中的数据动态化。当然还不涉及即时消息通讯,如果你已经搞定了数据界面,那么本文您可以简单的看一下,或者略过。

进入正题,layim帮我们定义好了数据规则,我们只要写一个接口实现那个json规范就可以了,剩下的事情就交给layim去做,看一下json格式。(对应文件夹:demo/json/getList/.json,demo/json/getMembers.json)

另外,大家可以参考官方文档:http://www.layui.com/doc/layim.html

 用户信息、好友、群组等格式

有的同学可能觉得这个json太复杂,不知道如何下手,其实很简单,我们先分析第一层

{"code":0,"msg":"","data":null}

是不是很简单,code就是数据是否合法,0 代表成功 1 或其他代表数据有异常,msg 就是程序上的信息,比如获取失败,或者参数不正确等等。data 最重要的一部分,就是我们下一步要分析的数据了。可以看到 data的格式如下:

复制代码
{
    mine: {
        //当前登录用户的个人信息,头像,昵称等
    },
    friend: [
        //friend 第一层 是group,代表好友分组,好友分组中又包含若干好友,所以,有一个list属性,list中的每个元素肯定就是好友了,同mine一样,也包含头像,昵称等信息
    ],
    group: [
        //群组比friend相对来说简单一点,就一层,就是群组信息,至于获取群成员就要看另外一个json了。(,demo/json/getMembers.json)
    ]
}
复制代码

数据分析就做到这里,.NET当中有自带的序列化方法,也有第三方序列化组件,这里我们完全不用担心,用MVC中的JsonResult就可以了。下面我们就要开始代码阶段了,打开LayIM.Model的项目。

好,先把最外层的写上,命名呢大致表达意思即可,在新建Model的时候要注意提取公共的字段,比如,每个对象,user 或者group都有id,那么我们就可以把id提取出来,然后写其他得类继承它就可以了。代码参考如下:

复制代码
 1  /// <summary>
 2     /// 返回结果
 3     /// </summary>
 4     public class JsonResultModel
 5     {
 6         public JsonResultType code { get; set; }
 7         public object data { get; set; }
 8         public string msg { get; set; }
 9     }
10 
11     /// <summary>
12     /// 成功失败
13     /// </summary>
14     public enum JsonResultType
15     {
16         Success = 0,
17         Failed = 1
18     }
复制代码
复制代码
 1 /// <summary>
 2     /// 基础信息json
 3     /// </summary>
 4     public class BaseListResult
 5     {
 6         public BaseListResult()
 7         {
 8             //friend = new List<FriendGroupEntity>();
 9             //group = new List<GroupEntity>();
10         }
11         public IEnumerable<FriendGroupEntity> friend { get; set; }
12         public IEnumerable<GroupEntity> group { get; set; }
13         public UserEntity mine { get; set; }
14     }
复制代码
复制代码
 /// <summary>
    /// 群员信息json
    /// </summary>
    public class MembersListResult
    {
        /// <summary>
        /// 群主
        /// </summary>
        public UserEntity owner { get; set; }
        /// <summary>
        /// 群成员列表
        /// </summary>
        public IEnumerable<GroupUserEntity> list { get; set; }
    }
复制代码

其中FriendGroupEntity代表好友分组信息,GroupEntity代表群组信息,UserEntity就是用户基础信息了,这里要注意,如果增加自己的业务,比如,用户好友备注昵称,可以加字段,但是不要少了layim中规定的字段。下面贴出 UserEntity的代码,当然,也可以不用继承来实现,定义若干个类,然后每个类对应相应的对象即可。

复制代码
 1  /// <summary>
 2     /// 基类
 3     /// </summary>
 4     public class BaseEntity
 5     {
 6         public int id { get; set; }
 7     }
 8 
 9     /// <summary>
10     /// 基类
11     /// </summary>
12     public class AvatarEntity : BaseEntity
13     {
14         public string avatar { get; set; } 
15     }
16 
17     /// <summary>
18     /// 用户
19     /// </summary>
20     public class UserEntity : AvatarEntity
21     {
22         public string status { get; set; }
23         public string username { get; set; }
24         public string sign { get; set; }
25     }
复制代码

好了,现在对象模型已经建好了,下一步我们该干嘛呢?没错,既然数据是活的,那么就要用到数据库了,当然这里的数据库你可以用mySQLSQLServer,mongodb等都可以。至于数据库设计就不多讲了,每个人有每个人的想法.我把表介绍一下:

  • 用户表 (包含用户基本信息,账号信息等,主键 用户id)
  • 用户好友分组表(每个用户有自己的好友分组,如果想让业务复杂一点,就加上系统分组,类似我的好友,黑名单等,这些都是不可删除的,主键 好友分组id)
  • 好友分组关系表(每个用户好友分组里面对应多个好友,外键关联 分组id,用户id)
  • 用户群表(用户加入的群或者创建的群信息,包含群主,群介绍等等,主键 群id)
  • 用户群关系表(每个群都有若干个用户,外键关联 群id,用户id)

以上五个表我们用来对接layim基本没什么大问题了。如果有些同学确实不会设计,可以在评论区留下你的邮箱,我把脚本发给你。(这里贴图太麻烦,所以具体的设计就不在做介绍了)

 

数据库设计完之后,就是很枯燥的CRUD了,这个不用我多说了吧,不理解的同学呢可以稍微学习一下,也不算太难。项目中我就引用了Macrosoft官方的SQLHelper,然后自己写了些帮助类。官方的SQLhelper如下:

复制代码
using System;
using System.Data;
using System.Xml;
using System.Data.SqlClient;
using System.Collections;

namespace Microsoft.ApplicationBlocks.Data
{
    /// <summary>
    /// The SqlHelper class is intended to encapsulate high performance, scalable best practices for 
    /// common uses of SqlClient
    /// </summary>
    public sealed class SqlHelper
    {
        #region private utility methods & constructors

        // Since this class provides only static methods, make the default constructor private to prevent 
        // instances from being created with "new SqlHelper()"
        private SqlHelper() { }

        /// <summary>
        /// This method is used to attach array of SqlParameters to a SqlCommand.
        /// 
        /// This method will assign a value of DbNull to any parameter with a direction of
        /// InputOutput and a value of null.  
        /// 
        /// This behavior will prevent default values from being used, but
        /// this will be the less common case than an intended pure output parameter (derived as InputOutput)
        /// where the user provided no input value.
        /// </summary>
        /// <param name="command">The command to which the parameters will be added</param>
        /// <param name="commandParameters">An array of SqlParameters to be added to command</param>
        private static void AttachParameters(SqlCommand command, SqlParameter[] commandParameters)
        {
            if (command == null) throw new ArgumentNullException("command");
            if (commandParameters != null)
            {
                foreach (SqlParameter p in commandParameters)
                {
                    if (p != null)
                    {
                        // Check for derived output value with no value assigned
                        if ((p.Direction == ParameterDirection.InputOutput ||
                            p.Direction == ParameterDirection.Input) &&
                            (p.Value == null))
                        {
                            p.Value = DBNull.Value;
                        }
                        command.Parameters.Add(p);
                    }
                }
            }
        }

        /// <summary>
        /// This method assigns dataRow column values to an array of SqlParameters
        /// </summary>
        /// <param name="commandParameters">Array of SqlParameters to be assigned values</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values</param>
        private static void AssignParameterValues(SqlParameter[] commandParameters, DataRow dataRow)
        {
            if ((commandParameters == null) || (dataRow == null))
            {
                // Do nothing if we get no data
                return;
            }

            int i = 0;
            // Set the parameters values
            foreach (SqlParameter commandParameter in commandParameters)
            {
                // Check the parameter name
                if (commandParameter.ParameterName == null ||
                    commandParameter.ParameterName.Length <= 1)
                    throw new Exception(
                        string.Format(
                            "Please provide a valid parameter name on the parameter #{0}, the ParameterName property has the following value: '{1}'.",
                            i, commandParameter.ParameterName));
                if (dataRow.Table.Columns.IndexOf(commandParameter.ParameterName.Substring(1)) != -1)
                    commandParameter.Value = dataRow[commandParameter.ParameterName.Substring(1)];
                i++;
            }
        }

        /// <summary>
        /// This method assigns an array of values to an array of SqlParameters
        /// </summary>
        /// <param name="commandParameters">Array of SqlParameters to be assigned values</param>
        /// <param name="parameterValues">Array of objects holding the values to be assigned</param>
        private static void AssignParameterValues(SqlParameter[] commandParameters, object[] parameterValues)
        {
            if ((commandParameters == null) || (parameterValues == null))
            {
                // Do nothing if we get no data
                return;
            }

            // We must have the same number of values as we pave parameters to put them in
            if (commandParameters.Length != parameterValues.Length)
            {
                throw new ArgumentException("Parameter count does not match Parameter Value count.");
            }

            // Iterate through the SqlParameters, assigning the values from the corresponding position in the 
            // value array
            for (int i = 0, j = commandParameters.Length; i < j; i++)
            {
                // If the current array value derives from IDbDataParameter, then assign its Value property
                if (parameterValues[i] is IDbDataParameter)
                {
                    IDbDataParameter paramInstance = (IDbDataParameter)parameterValues[i];
                    if (paramInstance.Value == null)
                    {
                        commandParameters[i].Value = DBNull.Value;
                    }
                    else
                    {
                        commandParameters[i].Value = paramInstance.Value;
                    }
                }
                else if (parameterValues[i] == null)
                {
                    commandParameters[i].Value = DBNull.Value;
                }
                else
                {
                    commandParameters[i].Value = parameterValues[i];
                }
            }
        }

        /// <summary>
        /// This method opens (if necessary) and assigns a connection, transaction, command type and parameters 
        /// to the provided command
        /// </summary>
        /// <param name="command">The SqlCommand to be prepared</param>
        /// <param name="connection">A valid SqlConnection, on which to execute this command</param>
        /// <param name="transaction">A valid SqlTransaction, or 'null'</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParameters to be associated with the command or 'null' if no parameters are required</param>
        /// <param name="mustCloseConnection"><c>true</c> if the connection was opened by the method, otherwose is false.</param>
        private static void PrepareCommand(SqlCommand command, SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, SqlParameter[] commandParameters, out bool mustCloseConnection)
        {
            if (command == null) throw new ArgumentNullException("command");
            if (commandText == null || commandText.Length == 0) throw new ArgumentNullException("commandText");

            // If the provided connection is not open, we will open it
            if (connection.State != ConnectionState.Open)
            {
                mustCloseConnection = true;
                connection.Open();
            }
            else
            {
                mustCloseConnection = false;
            }

            // Associate the connection with the command
            command.Connection = connection;

            // Set the command text (stored procedure name or SQL statement)
            command.CommandText = commandText;

            // If we were provided a transaction, assign it
            if (transaction != null)
            {
                if (transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
                command.Transaction = transaction;
            }

            // Set the command type
            command.CommandType = commandType;

            // Attach the command parameters if they are provided
            if (commandParameters != null)
            {
                AttachParameters(command, commandParameters);
            }
            return;
        }

        #endregion private utility methods & constructors

        #region ExecuteNonQuery

        /// <summary>
        /// Execute a SqlCommand (that returns no resultset and takes no parameters) against the database specified in 
        /// the connection string
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int result = ExecuteNonQuery(connString, CommandType.StoredProcedure, "PublishOrders");
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(string connectionString, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteNonQuery(connectionString, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns no resultset) against the database specified in the connection string 
        /// using the provided parameters
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int result = ExecuteNonQuery(connString, CommandType.StoredProcedure, "PublishOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(string connectionString, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");

            // Create & open a SqlConnection, and dispose of it after we are done
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();

                // Call the overload that takes a connection in place of the connection string
                return ExecuteNonQuery(connection, commandType, commandText, commandParameters);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns no resultset) against the database specified in 
        /// the connection string using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  int result = ExecuteNonQuery(connString, "PublishOrders", 24, 36);
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored prcedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(string connectionString, string spName, params object[] parameterValues)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteNonQuery(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteNonQuery(connectionString, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns no resultset and takes no parameters) against the provided SqlConnection. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int result = ExecuteNonQuery(conn, CommandType.StoredProcedure, "PublishOrders");
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(SqlConnection connection, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteNonQuery(connection, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns no resultset) against the specified SqlConnection 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int result = ExecuteNonQuery(conn, CommandType.StoredProcedure, "PublishOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connection == null) throw new ArgumentNullException("connection");

            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            bool mustCloseConnection = false;
            PrepareCommand(cmd, connection, (SqlTransaction)null, commandType, commandText, commandParameters, out mustCloseConnection);

            // Finally, execute the command
            int retval = cmd.ExecuteNonQuery();

            // Detach the SqlParameters from the command object, so they can be used again
            cmd.Parameters.Clear();
            if (mustCloseConnection)
                connection.Close();
            return retval;
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns no resultset) against the specified SqlConnection 
        /// using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  int result = ExecuteNonQuery(conn, "PublishOrders", 24, 36);
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(SqlConnection connection, string spName, params object[] parameterValues)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteNonQuery(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteNonQuery(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns no resultset and takes no parameters) against the provided SqlTransaction. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int result = ExecuteNonQuery(trans, CommandType.StoredProcedure, "PublishOrders");
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(SqlTransaction transaction, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteNonQuery(transaction, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns no resultset) against the specified SqlTransaction
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int result = ExecuteNonQuery(trans, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");

            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            bool mustCloseConnection = false;
            PrepareCommand(cmd, transaction.Connection, transaction, commandType, commandText, commandParameters, out mustCloseConnection);

            // Finally, execute the command
            int retval = cmd.ExecuteNonQuery();

            // Detach the SqlParameters from the command object, so they can be used again
            cmd.Parameters.Clear();
            return retval;
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns no resultset) against the specified 
        /// SqlTransaction using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  int result = ExecuteNonQuery(conn, trans, "PublishOrders", 24, 36);
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQuery(SqlTransaction transaction, string spName, params object[] parameterValues)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteNonQuery(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteNonQuery(transaction, CommandType.StoredProcedure, spName);
            }
        }

        #endregion ExecuteNonQuery

        #region ExecuteDataset

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the database specified in 
        /// the connection string. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(connString, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(string connectionString, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteDataset(connectionString, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the database specified in the connection string 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(connString, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(string connectionString, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");

            // Create & open a SqlConnection, and dispose of it after we are done
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();

                // Call the overload that takes a connection in place of the connection string
                return ExecuteDataset(connection, commandType, commandText, commandParameters);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the database specified in 
        /// the connection string using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(connString, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(string connectionString, string spName, params object[] parameterValues)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteDataset(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteDataset(connectionString, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlConnection. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(conn, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(SqlConnection connection, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteDataset(connection, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(conn, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connection == null) throw new ArgumentNullException("connection");

            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            bool mustCloseConnection = false;
            PrepareCommand(cmd, connection, (SqlTransaction)null, commandType, commandText, commandParameters, out mustCloseConnection);

            // Create the DataAdapter & DataSet
            using (SqlDataAdapter da = new SqlDataAdapter(cmd))
            {
                DataSet ds = new DataSet();

                // Fill the DataSet using default values for DataTable names, etc
                da.Fill(ds);

                // Detach the SqlParameters from the command object, so they can be used again
                cmd.Parameters.Clear();

                if (mustCloseConnection)
                    connection.Close();

                // Return the dataset
                return ds;
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(conn, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(SqlConnection connection, string spName, params object[] parameterValues)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteDataset(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteDataset(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlTransaction. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(trans, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(SqlTransaction transaction, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteDataset(transaction, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlTransaction
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(trans, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");

            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            bool mustCloseConnection = false;
            PrepareCommand(cmd, transaction.Connection, transaction, commandType, commandText, commandParameters, out mustCloseConnection);

            // Create the DataAdapter & DataSet
            using (SqlDataAdapter da = new SqlDataAdapter(cmd))
            {
                DataSet ds = new DataSet();

                // Fill the DataSet using default values for DataTable names, etc
                da.Fill(ds);

                // Detach the SqlParameters from the command object, so they can be used again
                cmd.Parameters.Clear();

                // Return the dataset
                return ds;
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified 
        /// SqlTransaction using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  DataSet ds = ExecuteDataset(trans, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDataset(SqlTransaction transaction, string spName, params object[] parameterValues)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteDataset(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteDataset(transaction, CommandType.StoredProcedure, spName);
            }
        }

        #endregion ExecuteDataset

        #region ExecuteReader

        /// <summary>
        /// This enum is used to indicate whether the connection was provided by the caller, or created by SqlHelper, so that
        /// we can set the appropriate CommandBehavior when calling ExecuteReader()
        /// </summary>
        private enum SqlConnectionOwnership
        {
            /// <summary>Connection is owned and managed by SqlHelper</summary>
            Internal,
            /// <summary>Connection is owned and managed by the caller</summary>
            External
        }

        /// <summary>
        /// Create and prepare a SqlCommand, and call ExecuteReader with the appropriate CommandBehavior.
        /// </summary>
        /// <remarks>
        /// If we created and opened the connection, we want the connection to be closed when the DataReader is closed.
        /// 
        /// If the caller provided the connection, we want to leave it to them to manage.
        /// </remarks>
        /// <param name="connection">A valid SqlConnection, on which to execute this command</param>
        /// <param name="transaction">A valid SqlTransaction, or 'null'</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParameters to be associated with the command or 'null' if no parameters are required</param>
        /// <param name="connectionOwnership">Indicates whether the connection parameter was provided by the caller, or created by SqlHelper</param>
        /// <returns>SqlDataReader containing the results of the command</returns>
        private static SqlDataReader ExecuteReader(SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, SqlParameter[] commandParameters, SqlConnectionOwnership connectionOwnership)
        {
            if (connection == null) throw new ArgumentNullException("connection");

            bool mustCloseConnection = false;
            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            try
            {
                PrepareCommand(cmd, connection, transaction, commandType, commandText, commandParameters, out mustCloseConnection);

                // Create a reader
                SqlDataReader dataReader;

                // Call ExecuteReader with the appropriate CommandBehavior
                if (connectionOwnership == SqlConnectionOwnership.External)
                {
                    dataReader = cmd.ExecuteReader();
                }
                else
                {
                    dataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                }

                // Detach the SqlParameters from the command object, so they can be used again.
                // HACK: There is a problem here, the output parameter values are fletched 
                // when the reader is closed, so if the parameters are detached from the command
                // then the SqlReader can磘 set its values. 
                // When this happen, the parameters can磘 be used again in other command.
                bool canClear = true;
                foreach (SqlParameter commandParameter in cmd.Parameters)
                {
                    if (commandParameter.Direction != ParameterDirection.Input)
                        canClear = false;
                }

                if (canClear)
                {
                    cmd.Parameters.Clear();
                }

                return dataReader;
            }
            catch
            {
                if (mustCloseConnection)
                    connection.Close();
                throw;
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the database specified in 
        /// the connection string. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(connString, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(string connectionString, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteReader(connectionString, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the database specified in the connection string 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(connString, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(string connectionString, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            SqlConnection connection = null;
            try
            {
                connection = new SqlConnection(connectionString);
                connection.Open();

                // Call the private overload that takes an internally owned connection in place of the connection string
                return ExecuteReader(connection, null, commandType, commandText, commandParameters, SqlConnectionOwnership.Internal);
            }
            catch
            {
                // If we fail to return the SqlDatReader, we need to close the connection ourselves
                if (connection != null) connection.Close();
                throw;
            }

        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the database specified in 
        /// the connection string using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(connString, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(string connectionString, string spName, params object[] parameterValues)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                AssignParameterValues(commandParameters, parameterValues);

                return ExecuteReader(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteReader(connectionString, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlConnection. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(conn, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(SqlConnection connection, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteReader(connection, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(conn, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            // Pass through the call to the private overload using a null transaction value and an externally owned connection
            return ExecuteReader(connection, (SqlTransaction)null, commandType, commandText, commandParameters, SqlConnectionOwnership.External);
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(conn, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(SqlConnection connection, string spName, params object[] parameterValues)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                AssignParameterValues(commandParameters, parameterValues);

                return ExecuteReader(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteReader(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlTransaction. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(trans, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(SqlTransaction transaction, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteReader(transaction, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlTransaction
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///   SqlDataReader dr = ExecuteReader(trans, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");

            // Pass through to private overload, indicating that the connection is owned by the caller
            return ExecuteReader(transaction.Connection, transaction, commandType, commandText, commandParameters, SqlConnectionOwnership.External);
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified
        /// SqlTransaction using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  SqlDataReader dr = ExecuteReader(trans, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReader(SqlTransaction transaction, string spName, params object[] parameterValues)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                AssignParameterValues(commandParameters, parameterValues);

                return ExecuteReader(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteReader(transaction, CommandType.StoredProcedure, spName);
            }
        }

        #endregion ExecuteReader

        #region ExecuteScalar

        /// <summary>
        /// Execute a SqlCommand (that returns a 1x1 resultset and takes no parameters) against the database specified in 
        /// the connection string. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(connString, CommandType.StoredProcedure, "GetOrderCount");
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(string connectionString, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteScalar(connectionString, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a 1x1 resultset) against the database specified in the connection string 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(connString, CommandType.StoredProcedure, "GetOrderCount", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(string connectionString, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            // Create & open a SqlConnection, and dispose of it after we are done
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();

                // Call the overload that takes a connection in place of the connection string
                return ExecuteScalar(connection, commandType, commandText, commandParameters);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a 1x1 resultset) against the database specified in 
        /// the connection string using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(connString, "GetOrderCount", 24, 36);
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(string connectionString, string spName, params object[] parameterValues)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteScalar(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteScalar(connectionString, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a 1x1 resultset and takes no parameters) against the provided SqlConnection. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(conn, CommandType.StoredProcedure, "GetOrderCount");
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(SqlConnection connection, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteScalar(connection, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a 1x1 resultset) against the specified SqlConnection 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(conn, CommandType.StoredProcedure, "GetOrderCount", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connection == null) throw new ArgumentNullException("connection");

            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();

            bool mustCloseConnection = false;
            PrepareCommand(cmd, connection, (SqlTransaction)null, commandType, commandText, commandParameters, out mustCloseConnection);

            // Execute the command & return the results
            object retval = cmd.ExecuteScalar();

            // Detach the SqlParameters from the command object, so they can be used again
            cmd.Parameters.Clear();

            if (mustCloseConnection)
                connection.Close();

            return retval;
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a 1x1 resultset) against the specified SqlConnection 
        /// using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(conn, "GetOrderCount", 24, 36);
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(SqlConnection connection, string spName, params object[] parameterValues)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteScalar(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteScalar(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a 1x1 resultset and takes no parameters) against the provided SqlTransaction. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(trans, CommandType.StoredProcedure, "GetOrderCount");
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(SqlTransaction transaction, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteScalar(transaction, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a 1x1 resultset) against the specified SqlTransaction
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(trans, CommandType.StoredProcedure, "GetOrderCount", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");

            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            bool mustCloseConnection = false;
            PrepareCommand(cmd, transaction.Connection, transaction, commandType, commandText, commandParameters, out mustCloseConnection);

            // Execute the command & return the results
            object retval = cmd.ExecuteScalar();

            // Detach the SqlParameters from the command object, so they can be used again
            cmd.Parameters.Clear();
            return retval;
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a 1x1 resultset) against the specified
        /// SqlTransaction using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  int orderCount = (int)ExecuteScalar(trans, "GetOrderCount", 24, 36);
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalar(SqlTransaction transaction, string spName, params object[] parameterValues)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // PPull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteScalar(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteScalar(transaction, CommandType.StoredProcedure, spName);
            }
        }

        #endregion ExecuteScalar    

        #region ExecuteXmlReader
        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlConnection. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  XmlReader r = ExecuteXmlReader(conn, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command using "FOR XML AUTO"</param>
        /// <returns>An XmlReader containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReader(SqlConnection connection, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteXmlReader(connection, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  XmlReader r = ExecuteXmlReader(conn, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command using "FOR XML AUTO"</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An XmlReader containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReader(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (connection == null) throw new ArgumentNullException("connection");

            bool mustCloseConnection = false;
            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            try
            {
                PrepareCommand(cmd, connection, (SqlTransaction)null, commandType, commandText, commandParameters, out mustCloseConnection);

                // Create the DataAdapter & DataSet
                XmlReader retval = cmd.ExecuteXmlReader();

                // Detach the SqlParameters from the command object, so they can be used again
                cmd.Parameters.Clear();

                return retval;
            }
            catch
            {
                if (mustCloseConnection)
                    connection.Close();
                throw;
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  XmlReader r = ExecuteXmlReader(conn, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="spName">The name of the stored procedure using "FOR XML AUTO"</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>An XmlReader containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReader(SqlConnection connection, string spName, params object[] parameterValues)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteXmlReader(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteXmlReader(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlTransaction. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  XmlReader r = ExecuteXmlReader(trans, CommandType.StoredProcedure, "GetOrders");
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command using "FOR XML AUTO"</param>
        /// <returns>An XmlReader containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReader(SqlTransaction transaction, CommandType commandType, string commandText)
        {
            // Pass through the call providing null for the set of SqlParameters
            return ExecuteXmlReader(transaction, commandType, commandText, (SqlParameter[])null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlTransaction
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  XmlReader r = ExecuteXmlReader(trans, CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command using "FOR XML AUTO"</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <returns>An XmlReader containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReader(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] commandParameters)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");

            // Create a command and prepare it for execution
            SqlCommand cmd = new SqlCommand();
            bool mustCloseConnection = false;
            PrepareCommand(cmd, transaction.Connection, transaction, commandType, commandText, commandParameters, out mustCloseConnection);

            // Create the DataAdapter & DataSet
            XmlReader retval = cmd.ExecuteXmlReader();

            // Detach the SqlParameters from the command object, so they can be used again
            cmd.Parameters.Clear();
            return retval;
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified 
        /// SqlTransaction using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  XmlReader r = ExecuteXmlReader(trans, "GetOrders", 24, 36);
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReader(SqlTransaction transaction, string spName, params object[] parameterValues)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                return ExecuteXmlReader(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                return ExecuteXmlReader(transaction, CommandType.StoredProcedure, spName);
            }
        }

        #endregion ExecuteXmlReader

        #region FillDataset
        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the database specified in 
        /// the connection string. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  FillDataset(connString, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"});
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)</param>
        public static void FillDataset(string connectionString, CommandType commandType, string commandText, DataSet dataSet, string[] tableNames)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (dataSet == null) throw new ArgumentNullException("dataSet");

            // Create & open a SqlConnection, and dispose of it after we are done
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();

                // Call the overload that takes a connection in place of the connection string
                FillDataset(connection, commandType, commandText, dataSet, tableNames);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the database specified in the connection string 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  FillDataset(connString, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"}, new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>
        public static void FillDataset(string connectionString, CommandType commandType,
            string commandText, DataSet dataSet, string[] tableNames,
            params SqlParameter[] commandParameters)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (dataSet == null) throw new ArgumentNullException("dataSet");
            // Create & open a SqlConnection, and dispose of it after we are done
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();

                // Call the overload that takes a connection in place of the connection string
                FillDataset(connection, commandType, commandText, dataSet, tableNames, commandParameters);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the database specified in 
        /// the connection string using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  FillDataset(connString, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"}, 24);
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>    
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        public static void FillDataset(string connectionString, string spName,
            DataSet dataSet, string[] tableNames,
            params object[] parameterValues)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (dataSet == null) throw new ArgumentNullException("dataSet");
            // Create & open a SqlConnection, and dispose of it after we are done
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();

                // Call the overload that takes a connection in place of the connection string
                FillDataset(connection, spName, dataSet, tableNames, parameterValues);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlConnection. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  FillDataset(conn, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"});
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>    
        public static void FillDataset(SqlConnection connection, CommandType commandType,
            string commandText, DataSet dataSet, string[] tableNames)
        {
            FillDataset(connection, commandType, commandText, dataSet, tableNames, null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  FillDataset(conn, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"}, new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        public static void FillDataset(SqlConnection connection, CommandType commandType,
            string commandText, DataSet dataSet, string[] tableNames,
            params SqlParameter[] commandParameters)
        {
            FillDataset(connection, null, commandType, commandText, dataSet, tableNames, commandParameters);
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  FillDataset(conn, "GetOrders", ds, new string[] {"orders"}, 24, 36);
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        public static void FillDataset(SqlConnection connection, string spName,
            DataSet dataSet, string[] tableNames,
            params object[] parameterValues)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (dataSet == null) throw new ArgumentNullException("dataSet");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                FillDataset(connection, CommandType.StoredProcedure, spName, dataSet, tableNames, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                FillDataset(connection, CommandType.StoredProcedure, spName, dataSet, tableNames);
            }
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset and takes no parameters) against the provided SqlTransaction. 
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  FillDataset(trans, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"});
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>
        public static void FillDataset(SqlTransaction transaction, CommandType commandType,
            string commandText,
            DataSet dataSet, string[] tableNames)
        {
            FillDataset(transaction, commandType, commandText, dataSet, tableNames, null);
        }

        /// <summary>
        /// Execute a SqlCommand (that returns a resultset) against the specified SqlTransaction
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  FillDataset(trans, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"}, new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        public static void FillDataset(SqlTransaction transaction, CommandType commandType,
            string commandText, DataSet dataSet, string[] tableNames,
            params SqlParameter[] commandParameters)
        {
            FillDataset(transaction.Connection, transaction, commandType, commandText, dataSet, tableNames, commandParameters);
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified 
        /// SqlTransaction using the provided parameter values.  This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <remarks>
        /// This method provides no access to output parameters or the stored procedure's return value parameter.
        /// 
        /// e.g.:  
        ///  FillDataset(trans, "GetOrders", ds, new string[]{"orders"}, 24, 36);
        /// </remarks>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>
        /// <param name="parameterValues">An array of objects to be assigned as the input values of the stored procedure</param>
        public static void FillDataset(SqlTransaction transaction, string spName,
            DataSet dataSet, string[] tableNames,
            params object[] parameterValues)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (dataSet == null) throw new ArgumentNullException("dataSet");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If we receive parameter values, we need to figure out where they go
            if ((parameterValues != null) && (parameterValues.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Assign the provided values to these parameters based on parameter order
                AssignParameterValues(commandParameters, parameterValues);

                // Call the overload that takes an array of SqlParameters
                FillDataset(transaction, CommandType.StoredProcedure, spName, dataSet, tableNames, commandParameters);
            }
            else
            {
                // Otherwise we can just call the SP without params
                FillDataset(transaction, CommandType.StoredProcedure, spName, dataSet, tableNames);
            }
        }

        /// <summary>
        /// Private helper method that execute a SqlCommand (that returns a resultset) against the specified SqlTransaction and SqlConnection
        /// using the provided parameters.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  FillDataset(conn, trans, CommandType.StoredProcedure, "GetOrders", ds, new string[] {"orders"}, new SqlParameter("@prodid", 24));
        /// </remarks>
        /// <param name="connection">A valid SqlConnection</param>
        /// <param name="transaction">A valid SqlTransaction</param>
        /// <param name="commandType">The CommandType (stored procedure, text, etc.)</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="dataSet">A dataset wich will contain the resultset generated by the command</param>
        /// <param name="tableNames">This array will be used to create table mappings allowing the DataTables to be referenced
        /// by a user defined name (probably the actual table name)
        /// </param>
        /// <param name="commandParameters">An array of SqlParamters used to execute the command</param>
        private static void FillDataset(SqlConnection connection, SqlTransaction transaction, CommandType commandType,
            string commandText, DataSet dataSet, string[] tableNames,
            params SqlParameter[] commandParameters)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (dataSet == null) throw new ArgumentNullException("dataSet");

            // Create a command and prepare it for execution
            SqlCommand command = new SqlCommand();
            bool mustCloseConnection = false;
            PrepareCommand(command, connection, transaction, commandType, commandText, commandParameters, out mustCloseConnection);

            // Create the DataAdapter & DataSet
            using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
            {

                // Add the table mappings specified by the user
                if (tableNames != null && tableNames.Length > 0)
                {
                    string tableName = "Table";
                    for (int index = 0; index < tableNames.Length; index++)
                    {
                        if (tableNames[index] == null || tableNames[index].Length == 0) throw new ArgumentException("The tableNames parameter must contain a list of tables, a value was provided as null or empty string.", "tableNames");
                        dataAdapter.TableMappings.Add(tableName, tableNames[index]);
                        tableName += (index + 1).ToString();
                    }
                }

                // Fill the DataSet using default values for DataTable names, etc
                dataAdapter.Fill(dataSet);

                // Detach the SqlParameters from the command object, so they can be used again
                command.Parameters.Clear();
            }

            if (mustCloseConnection)
                connection.Close();
        }
        #endregion

        #region UpdateDataset
        /// <summary>
        /// Executes the respective command for each inserted, updated, or deleted row in the DataSet.
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  UpdateDataset(conn, insertCommand, deleteCommand, updateCommand, dataSet, "Order");
        /// </remarks>
        /// <param name="insertCommand">A valid transact-SQL statement or stored procedure to insert new records into the data source</param>
        /// <param name="deleteCommand">A valid transact-SQL statement or stored procedure to delete records from the data source</param>
        /// <param name="updateCommand">A valid transact-SQL statement or stored procedure used to update records in the data source</param>
        /// <param name="dataSet">The DataSet used to update the data source</param>
        /// <param name="tableName">The DataTable used to update the data source.</param>
        public static void UpdateDataset(SqlCommand insertCommand, SqlCommand deleteCommand, SqlCommand updateCommand, DataSet dataSet, string tableName)
        {
            if (insertCommand == null) throw new ArgumentNullException("insertCommand");
            if (deleteCommand == null) throw new ArgumentNullException("deleteCommand");
            if (updateCommand == null) throw new ArgumentNullException("updateCommand");
            if (tableName == null || tableName.Length == 0) throw new ArgumentNullException("tableName");

            // Create a SqlDataAdapter, and dispose of it after we are done
            using (SqlDataAdapter dataAdapter = new SqlDataAdapter())
            {
                // Set the data adapter commands
                dataAdapter.UpdateCommand = updateCommand;
                dataAdapter.InsertCommand = insertCommand;
                dataAdapter.DeleteCommand = deleteCommand;

                // Update the dataset changes in the data source
                dataAdapter.Update(dataSet, tableName);

                // Commit all the changes made to the DataSet
                dataSet.AcceptChanges();
            }
        }
        #endregion

        #region CreateCommand
        /// <summary>
        /// Simplify the creation of a Sql command object by allowing
        /// a stored procedure and optional parameters to be provided
        /// </summary>
        /// <remarks>
        /// e.g.:  
        ///  SqlCommand command = CreateCommand(conn, "AddCustomer", "CustomerID", "CustomerName");
        /// </remarks>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="sourceColumns">An array of string to be assigned as the source columns of the stored procedure parameters</param>
        /// <returns>A valid SqlCommand object</returns>
        public static SqlCommand CreateCommand(SqlConnection connection, string spName, params string[] sourceColumns)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // Create a SqlCommand
            SqlCommand cmd = new SqlCommand(spName, connection);
            cmd.CommandType = CommandType.StoredProcedure;

            // If we receive parameter values, we need to figure out where they go
            if ((sourceColumns != null) && (sourceColumns.Length > 0))
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Assign the provided source columns to these parameters based on parameter order
                for (int index = 0; index < sourceColumns.Length; index++)
                    commandParameters[index].SourceColumn = sourceColumns[index];

                // Attach the discovered parameters to the SqlCommand object
                AttachParameters(cmd, commandParameters);
            }

            return cmd;
        }
        #endregion

        #region ExecuteNonQueryTypedParams
        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns no resultset) against the database specified in 
        /// the connection string using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on row values.
        /// </summary>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQueryTypedParams(String connectionString, String spName, DataRow dataRow)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteNonQuery(connectionString, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns no resultset) against the specified SqlConnection 
        /// using the dataRow column values as the stored procedure's parameters values.  
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on row values.
        /// </summary>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQueryTypedParams(SqlConnection connection, String spName, DataRow dataRow)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteNonQuery(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteNonQuery(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns no resultset) against the specified
        /// SqlTransaction using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on row values.
        /// </summary>
        /// <param name="transaction">A valid SqlTransaction object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An int representing the number of rows affected by the command</returns>
        public static int ExecuteNonQueryTypedParams(SqlTransaction transaction, String spName, DataRow dataRow)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // Sf the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteNonQuery(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteNonQuery(transaction, CommandType.StoredProcedure, spName);
            }
        }
        #endregion

        #region ExecuteDatasetTypedParams
        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the database specified in 
        /// the connection string using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on row values.
        /// </summary>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDatasetTypedParams(string connectionString, String spName, DataRow dataRow)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            //If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteDataset(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteDataset(connectionString, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the dataRow column values as the store procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on row values.
        /// </summary>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDatasetTypedParams(SqlConnection connection, String spName, DataRow dataRow)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteDataset(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteDataset(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlTransaction 
        /// using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on row values.
        /// </summary>
        /// <param name="transaction">A valid SqlTransaction object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>A dataset containing the resultset generated by the command</returns>
        public static DataSet ExecuteDatasetTypedParams(SqlTransaction transaction, String spName, DataRow dataRow)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteDataset(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteDataset(transaction, CommandType.StoredProcedure, spName);
            }
        }

        #endregion

        #region ExecuteReaderTypedParams
        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the database specified in 
        /// the connection string using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReaderTypedParams(String connectionString, String spName, DataRow dataRow)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteReader(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteReader(connectionString, CommandType.StoredProcedure, spName);
            }
        }


        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReaderTypedParams(SqlConnection connection, String spName, DataRow dataRow)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteReader(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteReader(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlTransaction 
        /// using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="transaction">A valid SqlTransaction object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>A SqlDataReader containing the resultset generated by the command</returns>
        public static SqlDataReader ExecuteReaderTypedParams(SqlTransaction transaction, String spName, DataRow dataRow)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteReader(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteReader(transaction, CommandType.StoredProcedure, spName);
            }
        }
        #endregion

        #region ExecuteScalarTypedParams
        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a 1x1 resultset) against the database specified in 
        /// the connection string using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalarTypedParams(String connectionString, String spName, DataRow dataRow)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connectionString, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteScalar(connectionString, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteScalar(connectionString, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a 1x1 resultset) against the specified SqlConnection 
        /// using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalarTypedParams(SqlConnection connection, String spName, DataRow dataRow)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteScalar(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteScalar(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a 1x1 resultset) against the specified SqlTransaction
        /// using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="transaction">A valid SqlTransaction object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An object containing the value in the 1x1 resultset generated by the command</returns>
        public static object ExecuteScalarTypedParams(SqlTransaction transaction, String spName, DataRow dataRow)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteScalar(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteScalar(transaction, CommandType.StoredProcedure, spName);
            }
        }
        #endregion

        #region ExecuteXmlReaderTypedParams
        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlConnection 
        /// using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An XmlReader containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReaderTypedParams(SqlConnection connection, String spName, DataRow dataRow)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteXmlReader(connection, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteXmlReader(connection, CommandType.StoredProcedure, spName);
            }
        }

        /// <summary>
        /// Execute a stored procedure via a SqlCommand (that returns a resultset) against the specified SqlTransaction 
        /// using the dataRow column values as the stored procedure's parameters values.
        /// This method will query the database to discover the parameters for the 
        /// stored procedure (the first time each stored procedure is called), and assign the values based on parameter order.
        /// </summary>
        /// <param name="transaction">A valid SqlTransaction object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="dataRow">The dataRow used to hold the stored procedure's parameter values.</param>
        /// <returns>An XmlReader containing the resultset generated by the command</returns>
        public static XmlReader ExecuteXmlReaderTypedParams(SqlTransaction transaction, String spName, DataRow dataRow)
        {
            if (transaction == null) throw new ArgumentNullException("transaction");
            if (transaction != null && transaction.Connection == null) throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            // If the row has values, the store procedure parameters must be initialized
            if (dataRow != null && dataRow.ItemArray.Length > 0)
            {
                // Pull the parameters for this stored procedure from the parameter cache (or discover them & populate the cache)
                SqlParameter[] commandParameters = SqlHelperParameterCache.GetSpParameterSet(transaction.Connection, spName);

                // Set the parameters values
                AssignParameterValues(commandParameters, dataRow);

                return SqlHelper.ExecuteXmlReader(transaction, CommandType.StoredProcedure, spName, commandParameters);
            }
            else
            {
                return SqlHelper.ExecuteXmlReader(transaction, CommandType.StoredProcedure, spName);
            }
        }
        #endregion

    }

    /// <summary>
    /// SqlHelperParameterCache provides functions to leverage a static cache of procedure parameters, and the
    /// ability to discover parameters for stored procedures at run-time.
    /// </summary>
    public sealed class SqlHelperParameterCache
    {
        #region private methods, variables, and constructors

        //Since this class provides only static methods, make the default constructor private to prevent 
        //instances from being created with "new SqlHelperParameterCache()"
        private SqlHelperParameterCache() { }

        private static Hashtable paramCache = Hashtable.Synchronized(new Hashtable());

        /// <summary>
        /// Resolve at run time the appropriate set of SqlParameters for a stored procedure
        /// </summary>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="includeReturnValueParameter">Whether or not to include their return value parameter</param>
        /// <returns>The parameter array discovered.</returns>
        private static SqlParameter[] DiscoverSpParameterSet(SqlConnection connection, string spName, bool includeReturnValueParameter)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            SqlCommand cmd = new SqlCommand(spName, connection);
            cmd.CommandType = CommandType.StoredProcedure;

            connection.Open();
            SqlCommandBuilder.DeriveParameters(cmd);
            connection.Close();

            if (!includeReturnValueParameter)
            {
                cmd.Parameters.RemoveAt(0);
            }

            SqlParameter[] discoveredParameters = new SqlParameter[cmd.Parameters.Count];

            cmd.Parameters.CopyTo(discoveredParameters, 0);

            // Init the parameters with a DBNull value
            foreach (SqlParameter discoveredParameter in discoveredParameters)
            {
                discoveredParameter.Value = DBNull.Value;
            }
            return discoveredParameters;
        }

        /// <summary>
        /// Deep copy of cached SqlParameter array
        /// </summary>
        /// <param name="originalParameters"></param>
        /// <returns></returns>
        private static SqlParameter[] CloneParameters(SqlParameter[] originalParameters)
        {
            SqlParameter[] clonedParameters = new SqlParameter[originalParameters.Length];

            for (int i = 0, j = originalParameters.Length; i < j; i++)
            {
                clonedParameters[i] = (SqlParameter)((ICloneable)originalParameters[i]).Clone();
            }

            return clonedParameters;
        }

        #endregion private methods, variables, and constructors

        #region caching functions

        /// <summary>
        /// Add parameter array to the cache
        /// </summary>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <param name="commandParameters">An array of SqlParamters to be cached</param>
        public static void CacheParameterSet(string connectionString, string commandText, params SqlParameter[] commandParameters)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (commandText == null || commandText.Length == 0) throw new ArgumentNullException("commandText");

            string hashKey = connectionString + ":" + commandText;

            paramCache[hashKey] = commandParameters;
        }

        /// <summary>
        /// Retrieve a parameter array from the cache
        /// </summary>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="commandText">The stored procedure name or T-SQL command</param>
        /// <returns>An array of SqlParamters</returns>
        public static SqlParameter[] GetCachedParameterSet(string connectionString, string commandText)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (commandText == null || commandText.Length == 0) throw new ArgumentNullException("commandText");

            string hashKey = connectionString + ":" + commandText;

            SqlParameter[] cachedParameters = paramCache[hashKey] as SqlParameter[];
            if (cachedParameters == null)
            {
                return null;
            }
            else
            {
                return CloneParameters(cachedParameters);
            }
        }

        #endregion caching functions

        #region Parameter Discovery Functions

        /// <summary>
        /// Retrieves the set of SqlParameters appropriate for the stored procedure
        /// </summary>
        /// <remarks>
        /// This method will query the database for this information, and then store it in a cache for future requests.
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <returns>An array of SqlParameters</returns>
        public static SqlParameter[] GetSpParameterSet(string connectionString, string spName)
        {
            return GetSpParameterSet(connectionString, spName, false);
        }

        /// <summary>
        /// Retrieves the set of SqlParameters appropriate for the stored procedure
        /// </summary>
        /// <remarks>
        /// This method will query the database for this information, and then store it in a cache for future requests.
        /// </remarks>
        /// <param name="connectionString">A valid connection string for a SqlConnection</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="includeReturnValueParameter">A bool value indicating whether the return value parameter should be included in the results</param>
        /// <returns>An array of SqlParameters</returns>
        public static SqlParameter[] GetSpParameterSet(string connectionString, string spName, bool includeReturnValueParameter)
        {
            if (connectionString == null || connectionString.Length == 0) throw new ArgumentNullException("connectionString");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                return GetSpParameterSetInternal(connection, spName, includeReturnValueParameter);
            }
        }

        /// <summary>
        /// Retrieves the set of SqlParameters appropriate for the stored procedure
        /// </summary>
        /// <remarks>
        /// This method will query the database for this information, and then store it in a cache for future requests.
        /// </remarks>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <returns>An array of SqlParameters</returns>
        internal static SqlParameter[] GetSpParameterSet(SqlConnection connection, string spName)
        {
            return GetSpParameterSet(connection, spName, false);
        }

        /// <summary>
        /// Retrieves the set of SqlParameters appropriate for the stored procedure
        /// </summary>
        /// <remarks>
        /// This method will query the database for this information, and then store it in a cache for future requests.
        /// </remarks>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="includeReturnValueParameter">A bool value indicating whether the return value parameter should be included in the results</param>
        /// <returns>An array of SqlParameters</returns>
        internal static SqlParameter[] GetSpParameterSet(SqlConnection connection, string spName, bool includeReturnValueParameter)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            using (SqlConnection clonedConnection = (SqlConnection)((ICloneable)connection).Clone())
            {
                return GetSpParameterSetInternal(clonedConnection, spName, includeReturnValueParameter);
            }
        }

        /// <summary>
        /// Retrieves the set of SqlParameters appropriate for the stored procedure
        /// </summary>
        /// <param name="connection">A valid SqlConnection object</param>
        /// <param name="spName">The name of the stored procedure</param>
        /// <param name="includeReturnValueParameter">A bool value indicating whether the return value parameter should be included in the results</param>
        /// <returns>An array of SqlParameters</returns>
        private static SqlParameter[] GetSpParameterSetInternal(SqlConnection connection, string spName, bool includeReturnValueParameter)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            if (spName == null || spName.Length == 0) throw new ArgumentNullException("spName");

            string hashKey = connection.ConnectionString + ":" + spName + (includeReturnValueParameter ? ":include ReturnValue Parameter" : "");

            SqlParameter[] cachedParameters;

            cachedParameters = paramCache[hashKey] as SqlParameter[];
            if (cachedParameters == null)
            {
                SqlParameter[] spParameters = DiscoverSpParameterSet(connection, spName, includeReturnValueParameter);
                paramCache[hashKey] = spParameters;
                cachedParameters = spParameters;
            }

            return CloneParameters(cachedParameters);
        }

        #endregion Parameter Discovery Functions

    }
}
复制代码

其中的难点就是获取每个好友分组下的好友,需要在程序中做些处理。比如你的DataSet结果是这样的。

需要做的业务处理就是,将第三个表中的每条好友信息对应到相应的好友分组中。(里面的字段是视图字段,非单张表)

至于数据灵活,肯定是只获取当前登录的用户好友,好友分组,群组等信息,所以,参数有一个用户id就可以了。(当然,是否合法登录的需要验证一下,登录逻辑就不介绍了。)

 

下面就是写对应接口了,打开UI项目,项目用的是MVC,新建 LayimApiController。代码如下:

复制代码
        [HttpGet]
        [ActionName("base")]
        /// <summary>
        /// 获取基本列表 layimapi/base
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public JsonResult GetBaseList(int userid)
        {
            var result = LayimUserBLL.Instance.GetChatRoomBaseInfo(userid);
            return Json(result, JsonRequestBehavior.AllowGet);
        }

        [HttpGet]
        [ActionName("member")]
        /// <summary>
        /// 获取群组员信息
        /// </summary>
        /// <param name="groupid"></param>
        /// <returns></returns>
        public JsonResult GetMembersList(int id)
        {
            var result = LayimUserBLL.Instance.GetGroupMembers(id);
            return Json(result, JsonRequestBehavior.AllowGet);

        }
复制代码
GetChatRoomBaseInfo 核心代码如下:(其中就做了业务处理,将好友对应到好友分组中)
复制代码
                //当前用户的信息
                var rowMine = ds.Tables[0].Rows[0];
                //用户组信息
                var rowFriendDetails = ds.Tables[2].Rows.Cast<DataRow>().Select(x => new GroupUserEntity
                {
                    id = x["uid"].ToInt(),
                    avatar = x["avatar"].ToString(),
                    groupid = x["gid"].ToInt(),
                    remarkname = x["remarkname"].ToString(),
                    username = x["nickname"].ToString(),
                    sign = x["sign"].ToString(),
                    status = ""
                });
                //用户组信息,执行分组
                var friend = ds.Tables[1].Rows.Cast<DataRow>().Select(x => new FriendGroupEntity
                {
                    id = x["id"].ToInt(),
                    groupname = x["name"].ToString(),
                    online = 0,
                    list = rowFriendDetails.Where(f => f.groupid == x["id"].ToInt()) //根据groupid过滤
                });
                //群组信息
                var group = ds.Tables[3].Rows.Cast<DataRow>().Select(x => new GroupEntity
                {
                    id = x["id"].ToInt(),
                    groupname = x["name"].ToString(),
                    avatar = x["avatar"].ToString(),
                    groupdesc = x["groupdesc"].ToString()
                });
         //最后这一部分很清晰了,mine,friend,group和layim中的接口定义字段一样
                BaseListResult result = new BaseListResult
                {
                    mine = new UserEntity
                    {
                        id = rowMine["id"].ToInt(),
                        avatar = rowMine["avatar"].ToString(),
                        sign = rowMine["sign"].ToString(),
                        username = rowMine["nickname"].ToString(),
                        status = "online"
                    },
                    friend = friend,
                    group = group
                };
                return result;
复制代码
接口写完了,我们只要将demo中的url改一下就可以了。


哦了,到这里已经接近尾声了。我们看一下效果吧。先看接口返回数据:

  其中remarkname,是我自己加的字段,就是为了实现类似好友备注的功能。这个数据已经符合layim接口数据了,看一下界面效果。


数据格式对了,毋庸置疑界面肯定是正确的啦。可以看到我暂时没有群组,不过不用担心,我们从数据库里加几条就可以了,刷新一下页面



  随着最后一张图上传完毕,本篇就接近尾声了,总结一下。本篇内容包括以下几点

  1.json格式解析
  2.建立业务模型
  3.数据库基础知识
  4.MVC的一些使用

  慢慢一步一步来是不是就感觉好很多呢,对我的博客有任何建议或者意见或者有问题的同学请在评论区留言,我看到了都会为大家一一解答。有些地方讲解的可能不太清晰,请包涵。

      下篇预告【初级】ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(二) 之 ChatServer搭建,连接服务器,以及注意事项。

 

   想要学习的小伙伴,可以关注我的博客哦,我的QQ:645857874,Email:fanpan26@126.com

GitHub:https://github.com/fanpan26/LayIM_NetClient/

ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据 - 丶Pz - 博客园

mikel阅读(626)

来源: ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据 – 丶Pz – 博客园

最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前端组件配合后台完成即时聊天等功能。当然用到的技术就是ASP.NET SingalR框架。本人不会css和前端的东西,只会少量的js,所以前端的代码不做介绍,喜欢前端的同学可以自行研究,闲言少叙,书归正传。

我们先看一下layim的效果,看起来还是挺友好的,界面也不错。不过,我做了些调整,具体其他细节可以自己完善。

界面看完了,那么看一下数据。demo里做的也不错,ajax也封装好了,那么我们就直接对照着他的demo看看吧。数据分别有:friend.json,group.json,groups.json,chatlog.json,他们的数据格式如下:(仅仅展示friend.json,其他类似)

 

复制代码
{
    "status": 1,
    "msg": "ok",
    "data": [
        {
            "name": "销售部",
            "nums": 36,
            "id": 1,
            "item": [
                {
                    "id": "100001",
                    "name": "郭敬明",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100002",
                    "name": "作家崔成浩",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "1000022",
                    "name": "韩寒",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "10000222",
                    "name": "范爷",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100002222",
                    "name": "小马哥",
                    "face":  "/images/default.jpg"
                }
            ]
        },
        {
            "name": "大学同窗",
            "nums": 16,
            "id": 2,
            "item": [
                {
                    "id": "1000033",
                    "name": "苏醒",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "10000333",
                    "name": "马云",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100003",
                    "name": "鬼脚七",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100004",
                    "name": "谢楠",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100005",
                    "name": "徐峥",
                    "face":  "/images/default.jpg"
                }
            ]
        },
        {
            "name": "H+后台主题",
            "nums": 38,
            "id": 3,
            "item": [
                {
                    "id": "100006",
                    "name": "柏雪近在它香",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100007",
                    "name": "罗昌平",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100008",
                    "name": "Crystal影子",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100009",
                    "name": "艺小想",
                    "face": "/images/default.jpg"
                },
                {
                    "id": "100010",
                    "name": "天猫",
                    "face":  "/images/default.jpg"
                },
                {
                    "id": "100011",
                    "name": "张泉灵",
                    "face":  "/images/default.jpg"
                }
            ]
        }
    ]
}
复制代码

数据格式有了,那么我们在后台简单建几个Model。 CS的是 CustomService缩写,命名。。。。。就留个槽点吧

复制代码
 public class CSGroupResult
    {
        public int id { get; set; }
        public string name { get; set; }
        public int nums { get { return item == null ? 0 : item.Count; } }
        public List<CSBaseModel> item { get; set; }
    }
    
   public class CSResult
    {
        public int status { get; set; }
        public string msg { get; set; }
        public object data { get; set; }
    }

 public class CSBaseModel
    {
        public int id { get; set; }
        public string name { get; set; }
      //  public string time { get; set; }
        public string face { get; set; }
    }

    public class CSFriend : CSBaseModel { }
    public class CSGroup : CSBaseModel { }
    public class CSGroups : CSBaseModel { }
复制代码

好的,很简单的几个Model建好了,就是根据layim里的json格式建的,这样,输出结果可以直接符合layim的接口JSON要求。下面,构建几个假数据,本来打算用数据库的,后来想想,只是写一下思路吧,就不在用数据库了。其实不仅是数据库,只要是可存储的东西都可以来放这些用户,聊天,群组等信息。

复制代码
  public class DBHelper
    {
        /// <summary>
        /// 获取好友列表
        /// </summary>
        /// <returns></returns>
        public static CSResult GetFriends()
        {
            var friends = new List<CSBaseModel>();
            for (int i = 0; i < 9; i++) {
                friends.Add(new CSFriend { id = i, name = "好友" + i, face = "/photos/00" + i + ".jpg" });
            }

            var friendGroup = new List<CSGroupResult>();

            friendGroup.Add(new CSGroupResult { id = 1, item = friends, name = "我的分组一" });
            friendGroup.Add(new CSGroupResult { id = 2, item = friends, name = "我的分组二" });
            friendGroup.Add(new CSGroupResult { id = 3, item = friends, name = "我的分组三" });

            CSResult result = new CSResult
            {
                msg = "ok",
                status = 1,
                data = friendGroup
            };
            return result;
        }

        /// <summary>
        /// 获取分组列表
        /// </summary>
        /// <returns></returns>
        public static CSResult GetGroup()
        {
            var groups = new List<CSBaseModel>();
            for (int i = 0; i < 3; i++)
            {
                groups.Add(new CSGroup { id = i, name = "分组" + i, face = "/photos/00" + i + ".jpg" });
            }

            var friendGroup = new List<CSGroupResult>();

            friendGroup.Add(new CSGroupResult { id = 1, item = groups, name = "分组名称一" });
            friendGroup.Add(new CSGroupResult { id = 2, item = groups, name = "分组名称二" });
            friendGroup.Add(new CSGroupResult { id = 3, item = groups, name = "分组名称三"});

            CSResult result = new CSResult
            {
                msg = "ok",
                status = 1,
                data = friendGroup
            };
            return result;
        }

    }
复制代码

我后台使用的是ASP.NET MVC。新建Controller,就叫CustomServiceController吧,写好方法,配置路由,这里就不多介绍了。

复制代码
  /// <summary>
        /// 获取数据
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public JsonResult GetData(string type)
        {
            var result = DBHelper.GetResult(type);
            return Json(result, JsonRequestBehavior.AllowGet);
        }
复制代码

 

那么到这里,我们测试一下方法返回结果(路由配置的是getdata),浏览器里输入:http://localhost:20237/getdata?type=friend F12看一下结果:

好了,结果出来了,怎么和layim结合呢?很简单,看一下,layim.js代码,找到config.api,将接口改成自己的服务器接口就可以了(如果想做完整插件服务的话,这里可能有跨域问题吧)

复制代码
 var config = {
        msgurl: 'mailbox.html?msg=',
        chatlogurl: 'mailbox.html?user=',
        aniTime: 200,
        right: -232,
        api: {
            friend: '/getdata?type=friend', //好友列表接口。    将这个接口改为服务器的接口就可以了,下面两个暂时没做,后续会补上,同理
            group: '/getdata?type=group', //群组列表接口
            chatlog: '/scripts/layim/chatlog.json', //聊天记录接口
            groups: '/scripts/layim/groups.json', //群组成员接口
            sendurl: '' //发送消息接口
        },
        user: { //当前用户信息
            name: '游客',
            face: '/images/default.jpg'
        },
复制代码

到这里,数据整合部分就完成了,是不是很简单啊,想看运行效果?其实刚开始的图1,和图3就是运行效果了。要实现图二的话,只要重复上面的步骤,将接口方法写好,然后配置上接口路径就可以了。

补充:重新调整了一下代码,考虑到有的同学用的不是MVC,所以采用ashx的方式实现数据获取,同时,代码也调整了一下,DBHelper增加逻辑判断方法 GetResult(string type)

复制代码
        /// <summary>
        /// 在封装一层业务,根据type返回不同的结果
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static CSResult GetResult(string type)
        {
            CSResult result = null;
            switch (type)
            {
                case "friend":
                    result = DBHelper.GetFriends();
                    break;
                case "group":
                    result = DBHelper.GetGroup();
                    break;
                case "log":
                    result = DBHelper.GetChatLog();
                    break;
                case "groups":
                    result = DBHelper.GetGroupMember();
                    break;
                default:
                    result = new CSResult { status = 0, data = null, msg = "无效的请求类型" };
                    break;
            }
            return result;
        }
复制代码

那么controller和ashx里面调用方法就简单了:

复制代码
        /// <summary>
        /// Controller中获取数据
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public JsonResult GetData(string type)
        {
            var result = DBHelper.GetResult(type);
            return Json(result, JsonRequestBehavior.AllowGet);
        }

//ashx获取数据方法
  public void ProcessRequest(HttpContext context)
        {
            //这里的类型要改成json,否则,前端获取的数据需要调用JSON.parse方法将文本转成json,
            //为了不用改变前端代码,这里将text/plain改为application/json
            context.Response.ContentType = "application/json";
            //接收type 参数
            string type = context.Request.QueryString["type"] ?? context.Request.QueryString["type"].ToString();
            //调用业务处理方法获取数据结果
            var result = DBHelper.GetResult(type);
            //序列化
            var json = ScriptSerialize(result);
            context.Response.Write(json);
        }

        /// <summary>
        /// 序列化方法,(暂时放在这里)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        private string ScriptSerialize<T>(T t)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            return serializer.Serialize(t);
        }
复制代码

然后我们将layim.js里的获取好友列表的接口地址改成 /getdata.ashx?type=friend 是不是一样就可以调用了呢。(截图略)

再补充:有人问怎么用webservice做接口,我尝试了一下,虽然比较麻烦,但最终还是调通了,具体webservice不多做介绍了,下面看详细修改的代码,首先,为了保证程序变化不大,尽量少修改,我加了一些参数判断,在layim.js

红框地方就是代码修改过的地方,然后在调用config.json里面,由于webservice是post请求,所以参数就不能那么用了,改为 “{“type”:”friend”}”形式

最后,webservice后台方法与ashx一致:

复制代码
  public class getdataWebService : System.Web.Services.WebService
    {

        [WebMethod]
        public string GetResult(string type)
        {
            //调用业务处理方法获取数据结果
            var result = DBHelper.GetResult(type);
            var json = MessageUtilcs.ScriptSerialize(result);
            return json;
        }
    }
复制代码

具体呢,到这里就终于把webservice调通了,不过需要改一些layim的js来配合。这儿有点麻烦。不过知道其中的原理,人是活的,代码同样可以写活。OK,最后的补充了。运行试试吧,是不是现在支持 MVC接口,ashx接口和webservice接口了呢,如果有机会再把webapi接口加上~~

第一篇就先到这里吧,还没有涉及到SingalR的东西,下一步,我们就搭建SingalR环境,实现聊天功能。 写的有点粗糙哦,希望你喜欢。PS:想要代码的同学留下邮箱或者等我的博客写完了,放到github上,会告诉大家地址的哦。

八个Docker的真实应用场景 - DockOne.io

mikel阅读(849)

来源: 八个Docker的真实应用场景 – DockOne.io

【编者的话】Flux 7介绍了常用的8个Docker的真实使用场景,分别是简化配置、代码流水线管理、提高开发效率、隔离应用、整合服务器、调试能力、多租户环境、快速部署。我们一直在谈Docker,Docker怎么使用,在怎么样的场合下使用?也许本文可以帮到你。有需要交流的地方,可以通过评论与我们交流。

docker-use-cases.png

几周前我们参加了DockerCon ,Dockercon是首个以Docker为中心的技术大会。它面向开发者以及对在Docker开放平台上构建、交付、运行分布式应用感兴趣的从业者,不论这些开放平台是运行于自用笔记本上或者是数据中心的虚拟机上。我们参加了这次大会,Flux7是Docker基础的系统集成合作伙伴,同时也是演讲嘉宾。

我们的CEO Aater Suleman和我们的一位客户一同进行了演讲。虽然DockerCon大会十分有趣,但我觉得大会太关注Docker的具体细节,而忽略了Docker的使用场景。所以,在这篇文章中,我想介绍并分享一些Docker的实际应用案例。

在我们讨论Docker的使用场景之前,先来看看Docker这个工具有什么特别的地方吧。

Docker提供了轻量级的虚拟化,它几乎没有任何额外开销,这个特性非常酷。

首先你在享有Docker带来的虚拟化能力的时候无需担心它带来的额外开销。其次,相比于虚拟机,你可以在同一台机器上创建更多数量的容器。

Docker的另外一个优点是容器的启动与停止都能在几秒中内完成。Docker公司的创始人 Solomon Hykes曾经介绍过Docker在单纯的LXC之上做了哪些事情,你可以去看看。

下面是我总结的一些Docker的使用场景,它为你展示了如何借助Docker的优势,在低开销的情况下,打造一个一致性的环境。

1. 简化配置

这是Docker公司宣传的Docker的主要使用场景。虚拟机的最大好处是能在你的硬件设施上运行各种配置不一样的平台(软件、系统),Docker在降低额外开销的情况下提供了同样的功能。它能让你将运行环境和配置放在代码中然后部署,同一个Docker的配置可以在不同的环境中使用,这样就降低了硬件要求和应用环境之间耦合度。

2. 代码流水线(Code Pipeline)管理

前一个场景对于管理代码的流水线起到了很大的帮助。代码从开发者的机器到最终在生产环境上的部署,需要经过很多的中间环境。而每一个中间环境都有自己微小的差别,Docker给应用提供了一个从开发到上线均一致的环境,让代码的流水线变得简单不少。

3. 提高开发效率

这就带来了一些额外的好处:Docker能提升开发者的开发效率。如果你想看一个详细一点的例子,可以参考Aater在DevOpsDays Austin 2014 大会或者是DockerCon上的演讲。

不同的开发环境中,我们都想把两件事做好。一是我们想让开发环境尽量贴近生产环境,二是我们想快速搭建开发环境。

理想状态中,要达到第一个目标,我们需要将每一个服务都跑在独立的虚拟机中以便监控生产环境中服务的运行状态。然而,我们却不想每次都需要网络连接,每次重新编译的时候远程连接上去特别麻烦。这就是Docker做的特别好的地方,开发环境的机器通常内存比较小,之前使用虚拟的时候,我们经常需要为开发环境的机器加内存,而现在Docker可以轻易的让几十个服务在Docker中跑起来。

4. 隔离应用

有很多种原因会让你选择在一个机器上运行不同的应用,比如之前提到的提高开发效率的场景等。

我们经常需要考虑两点,一是因为要降低成本而进行服务器整合,二是将一个整体式的应用拆分成松耦合的单个服务(译者注:微服务架构)。如果你想了解为什么松耦合的应用这么重要,请参考Steve Yege的这篇论文,文中将Google和亚马逊做了比较。

5. 整合服务器

正如通过虚拟机来整合多个应用,Docker隔离应用的能力使得Docker可以整合多个服务器以降低成本。由于没有多个操作系统的内存占用,以及能在多个实例之间共享没有使用的内存,Docker可以比虚拟机提供更好的服务器整合解决方案。

6. 调试能力

Docker提供了很多的工具,这些工具不一定只是针对容器,但是却适用于容器。它们提供了很多的功能,包括可以为容器设置检查点、设置版本和查看两个容器之间的差别,这些特性可以帮助调试Bug。你可以在《Docker拯救世界》的文章中找到这一点的例证。

7. 多租户环境

另外一个Docker有意思的使用场景是在多租户的应用中,它可以避免关键应用的重写。我们一个特别的关于这个场景的例子是为IoT(译者注:物联网)的应用开发一个快速、易用的多租户环境。这种多租户的基本代码非常复杂,很难处理,重新规划这样一个应用不但消耗时间,也浪费金钱。

使用Docker,可以为每一个租户的应用层的多个实例创建隔离的环境,这不仅简单而且成本低廉,当然这一切得益于Docker环境的启动速度和其高效的diff命令。

你可以在这里了解关于此场景的更多信息。

8. 快速部署

在虚拟机之前,引入新的硬件资源需要消耗几天的时间。虚拟化技术(Virtualization)将这个时间缩短到了分钟级别。而Docker通过为进程仅仅创建一个容器而无需启动一个操作系统,再次将这个过程缩短到了秒级。这正是Google和Facebook都看重的特性。

你可以在数据中心创建销毁资源而无需担心重新启动带来的开销。通常数据中心的资源利用率只有30%,通过使用Docker并进行有效的资源分配可以提高资源的利用率。Vsa

原文链接:8 Ways to Use Docker in the Real World(翻译:钟最龙 审校:李颖杰)

Workerman+LayIM+ThinkPHP5的webIM,即时通讯系统 - ThinkPHP框架

mikel阅读(1560)

提供各种官方和用户发布的代码示例,代码参考,欢迎大家交流学习

来源: Workerman+LayIM+ThinkPHP5的webIM,即时通讯系统 – ThinkPHP框架

一个美观的Workerman+LayIM+ThinkPHP5的webIM即时通讯系统。
3.0版本正在和 layim 官方合作中,已开通线上预览地址:http://ichat.baiyf.com/ 欢迎前来授权获取源码。

这两天看了一下websocket,再加上上一篇文章,整合了一个第三方的webIM系统,那个只是调用接口,然并卵的东西。有人回复说,你那个根本没用,整合一个workerman出来那还差不多。那好吧,workerman就workerman了。早就听说了workerman,但是一直没有去用过,借助这次机会,正好看看是个怎么样的一个东西。当然了我先看了一下websocket通信,写了一篇文章,当然了,我写的不咋地,我引用的那两篇文章写的不错。http://www.cnblogs.com/nickbai/articles/5816689.html想了解websocket的可以看一下。

好了,现在我们开始切人正题吧。
首先先粗略的介绍一下workerman,我本次采用的是GatewayWorker,话说这个是个什么鬼?请看wokerman的官方解释:
GatewayWorker是基于Workerman开发的一套TCP长连接的应用框架, 实现了单发、群发、广播等接口,内置了mySQL类库, GatewayWorker分为Gateway进程和Worker进程,天然支持分布式部署,能够支持庞大的连接数(百万甚至千万连接级别的应用)。 可用于开发IM聊天应用、移动通讯、游戏后台、物联网、智能家居后台等等。

是不是很6的东西,是不是!

再来介绍一下LayIM,相信很多人都用过layer,那个是谁用谁知道,美观且功能强大。作者 贤心 之前在阿里任职,现在待业在家专心搞layerUI。为什么说这个呢?以为我本次用的LayIM也是出自贤心大神,重点强调这个是因为,layerIM并不开源!所以我的项目你们拿到本地并不能运行。一个良好的开源项目想要运行下去,需要大家共同的努力,这里就当我给贤心大神做个广告,http://layim.layui.com/想用layerIM的话,去前面这个地址了解详情吧。我的目录结构如下,你们拿到授权了可以这么放:

说一下我这个项目的进度,目前只实现了单对单的聊天,整体架构已经整合完成,后面就是根据需求按照手册填空了。相信聪明的你一定会完成的,本例子是基于windows平台的,后面可能会讲所有的功能补全(看心情,哈哈)。

给大家看一下效果吧:

重点来了,说一下项目怎么配置:
1、去我的github上下载整合好的demo v1.0版
https://github.com/nick-bai/laychat,当然你说,你这个太垃圾了,你可以自己去下载workerman自己去做。
2、配置好你的项目,绑定虚拟域名,保证可以访问。
3、vendor\Workerman下面 的start_for_win.bat看到如下 的页面:

表示你workerman启动成功!这里我没有用workerman建立HTTP服务器。
4、最关键的一步( 这部是要钱的 ¥ 100 )支援一下开源项目吧,获取layerIM的授权文件,放入static文件夹下,目录可以参考我给出的。
5、访问你的tp项目,登录,

目前只有这三个账号可以登录,记住:我是根据session来标识登录状态的,请打开两个浏览器去模拟两个账号聊天,否则不行。
测试马云给纸飞机发信息:

我的github项目地址:https://github.com/nick-bai/laychat觉得对你有用的话,不要吝啬你的小星。

Asp.NET MVC 使用 SignalR 实现推送功能二(Hubs 在线聊天室 获取保存用户信息) - 果冻布丁喜之郎 - 博客园

mikel阅读(733)

来源: Asp.NET MVC 使用 SignalR 实现推送功能二(Hubs 在线聊天室 获取保存用户信息) – 果冻布丁喜之郎 – 博客园

简单介绍

关于SignalR的简单实用 请参考 Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室)

在上一篇中,我们只是介绍了简单的消息推送,今天我们来修改一下,实现保存消息,历史消息和用户在线

由于,我这是在一个项目(【无私分享:从入门到精通ASP.NET MVC】从0开始,一起搭框架、做项目 目录索引)的基础上做的,所以使用到的一些借口和数据表,不详细解析,只是介绍一下思路和实现方式,供大家参考

 

用户登录注册信息

当用户登录之后,我们注册一下用户的信息,我们在ChatHub中 新建一个方法 Register(用户帐号,用户密码)

前台js调用这个方法实现用户注册

1 $.connection.hub.start().done(function () {            
2             chat.server.register('用户帐号', '用户密码');           
3         });

Register方法的实现:

复制代码
 1 /// <summary>
 2         /// 用户登录注册信息
 3         /// </summary>
 4         /// <param name="id"></param>
 5         public void Register(string account,string password)
 6         {
 7             try
 8             {
 9                 //获取用户信息
10                 var User = UserManage.Get(p => p.ACCOUNT == account);
11                 if (User != null && User.PASSWORD == password)
12                 {
13                     //更新在线状态
14                     var UserOnline = UserOnlineManage.LoadListAll(p => p.FK_UserId == User.ID).FirstOrDefault();
15                     UserOnline.ConnectId = Context.ConnectionId;
16                     UserOnline.OnlineDate = DateTime.Now;
17                     UserOnline.IsOnline = true;
18                     UserOnline.UserIP = Utils.GetIP();
19                     UserOnlineManage.Update(UserOnline);
20                     
21 
22                     //超级管理员
23                     if (User.ID == ClsDic.DicRole["超级管理员"])
24                     {
25                         //通知用户上线
26                         Clients.All.UserLoginNotice("超级管理员:" + User.NAME + " 上线了!");
27 
28                     }
29                     else
30                     {
31                         //获取用户一级部门信息
32                         var Depart = GetUserDepart(User.DPTID);
33                         if (Depart != null && !string.IsNullOrEmpty(Depart.ID))
34                         {
35                             //添加用户到部门群组 Groups.Add(用户连接ID,群组)
36                             Groups.Add(Context.ConnectionId, Depart.ID);
37                             //通知用户上线
38                             Clients.All.UserLoginNotice(Depart.NAME + " - " + CodeManage.Get(m => m.CODEVALUE == User.LEVELS && m.CODETYPE == "ZW").NAMETEXT + ":" + User.NAME + " 上线了!");
39                             
40 
41                         }
42                     }
43                     //刷新用户通讯录
44                     Clients.All.ContactsNotice(JsonConverter.Serialize(UserOnline));                    
45                 }
46             }
47             catch(Exception ex)
48             {
49                 Clients.Client(Context.ConnectionId).UserLoginNotice("出错了:" + ex.Message);
50                 throw ex.InnerException;
51             }
52             
53         }
复制代码

用户上线通知,大家可以在对话框内已系统消息的方式提示,我这里是一个toastr插件的提示

1 //用户上线通知
2         chat.client.UserLoginNotice = function (message) {
3             if ($("#LoginNotice").prop("checked")) { toasSuccess.message_t(message); }
4         };

这里面有个判断,如果用户允许提醒,就提示,如果不允许,就不提示,就是个checkbox

当用户登录后,刷新其它用户通讯录用户的在线状态,离线用户排到底部,并且如果用户离线,点击用户的时候,提示用户离线发送邮件还是离线消息

复制代码
 1 //通讯录用户上下线
 2         chat.client.ContactsNotice = function (message) {
 3             var data = eval("(" + message + ")");
 4             if (!data.IsOnline) {
 5                 var current = $("#charUser-" + data.FK_UserId).addClass("offline");
 6                 var parentId = current.parent().parent().prop("id");
 7                 current.remove().appendTo($("#" + parentId + " .panel-body"));
 8             }else
 9             {
10                 var current = $("#charUser-" + data.FK_UserId).removeClass("offline").attr("data-connectid", data.ConnectId);
11                 var parentId = current.parent().parent().prop("id");
12                 current.insertBefore($("#" + parentId + " .panel-body .offline").eq(0));
13             }
14             $(".panel-body .chat-user").click(function () {
15                 if ($(this).hasClass("offline")) {
16                     var MailId = $(this).attr("data-Email");
17                     var ConnectId = $(this).attr("data-connectid");
18                     var UserName = $(this).attr("data-username");
19                     swal({ title: "用户离线", text: "当前用户不在线,是否发送邮件?", type: "warning", showCancelButton: true, confirmButtonColor: "#DD6B55", confirmButtonText: "发送邮件", cancelButtonText: "发送离线消息", closeOnConfirm: false, closeOnCancel: false }, function (isConfirm) { if (isConfirm) { sweetAlert.close(); document.getElementById(MailId).click(); } else { sweetAlert.close(); ChatPerson(ConnectId, UserName); } });
20                 }
21                 else {
22                     ChatPerson($(this).attr("data-connectid"), $(this).attr("data-username"));
23                 }
24             });
25 
26         };
复制代码

 

在线:

 

离线:

 

用户离线

我们重写OnDisconnected方法,当用户离线时,更新用户状态 同时刷新其它用户通讯录用户在线状态

复制代码
 1  //使用者离线
 2         public override Task OnDisconnected(bool stopCalled)
 3         {
 4             //更新在线状态
 5             var UserOnline = UserOnlineManage.LoadListAll(p => p.ConnectId == Context.ConnectionId).FirstOrDefault();
 6             UserOnline.ConnectId = Context.ConnectionId;
 7             UserOnline.OfflineDate = DateTime.Now;
 8             UserOnline.IsOnline = false;
 9             UserOnlineManage.Update(UserOnline);
10 
11             //获取用户信息
12             var User = UserManage.Get(p => p.ID == UserOnline.FK_UserId);
13 
14             Clients.All.UserLogOutNotice(User.NAME + ":离线了!");
15             //刷新用户通讯录
16             Clients.All.ContactsNotice(JsonConverter.Serialize(UserOnline));
17 
18             return base.OnDisconnected(true);
19         }
复制代码

 

前台离线通知

1  //用户离线通知
2         chat.client.UserLogOutNotice = function (message) {
3             if ($("#LogOutNotice").prop("checked")) { toasInfo.message_t(message); }
4         };

 

获取历史消息

我是在用户登录的时候获取用户消息的,大家可以放到其它逻辑中

Register方法添加用户历史消息

复制代码
 1 /// <summary>
 2         /// 用户登录注册信息
 3         /// </summary>
 4         /// <param name="id"></param>
 5         public void Register(string account,string password)
 6         {
 7             try
 8             {
 9                 //获取用户信息
10                 var User = UserManage.Get(p => p.ACCOUNT == account);
11                 if (User != null && User.PASSWORD == password)
12                 {
13                     //更新在线状态
14                     var UserOnline = UserOnlineManage.LoadListAll(p => p.FK_UserId == User.ID).FirstOrDefault();
15                     UserOnline.ConnectId = Context.ConnectionId;
16                     UserOnline.OnlineDate = DateTime.Now;
17                     UserOnline.IsOnline = true;
18                     UserOnline.UserIP = Utils.GetIP();
19                     UserOnlineManage.Update(UserOnline);
20 
21                     //获取历史消息
22                     int days = Int32.Parse(System.Configuration.ConfigurationManager.AppSettings["HistoryDays"]);
23                     DateTime dtHistory = DateTime.Now.AddDays(-days);
24                     var ChatMessageList = ChatMessageManage.LoadAll(p => p.MessageDate > dtHistory);                    
25 
26                     //超级管理员
27                     if (User.ID == ClsDic.DicRole["超级管理员"])
28                     {
29                         //通知用户上线
30                         Clients.All.UserLoginNotice("超级管理员:" + User.NAME + " 上线了!");
31 
32                         var HistoryMessage = ChatMessageList.OrderBy(p=>p.MessageDate).ToList().Select(p => new
33                         {
34                             UserName = UserManage.Get(m => m.ID == p.FromUser).NAME,
35                             UserFace = string.IsNullOrEmpty(UserManage.Get(m => m.ID == p.FromUser).FACE_IMG) ? "/Pro/Project/User_Default_Avatat?name=" + UserManage.Get(m => m.ID == p.FromUser).NAME.Substring(0, 1) : UserManage.Get(m => m.ID == p.FromUser).FACE_IMG,
36                             MessageType=GetMessageType(p.MessageType),
37                             p.FromUser,
38                             p.MessageContent,
39                             MessageDate = p.MessageDate.GetDateTimeFormats('D')[1].ToString() + " - " + p.MessageDate.ToString("HH:mm:ss"),
40                             ConnectId = UserOnlineManage.LoadListAll(m => m.SYS_USER.ID == p.FromUser).FirstOrDefault().ConnectId
41                         }).ToList();
42 
43                         //推送历史消息
44                         Clients.Client(Context.ConnectionId).addHistoryMessageToPage(JsonConverter.Serialize(HistoryMessage));
45                     }
46                     else
47                     {
48                         //获取用户一级部门信息
49                         var Depart = GetUserDepart(User.DPTID);
50                         if (Depart != null && !string.IsNullOrEmpty(Depart.ID))
51                         {
52                             //添加用户到部门群组 Groups.Add(用户连接ID,群组)
53                             Groups.Add(Context.ConnectionId, Depart.ID);
54                             //通知用户上线
55                             Clients.All.UserLoginNotice(Depart.NAME + " - " + CodeManage.Get(m => m.CODEVALUE == User.LEVELS && m.CODETYPE == "ZW").NAMETEXT + ":" + User.NAME + " 上线了!");
56                             //用户历史消息
57                             int typeOfpublic = Common.Enums.ClsDic.DicMessageType["广播"];
58                             int typeOfgroup = Common.Enums.ClsDic.DicMessageType["群组"];
59                             int typeOfprivate = Common.Enums.ClsDic.DicMessageType["私聊"];
60                             var HistoryMessage = ChatMessageList.Where(p => p.MessageType == typeOfpublic || (p.MessageType == typeOfgroup && p.ToGroup == Depart.ID) || (p.MessageType == typeOfprivate && p.ToGroup == User.ID.ToString())).OrderBy(p => p.MessageDate).ToList().Select(p => new
61                             {
62                                 UserName = UserManage.Get(m => m.ID == p.FromUser).NAME,
63                                 UserFace = string.IsNullOrEmpty(UserManage.Get(m => m.ID == p.FromUser).FACE_IMG) ? "/Pro/Project/User_Default_Avatat?name=" + UserManage.Get(m => m.ID == p.FromUser).NAME.Substring(0, 1) : UserManage.Get(m => m.ID == p.FromUser).FACE_IMG,
64                                 MessageType = GetMessageType(p.MessageType),
65                                 p.FromUser,
66                                 p.MessageContent,
67                                 MessageDate = p.MessageDate.GetDateTimeFormats('D')[1].ToString() + " - " + p.MessageDate.ToString("HH:mm:ss"),
68                                 ConnectId = UserOnlineManage.LoadListAll(m => m.SYS_USER.ID == p.FromUser).FirstOrDefault().ConnectId
69                             }).ToList();
70                            
71                             //推送历史消息
72                             Clients.Client(Context.ConnectionId).addHistoryMessageToPage(JsonConverter.Serialize(HistoryMessage));
73 
74                         }
75                     }
76                     //刷新用户通讯录
77                     Clients.All.ContactsNotice(JsonConverter.Serialize(UserOnline));                    
78                 }
79             }
80             catch(Exception ex)
81             {
82                 Clients.Client(Context.ConnectionId).UserLoginNotice("出错了:" + ex.Message);
83                 throw ex.InnerException;
84             }
85             
86         }
复制代码

 

前台:

复制代码
 1 //接收历史信息
 2         chat.client.addHistoryMessageToPage = function (message) {
 3             var data = eval("(" + message + ")");
 4             var html = '';
 5             for(var i=0;i<data.length;i++)
 6             {
 7                //处理消息
 8             }                        
 9             $(html).appendTo(".chat-discussion");
10             $('<div class=" col-xs-12 m-t-sm m-b-sm text-center sysmessage"> — 以上是历史消息 — </div>').appendTo(".chat-discussion");                        
11         };
复制代码

 

发送广播、组播消息

复制代码
 1 /// <summary>
 2         /// 发送消息(广播、组播)
 3         /// </summary>
 4         /// <param name="message">发送的消息</param>
 5         /// <param name="message">发送的群组</param>
 6         public void Send(string message,string groupId)
 7         {
 8             try 
 9             {
10                 //消息用户主体
11                 var UserOnline = UserOnlineManage.LoadListAll(p => p.ConnectId == Context.ConnectionId).FirstOrDefault();
12                 
13                 //广播
14                 if(string.IsNullOrEmpty(groupId))
15                 {
16                     //保存消息
17                     ChatMessageManage.Save(new Domain.SYS_CHATMESSAGE() { FromUser = UserOnline.FK_UserId, MessageType = Common.Enums.ClsDic.DicMessageType["广播"], MessageContent = message, MessageDate = DateTime.Now, MessageIP = Utils.GetIP() });
18                     //返回消息实体
19                     var Message = new Message() { ConnectId = UserOnline.ConnectId, UserName = UserOnline.SYS_USER.NAME, UserFace = string.IsNullOrEmpty(UserOnline.SYS_USER.FACE_IMG) ? "/Pro/Project/User_Default_Avatat?name=" + UserOnline.SYS_USER.NAME.Substring(0, 1) : UserOnline.SYS_USER.FACE_IMG, MessageDate = DateTime.Now.GetDateTimeFormats('D')[1].ToString() + " - " + DateTime.Now.ToString("HH:mm:ss"), MessageContent = message, MessageType = "public", UserId = UserOnline.SYS_USER.ID };
20 
21                     //推送消息
22                     Clients.All.addNewMessageToPage(JsonConverter.Serialize(Message));
23                 }
24                 //组播
25                 else
26                 {
27                     //保存消息
28                     ChatMessageManage.Save(new Domain.SYS_CHATMESSAGE() { FromUser = UserOnline.FK_UserId, MessageType = Common.Enums.ClsDic.DicMessageType["群组"], MessageContent = message, MessageDate = DateTime.Now, MessageIP = Utils.GetIP(), ToGroup = groupId });
29                     //返回消息实体
30                     var Message = new Message() { ConnectId = UserOnline.ConnectId, UserName = UserOnline.SYS_USER.NAME, UserFace = string.IsNullOrEmpty(UserOnline.SYS_USER.FACE_IMG) ? "/Pro/Project/User_Default_Avatat?name=" + UserOnline.SYS_USER.NAME.Substring(0, 1) : UserOnline.SYS_USER.FACE_IMG, MessageDate = DateTime.Now.GetDateTimeFormats('D')[1].ToString() + " - " + DateTime.Now.ToString("HH:mm:ss"), MessageContent = message, MessageType = "group", UserId = UserOnline.SYS_USER.ID };
31 
32                     //推送消息
33                     Clients.Group(groupId).addNewMessageToPage(JsonConverter.Serialize(Message));
34                     //如果用户不在群组中则单独推送消息给用户
35                     var Depart = GetUserDepart(UserOnline.SYS_USER.DPTID);
36                     if(Depart==null)
37                     {
38                         //推送给用户
39                         Clients.Client(Context.ConnectionId).addNewMessageToPage(JsonConverter.Serialize(Message));
40                     }
41                     else if(Depart.ID!=groupId)
42                     {
43                         //推送给用户
44                         Clients.Client(Context.ConnectionId).addNewMessageToPage(JsonConverter.Serialize(Message));
45                     }
46                 }                               
47             }
48             catch(Exception ex)
49             {
50                 //推送系统消息
51                 Clients.Client(Context.ConnectionId).addSysMessageToPage("系统消息:消息发送失败,请稍后再试!");
52                 throw ex.InnerException;
53             }            
54         }
复制代码

 

发送私聊消息

复制代码
 1 /// <summary>
 2         /// 发送给指定用户(单播)
 3         /// </summary>
 4         /// <param name="clientId">接收用户的连接ID</param>
 5         /// <param name="message">发送的消息</param>
 6         public void SendSingle(string clientId, string message)
 7         {
 8             try
 9             {
10                 //接收用户连接为空
11                 if (string.IsNullOrEmpty(clientId))
12                 {
13                     //推送系统消息
14                     Clients.Client(Context.ConnectionId).addSysMessageToPage("系统消息:用户不存在!");
15                 }
16                 else
17                 {
18                     //消息用户主体
19                     var UserOnline = UserOnlineManage.LoadListAll(p => p.ConnectId == Context.ConnectionId).FirstOrDefault();
20                     //接收消息用户主体
21                     var ReceiveUser = UserOnlineManage.LoadListAll(p => p.ConnectId == clientId).FirstOrDefault();
22                     if (ReceiveUser == null)
23                     {
24                         //推送系统消息
25                         Clients.Client(Context.ConnectionId).addSysMessageToPage("系统消息:用户不存在!");
26                     }
27                     else
28                     {
29                         //保存消息
30                         ChatMessageManage.Save(new Domain.SYS_CHATMESSAGE() { FromUser = UserOnline.FK_UserId, MessageType = Common.Enums.ClsDic.DicMessageType["私聊"], MessageContent = message, MessageDate = DateTime.Now, MessageIP = Utils.GetIP(), ToGroup = UserOnline.SYS_USER.ID.ToString() });
31                         //返回消息实体
32                         var Message = new Message() { ConnectId = UserOnline.ConnectId, UserName = UserOnline.SYS_USER.NAME, UserFace = string.IsNullOrEmpty(UserOnline.SYS_USER.FACE_IMG) ? "/Pro/Project/User_Default_Avatat?name=" + UserOnline.SYS_USER.NAME.Substring(0, 1) : UserOnline.SYS_USER.FACE_IMG, MessageDate = DateTime.Now.GetDateTimeFormats('D')[1].ToString() + " - " + DateTime.Now.ToString("HH:mm:ss"), MessageContent = message, MessageType = "private", UserId = UserOnline.SYS_USER.ID };                                        
33                         if (ReceiveUser.IsOnline)
34                         {
35                             //推送给指定用户
36                             Clients.Client(clientId).addNewMessageToPage(JsonConverter.Serialize(Message));
37                         }
38                         //推送给用户
39                         Clients.Client(Context.ConnectionId).addNewMessageToPage(JsonConverter.Serialize(Message));
40                         
41                     }
42                 }
43             }
44             catch (Exception ex)
45             {
46                 //推送系统消息
47                 Clients.Client(Context.ConnectionId).addSysMessageToPage("系统消息:消息发送失败,请稍后再试!");
48                 throw ex.InnerException;
49             }            
50         }
复制代码

 

前台发送消息:

复制代码
 1 $.connection.hub.start().done(function () {            
 2             chat.server.register('用户帐号', '用户密码');
 3             //文本框回车推送
 4             $("#sendMessage").keyup(function (event) {
 5                 if (event.keyCode == 13 || event.which == 13)
 6                 {
 7                     if ($.trim($(this).val()) == '' || $.trim($(this).val()).length < 1) {
 8                         $(this).val($.trim($(this).val()));
 9                         $(this).focus();
10                         return false;
11                     }
12                     else {
13                         //私聊
14                         if ($.trim($("#person").val()) != '' && $.trim($("#person").val()).length > 1) {
15                             chat.server.sendSingle($("#person").val(), $(this).val());
16                         }
17                         else {
18                             chat.server.send($(this).val(), $("#group").val());
19                         }
20                         $(this).val("").focus();                       
21                     }
22                     
23                 }
24             });
25             
26         });
复制代码

 

前台接收消息:

复制代码
 1 //接收服务器信息
 2         chat.client.addNewMessageToPage = function (message) {            
 3             var data = eval("(" + message + ")");
 4             var html = '';
 5             //处理消息

19             $(html).appendTo(".chat-discussion");
20             $('.chat-discussion').scrollTop($('.chat-discussion')[0].scrollHeight);
21             if(!$("#small-chat-box").hasClass("active"))
22             {
23                 messagecount = messagecount + 1;
24             }
25             if (messagecount > 0)
26             {
27                 $("#small-chat .badge").html(messagecount);
28             }
29         };
30         //接收系统消息
31         chat.client.addSysMessageToPage = function (message) {
32             $('<div class=" col-xs-12 m-t-sm m-b-sm text-center sysmessage">' + message + '</div>').appendTo(".chat-discussion");
33             $('.chat-discussion').scrollTop($('.chat-discussion')[0].scrollHeight);
34         };
复制代码

 

 

离线:

上线:

私聊:

 

组聊:

 

 

 

原创文章 转载请尊重劳动成果 http://yuangang.cnblogs.com