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

mikel阅读(623)

来源: 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阅读(840)

来源: 八个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阅读(1556)

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

来源: 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阅读(731)

来源: 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

未能加载文件或程序集,或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配-坤哥网

mikel阅读(1873)

来源: 未能加载文件或程序集,或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配-坤哥网

我们在开发中经常会出现程序引用不匹配的问题。

下面是遇到的一个具体实例:

InnerException = {“未能加载文件或程序集“log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=1b44e1d426115821”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)”:”log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=1b44e1d426115821″}

项目引用的是 2.0.8.0 版本。

这个实例刚开始压根没找到原因,因为在配置文件中的程序集并没有配置:

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>    
    </assemblyBinding>

原来项目中引用的另外的 dll 引用了 老版本的 log4net。

把项目中的引用的 log4net 改成老版本,但是有其他的引用又引用了新版本的 log4net,真是头大:

log4net:ERROR XmlHierarchyConfigurator: Could not create Appender [KafkaAppender] of type [Cn.Vcredit.ELK.LogAppender.Log4NetForKFKExt.Appender.KafkaAppender, Cn.Vcredit.ELK.LogAppender]. Reported error follows.
System.IO.FileLoadException: 未能加载文件或程序集“log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
文件名:“log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a”
   在 System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMarkHandle stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName, ObjectHandleOnStack type)
   在 System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean loadTypeFromPartialName)
   在 System.Type.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase)
   在 log4net.Util.SystemInfo.GetTypeFromString(Assembly relativeAssembly, String typeName, Boolean throwOnError, Boolean ignoreCase)
   在 log4net.Util.SystemInfo.GetTypeFromString(String typeName, Boolean throwOnError, Boolean ignoreCase)
   在 log4net.Repository.Hierarchy.XmlHierarchyConfigurator.ParseAppender(XmlElement appenderElement)

=== 预绑定状态信息 ===
日志: DisplayName = log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a
 (Fully-specified)
日志: Appbase = file:///D:/svn/Loan/trunk/VBS_Console/Cn.Vcredit.VBS.BlackStatistics/Cn.Vcredit.VBS.BlackStatistics/bin/Debug/
日志: 初始 PrivatePath = NULL
调用程序集: Cn.Vcredit.ELK.LogAppender, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null。
===
日志: 此绑定从 default 加载上下文开始。
日志: 正在使用应用程序配置文件: D:\svn\Loan\trunk\VBS_Console\Cn.Vcredit.VBS.BlackStatistics\Cn.Vcredit.VBS.BlackStatistics\bin\Debug\Cn.Vcredit.VBS.BlackStatistics.vshost.exe.Config
日志: 使用主机配置文件:
日志: 使用 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config 的计算机配置文件。
日志: 策略后引用: log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a
日志: 尝试下载新的 URL file:///D:/svn/Loan/trunk/VBS_Console/Cn.Vcredit.VBS.BlackStatistics/Cn.Vcredit.VBS.BlackStatistics/bin/Debug/log4net.DLL。
警告: 比较程序集名称时发生不匹配: 主版本
错误: 未能完成程序集的安装(hr = 0x80131040)。探测终止。

log4net:ERROR XmlHierarchyConfigurator: Appender named [KafkaAppender] not found.

如何解决这个问题呢?

方案一:

这种情况可以让两个版本共存,如果是 publicKeyToken 需要使用两个 dependentAssembly 节点。

如何获取 publicKeyToken(公钥标记)  值,只需要使用 SN 命令即可,找到 SN.exe 程序,可以使用搜索工具找到,如下:

2e8bcfc2-4308-4d73-ac36-ac5d98ff55c8.png

选择一个比如:C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\x6464e2fe5f-6786-4dcc-8ede-e9c781c9531a.png

打开命令窗口,定位到这个目录,使用命令 SN -T “dll的路径”:20e5eb6c-48d9-4fda-a690-853ac4cf589e.png

在项目的配置文件中找到 assemblyBinding 节点下面添加 dependentAssembly:

<dependentAssembly>  
      <assemblyIdentity name="log4net" publicKeyToken="1b44e1d426115821" />  
      <codeBase version="1.2.10.0" href="bin\Debug\log4netdll\1_2_10\log4net.dll" />  
    </dependentAssembly>  
    <dependentAssembly>  
      <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" />  
      <codeBase version="2.0.8.0" href="bin\Debug\log4netdll\2_0_8\log4net.dll" />  
    </dependentAssembly>

方案二:

将引用的dll中对目标dll的引用改成统一版本。

ASP.NET SignalR 系列(一)之SignalR介绍 - 黄明辉 - 博客园

mikel阅读(1065)

来源: ASP.NET SignalR 系列(一)之SignalR介绍 – 黄明辉 – 博客园

一、SignalR介绍

ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化将实时 web 功能添加到应用程序的过程。 实时 web 功能是让服务器代码将内容推送到连接的客户端立即可用,而不是让服务器等待客户端请求新数据的能力。

废话不多说

传输模式

 

二、SignalR 和 WebSocket的区别

简单的说呢,websocket对客户端和服务端都有要求,而SignalR对兼容性就比较好,它集成了WebScoket和http两种方式,当条件满足时,自动使用WebScoket,否则使用http。可以简单的理解为,WebScoket是SignalR的子集。

 

三、SignalR的两种模式
永久连接

长连接(长轮询)

1、如果浏览器是 Internet Explorer 8 或更早版本,则使用长轮询。

2、如果配置 JSONP (即jsonp参数设置为true启动连接时),使用长轮询。

3、如果操作正在进行的跨域连接,(即,如果 SignalR 终结点不在托管的页面所在的域中),然后 WebSocket 将在满足以下条件:

                      1、客户端支持 CORS (跨域资源共享)。

                      2、客户端支持 WebSocket

                      3、服务器支持 WebSocket

                     4、如果不满足任何这些条件,则将使用长轮询。

4、如果未配置 JSONP 并且连接不跨域,如果客户端和服务器支持它,则将使用 WebSocket。

5、如果客户端或服务器不支持 WebSocket,如果可用,则使用服务器发送事件。

6、如果服务器发送事件不可用,请尝试使用永久帧。

7、如果永久连接失败,则使用长轮询。

文章介绍参考:https://docs.microsoft.com/zh-cn/aspnet/signalr/overview/getting-started/introduction-to-signalr#signalr-and-websocket

不在多介绍了,下章开始进入撸代码。

.net/c# 引用dll版本冲突解决办法_.Net/C#_架构师_程序员

mikel阅读(2232)

来源: .net/c# 引用dll版本冲突解决办法_.Net/C#_架构师_程序员

未能加载文件或程序集“Common.Logging, Version=2.1.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。

异常详细信息: System.IO.FileLoadException: 未能加载文件或程序集“Common.Logging, Version=2.1.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

源错误:

执行当前 Web 请求期间生成了未经处理的异常。可以使用下面的异常堆栈跟踪信息确定有关异常原因和发生位置的信息。

程序集加载跟踪: 下列信息有助于确定程序集“Common.Logging, Version=2.1.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e”未能加载的原因。

有时候,我们会看到上面的错误。

错误原因

A项目引用了xxx.dll,B项目也引用了xxx.dll,A项目引用了B项目,如下图:

A项目和B项目都引用了xxx.dll,但是,A项目引用的是高版本的dll,B项目引用的是低版本的dll,当A项目运行的时候,就会报错,找不到低版本的dll

解决办法

利用<bindingRedirect>元素,将一个程序集版本重定向到另一个版本。

oldVersion       

  • 必需的特性
  • 指定最初请求的程序集的版本。 程序集版本号的格式是major.minor.build.revision。 该版本号的每个部分的有效值介于 0 和 65535 之间。
  • 你还可以按下列格式指定版本范围:
  • n.n.n.n-n.n.n.n

newVersion

  • 必需的特性。
  • 指定要使用而不是最初请求的版本格式的程序集的版本: n.n.n.n
  • 此值可以指定 oldVersion 之前的版本。

文档链接:https://docs.microsoft.com/zh-cn … ingredirect-element

官方例子:

  1. <configuration>
  2.    <runtime>
  3.       <assemblyBinding xmlns=”urn:schemas-microsoft-com:asm.v1″>
  4.          <dependentAssembly>
  5.             <assemblyIdentity name=”myAssembly”
  6.                               publicKeyToken=”32ab4ba45e0a69a1″
  7.                               culture=”neutral” />
  8.             <bindingRedirect oldVersion=”1.0.0.0″
  9.                              newVersion=”2.0.0.0″/>
  10.          </dependentAssembly>
  11.       </assemblyBinding>
  12.    </runtime>
  13. </configuration>

复制代码

所以,我的解决办法是在web.config设置如下:

  1. <dependentAssembly>
  2.         <assemblyIdentity name=”Common.Logging” publicKeyToken=”af08829b84f0328e” culture=”neutral” />
  3.         <bindingRedirect oldVersion=”0.0.0.0-3.3.1.0″ newVersion=”3.3.1.0″ />
  4.       </dependentAssembly>

Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室) - 果冻布丁喜之郎 - 博客园

mikel阅读(703)

来源: Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室) – 果冻布丁喜之郎 – 博客园

简介

      ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信。什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端可以互相通知消息及调用方法,当然这是实时操作的。
WebSockets是HTML5提供的新的API,可以在Web网页与服务器端间建立Socket连接,当WebSockets可用时(即浏览器支持Html5)SignalR使用WebSockets,当不支持时SignalR将使用其它技术来保证达到相同效果。
SignalR当然也提供了非常简单易用的高阶API,使服务器端可以单个或批量调用客户端上的JavaScript函数,并且非常 方便地进行连接管理,例如客户端连接到服务器端,或断开连接,客户端分组,以及客户端授权,使用SignalR都非常 容易实现。
SignalR 将与客户端进行实时通信带给了ASP .NET 。当然这样既好用,而且也有足够的扩展性。以前用户需要刷新页面或使用Ajax轮询才能实现的实时显示数据,现在只要使用SignalR,就可以简单实现了。
最重要的是您无需重新建立项目,使用现有ASP .NET项目即可无缝使用SignalR。
  以上是来自百度百科的解释,个人觉得通俗来讲就是WebSockets是一种握手协议,当用户于服务器建立连接(握手成功)时,双方就建立了一个连接通道,互相传递时时数据。在这之前,我们一般来说实现这个功能的方式就是Ajax轮询,通过Ajax循环获取数据,这当然是非常消耗资源的,并且给服务器带来一定的压力。而WebSockets虽然可达到全双工通信,但依然需要发出请求,不过这种请求的Header是很小的-大概只有 2 Bytes。这里我们重点要知道的一点就是,WebSockets这个通道是双工通道,简单理解就是客户端(JavaScriptJQuery)的方法不但可以调取服务器的功能程序方法,并且服务器的功能程序也可以调取全部(广播)或某个(单播)或某一类(组播)客户端(JavaScriptJQuery)的方法。而SignalR则是微软给我们集成的一个WebSockets API,原理跟WebSockets是一致的,只是当WebSockets可用时(即浏览器支持Html5)SignalR使用WebSockets,当不支持时SignalR将使用其它技术来保证达到相同效果。

使用

很多新的技术没有必要非得理解,只需要知道你的应用环境可以用这项技术很简便的去实现,用的多了,用的久了,自然而然就会慢慢的理解这项技术的原理了。

今天我们一步一步来介绍一下如何使用SignalR,这一篇文章介绍的是使用Hubs SignalR集线器去实现,下一篇我们将介绍使用SignalR永久连接类去实现,我们做个简单的聊天室。

先给大家贴一下Demo的效果图:

一、准备:

SignalR 运行在 .NET 4.5 平台上,所以需要安装 .NET 4.5。为了方便演示,本示例使用 ASP.NET MVC 在 Win 7 系统来实现。这需要安装 ASP.NET MVC 3 或 ASP.NET MVC 4

二:Hubs代码示例:

1、首先我们创建一个MVC项目工程名字叫做SignalR_Chat

2、然后打开 工具 – NuGet程序包管理器 – 程序包管理器控制台

3、安装SignalR

输出NuGet命令:Install-Package Microsoft.AspNet.SignalR

安装成功后我们发现我们的bin里已经添加了我们需要的组件,并且在Scripts文件夹下添加了SignalR的JQuery引用

4、我们新建一个文件夹叫Hubs,然后添加SignalR集线器类ChatHub.cs

在上面的代码中:

(1)HubName 这个特性是为了让客户端知道如何建立与服务器端对应服务的代理对象,如果没有设定该属性,则以服务器端的服务类名字作为 HubName 的缺省值;

(2)Chat 继承自 Hub,从下面 Hub 的接口图可以看出:Hub 支持向发起请求者(Caller),所有客户端(Clients),特定组(Group) 推送消息。

5、添加OWIN Startup Class

修改 Configuration方法

复制代码
using Microsoft.Owin;
using Owin;
using SignalR_Chat.Connections;

[assembly: OwinStartupAttribute(typeof(SignalR_Chat.Startup))]
namespace SignalR_Chat
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
       //这个是下一篇永久连接类的 我们先不用
            //app.MapSignalR<MyConnection>("/echo");
        }
    }
}
复制代码

 

6、我们实现一个聊天室,代码我是分步贴出来的,后面我会附上完整的代码和Demo。

首先,我们新建一个类 标记用户和在线状态

复制代码
    public class OnlineUserInfo
        {
            //用户ID
            public string UserId { get; set; }
            //用户连接ID
            public string ConnectionId { get; set; }
            //用户昵称
            public string UserNickName { get; set; }
            //用户头像
            public string UserFaceImg { get; set; }
            //用户状态
            public string UserStates { get; set; }
        }
复制代码

然后,我们在ChatHub中实例化一下这个类

复制代码
       /// <summary>
        /// 这个是通过Hub集线器 大家可以参考 Connections下的 MyConnection 永久连接
        /// </summary>
        [HubName("chat")]
        public class ChatHub : Hub
        {
            static List<OnlineUserInfo> UserList = new List<OnlineUserInfo>();
        }
复制代码

我们写一个用户连接时注册一个群组,用户后面的组播

复制代码
            /// <summary>
            /// 注册群组 注册用户信息
            /// </summary>
            /// <param name="groupid">群组ID</param>
            /// <param name="usernickname">用户昵称</param>
            /// <param name="userfaceimg">用户头像</param>
            /// <param name="userid">用户在网站中的唯一标识ID</param>
            public void Group(string groupid, string usernickname, string userfaceimg, string userid)
            {
                //添加用户到群组 Groups.Add(用户连接ID,群组)
                Groups.Add(Context.ConnectionId, groupid);

                //如果说是一个简单的聊天室 下面这段代码是没有什么作用的 因为Context.ConnectionId是唯一的用户于服务器之间的连接
                //这里我传递进来了 用户的昵称和头像 还有网站中用户的ID 所以我要把用户的信息添加到我们上面建立的那个列表类中

                //如果用户不存在在线列表中
                if (UserList.Where(p => p.UserId == userid).FirstOrDefault() == null)
                {
                    //我们在列表中 添加这个用户 并且标记用户在线 UserStates = "True"
                    UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "True" });
                }
                    //如果用户已经存在于在线列表中
                else
                {
                    //我们更新用户列表中用户的信息 (这里更新的信息主要是用户的连接ID  ConnectionId = Context.ConnectionId)
                    var UserInfo = UserList.Where(p => p.UserId == userid).FirstOrDefault();
                    UserList.Remove(UserInfo);
                    UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "True" });
                }
                //这个方法是调用客户端LoginUser方法 并且传递当前用户列表 客户端会刷新当前用户列表 调用的是全部的已连接的用户 Clients.All
                Clients.All.LoginUser(Common.JsonConverter.Serialize(UserList));
                //这个方法是调用客户端的 addNewMessageToPage方法 目的是实现 当一个用户上线是 提醒所有的用户 某个用户上线了 提醒的是所有的已连接用户 所以也是Clients.All
                Clients.All.addNewMessageToPage("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系统消息:" + DateTime.Now.ToString("HH:mm:ss") + "&nbsp;" + usernickname + "&nbsp;上线了<dd></dl>");
            }
复制代码

为了方便大家理解,我这里就先把客户端的LoginUser和addNewMessageToPage方法贴出来,让大家好理解服务器是怎样调用客户端的js方法的

复制代码
    //接收服务器信息
    chat.client.addNewMessageToPage = function (message) {
        //#chatContent就是一个div层 我们把服务器返回的信息追加到这个层上 跟QQ聊天相反,新的信息我们追加顶部
        $('#chatContent').prepend(message);
    };
    //服务器端调用的LoginUser方法,根据返回的用户列表 输出用户列表到页面上
    chat.client.LoginUser = function (UserList) {
        //在下一篇介绍的持久连接类中 是可以直接返回Json的 这里不知道怎么回事 接收的Json总是被接收成字符串 所以这里我们解析一下
        var data = eval("(" + UserList + ")");
        var html = "";
        for(var i=0;i<data.length;i++)
        {
            //这里我们做了一个判断 就是 解析用户列表Json时 如果用户的ID 就是当前用户的ID 那么就不添加 这跟QQ不一样啊 QQ中好友列表中是有自己的
            if (data[i].UserId != $("#Juser-userid").val()) {
                //如果用户的在线状态是在线呢 我们就添加onclick方法 实现 点击用户的用户 可以私聊 如果不在线 就不添加了 因为我们这个是没有存数据库的 所以没有做离线消息
                if (data[i].UserStates == "True") {
                    html += "<dl onclick=\"javascript:sendPerMessage('" + data[i].ConnectionId + "','" + data[i].UserNickName + "')\" class=\"clearfix tab-item-1\"><dt><img src=\"" + data[i].UserFaceImg + "\"></dt><dd>" + data[i].UserNickName + "</dd></dl>";
                }
                else
                {
                    html += "<dl onclick=\"javascript:void(0)\" class=\"clearfix tab-item-1 liveout\"><dt><img src=\"" + data[i].UserFaceImg + "\"></dt><dd>" + data[i].UserNickName + "</dd></dl>";
                }
            }
        }
        //更新页面用户列表
        $("#OnlineUsers").html(html);
    };
复制代码

这里注意的是服务器调用的客户端方法跟客户端写的JS方法大小写是一样的,后面我们介绍客户端调用服务器方法的时候会将 客户端调用服务器方法的时候 服务器方法的首字母是小写的,这里提醒一下。

下面是我们的发送消息的方法,当我发送消息的时候 传递我的头像和昵称给服务器 让别人显示消息的时候能显示出谁发送的(这个跟QQ消息类似啊 方便大家理解)

一个是群组消息当然也可以是全部消息,另一个是私聊,就是指定发送个某一个用户

复制代码
/// <summary>
            /// 发送消息 自定义判断是发送给全部用户还是某一个组(类似于群聊啦)
            /// </summary>
            /// <param name="groupid">接收的组</param>
            /// <param name="userfaceimg">发送用户的头像</param>
            /// <param name="usernickname">发送用户的昵称</param>
            /// <param name="message">发送的消息</param>
            public void Send(string groupid, string userfaceimg, string usernickname, string message)
            {
                if (groupid == "All")//全部用户(广播)
                {
                    //调用所有客户端的addNewMessageToPage方法 推送一条消息
                    Clients.All.addNewMessageToPage("<dl class=\"clearfix\"><dt><img src=\"" + userfaceimg + "\" /></dt><dd><i></i><div class=\"J_Users\">" + usernickname + "</div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                }
                else//指定组(组播)
                {
                    //调用指定客户端的addNewMessageToPage方法 推送一条消息(所有属于组groupid的已连接用户)
                    Clients.Group(groupid).addNewMessageToPage("<dl class=\"clearfix\"><dt><img src=\"" + userfaceimg + "\" /></dt><dd><i></i><div class=\"J_Users\">" + usernickname + "</div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                }
            }

            /// <summary>
            /// 发送给指定用户(单播)
            /// </summary>
            /// <param name="clientId">接收用户的连接ID</param>
            /// <param name="userfaceimg">发送用户的头像</param>
            /// <param name="usernickname">发送用户的昵称</param>
            /// <param name="message">发送的消息</param>
            public void SendSingle(string clientId, string userfaceimg, string usernickname, string message)
            {
                //首先我们获取一下接收用户的信息
                var UserInfo = UserList.Where(p => p.ConnectionId == clientId).FirstOrDefault();
                //如果用户不存在或用户的在线状态为False 那么提醒一下 发送用户 对方不在线
                if (UserInfo == null || UserInfo.UserStates == "False")
                {
                    Clients.Client(Context.ConnectionId).addNewMessageToPage("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系统消息:当前用户不在线<dd></dl>");
                }
                else
                {
                    //如果用户存在并且在线呢 就把消息推送给接收的用户 并且加上当前用户信息 以及添加一个onclick事件 让接收的用户 可以直接点击消息的用户 回复 私聊信息 (不然还要在用户列表中找到谁给我发的消息 点击回复 这不科学...)
                    Clients.Client(clientId).addNewMessageToPage("<dl class=\"clearfix\"><dt onclick=\"javascript:sendPerMessage('" + Context.ConnectionId + "','" + usernickname + "')\"><img src=\"" + userfaceimg + "\" /></dt><dd class=\"per\"><s></s><div onclick=\"javascript:sendPerMessage('" + Context.ConnectionId + "','" + usernickname + "')\" class=\"J_Users\">" + usernickname + "<span>私聊</span></div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                    //这句是发送给发送用户的 总不能我发送个私聊 对方收到了信息 我这里什么都不显示是吧 我也显示我发送的私聊信息 因为发送发就是我自己 所以不加onclick事件了 不允许自己跟自己聊天哦
                    Clients.Client(Context.ConnectionId).addNewMessageToPage("<dl class=\"clearfix\"><dt><img src=\"" + userfaceimg + "\" /></dt><dd class=\"per\"><s></s><div class=\"J_Users\">" + usernickname + "<span>私聊</span></div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                }
            }
复制代码

这里我贴一下前台代码

复制代码
    $.connection.hub.start().done(function () {
        //用户连接时 注册一下群组和个人信息哦 这个的服务器代码 我们上面贴出来了
        //这个Demo是为了让大家理解SigalR所以没有做多复杂的流程 个人信息 我是直接传递的 
        //$("#groupid").val()这个是要注册的群组,可以自己定义 组播的时候 只要是在这一个组里的都会收到 
        //$("#Juser-login").val()这个是发送方也就是我的昵称
        //$("#Juser-faceimg").val()这个是我的头像
        //$("#Juser-userid").val()这个是我在网站中的唯一标识ID,用户连接的ID(Context.ConnectionId)也是唯一的,那么为什么还要我在这个网站中的ID呢?
        //解释一下子:单页面的聊天室是没有多大必要的,但是比如我们这个功能是放到公用里的,就像网站的在线客服一样,你总不能每个页面都写一套吧 既然是引用的这一个页面 
        //那么用户打开其他页面的时候 这个Context.ConnectionId是会变的,那我怎么知道这又是谁呢 我们就用用户在网站中的唯一标识ID作为参照,当新的连接进来时 我们看下是不是ID一样 
        //一样的话我们就更新用户列表中这个唯一标识ID用户的Context.ConnectionId和在线状态 不一样的话就添加新用户
        chat.server.group($("#groupid").val(), $("#Juser-login").val(), $("#Juser-faceimg").val(), $("#Juser-userid").val());
        $('.sendBtn').click(function () {
            //这里做一下判断 如果没有输入消息就发送 那么提示一下
            if ($('#MessageBox').val().length <= 0)
            {
                $('#chatContent').prepend("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系统消息:请输入信息<dd></dl>");
            }
            else
            {
                //sendToConnectId 是我们自定义的一个字段 如果你点击了某一个用户 那么就把他的ConnectionId赋给sendToConnectId 我们知道是私聊
                //当然,用户点击退出私聊的时候 这个字段会被赋为空 表示是群聊 这个大家在Demo中一看就明白了
                if (sendToConnectId != "" && sendToConnectId.length > 0) {
                    //调用服务器私聊方法 !!!注意啊!!!服务器的私聊方法是 public void SendSingle(string clientId, string userfaceimg, string usernickname, string message)
                    //这里是chat.server.sendSingle 首字母小写啊 客户端调用的服务器方法首字母要小写  服务器调用的客户端方法 大小写一致 
                    chat.server.sendSingle(sendToConnectId, $("#Juser-faceimg").val(), $("#Juser-login").val(), $('#MessageBox').val());
                    $('#MessageBox').val("").focus();
                }
                else {
                    //这里是群聊 我们演示的没有做群组聊天 所以这里传递的是"All"表示 全部,会发送给全部用户
                    //说明一下方便理解:比如我们有这么一个情景,这个聊天是一个讨论,对某一篇文章或产品的讨论,那么是不是应该只在这篇文章或这个产品页面的用户才能收发属于这篇文章或产品消息呢,在其他页面
                    //的用户不应该能收发这里的消息呀 那么我们上面的代码chat.server.group中传递的groupid就应该是某篇文章或产品的标识,把他们划分到一个组里比如chat.server.group("A123")一个自定义字符串加上文章或产品ID,
                    //或直接用文章或产品的IDchat.server.group("123") 这里就chat.server.send("123", ...);就实现了 只有在这个页面中的用户才能收到此消息 就跟QQ的群是一样的
                    chat.server.send("All", $("#Juser-faceimg").val(), $("#Juser-login").val(), $('#MessageBox').val());
                    $('#MessageBox').val("").focus();
                }
            }
        });
    });
复制代码

使用者离线或重新连接 重写Hub的方法

复制代码
 //使用者离线
            public override Task OnDisconnected(bool stopCalled)
            {
                var UserInfo = UserList.Where(p => p.ConnectionId == Context.ConnectionId).FirstOrDefault();
                var userid = UserInfo.UserId;
                var usernickname = UserInfo.UserNickName;
                var userfaceimg = UserInfo.UserFaceImg;
                UserList.Remove(UserInfo);
                UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "False" });

                Clients.All.LoginUser(Common.JsonConverter.Serialize(UserList));
                Clients.All.addNewMessageToPage("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系统消息:" + DateTime.Now.ToString("HH:mm:ss") + "&nbsp;" + usernickname + "&nbsp;离线了<dd></dl>");

                return base.OnDisconnected(true);
            }

            //使用者重新连接
            public override Task OnReconnected()
            {
                var UserInfo = UserList.Where(p => p.ConnectionId == Context.ConnectionId).FirstOrDefault();
                if (UserInfo != null)
                {
                    var userid = UserInfo.UserId;
                    var usernickname = UserInfo.UserNickName;
                    var userfaceimg = UserInfo.UserFaceImg;
                    UserList.Remove(UserInfo);
                    UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "True" });
                    Clients.All.LoginUser(Common.JsonConverter.Serialize(UserList));
                }
                return base.OnReconnected();
            }
复制代码

还有一些辅助的JS方法,在这里我就不一一贴出来了,我把demo地址留给大家 ,大家可以搭建起来研究一下。

这篇文章仅仅是个人的一些理解和实现,可能中间会出现一些不合理的地方或是错误,请大家指正,我们共同学习研究。

Demo是用VS 2013写的

下载:百度网盘

补充:Demo是我写这个博客之前写的 没有用到HubName 这个特性 所以Demo跑起来会有错误 大家删除这个特性就没有错误了 在Hubs文件夹下的ChatHub.cs

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

.Net 开源项目资源大全 - 特洛伊-Micro - 博客园

mikel阅读(649)

来源: .Net 开源项目资源大全 – 特洛伊-Micro – 博客园

Awesome DotNet,这又是一个 Awesome XXX 系列的资源整理,由 quozd 发起和维护。内容包括:编译器、压缩、应用框架、应用模板、加密、数据库、反编译、IDE、日志、风格指南等。

伯乐在线已在 GitHub 上发起「DotNet 资源大全中文版」的整理。欢迎扩散、欢迎加入。

https://github.com/jobbole/awesome-dotnet-cn


(注:下面用 [$] 标注的表示收费工具,但部分收费工具针对开源软件的开发/部署/托管是免费的)

API

  • 框架
    • NancyFx:轻量、用于构建 HTTP 基础服务的非正式(low-ceremony)框架,基于.Net 及 Mono 平台。
    • ASP.NET WebAPI:快捷创建 HTTP 服务的框架,可以广泛用于多种不同的客户端,包括浏览器和移动设备。
    • ServiceStack :架构缜密、速度飞快、令人愉悦的 web 服务。
    • Nelibur:Nelibur 是一个使用纯 WCF 构建的基于消息的 web 服务框架。Nelibur 可以便捷地创建高性能、基于消息的 web 服务,使得你全面拥有 WCF 的强大能力。
  • WebAPI Contrib:帮助你提高 ASP.NET Web API 能力的开源项目集合。

应用框架(Application Frameworks)

  • ASP.NET Boilerplate:现代 ASP.NET MVC web 应用程序的入门,包含最佳实践和最流行的工具。
  • Orleans:Orleans 框架提供了直接构建分布式、大规模计算应用的方法,无需学习和使用复杂的并行或扩展模型。
  • CoreFX:corefx 仓库包含了 .NET 核心功能库的实现(被称为“CoreFX”)。包含了 System.Collections、System.IO、System.Xml 以及诸多其它组件。目前构建并运行于 Windows 平台。你可以关注这个仓库,了解在未来的几个月内即将增加的对 Linux 和 Mac 的支持。
  • CSLA .NET:业务层开发框架 http://www.cslanet.com
  • Mono:Mono 是 ECMA CLI、C# 以及 .NET 的开源实现。
  • Mono-Addins:Mono.Addins 是一个通用框架,用于创建可扩展的应用程序,以及这些应用程序的扩展插件。
  • Spring.Net:Spring.NET 是一个开源的应用程序框架,可以便捷地创建企业级 .NET 项目。

应用模板(Application Templates)

  • MVC.TemplateASP.NET MVC 5 入门项目模板。
  • ProjectScaffold:F# 基金会推荐的 .NET 解决方案的原型——包括文件系统的搭建、用于管理依赖的 Paket 以及用于自动化构建、测试的 FAKE。默认情况下,构建流程也会对文档进行编译,并生成 NuGet 程序包。
  • Side-Waffle :包含大量有用的 Web 和桌面开发模板。
  • Template10 :带有设计模式的 Windows 10 模板。

人工智能(Artificial Intelligence)

  • AIMLBot(Program#):使用 C# 编写的一个小型、快速、兼容标准、易于定制的聊天机器人,基于 AIML (人工智能标记语言 Artificial Intelligence Markup Language)。
  • SIML :智能综合智能标记语言(Synthetic Intelligence Markup Language),下一代聊天机器人及数字助手语言。

程序集处理(Assembly Manipulation)

  • dnSpy:dnSpy 是一个 .NET 程序集编辑器、反编译器和调试器,来自 ILSpy 分支。
  • Fody:织入(weaving).net 程序集的可扩展工具。
  • Mono.Cecil:Cecil 类库用于生成和检查 ECMA CIL 程序和库。

资源(Assets)

  • Cassette:管理 .NET web 应用程序资源(脚本、css 和 模板)
  • NodeAssets:.net 资源管理器,通过 SignalR 实时更新 css,也可以使用 NodeJS 编译器。
  • Bundler:编译和最小化 Less、Sass、Stylus、Css、JS、CoffeeScript、LiveScript 文件。MVC集成了 MVC 和 ServiceStack。
  • SquishIt:让你轻松合并一些 css 和 JavaScript

认证和授权(Authentication and Authorization)

  • ASP.NET Identity:用于 ASP.NET 应用程序的新身份系统。
  • DotNetOpenAuth:OpenID、OAuth 和 InfoCard 协议的一个 C# 实现。
  • Logibit Hawk:一个 F# Hawk 认证库。
  • IdentityModel:.NET 4.5 和 MVC4、Web API 身份和访问控制的辅助库。
  • IdentityServer:可扩展的 OAuth2 和 OpenID 连接提供程序框架。
  • OAuth:超轻量级 OAuth 1.0a 签名生成库,C# 编写。

自动构建(Build Automation)

  • Psake:基于 .NET 的自动化构建工具,使用 PowerShell 编写。
  • FAKE:F# Make,一个跨平台自动构建系统。
  • Invoke-Build:PowerShell 自动构建和测试工具,灵感来自 Psake。
  • MSBuild:微软构建引擎(MSBuild)是 .NET 和 Visual Studio 的构建平台。
  • Cake:Cake(C# Make)使用 C# DSL 的跨平台自动构建系统。

缓存(Caching)

  • CacheCow:ASP.NET Web API HTTP 客户端和服务器端缓存实现。
  • Akavache:一个异步、持久化的键值存储。

CLI

  • Command Line Parser:Command Line Parser 类库为 CLR 应用程序提供了一套简洁的 API,用于处理命令行参数及相关任务。
  • Fluent Command Line Parser:一个简单、强类型的 .NET C# 命令行解析库,交互方式流畅易用。
  • Power Args:PowerArgs 将命令行参数转换为 .NET 对象,便于程序使用。它还提供了大量可选的扩展,例如参数校验、自动生成使用帮助、tab 补全等等。
  • UnionArgParser:针对 F# 应用程序的声明式 CLI 参数和 XML 配置解析器。

CLR

  • CoreCLR:coreclr repo 包含了完整的 .NET 核心运行时实现(称为“CoreCLR”)。它包括 RyuJIT、.NET GC、非托管代码交互(native interop)等诸多组件。它目前构建和运行于 Windows 平台。你可以关注这个仓库,了解未来的几个月内即将增加的对 Linux 和 Mac 的支持。

CMS

  • Composite C1:一个 web 内容管理系统,着重在用户体验及适应性。
  • mojoPortal:MojoPortal 是一个可扩展、跨数据库、移动友好的 web 内容管理系统(CMS)和 web 应用程序框架,使用 C# ASP.NET 编写。
  • N2CMS:开源、轻量、代码优先的 CMS,可以无缝地集成到任何 MVC 项目中。
  • Orchard:免费、开源、专注社区的项目,目标是在 ASP.NET 平台上提供应用程序和可重用组件。
  • Piranha CMS:Piranha 是一个有趣、快速、轻量级的 .NET 框架,用于开发基于 cms 附带其它功能的 web 应用程序。它基于 ASP.NET MVC 和 Web 页面创建,完全兼容 Visual Studio 和 WebMatrix。http://piranhacms.org
  • Umbraco:Umbraco 是一个免费开源的内容管理系统,基于 ASP.NET 平台构建

代码分析和度量(Code Analysis and Metrics)

  • CodeMaid:Visual studio 扩展,用于清理、挖掘和简化 C#、C++、F#、VB、PHP、JSON、XAML、XML、ASP、HTML、CSS、LESS、SCSS、JavaScript 和 TypeScript 代码。
  • StyleCop:StyleCop 使用一组风格和一致性规则,对 C# 源代码进行分析和强制性检查。
  • Gendarme:可扩展的、基于规则的工具,用于在 .NET 应用程序和类库中查找问题。
  • Metrics-Net:捕获 CLR 和应用程序级别的度量值。所以你知道它的功能。

编译器(Compiler)

  • Bridge.NET:将 C# 编译成 JavaScript 的开源编译器 http://bridge.net/
  • ClojureCLR:从 Clojure 到 CLR 的转换,是 Clojure 项目的一部分。
  • F#:F# 编译器、核心库和工具——更安全、更快、代码更好的函数式编程语言。
  • FunScript:F# 到 JavaScript 的编译器,可以通过 TypeScript 类型提供程序使用 JQuery 等 JavaScript 库。
  • JSIL:CIL 到 Javascript 的编译器 http://jsil.org/
  • Mono-basic:Visual Basic 编译器和运行时。
  • Nemerle:Nemerle 是一个 .NET 平台高级静态类型编程语言。它提供函数式、面向对象式和命令式编程语言的特性。它拥有一个简单的类似 C# 的语法和强大的元编程(meta-programming)系统。 http://nemerle.org
  • Netjs:.NET 到 TypeScript 和 JavaScript 编译器。兼容可移植类库。你甚至可以使用 EXE 文件。
  • Roslyn:.NET 编译平台(“Roslyn”)提供开源的 C# 和 Visual Basic 编译器,包含丰富的代码分析 API。它可以使用和 Visual Studio 一样的 API 来构建代码分析工具。
  • VisualFSharp:Visual F# 编译器和工具。

压缩(Compression)

  • SharpCompress:SharpCompress 是一个用于 .NET、Mono、Silverlight、WP7 的压缩类库,可以解压rar、7zip、zip、tar、bzip2 和 gzip,提供单向读取和随机文件访问 API。支持对 zip/tar/bzip2/gzip 进行写入的实现。
  • DotNetZip.Semverd:一个开源项目,提供对 ZIP 文件处理的 .NET 类库和相关工具。 (分支自 已经不再维护的 DotNetZip
  • SharpZipLib:一个 Zip、GZip、Tar 和 BZip2 的类库,完全由 C# 编写,面向 .NET 平台。

持续集成(Continuous Integration)

  • TeamCity:可以直接使用的,可扩展、面向开发人员友好的构建服务器——开箱即用[$]
  • CruiseControl.NET:一个自动化持续集成服务器,使用 .NET Framework 实现。
  • MyGet:为NuGet、NPM、Bower 和 VSIX 提供持续集成、部署、宿主程序包仓库的服务。[开源软件免费] [$]
  • AppVeyor:.NET 持续构建和部署服务。 [$] [开源软件免费]

加密(Cryptography)

  • BouncyCastle:和 .Net 的 System.Security.Cryptography 一起,在 CLR 上提供加密算法的实现。
  • HashLib:HashLib 包含了几乎所有你见过的哈希算法,它几乎支持所有东西并且非常容易使用。
  • libsodium-net:libsodium for .NET——一个安全加密库。
  • StreamCryptor :使用 libsodium 和 protobuf 对流进行加密和解密。

数据库(Database)

数据库驱动(Database Drivers)

  • MySQL Connector:完全托管的 MySQL ADO.NET 数据库提供程序、连接器。
  • Npgsql:Postgresql 的 .Net 数据提供程序。
  • MongoDB:MongoDB 官方 C# 驱动。
  • ServiceStack Redis:.NET 领先的 C# Redis 客户端。
  • StackExchange Redis:来自 StackExchange 的通用 redis 客户端。
  • Cassandra:DataStax 开发的 Apache Cassandra .NET 驱动程序。
  • Couchbase:couchbase 官方 .NET 客户端库,基于 Enyim memcached 客户端。
  • Firebird.NET:由C# 编写的 .NET 数据提供程序,提供对 Firebird API 的高性能原生实现。

反编译(Decompilation)

部署(Deployment)

  • Unfold:基于 Powershell 的 .net web 应用程序部署解决方案。

DirectX

  • SlimDX:为 .NET 应用程序提供的 DirectX 封装。
  • SharpDX:SharpDX 是一个开源项目,为 .Net 及所有 Windows 平台提供完整的 DirectX API,可以开发高性能的游戏、2D/3D图形渲染以及实时音频应用程序。

分布式计算(Distributed Computing)

  • Project Orleans:Orleans 框架提供了直接构建分布式、大规模计算应用的方法,无需学习和使用复杂的并行或扩展模型。由微软研究院开发。
  • Akka.net:Akka.NET 是流行的 Java/Scala 框架 Akka 的 .NET 版本。它由社区提供,与 Typesafe(原始的 Java、Scala 版本的开发商)无关。

文档(Documentation)

  • Sandcastle:Sandcastle 帮助文件生成器和 NDoc 类似。
  • SharpDox:一个 c# 文档工具。
  • Swashbuckle:向 WebApi 项目无缝地添加 swagger 文档(译者注:swagger 是一套用于生成、描述、展现 RESTful 风格 web 服务文档的框架和规范)。
  • F# Formatting:F# 和 C# 项目的文档工具,文档生成自 F# 脚本文件、Markdown 文档、内嵌 XML 或 Markdown评论。

电子商务和支付(E-Commerce and Payments)

环境管理(Environment Management)

  • DNVM:.NET SDK 管理器,一组命令行工具,用于更新和配置需要使用的运行时环境(DNX)。

ETL

  • Reactive ETL:Reactive ETL 使用 .NET 反应性扩展框架(reactive extensions) 重写了 Rhino ETL。

游戏(Game)

  • MonoGame:一个用来创建跨平台游戏的强大框架。
  • CocosSharp:CocosSharp 是 Cocos2D 和 Cocos3D API 的 C# 实现版本,可以在所有支持 MonoGame 的平台上运行。
  • Duality:Duality 是一个 2D 游戏开发框架。专注于功能的模块化,自带一个可视化编辑器。
  • Paradox:Paradox 游戏引擎。http://paradox3d.net

地理信息系统(Gis)

  • NetTopologySuite:一个 在 .NET 平台上实现快速、可靠的 GIS 系统解决方案。
  • SharpMap:一个易于使用的地图库,可以用于 web 和桌面应用程序。

Git工具(Git Tools)

  • Bonobo Git Server:Bonobo Git Server for Windows 是一个 web 应用程序,可以安装在你自己的 IIS 上,用于管理和连接你的 git 仓库。 http://bonobogitserver.com
  • GitExtensions:GitExtensions 包含资源管理器扩展、Visual Studio 2008/2010/2012/2013 插件和一个独立的 Git 仓库工具。 http://gitextensions.github.io/
  • GitLink:让用户可以单步调试托管在 GitHub 或 BitBucket 上的代码。
  • GitVersion:根据你的 Git 仓库的状态生成一个语义化版本号(Semantic Version Number)。
  • LibGit2Sharp:LibGit2Sharp 带来了 libgit2 所有的功能和速度,是一个本地 Git 实现,可以运行在 .Net 和 Mono 平台。
  • NGit:NGit 是 JGit 移植到 C# 的版本。
  • posh-git:Git 的 PowerShell 环境。

图形(Graphics)

  • Oxyplot:OxyPlot 是一个 .NET 跨平台绘图库。
  • OpenTK:Open Toolkit 是一个封装了 OpenGL、OpenCL 和 OpenAL 的 高级底层 C# 开发库。
  • NGraphics:NGraphics 是一个 .NET 跨平台矢量图形渲染库。

GUI

  • MahApps.Metro:用于创建 Metro 风格 WPF 应用的工具箱。
  • Callisto:用于 Windows 8 XAML 应用的控件工具箱。包含若干 UI 控件,让你更容易地创建符合 Windows UI 风格规范的Windows 商店应用。
  • ObjectListView:ObjectListView 使用 C# 封装了 .NET 的 ListView 控件。它使得 ListView 更加易用,并且加入了一些新特性。
  • DockPanelSuite :灵感来自 Visual Studio 的停靠窗口(docking)类库,用于 .NET WinForm 应用。
  • AvalonEdit :在 SharpDevelop 中使用,基于 WPF 的文本编辑器组件。
  • XWT:跨平台 UI 工具箱,用于创建 .NET 和 Mono 桌面应用程序。
  • Gtk#:Gtk# 是 Gtk+ GUI 工具箱的 Mono/.NET 版本,绝大多数 Mono 中的 GUI 应用都基于它构建。
  • MaterialDesignInXamlToolkit:用于创建 Material Design 风格 WPF 应用的工具箱。
  • Eto.Forms:跨平台的 GUI 框架,用于 .NET 和 Mono 下的桌面和移动应用程序。
  • Dragablz:可拖拽、可分离(tearable,译者注:即拖拽标签页成为独立窗口)的 WPF 标签页控件(类似 Chrome)。支持布局和主题,包含兼容 MahApps 和 Material Design 的主题。
  • Fluent.Ribbon:Fluent Ribbon Control Suite 是一个在 WPF 中实现 Office 和 Windows 8 风格的 Ribbon 库。

HTML 和 CSS(HTML and CSS)

HTTP

  • Http.fs[F#] 中的一个函数式 HTTP 客户端。
  • RestSharp:.NET 下简单的 REST 和 HTTP API 协议客户端。
  • EasyHttp:C# Http开发库。
  • Refit :Xamarin 和 .NET 下自动生成强类型的 REST 库。
  • RestEase:类型安全且易于使用的 REST API 客户端库,简单可定制。大部分灵感来自 Refit。

IDE

图像处理(Image Processing)

  • ImageResizer:在图片 URL 后面增加命令,在几毫秒内获取修改后的版本,支持实时的对图片进行缩放、编辑。
  • ImageProcessor:开源 .NET 库,用于实时处理图片。
  • DynamicImage:高性能开源图片处理库,用于 ASP.NET。
  • MetadataExtractor:从图片中提取 Exif、IPTC、XMP、ICC 等其它元数据信息。
  • Emgu CV:OpenCV 的 .NET 跨平台封装。

安装工具(Install Tools)

  • Wix Toolset:强大的工具集,用于创建你自己的 Windows 安装程序。
  • Squirrel:Squirrel 即是一套工具也是一个类库,可以无安全管理 Windows 桌面程序的安装和更新。

国际化(Internationalization)

  • i18n:ASP.NET MVC 智能国际化工具。

互操作(Interoperability)

  • CefSharp:Chromium Embedded Framework 的 .NET 支持(WPF 和 WinForm)。
  • CppSharp:在 C# 中平滑使用 C++ API 的工具。
  • Sharpen:Sharpen 是 db4o 编写的 Eclipse 插件,可以让你把 Java 项目转换为 C#。
  • CXXI:C++ 互操作框架。

IoC

  • Castle Windsor:Castle Windsor 是一个用于 .NET 和 Silverlight 的成熟的 控制反转(IoC) 容器。
  • Unity:轻量级、可扩展的依赖注入容器,支持构造函数、属性和方法调用注入。
  • Autofac:令人着迷的 .NET IoC 容器。
  • Ninject:.net 依赖注入的忍者。
  • StructureMap:.Net 最早的 IoC/ID 容器。
  • Spring.Net:Spring.NET 是一个开源应用程序框架,可以便捷地创建企业级 .NET 应用。
  • LightInject :一个超轻量级 IoC 容器。
  • TinyIoC:单文件、简单、跨平台的 IoC 容器。

日志(Logging)

  • Essential Diagnostics:为内置System.Diagnostics 命名空间扩展功能,提供更灵活的日志功能。
  • NLog:先进的 .NET 和 Silverlight 日志工具。
  • ELMAH:ELMAH 官方网站
  • Elmah MVC:MVC 版 Elmah。
  • Logary:Logary 是一个 mono 和 .Net 平台下高性能、多目标的日志、度量、追踪和健康检查库。支持多目标,为微服务构建。
  • Log4Net:Apache log4net 工具库能够帮助程序员向多种不同的目标输出日志语句。
  • Serilog:一个 NoSQL 时代下简单直接的日志库。将多个优秀的传统结构化分析日志功能合并到一个易于使用的程序集中。
  • StackExchange.Exceptional:Stack Exchange 网络使用的错误处理程序。
  • Semantic Logging Application Block (SLAB):为内置 System.Diagnostics.Tracing 命名空间(EventSource类)扩展功能,支持将日志记录到多个容器中,包括 Azure 表存储、数据库、文件(JSON、XML、文本文件)。通过 ETW 支持进程内和进程外的日志记录,支持 Rx 进行实时的事件过滤和聚合。

机器学习和数据科学(Machine Learning and Data Science)

  • Accord.NET:机器学习框架,包含了音频和图像处理的库(计算机视觉、计算机听觉、信号处理和统计)。
  • Accord.NET Extensions:高级图像处理和计算机视觉算法扩展。
  • AForge.NET:为计算机视觉和人工智能领域的开发者和研究人员提供的框架(包括图像处理、神经网络、遗传算法、机器学习、机器人科学)。
  • Deedle:处理探索性数据的数据帧和时序库,支持 C# 和 F#。
  • FsLab:数据科学和机器学习库的集合,支持 F# 和 .NET。
  • numl :包含最流行的监督学习和无监督学习算法,尽量减少创建预测模型时的冲突。
  • R Provider :将 R 语言包和函数封装为类型安全的类型提供程序供 F# 调用。
  • F# Data:F# 类型提供程序,访问 XML、JSON、CSV 和 HTML 文件(基于样例文档),以及 WorldBank 数据。

Markdown 处理(Markdown Processors)

  • MarkdownSharp:C# 实现的开源 Markdown 处理器,在 Stack Overflow 中使用。
  • F# Formatting:用于生成 F# 和 C# 项目文档的工具。该工具库的核心组件中包含了可扩展的 Markdown 解析器。
  • CommonMark.NET:CommonMark 规范的 C# 实现,用于将 Markdown 文档转换为 HTML。为最佳性能和可移植性进行了优化。

邮件(Mail)

  • FluentEmail:System.Net.Mail 的一个流式(Fluent)封装,支持 razor 模板引擎。
  • MailKit:完整的跨平台的邮件协议栈,包括 IMAP、POP3、SMTP,支持验证等特性。基于 MimeKit 构建。
  • MimeKit:跨平台 .NET MIME 创建和解析库,支持 S/MIME、PGP、TNEF 和 Unix mbox spools。
  • PreMailer.Net:一个 C# 开发库,将你的样式表嵌入到内置的 style 属性中,最大限度支持邮件客户端。

数学(Mathematics)

  • MathNet:Math.NET 是一个开源项目,旨在创建和维护涵盖基础数学在内的工具集,面向高级和日常需要使用此类功能的 .Net 开发人员。

多媒体(Media)

  • TagLib#:TagLib#(即 taglib-sharp)是一个读写媒体文件元数据的库,支持视频、音频和照片格式。

度量(Metrics)

微框架(Micro Framework)

  • .NET Micro Framework Interpreter:Microsoft® .NET Micro Framework(NETMF)是一个在 Visual Studio 中提供为小型设备上编写嵌入式应用的框架。

杂项(Misc)

  • .NET Fiddle:在浏览器中编写、编译并运行 C# 代码。相当于 C# 版本的 JSFiddle。
  • AzureCrawler:从 Angular、Ember、Durandal 或任何 JavaScript 应用中获取 HTML 快照。
  • BitSharp:C# 比特币节点。
  • CSScript:CS-Script 是一个基于 CLR 的脚本系统,使用 C# 作为编程语言。CS-Script 目前针对微软的 CLR 实现(.NET 2.0/3.0/3.5/4.0/4.5)并完整支持 Mono。附带很多附加特性,比如脚本宿主。
  • CsvHelper:帮助读写 CSV 文件的开发库。http://csvhelper.com
  • FluentValidation:一个小型的 .NET 校验库,使用流式接口和 lambda 表达式构建校验规则。
  • Humanizer:Humanizer 能够在 .NET 平台上满足所有针对字符串、枚举、日期、时间、时间范围、数字等类型数据的操作和显示要求。
  • LINQPad:一个 C#/VB/F# 的便签本,能够立即执行任何表达式、语句块或程序,带有富文本显示等有用的特性。同样可以让你使用 LINQ 进行交互性数据库查询。 [$](译者注:也有免费许可,但是功能受限)
  • Polly:快捷便利的异常处理策略,例如 重试、始终重试、等待并重试或断路(Circuit Break)等行为。 (.NET 3.5、4.0、4.5、PCL、Xamarin)
  • Rant:Rant 程序文本生成 DSL(领域特定语言)。 http://berkin.me/rant
  • ScriptCS:使用文本编辑器、nuget 和强大的 Roslyn 来编写 C# 应用!

MVVM

  • Caliburn.Micro :小型但功能强大的框架,可以用来给所有 XAML 平台创建应用。它对 MV* 模式的强大支持可以让你快速构建你的解决方案,与此同时不必牺牲代码质量和可测试能力。
  • MVVM Light Toolkit:该工具箱主要是用来加速创建和开发 MVVM 应用,适用于 WPF、Silverlight、Windows Store(RT)和 Windows Phone 平台。
  • Catel:Catel 是一个应用开发平台,主要着眼在 MVVM(WPF、Silverlight、Windows Phone 及 WinRT)以及 MVC(ASP.NET MVC)。Catel 的核心部分包含 IoC 容器、模型、校验、备忘录模式(memento)、消息中介(message mediator)、参数检查等。
  • UpdateControls:Update Controls 不需要你实现 INotifyPropertyChanged 接口或声明一个 DependencyProperty。可以将控件直接关联到 CLR 属性,这使得它完美贴合 MVVM 模式。
  • ReactiveUI:一个 .NET 下的 MVVM 框架,集成了反应性扩展框架(Rx),允许开发者使用 WPF、Windows Store Apps、WP8 或 Xamarin 创建优雅的、可测试的应用。
  • Okra App Framework:一个以 app 为中心的 Windows 8.1 MVVM 框架,融合依赖注入,包含一套完整的 Visual Studio MVVM 模板。
  • WPF Application Framework (WAF):一个轻量级的框架,能够帮助你创建结构优雅的 WPF 应用。支持你使用分层架构和 MVVM 模式。
  • MVVMCross:跨平台mvvm 移动开发框架,针对 WP7 和 WP8 的 WPF/Silverlight、Android 的 Mono、iOS 的 MonoTouch 以及 WPA8.1/Windows 8.1 商店应用的 Windows Universal 项目。它使用可移植类库(PCL)来支持可维护的跨平台 C# 原生应用。
  • Stylet:最小化 MVVM 框架(参考 Caliburn Micro),包含了良好的文档、高覆盖率的测试以及自带的 IoC 容器。
  • Gemini:类似 Visual Studio Shell 的 IDE 框架。基于 WPF、AvalonDock 和 Caliburn Micro 构建。

Office

  • ClosedXML:ClosedXML 能够让开发人员更便捷地创建 Excel 2007、2010 文件。
  • NPOI:该项目是 .NET 版本的 POI 项目,原始的 Java 版本位于http://poi.apache.org/ 
  • EPPlus:EPPlus 是一个 .net 类库,用于读写 Open Office Xml 格式的 Excel 2007、2010 文件(xlsx)。
  • Open XML SDK:Open XML SDK 是一个开源库,用于处理 Open XML 文档(包括 DOCX、XLSX 和 PPTX)。

ORM

  • Entity Framework:对象关系映射器(ORM)框架,让 .NET 开发人员使用领域特定的对象来处理关系型数据。
  • BL Toolkit:.NET 平台的业务逻辑工具箱。
  • Dapper:一个超小型、快捷轻便的 ORM 框架。
  • Dapper Extensions:小型类库,对 Dapper 的功能进行补充,为你的简单传统 CLR 对象提供基础的 CRUD(Get、Insert、Update、Delete) 操作。
  • NHibernate:NHibernate 对象关系映射器(移植自 Java 平台的 Hibernate)。
  • Fluent NHibernate:便捷、无需使用 XML、编译安全、自动化、基于约定命名的 NHibernate 映射库。
  • FluentMigrator:.net 下的便捷的迁移框架。
  • ServiceStack.OrmLite:轻量、简单、快速、基于命名约定的 POCO ORM。
  • Massive:小型、令人愉悦的数据访问工具,始终关爱你直到永远。
  • LINQ to DB:最快的 LINQ 数据库访问库,简单、轻量、快速、类型安全,在你的对象(POCO)和数据库之间搭建桥梁。

包管理(Package Management)

PDF

  • ITextSharp:iText 是一个PDF库,用于创建、修改、检查和维护 Portable Document Format(PDF)格式的文档 [$]**[开源软件免费]**

Profiler

推送通知(Push Notifications)

  • PushSharp:服务器端的推送通知类库,支持 iOS、OSX、Android、Chrome、Windows Phone、Windows 8、Backberry 和 Amazon 设备。

队列(Queue)

  • NServiceBus:.NET 平台下最流行的服务总线。
  • Hangfire:在 ASP.NET 应用中,超简单地实现自主引导(fire-and-forget)、延迟和周期重复任务。
  • RabbitMQ.NET:AMQP 客户端的 C# 实现,通过 WCF 绑定到已有的 AMQP 服务。
  • NetMQ:NetMQ 是 ZeroMQ 纯 C# 移植版本。
  • MassTransit:MassTransit 是一个精简服务总线(lean service bus)的实现,使用 .NET Framework 来构建松耦合应用程序。
  • Rebus:Rebus 是一个 .NET 平台的精简服务总线和 NServiceBus、MassTransit类似,只不过更加精简。
  • EasyNetQ:易于使用的 RabbitMQ .NET API。
  • Warewolf ESB:易于使用的服务总线和微服务平台,可以在一个可视化 IDE 中便捷的创建应用和服务。

响应式编程(Reactive Programming)

  • Rx.NET:Reactive Extensions (Rx)库使用观察者序列(observable sequences)和 LINQ 风格的查询操作,来进行异步和基于事件的程序开发
  • Dynamic Data:用于集合的响应式编程框架。

计划调度(Scheduling)

  • QuartzNet:Quartz 是 .NET 平台的企业级调度器。

SDK 和 API 客户端(SDK and API Clients)

  • AWS SDK:AWS SDK for .NET 让 .NET 开发者可以便捷地操作 Amazon Web Services。
  • Azure PowerShell:一组 PowerShell 命令行,让开发者和管理员开发、部署和管理 Microsoft Azure 应用。
  • Octokit.NET:.NET 平台下的 GitHub API 客户端库。
  • DropNet:Dropbox API 客户端开发库。

搜索(Search)

  • Elasticsearch .NET:Elasticsearch.Net & NEST。
  • PlainElastic.Net:ElasticSearch 的一个简单的 .Net 客户端。
  • SolrNet:.Net 平台下的 Solr 客户端。
  • Lucene.net:Lucene.Net 是 Lucene 搜索引擎库的移植,使用 C# 编写,面向 .NET 环境的用户。

序列化(Serialization)

  • Protobuf.NET:Protocol buffers 是 Google 使用的二进制序列化格式,在 Google 数据通讯中大量使用。
  • Json.NET:.NET 平台下流行的、高性能 JSON 框架。
  • ServiceStack.Text:在 servicestack.net 中使用的 JSON、JSV、CSV 文本序列化器。
  • Msgpack-Cli:MessagePack 的 CLI 实现。
  • Jil:.NET 平台下快速的 JSON 序列化器,基于 Sigil (在 StackOverflow 中使用)。
  • ProtoBuf:根据 .proto 规范,为 protocol buffer 序列化内容生成 C# 代码。
  • F# Data:F# 类型提供程序,访问 XML、JSON、CSV 和 HTML 文件(基于样例文档),以及访问 WorldBank 数据。
  • Bond:跨平台框架,用于处理系统化(schematized)数据。支持跨平台的序列化、反序列化,以及强大的通用机制来高效处理数据。

状态机(State machines)

  • Stateless:直接使用 .NET 代码创建一个状态机和轻量的基于状态机的工作流。
  • Automatonymous:.Net 平台的状态机库,允许你编写流式API风格的状态机。

静态网站生成(Static Site Generators)

  • Pretzel:.NET 平台下的网站生成工具(包含后续的一些功能)。
  • Sandra.Snow:.NET 平台的静态网站生成工具,借鉴 Jekyll。
  • Wyam:简单易用、高度模块化、拥有强大配置能力的静态网站生成工具。

风格指南(Style Guide)

模板引擎(Template Engine)

  • RazorEngine:基于微软 Razor 解析器引擎的开源模板引擎。
  • Nustache :无逻辑模板的开源库。
  • DotLiquid:Ruby Liquid 模板语言的 C# 移植版本。

测试(Testing)

  • AutoFixture:AutoFixture 是一个用于 .NET 的开源框架,用于简化单元测试中的设置(Arrange)阶段。
  • FakeItEasy:.NET 平台的一个简单的 mocking 库。http://fakeiteasy.github.io
  • Fluent Assertions:一组 .NET 扩展方法,可以让你更自然地指定 TDD 或 BDD 风格测试的期望值。
  • Fuchu:F# 的单元测试库,通过 tests-as-values 机制让你更容易创建领域特定语言(DSL)。
  • Machine.Specifications:Machine.Specifications (MSpec)是一个上下文、规范框架,忽略了语言本身的干扰,简化了测试。
  • Moq:.NET 平台下最流行和友好的 mocking 框架。
  • NBuilder:快速创建测试对象。
  • NSubstitute:一个友好的 .NET mocking 框架。
  • NUnit
  • Rhino Mocks:.NET 平台的动态 Mocking 框架
  • Shouldly:Shouldly 是一个断言(assertion)框架,主要功能是在断言失败时,给出简单明了并且友好的错误信息。
  • SpecFlow:将业务需求绑定到 .Net 代码。
  • xUnit:xUnit.net 是一个 .NET 平台下免费、开源、专注社区的单元测试框架。

交易(Trading)

Visual Studio 插件(Visual Studio Plugins)

  • Web Essentials:Web Essentials 为 Visual Stduio 扩展了大量的特性,提供了 web 开发人员盼望多年的功能。
  • VsVIM:Visual Studio 中的 VIM。
  • Nuget Package Manager:NuGet 是微软开发平台(包括 .NET)的包管理器。
  • SideWaffle:Visual Studio 2012、2013 中的一组项目模板集合,让 web 开发人员更加轻松。
  • Resharper:Visual Studio 开发人员生产力工具 [$]
  • Refactoring Essentials:开源 C# 和 VB.NET 重构扩展,包括代码最佳实践分析器。
  • CodeContracts:.NET CodeContracts 工具源代码。
  • Git Diff Margin:在 Visual Studio 滚动条区域实时显示当前文件在 Git 上的差异。
  • Productivity Power Tools:一组 Visual Studio 专业版(及更高版本)的扩展,用于提高开发人员的生产力。

Web 框架(Web Frameworks)

  • ASP.NET MVC:ASP.NET 是一个免费的 web 框架,用于创建优秀的 web 站点和应用程序。
  • FubuMVC:.NET 平台下前端控制器(front-controller)风格的 MVC 框架。http://fubuworld.com/fubumvc
  • NancyFx:在 .Net 和 Mono 平台上创建 HTTP 服务的一个轻量级、非正式的框架。
  • IISNode:在 IIS 中宿主 NodeJS 应用程序。
  • Suave.IO:一个框架/库/web 服务器,当你看到你使用优美的 F# 编写的代码提前完成了你的项目时,它的存在会让你喜极而泣。(一个用 F# 编写 Web 应用的框架和服务器端)

Web 服务器(Web Servers)

  • EmbedIO :基于 Mono 编写的跨平台的 Web 服务器。
  • XSP:Mono 中的 ASP.NET 宿主服务器。该模块包含了 Apache 模块、FastCGI 模块,可以用于挂载到其它 web 服务器或是作为一个测试用的独立服务器存在(类似微软的 Cassini 项目)。

WebSocket

  • SignalR:ASP.NET 库,开发者可以通过它在 web 应用程序中非常简单地实现实时功能。
  • Fleck:Fleck 是一个 C# 实现的 WebSocket 服务器。分支自 Nugget 项目。
  • Websocket-Sharp:WebSocket 协议的 C# 实现,包含客户端和服务器端。
  • XSockets:提供了一组工具,可以在微软 .NET 等平台上构建一个实时应用程序。
  • WebSocket4NET:.NET 2.0+、Xamarin、Mono、Silverlight、Windows Phone 以及 WinRT 下的 WebSocket 客户端。

Windows 服务(Windows Services)

  • TopShelf:一个简单的服务宿主框架,使用 .NET 构建 Windows 服务。

其他列表(Other Lists)

Git基础概念与Flow流程介绍 - 陈晨_软件五千言 - 博客园

mikel阅读(841)

来源: Git基础概念与Flow流程介绍 – 陈晨_软件五千言 – 博客园

本文会分为两部分讲解,第一部分介绍Git的基础概念、常见客户端、常用命令,是一个基础说明。第二部分介绍Git的管理流程,主要是GitFlow,Github Flow、Gitlab Flow和ExeFlow四种。

Git相关

基本概念

Git是一个基于GNU协议的开源分布式版本控制系统,是由Linux的创始人Linus Torvalds在2005为了进行Linux内核的研发时自己编写的。不同于之前的大部分客户端-服务器模式的代码管理系统,在每台电脑上的每个Git目录都是一个完整的代码仓库,包含了历史所有的提交记录并且可以完整查看所有版本,而不需要有服务器或者网络连接。
2019年9月Git的当前的最新版本为2.23.0.

常见客户端

TortoiseGit

TortoiseGit,就是我们俗称小乌龟。他们为Svn也提供了很优秀的windows客户端。而且这是一个开源的软件,当前最新的版本位2.8.0 。
安装之后会自动注入系统的右键菜单,在任何路径右击之后都会出现

如图的选项,这是在非git仓库时。当在git仓库右键点击时完整菜单如下

查看提交记录

Sourcetree

Sourcetree是Atlassian提供的一款免费的Git客户端工具,目前的版本是3.2.6。
安装之后直接打开客户端使用,整体界面大致如图。

Intellij Idea

Idea是Java开发非常热门的IDE,其中也集成了多种SCM工具,自然也包括Git的客户端,整体的使用感受还是不错的。

命令行

最后名不符实的再加上一个命令行吧,有很多同学还是习惯手敲命令的。

常用命令

存储区域

Git主要有四块存储区域:

  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库
    工作区是本地计算机存储,平时项目代码存储的地方。
    暂存区也是在本地存储,当你在修改代码但是还没有执行commit操作时临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息。
    仓库区就是本地版本库,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本。
    远程仓库就是比如Gitlab或者Github等。
    几个存储区域之间的工作流程一般是这样:

还有另外一个版本的流程:

命令之 add & commit &push

add & commit 实际上就是一个文件加入版本控制的过程,在受git管理的目录下新建一个文件,首先要将它标记为add,接下来commit到本地的仓库,就完成了本地的版本管理。
之后可以选择是否执行push操作,如果执行就是将本地的变更提交到远程服务器上,这样别人就可以获取到你的更新了。

命令之 branch & checkout

branch是常见的创建分支应用的语法,git和传统的cs模式的SCM的工具相比branch的代价是非常小的,影响也是非常小的。git的branch可以只存在与本地,轻易和合并与删除。

上图就是很多Flow流程中可能存在的多个分支(实际上这些分支本地和远程都是存在的)。
我们本地的一个项目可能就会存在多个分支,我们使用checkout命令,签出一个分支之后,环境中的文件都会变为该分支的相关文件。

命令之 cherry-pick

pick是挑选的意思,我们看一下示意图

这是要求将 bugFix 分支上的 C3 、side 分支上的 C4 以及another分支上的C7通过cherry-pick的形式拿到 master分支上。这是很典型的一个使用cherry-pick的场景,bug修复的合并。

命令之 merge & rebase

通过上面的branch和push,我们已经切出不同的分支并且提交了,接下来就是要合并我们的提交内容到主分支上,这时我们可能会面临两个命令选择 merge和rebase。
这两个命令有什么区别呢?我们通过一个模拟界面来看一下:
使用merge命令

使用rebase命令

建议:
rebase会把你当前分支的 commit 放到公共分支的最后面,所以叫变基。就好像你从公共分支又重新拉出来这个分支一样。
merge 会把公共分支和你当前的commit 合并在一起,形成一个新的 commit 提交。
为什么建议使用rebase,因为通常我们是feature分支基于master分支进行rebase,master是长期固定分支,feature是临时分支,我们不希望因为临时分支的变更影响到master分支的提交记录,而且通常情况下master分支是锁定不允许直接提交的。所以建议大家使用rebase,这样能够在不影响master分支的情况下还能够合并最新的内容。

Flow相关

我们这里主要讲四种flow进行对比,GitFlow,GitHub Flow,GitLab Flow,ExeFlow
我对于项目管理的核心理解有两点:

  • 需求边界
  • 时间边界
    也就是一次迭代,就是在规定的时间边界与规定的需求边界范围内完成。如果两方面都能够保质保量,自然没有问题。其实那种Flow我觉得差异不算很大。但是当其中有一点无法满足时,不同的Flow能够达到的效果就不同了。这点自然是不同的企业有不同的场景与要求,有的企业可以拖延时间(工期),但是一定要保证规划内的需求全部开发测试完成。有的公司则是对时间节点的要求更高,可以减少某些需求,但是一定要在规定时间点发布上线。我们在理解每种flow时,也会对项目管理的两点影响说明一下。

GitFlow

GitFlow来源应该是 Vincent Driessen 在2010年1月发表的这篇《A successful Git branching model》,基本是现在Git中最出名的流程管理方法了。

我整理了其对应的流程图,其中加粗的是指长期分支。可以看到master和develop是其中的长期分支,对外的发布基于master分支,对内的研发基于develop分支。需要发布版本时,是从develop分支切出release分支,最终合并至master。所以我理解在GitFlow中,最重要的实际上是develop分支,它承载了实际功能的开发修正和发布,而master最大的作用有两个,一是发布,二是热修正(hotfix)。
GitFlow的流程我理解时偏重时间,而非需求的。也就是说如果临近发布时间,而需求没有完成,其唯一的选择就是延期,因为按照流程,release分支是从develop分支切出,如果测试的环境是基于develop,很可能是没有打到发布标准的,所以只能等打到发布标准后才能进行后续步骤。但是好处在于开发只用基于develop分支,很难出现漏合并代码或者bug修正之类的情况。我也觉得这份流程在大型企业内部是比较难推进的,因为它的环境管理还不够,它的release、master分别对应预发布和正式环境,develop我觉得还不够清晰,因为这块我们通常是需要两个环境,开发和测试环境。所以对于多环境管理我觉得是需要根据不同的企业的要求进行改造的。

GitHub Flow

顾名思义,GitHub Flow就是GitHub推荐的管理流程

可以看到,只能用简单2字形容,只有master和feature两个分支概念,其中master是长期分支。所以我还加上了Github官方的一些说明。
我觉得这不是一份可以用于企业级复杂项目管控的Git 流程,最基础的就是没有多环境管理,无法想象多角色配合时只基于master分支如何进行。master究竟用于测试环境还是随时可发布的正式环境呢?其次这份流程应该也是偏重于需求而非时间。理由和GitFlow一样。
我觉得GitHub推出的这份flow主要是应用于其平台上大部分托管的小型开源项目,并且尽量结合平台提供的其他组件。对于企业内部不够合适。

GitLab Flow

GitLab Flow自然是基于GitLab环境的flow管理流程

这个Flow实际上最核心的分支是基于master,生产环境是production,预发布环境是pre-production。我觉得对于多环境的问题实际上和GitFlow很相似,也是开发和测试环境不够清晰。
提交、审核基于GitLab的MR进行。截图的部分就是GitLab官方的MR的说明。
GitLab Flow与GitFlow的差别我觉得仅仅在于master与develop的分支命名,还有就是加入了MR(但是由于GitFlow不基于任何服务端环境,所以这块是可以整合的),问题也很相似。

ExeFlow

由于上面几种Flow在时间和需求都是选择了需求而延迟了时间,我也顺便讲一下选择时间而放弃一些需求的Flow流程吧。
这是我之前一家公司的流程设计,服务端是GitLab。
实际工作场景:
我们的系统由于主要是面向大型企业内部使用,存在复杂的分发流程和权限控制,经过长时间的累积业务模型也很复杂各种关联和引用,所以有一些大型任务的开发周期可能会比较长,到达2-3个月的周期。
我们的迭代周期正常是1个月。流程大概如下:

  • 上月末进行迭代计划评估与安排,这里会确认下月迭代目标的Story内容与数据。各自主管进行子任务的拆分评估与排期。
  • 开发时间一般是2周,我们基本是会在月中设定研发截止线,所有研发任务要在截止线前完成提测。期间有完成的任务可以随时提测。【涉及分支:Feature,Local,Develop】
  • 完整的系统集成测试时间一般是安排在第三周,测试会进行全面的测试。本周研发的主要任务一方面是处理Bug,一方面可以介入下月迭代大项的需求说明与分析。【涉及分支:Feature,Local,Develop】
  • 第四周的前三天,我们会切出预发布的分支在第四周周一时,会给出明确本次能够上线的Story List,不在清单内的都不允许合并只预发布环境(也就是我们实际上运行需求在预发布之前仍旧有变更,只要测试人员通过了集成测试环境,就可以合并并且发布),本次发版的具体内容和通知也是当天发出。【涉及分支:Feature,Release】
  • 发版一般安排在当月的最后一个周四(为了防止有线上问题,所以不能是周五第二天会没有人员值守)。【涉及分支:Release,Master】

流程图:

这里有几个特点:

  • develop对于测试环境,local对应开发联调环境。
  • 所有的分支起始时都是由master分支切出
  • 固定长期分支有三个:master,develop,local
  • 所有的分支不能直接向master提交,必须通过release验证后合并至master。
  • master分支锁定需要权限审核提交
  • release是临时分支,指定时间从master切出,直接从feature合并。

先来说说有点,根据之前提供的流程来看,我们企业要求的是时间保障而需求可以调整。所以在指定时间节点到来时,我们会确定一份可以发布的需求清单,根据这份清单来合并至release,从而最终能够发布。这是一个比较典型的保时间点,放需求的流程管理。
但是这个会有下面几个问题:

  • develop和local分支从master切出后,长期和master不同步,所以需要定期的rebase,否则会产生很多环境不一致导致的问题。
  • hotfix或者feature提交之后的bug修正,都是需要提交最多三个分支的。研发人员的操作会非常复杂。

但是由于这个流程解决了我们实际生产流程中的问题,所以整体在控制和配合上带来的收益远大于我们的障碍。所以这套流程还是长期推广了起来。

总结

Git是一个工具,Flow是一套管理流程。最重要的并不是我们在用什么,而是我们解决了什么问题?我们试图解决的问题到底是不是真实的问题,还是我们一厢情愿臆想出来的。整个过程,实际上是我们针对企业的环境和问题进行发掘的解决的过程,而不是单纯挑选工具和流程的过程。
记得先问自己,我现在到底要解决的是什么问题?