C# .Net中获取Windows AD域用户缺失的故障解决

因为公司的OA系统使用的Windows域账号集成认证,近期新来的同事账号却没能自动导入账号到OA系统,花了快一天时间发现并解决了问题。

Windows AD域用户清单获取不全,原来这里的写法有问题:ds.Filter = “(objectClass=user)”改为ds.Filter = “(&(objectClass=user))”

尽管问题很简单,但因此收获了一篇很全面的关于在C#中访问活动域的技术文章,原文地址:Howto: (Almost) Everything In Active Directory via C#

.NET Core、.NET Framework与XAMARIN三兄弟

.NET Core出来了很久了,时常关注,但一直未在项目中实操,但近期开始在Xamarin平台做些企业级应用开发。

相信很多读者也是Windows平台下工作的居多,今天就来普及一下概念,.NET Core就是.NET Framework的开源、跨平台版本,前者即可跑在Windows,也可跑在各种Linux(Unix Like)系统上,后者只能跑在Windows上。

为了同时支持2者,微软抽象出来一个标准库。.NET Core 与 .NET Framework 都必须实现标准库的API 。而Xamarin是跨平台的移动端解决方案,.NET层基于.NET STANDARD,目标市场不再是应对移动互联网,而是企业级移动互联网。

就这样.NET Core、.NET Framework、XAMARIN这三兄弟,分别为不同的平台服务。(还有一种微软7龙珠的说法,下一篇再来介绍)

八元素 Tuple 的最后一个元素必须为 Tuple。

标题是来自C#中的一个程序报错提示,让我困惑的是定义元组Tuple时,超过8个,就算不为第8个成员创建新的Tuple,编译阶段并不报错,但执行时报错。

趟过了这个坑,再来回顾一下元组:元组就是一些对象的集合,在我们编程时,比如一个人的信息,我们常常创建一个Person类去描述一个人,传统的做法如下:

public class Person{
public int ID{get;set;}
public string Name{get;set;}
}

Person p=new Person(){Id=1,Name=’Troy’};
Console.WriteLine(p.Name);

那么我们使用元组可以怎么做呢?如下所示

//直接使用元组对象,不需要创建自定义的对象
Tuple<int,string> p=new Tuple<int,string>(1,’Troy’);

//Item1 代表第一个,Item2代表第二个,每一个元组对象都有一个默认的item属性Console.WriteLine(p.Item2);

由此可见,元组一个很方便的用途就是不用为了一些简单的结构或对象而去新建一个类了。
需要特别注意的是Tuple最多支持8个成员,如果成员超过了8个我们必须将第8个成员当成一个元组,通过元祖的嵌套去完成。

举例:Tuple<string, int, int, int, int, int, int, Tuple<string, int, int>>

红色部分是第8个,这里的第8个成员又是一个元组。

在Global.asax中获取Session的注意事项

几年前给朋友珠宝公司开发过一套旺财珠宝库存管理系统,用得还是web Form老技术,但是更多的走Ashx+Ajax,但前端可是HTML5+jQuery+BootStrap等新技术,所以不论功能还是用户体验,都能很完美的满足用户要求(用户才不管你用的是什么技术,先进的和古老的都必须解决他的问题,然后还需要好用)。近期特别反馈说有些页面比较慢,我觉得用了几年了,数据库就近2个G了,可能是数据库查询的问题,也可能是程序执行的问题,也可能用户网络问题。数据库可以在服务器上用Sql Server Profiler进行查询分析,但页面上还得做点跟踪。于是就用Global.asax来实现,本来很方便的,但为了获取当前登录用户,需要在Global.asax中获取Session,花了点时间才搞定,记录下来分享一下。

本来想在Application_BeginRequest或者Session_Start里面获取的,可怎么也获取不到,于是翻看MSDN了解Global.asax的事件及执行顺序,在Application_AcquireRequestState中才获取到。

    protected DateTime StartDateTime;

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        //开始执行时间
        StartDateTime = DateTime.Now;
    }
    protected BaseUserInfo CurrentUserInfo;
    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        if (Utilities.UserIsLogOn())
        {
            CurrentUserInfo = Utilities.GetUserInfo();
        }
    }

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        DateTime endDateTime = DateTime.Now;
        TimeSpan ts = endDateTime - StartDateTime;
        //5秒以上的慢页面进行记录
        if (ts.TotalMilliseconds >= 5000)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("时间:" + endDateTime.ToString("yyyy-MM-dd hh:mm:ss fff") + ",当前请求URL:" + HttpContext.Current.Request.Url + ",请求的参数为:" + HttpContext.Current.Request.QueryString + ",页面加载的时间:" + ts.TotalMilliseconds + " 毫秒");
            if (CurrentUserInfo != null)
            {
                sb.Append(",用户:" + CurrentUserInfo.UserName);
            }
            FileUtil.WriteMessage(sb.ToString(), BaseSystemInfo.StartupPath + "//Log//Slow/" + DateTime.Now.ToString(BaseSystemInfo.DateFormat) + ".txt");
        }

    }

通过上述代码就可方便的获取哪些页面比较慢,何时、何人、何参数、何地(IP)发生的。

2018-05-11 03:33:18 947:[当前请求URL:Modules/WMS/ItemMaster/ItemMasterPlan.aspx;请求的参数为:;页面加载的时间:8151.3672 毫秒]
2018-05-11 04:09:25 181:[当前请求URL:Modules/WholesaleWMS/tools/WholesaleBPStatement.ashx?action=RefreshStatement;请求的参数为:action=RefreshStatement;页面加载的时间:19720.7031 毫秒]
2018-05-11 05:18:10 486:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16742.1875 毫秒]

2018-05-12 10:33:59 305:[当前请求URL:Modules/WMS/PurchaseDemand/PurchaseDemandAdmin.aspx;请求的参数为:;页面加载的时间:9375.9765 毫秒]
2018-05-12 10:49:19 497:[当前请求URL:Modules/WMS/ItemMaster/ItemMasterPlan.aspx;请求的参数为:;页面加载的时间:5278.3203 毫秒]
2018-05-12 01:24:36 673:[当前请求URL:Modules/WMS/InboundOrderLine/InboundOrderLineListReport.aspx;请求的参数为:;页面加载的时间:11416.0156 毫秒]
2018-05-12 01:24:42 045:[当前请求URL:Modules/WMS/InboundOrderLine/InboundOrderLineListReport.aspx;请求的参数为:;页面加载的时间:6209.9609 毫秒]
2018-05-12 01:24:42 611:[当前请求URL:Modules/WMS/InboundOrderLine/InboundOrderLineListReport.aspx;请求的参数为:;页面加载的时间:10142.5781 毫秒]
2018-05-12 04:39:35 251:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16623.0469 毫秒]
2018-05-12 04:51:31 401:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16648.4375 毫秒]
2018-05-12 04:57:15 362:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16552.7343 毫秒]

最后附上MSDN上对Global.asax的解释:

按执行顺序来解释一下Global.asax.cs中相应的事件处理方法的含义

  1. Application_BeginRequest:BeginRequest是在收到Request时第一个触发的事件,这个方法自然就是第一个执行的了。
  2. Application_AuthenticateRequest:当安全模块已经建立了当前用户的标识后执行。
  3. Application_AuthorizeRequest:当安全模块已经验证了当前用户的授权时执行。
  4. Application_ResolveRequestCache:当ASP.NET完成授权事件以使缓存模块从缓存中为请求提供服务时发生,从而跳过处理程序(页面或者是WebService)的执行。这样做可以改善网站的性能,这个事件还可以用来判断正文是不是从Cache中得到的。
  5. Application_AcquireRequestState:当ASP.NET获取当前请求所关联的当前状态(如Session)时执行(真是拗口啊,msdn上就这样写的,我自己想不出什么好句子了)。
  6. Application_PreRequestHandlerExecute:当ASP.Net即将把请求发送到处理程序对象(页面或者是WebService)之前执行。这个时候,Session就可以用了。
  7. Application_PostRequestHandlerExecute:当处理程序对象(页面或者是WebService)工作完成之后执行。
  8. Application_ReleaseRequestState:在ASP.NET执行完所有请求处理程序后执行。ReleaseRequestState事件将使当前状态数据被保存。
  9. Application_UpdateRequestCache:在ASP.NET执行完处理程序后,为了后续的请求而更新响应缓存时执行。
  10. Application_EndRequest:同上,EndRequest是在响应Request时最后一个触发的事件,这个方法自然就是最后一个执行的了。

再附上两个无顺序的,随时都可能执行的

  1. Application_PreSendRequestHeaders:向客户端发送Http标头之前执行。
  2. Application_PreSendRequestContent:向客户端发送Http正文之前执行。

C#开发代码规范中PascalCase和camelCase的两个有用的方法类

#region 代码规范风格化
        /// <summary>
        /// 转换为Pascal风格-每一个单词的首字母大写
        /// </summary>
        /// <param name="fieldName">字段名</param>
        /// <param name="fieldDelimiter">分隔符</param>
        /// <returns></returns>
        public static string ConvertToPascal(string fieldName, string fieldDelimiter)
        {
            string result = string.Empty;
            if (fieldName.Contains(fieldDelimiter))
            {
                //全部小写
                string[] array = fieldName.ToLower().Split(fieldDelimiter.ToCharArray());
                foreach (var t in array)
                {
                    //首字母大写
                    result += t.Substring(0, 1).ToUpper() + t.Substring(1);
                }
            }
            else if (string.IsNullOrWhiteSpace(fieldName))
            {
                result = fieldName;
            }
            else if (fieldName.Length == 1)
            {
                result = fieldName.ToUpper();
            }
            else
            {
                result = fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
            }
            return result;
        }
        /// <summary>
        /// 转换为Camel风格-第一个单词小写,其后每个单词首字母大写
        /// </summary>
        /// <param name="fieldName">字段名</param>
        /// <param name="fieldDelimiter">分隔符</param>
        /// <returns></returns>
        public static string ConvertToCamel(string fieldName, string fieldDelimiter)
        {
            //先Pascal
            string result = ConvertToPascal(fieldName, fieldDelimiter);
            //然后首字母小写
            if (result.Length == 1)
            {
                result = result.ToLower();
            }
            else
            {
                result = result.Substring(0, 1).ToLower() + result.Substring(1);
            }
            
            return result;
        }
        #endregion

近期为统一Oracle数据库下大写表名和字段,以及下划线_分隔符的特点,升级了旺财C#.NET代码生成器,将规范化的代码写了2个方法用于Camel和Pascal风格化,用于有表字段分隔符的场景。

骆驼拼写法,英文名CamelCase。分为两种:

第一个词的首字母小写,后面每个词的首字母大写,叫做“小骆驼拼写法”(lowerCamelCase);

第一个词的首字母,以及后面每个词的首字母都大写,叫做“大骆驼拼写法”(UpperCamelCase),又称“帕斯卡拼写法”(PascalCase)

两者核心差别:PascalCase第一个单词的首字母大写,而CamelCase第一个单词的首字母小写。

C#开发中Windows域认证登录2(扩展吉日嘎拉GPM系统)

上午写了一篇《C#开发中Windows域认证登录》,然后跟吉日嘎拉沟通了一下,还是把这个Windows AD用户登录的功能扩展到DotNet.Business中,重新命名为LDAP方式的登录,因为需要引用System.DirectoryServices,暂时用不到此功能的朋友,可以exclude此文件(DotNet.Business\WebUtilities\Utilities.LogOnLDAP.cs)。

<br/>//-----------------------------------------------------------------<br/>// All Rights Reserved , Copyright (C) 2013 , Hairihan TECH, Ltd .<br/>//-----------------------------------------------------------------<br/><br/>using System;<br/>using System.Collections.Generic;<br/>using System.Configuration;<br/>using System.Data;<br/>using System.Text;<br/>using System.Web;<br/>using System.Web.Caching;<br/>using System.Web.Security;<br/>using System.DirectoryServices;<br/>using DotNet.Utilities;<br/><br/>namespace DotNet.Business<br/>{<br/>    /// <summary><br/>    /// LDAP登录功能相关部分<br/>    /// </summary><br/>    public partial class Utilities<br/>    {<br/>        // LDAP域用户登录部分:包括Windows AD域用户登录<br/>        #region public static BaseUserInfo LogOnByLDAP(string domain, string lDAP, string userName, string password, string permissionCode, bool persistCookie, bool formsAuthentication, out string statusCode, out string statusMessage)<br/>        /// <summary><br/>        /// 验证LDAP用户<br/>        /// </summary><br/>        /// <param name="domain">域</param><br/>        /// <param name="lDAP">LDAP</param><br/>        /// <param name="userName">域用户名</param><br/>        /// <param name="password">域密码</param><br/>        /// <param name="permissionCode">权限编号</param><br/>        /// <param name="persistCookie">是否保存密码</param><br/>        /// <param name="formsAuthentication">表单验证,是否需要重定位</param><br/>        /// <param name="statusCode"></param><br/>        /// <param name="statusMessage"></param><br/>        /// <returns></returns><br/>        public static BaseUserInfo LogOnByLDAP(string domain, string lDAP, string userName, string password, string permissionCode, bool persistCookie, bool formsAuthentication, out string statusCode, out string statusMessage)<br/>        {<br/>            DirectoryEntry dirEntry = new DirectoryEntry();<br/>            dirEntry.Path = lDAP;<br/>            dirEntry.Username = domain + "\\" + userName;<br/>            dirEntry.Password = password;<br/>            dirEntry.AuthenticationType = AuthenticationTypes.Secure;<br/><br/>            try<br/>            {<br/>                DirectorySearcher dirSearcher = new DirectorySearcher(dirEntry);<br/>                dirSearcher.Filter = String.Format("(&(objectClass=user)(samAccountName={0}))", userName);<br/>                System.DirectoryServices.SearchResult result = dirSearcher.FindOne();<br/>                if (result != null)<br/>                {<br/>                    // 统一的登录服务<br/>                    DotNetService dotNetService = new DotNetService();<br/>                    BaseUserInfo userInfo = dotNetService.LogOnService.LogOnByUserName(Utilities.GetUserInfo(), userName, out statusCode, out statusMessage);<br/>                    // 检查身份<br/>                    if (statusCode.Equals(Status.OK.ToString()))<br/>                    {<br/>                        userInfo.IPAddress = GetIPAddressId();<br/><br/>                        bool isAuthorized = true;<br/>                        // 用户是否有哪个相应的权限<br/>                        if (!string.IsNullOrEmpty(permissionCode))<br/>                        {<br/>                            isAuthorized = dotNetService.PermissionService.IsAuthorized(user
Info, permissionCode, null);<br/>                        }<br/>                        // 有相应的权限才可以登录<br/>                        if (isAuthorized)<br/>                        {<br/>                            if (persistCookie)<br/>                            {<br/>                                // 相对安全的方式保存登录状态<br/>                                // SaveCookie(userName, password);<br/>                                // 内部单点登录方式<br/>                                SaveCookie(userInfo);<br/>                            }<br/>                            else<br/>                            {<br/>                                RemoveUserCookie();<br/>                            }<br/>                            LogOn(userInfo, formsAuthentication);<br/>                        }<br/>                        else<br/>                        {<br/>                            statusCode = Status.LogOnDeny.ToString();<br/>                            statusMessage = "访问被拒绝、您的账户没有后台管理访问权限。";<br/>                        }<br/>                    }<br/><br/>                    return userInfo;<br/>                }<br/>                else<br/>                {<br/>                    statusCode = Status.LogOnDeny.ToString();<br/>                    statusMessage = "应用系统用户不存在,请联系管理员。";<br/>                    return null;<br/>                }<br/>            }<br/>            catch (Exception e)<br/>            {<br/>                //Logon failure: unknown user name or bad password.<br/>                statusCode = Status.LogOnDeny.ToString();<br/>                statusMessage = "域服务器返回信息" + e.Message.Replace("\r\n", "");<br/>                return null;<br/>            }<br/><br/>            <br/>        }<br/>        #endregion<br/><br/>    }<br/>}<br/>

前端的登录文件-SigninLDAP.aspx,代码较多可参考Signin.aspx。