升级VS2019 16.11.5引起的error CS0246: The type or namespace name ‘FieldDescription’ could not be found (are you missing a using directive or an assembly reference?)

昨晚上看到VS2022的新的Preview版本发布,就连同VS2019一起升级到了最新版。

可没想到的是,我有个项目中的DLL引用就开始编译报错了。

昨晚上弄了几个小时,删掉引用,重新添加也不行。从GitHub上下载老版本同样编译报错。

找同事试了一下,他用的老版本的VS 2019,没有编译问题。

那可以肯定的是我本地VS2019的问题!

于是上午用VS2022打开编译,同样也是报错。

后来我就到Project属性当中看到Reference Path,以前也没关注过这个的。尝试着增加了我外部引用的DLL路径。

你猜怎么着?

没报错了!

我看了一个修改后的GitHub的Git Changes提示也没有,原来这个设置是保存在了csproj.user文件中。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <ProjectView>ShowAllFiles</ProjectView>
  </PropertyGroup>
  <PropertyGroup>
    <ReferencePath>E:\Github\DotNet.WangCaiSoft\src\DLL\DotNet\</ReferencePath>
  </PropertyGroup>
</Project>

难道是我的ToolsVersion为Current的缘故?

哪位高手如果碰到类似情况,不让忘记这个error CS0246的报错,看看你的Reference Path增加上是否就解决了。

我还有一个猜测,就是我同时也安装了VS2022预览版的缘故?

Loading

.net framework 4.0的NuGet包制作

为啥做这个呢,因为要将公司内部的老的.NET程序所引用的DLL进行统一管理。这里服务端使用了一个叫做NuGetServer(官网:NuGet Server)的开源工具,部署在内部的Web服务器上。

制作NuGet包,我是先从.NET Standard 2.0的SDK风格的文件去创建的,特别容易。但是.NET 4.0这种废了好大周折!

试过直接通过DLL生成,但是会遇到包描述、版本等信息不自动更新的问题,最重要的DLL所引用的NuGet.org的包,不能自动包含进去。

也试过通过命令行进行每个单独的Project进行生成,也遇到包描述、版本等信息不自动更新的问题。NuGet.Org的包没问题。

最后呢使用Tools>External Tools(工具>外部工具)定义了一个命令。

要确保MSBUILD和NUGET好用,需要找到系统环境变量,添加路径(记得重启电脑,以便生效)。

找到Path项
1、增加:C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin
2、增加:E:\VS\DotNet.WMS.US\DLL\DotNet

第一个是MSBuild,如果你是VS 2019社区版,直接复制,如果不是,请找到相应的路径。
第二个是NuGet.exe的目录,请选择您自己的目录。

这块设置参考这篇文章:https://www.cnblogs.com/chenug/p/9290281.html

命令:del *.nupkg ; $roj=dir *.csproj ;MSBuild $roj; nuget pack $roj ; $nupkg= dir *.nupkg;
执行目录选择$(ProjectDir)

需要生成的时候,点击一下要生成的Project,然后就可以点击Tools > 4.0NuGet命令

稍等片刻就生成了。

这样生成的包在Project的根目录,但是版本和描述信息呢都不对,请自动修改文件名和文件内部的描述文件内容。

用啥软件打开,7-ZIP即可,因为包就是一个压缩包。

Loading

.Net下请求Infor LN ERP WebService的5种方式

随着这几年Restful API的兴起,Web API遍地都是。以前老的WCF、WebService等的SOAP的份额越来越少。但总有些古老的应用或者企业级如ERP应用还是通过Web Service的方式提供对外集成接口。Infor LN ERP就是其中一个。

我这几年尝试了以下5中方法,与LN进行对接。

1、客户端代理类
2、动态代理类
3、WebClient
4、WebRequest
5、HttpClient

但前两种已经被我放弃了,后三种呢其实都是HTTP Request,通过标准的写法都可以很方便的与LN对接,注意两点:

1、拼接发起请求的XML
2、LN WebService返回500报错的时候,报错信息的转换
3、不要直接用using的方式使用HttpClient,因为由来已久的高并发时无法释放资源的bug,会让你崩溃。

至于后面三个方法,你可以从网络上找到一大把的Util/Helper类库,我就不贴出来了。

Loading

微软序列化时出现k_BackingField怎么处理

当你用微软自带的System.Runtime.Serialization序列化类时,如果出现了json字段中有k_BackingField前缀,请不要见外,因为你没有设置好DataContract和DataMember属性,如果你想继续使用[Serializable],你也可以通过设置Global.asax进行全局设置。

在Application_Start()中加入下面一行代码: GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver { IgnoreSerializableAttribute = true };

Loading

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#

Loading

.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龙珠的说法,下一篇再来介绍)

Loading

八元素 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个成员又是一个元组。

Loading

在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正文之前执行。

Loading

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第一个单词的首字母小写。

Loading

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。

Loading