头像

在C#中生成与PHP一样的MD5 Hash Code

最近在对一个现有的系统进行C#改造,该系统以前是用PHP做的,后台的管理员登陆用的是MD5加密算法。在PHP中,要对一个字符串进行MD5加密非常简单,一行代码即可:

md5("Something you want to encrypt.")

直接调用md5()方法,然后将要进行MD5加密的字符串传进去,就可以得到返回的hash code。在C#中应该也会有对应的算法吧!对吗?我首先尝试了下面的代码,结果得到的hash code和PHP不一样。

public static string MD5(string stringToHash)
{
    return FormsAuthentication.HashPasswordForStoringInConfigFile(stringToHash, "md5");
}

所以,我们不得不借用C#的MD5CryptoServiceProvider对象自己写代码进行转换。

1. 实例化MD5CryptoServiceProvider对象

2. 将字符串转换成byte数组

3. 使用MD5CryptoServiceProvider对象的ComputeHash()方法将byte数组进行加密,返回转换后的byte数组

4. 在讲byte数组转换成字符串之前,还需要对其进行遍历并做如下转换:

myByte.ToString("x2").ToLower()

然后,你才能得到和PHP中一样的MD5 hash code。为什么在.NET中要这么麻烦,或许这也是为什么那么多的开发人员仍然热衷于PHP开发的理由之一,每一门编程语言都有它自身的魅力,也都有它存在的意义!

基于上面的讨论,完整的代码如下:

public static string MD5ForPHP(string stringToHash)
{
    var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
    byte[] emailBytes = Encoding.UTF8.GetBytes(stringToHash.ToLower());
    byte[] hashedEmailBytes = md5.ComputeHash(emailBytes);
    StringBuilder sb = new StringBuilder();
    foreach (var b in hashedEmailBytes)
    {
        sb.Append(b.ToString("x2").ToLower());
    }
    return sb.ToString();
}

或者,你也可以把上面的方法写成一个C#扩展方法,只需要修改方法签名即可。

public static string MD5ForPHP(this String, string stringToHash)
{
    // Your code here.
}

PHP程序和C#程序在许多方面都会涉及到格式之间的转换,如果运行PHP的服务器是UNIX类型的,则还会存在日期格式之间的转换。下面的两个方法展示了如何将UNIX时间转换成C# DateTime以及如何将C# DateTime转换成UNIX时间。

public static DateTime UnixTimeStampToDateTime(long unixTimeStamp)
{
    // Unix timestamp is seconds past epoch
    DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
    return dtDateTime.AddSeconds(unixTimeStamp);
}

public static long DateTimeToUnixTimeStamp(DateTime datetime)
{
    TimeSpan span = (datetime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc));
    return (long)span.TotalSeconds;
}
头像

无法序列化会话状态。在“StateServer”或“SQLServer”模式下,ASP.NET 将序列化

开发asp.net应用时,修改web.config中的SessionState节点。

<sessionState mode=“StateServer” stateConnectionString=“tcpip=127.0.0.1:42424″ sqlConnectionString=“data source=127.0.0.1;Trusted_Connection=yes” cookieless=“false” timeout=“120”/>

<sessionState mode=“InProc” stateConnectionString=“tcpip=127.0.0.1:42424″ sqlConnectionString=“data source=127.0.0.1;Trusted_Connection=yes” cookieless=“false” timeout=“120”/>

InProc模式
优点:获取session状态的速度快,session状态直接存储在iis的进程中。
缺点:易丢失,经常需要重新登录

StateServer模式
优点:session状态单独存储在一个进程中,不会因为iis或者应用的重启而丢失状态
缺点:获取session状态的速度比InProc慢一些,毕竟是两个不同的进程。

在开发的时候,对应用有一点修改,就会导致应用的重启,这时候如果使用InProc模式
,那么每次都需要重新登录,比较浪费时间.建议使用StateServer模式。并在iis里面设置超时时间长一些。

注:使用StateServer模式的时候
1、要开启“ASP.NET State Service”服务(设为“自动”)

2、如果stateConnectionString的值不是127.0.0.1或者localhost等代表本地地址的值,需要修改注册表:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state \Parameters 节点 → 将 AllowRemoteConnection 的键值设置成“1”(1 为允许远程电脑的连接,0 代表禁止)→ 设置 Port (端口号)

3、session中存储非序列化的对象,如果违反会抛出  无法序列化会话状态。在“StateServer”或“SQLServer”模式下,ASP.NET 将序列化会话状态对象,因此不允许使用无法序列化的对象或 MarshalByRef 对象。如果自定义会话状态存储在“Custom”模式下执行了类似的序列化,则适用同样的限制。这样的异常。如果向session存储自定义的对象,那么该对象的类上一定要加上[Serializable]注释。

头像

entity framework 实现按照距离排序

纯SQL语句实现方法:

SELECT
    es_name,
    es_lon,
    es_lat,
    ROUND(
        6378.138 * 2 * ASIN(
            SQRT(
                POW(
                    SIN(
                        (
                            30.611842 * PI() / 180 - es_lat * PI() / 180
                        ) / 2
                    ),
                    2
                ) + COS(30.611842 * PI() / 180) * COS(es_lat * PI() / 180) * POW(
                    SIN(
                        (
                            104.074666 * PI() / 180 - es_lon * PI() / 180
                        ) / 2
                    ),
                    2
                )
            )
        ) * 1000
    ) AS distance_um
FROM
    c_ershuai
ORDER BY
    distance_um ASC

 

 

但是我比较习惯使用 entity framework,于是我就想着能不能用 entity framework 实现按照距离排序。

 

以下是我采用的方案

首先定义一个接口,用来表示具有经纬度信息的实体。

    /// <summary>
    /// 具有经纬度
    /// </summary>
    public interface IHasLngAndLat
    {
        /// <summary>
        /// 经度
        /// </summary>
        double Lng { get; set; }
        /// <summary>
        /// 纬度
        /// </summary>
        double Lat { get; set; }
    }

然后创建泛型类,用来包装计算的距离。

    /// <summary>
    /// 带距离的数据
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public class DataWithDistance<TEntity>
    {
        /// <summary>
        /// 距离(km)
        /// </summary>
        public double Distance { get; set; }
        /// <summary>
        /// 实体数据
        /// </summary>
        public TEntity Entity { get; set; }
    }

最后编写根据距离排序的扩展方法

注意:这个方法是采用的 SqlFunctions 类,所以仅支持SqlServer数据库,如果是其它数据库,需要将 SqlFunctions 更换成对应的类

    /// <summary>
    /// IQueryable扩展类
    /// </summary>
    public static class QueryableExtension
    {
        /// <summary>
        /// 根据距离排序
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="lng">经度</param>
        /// <param name="lat">纬度</param>
        /// <returns></returns>
        public static IQueryable<DataWithDistance<TEntity>> OrderByDistance<TEntity>(this IQueryable<TEntity> queryable, double lng, double lat) where TEntity : class, IHasLngAndLat
        {
            var rtn = from q in queryable
                      let radLat1 = lat * Math.PI / 180.0
                      let radLat2 = q.Lat * Math.PI / 180.0
                      let a = radLat1 - radLat2
                      let b = lng * Math.PI / 180.0 - q.Lng * Math.PI / 180.0
                      let s = 2 * SqlFunctions.Asin(SqlFunctions.SquareRoot(Math.Pow((double)SqlFunctions.Sin(a / 2), 2) +
               SqlFunctions.Cos(radLat1) * SqlFunctions.Cos(radLat2) * Math.Pow((double)SqlFunctions.Sin(b / 2), 2))) * 6378.137
                      let d = Math.Round((double)s * 10000) / 10000
                      orderby d
                      select new DataWithDistance<TEntity> { Entity = q, Distance = d };

            return rtn;
        }
    }

以上就完成了 entity framework 按照距离排序的功能。

 

接下来我们用它来写一个小小的demo

首先创建一个商店实体类,具有经纬度字段,实现了  IHasLngAndLat 接口。

    /// <summary>
    /// 商店实体
    /// </summary>
    public class Shop : IHasLngAndLat
    {
        /// <summary>
        /// 主键
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 商店名称
        /// </summary>
        [Required]
        [StringLength(64)]
        public string ShopName { get; set; }
        /// <summary>
        /// 经度
        /// </summary>
        public double Lng { get; set; }
        /// <summary>
        /// 纬度
        /// </summary>
        public double Lat { get; set; }
    }

然后创建EF上下文类

    /// <summary>
    /// EF上下文
    /// </summary>
    public class DemoDbContext : DbContext
    {
        public DemoDbContext()
            : base("name=DemoDbContext")
        {
        }
        public virtual DbSet<Shop> Shop { get; set; }
    }

最后我们分页查询商店,并按照距离由近到远排序

            #region 入参
            double user_lng = 113.46, user_lat = 22.27;  //用户经纬度
            int pageIndex = 3; //当前页码
            int pageSize = 10; //每页条数
            #endregion

            using (DemoDbContext context = new DemoDbContext())
            {
                var queryable = context.Shop.AsNoTracking().AsQueryable();
                IQueryable<DataWithDistance<Shop>> sort_queryable = queryable.OrderByDistance(user_lng, user_lat);  //按照用户的距离从近到远排序
                List<DataWithDistance<Shop>> data = sort_queryable.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();   //分页并执行sql查询获取数据

                //TODO:将查到的数据映射成DTO对象,并返回给客户端
            }

好了,entity framework 实现按照距离排序 也就全部完成了。