6月 05

写了个简单的代码生成工具。不愿意使用拼接字符串的方式,所以将目光投向了Razor

在.Net Framework时代,我们可以直接只用 RazorEngine 做执行输出。

但是我试了发现不支持.Net Core大的版本,所以上github看了看,发现了替代品RazorLight

 

使用起来很简单,在Nuget上有包

Install-Package RazorLight

一般调用方式

            var engine = new RazorLightEngineBuilder()
              .UseFilesystemProject(@"D:\Test\CoreTest\ConsoleApp.RazorConsole")
              .UseMemoryCachingProvider()
              .Build();
            string result = engine.CompileRenderAsync("Ocean.cshtml",
                new { Name = "Ocean" }).Result;

Ocean.cshtml是我的razor文件

@{
}
这是一个测试,我是 @Model.Name

 

另外我做了一个简单封装

        public static string ExeRazor(string template,object obj)
        {
            var path = IOHelper.RootDevPath; 
            var engine = new RazorLightEngineBuilder()
             .UseFilesystemProject(path)
             .UseMemoryCachingProvider()
             .Build(); 
            string result = engine.CompileRenderAsync(template, obj).Result;
            return result;
        }

 

好了,这样就可以使用razor做一些string的输出了,比拼接字符串是方便了不少。

最关键的是,我可以直接打包进dll中,既干净又清爽,实在是居家旅行之良品。

 

项目官网上有更多的使用方法。

项目地址:https://github.com/toddams/RazorLight

written by ocean

5月 28

分布式锁就不多介绍了,刚好有一个这样的需求,就上github上找一找,发现一个分布式锁的项目,使用的是redis

项目地址:https://github.com/samcook/RedLock.net

首先使用nuget安装dll

 RedLock.net

我这边做了一个模拟测试,因为实在太简单了,直接看注释就可以了

        static int MAX_THREAD = 5;//模拟5个线程

        static void Main(string[] args)
        {

            var start = DateTime.Now;

            var endPoints = new List<RedLockEndPoint>
            {
                new DnsEndPoint("172.28.3.125", 6379),
            };
            var redlockFactory = RedLockFactory.Create(endPoints);
             
            List<Task> list = new List<Task>();
            for (int i = 0; i < MAX_THREAD; i++)
            {
                Task t = new Task(() =>
                {
                    for (int index = 0; index < 100; index++)
                    { 
                        var id = index % 10; 
                        var expiry = TimeSpan.FromSeconds(30);
                        var key = "key" + id;
                        var wait = TimeSpan.FromSeconds(30);
                        var retry = TimeSpan.FromSeconds(1);
                        using (var redLock = redlockFactory.CreateLock(key, expiry, wait, retry))
                        {
                            if (redLock.IsAcquired)
                            {
                                var info = LockTestDAL.GetLockTestInfo(id);
                                info.Result1++;
                                LockTestDAL.Update(info);
                                info.Result2++;
                                LockTestDAL.Update(info);
                                info.Result3++;
                                LockTestDAL.Update(info);
                            }
                        } 
                    }
                });
                list.Add(t);
            }
            for (int i = 0; i < list.Count; i++)
            {
                list[i].Start();
            }

            Task.WaitAll(list.ToArray());

            Console.WriteLine("done");
            var d = (DateTime.Now - start).TotalSeconds;
            Console.WriteLine($"耗时{d}秒");
            Console.ReadLine(); 
        }

其中有几个参数

expiry是指的超时时间,如果内部执行超过expity则会释放锁

key就是锁定时候的数据特征,一般是主键

wait是指获取锁的时候等待的时间

retry是指每隔多少时间请求一次。

 

一定要注意,锁不是一定能拿到的,所以需要加判断

if (redLock.IsAcquired) //判断是否拿到锁

 

在我的测试用例中,表中有多个数据需要更新,如果不加锁的话会有脏读脏写,数据明显错误,执行大概34秒。

加锁之后数据完全正确,执行时间会稍长,大概38秒。

written by ocean

4月 15

做多服务器集群的时候,需要处理session。

一般两种处理方法,

第一,无session,就是不用session,不用session的话自然没有问题

第二,集中化session,所有session集中放在redis种。这样也没有问题

 

记录一下集中化session

首先引用包

Microsoft.AspNetCore.Session
Microsoft.Extensions.Caching.Redis.Core

 在startup中添加中间件

            services.AddDistributedRedisCache(option =>
            {
                //redis 数据库连接字符串
                option.Configuration = "192.168.1.132:6379";
                //redis 实例名
                option.InstanceName = "master";
            });
            services.AddSession();
            
            //添加使用
            app.UseSession();

这样就可以了。真是太方便了

 

【Session 写入方法】

HttpContext.Session.SetString("key", "strValue");

【Session 读取方法】

HttpContext.Session.GetString("key")

 另外,页面上为了区分不同的server,这里取一下hostname

var hostname= Environment.GetEnvironmentVariable("COMPUTERNAME") ??Environment.GetEnvironmentVariable("HOSTNAME");

 

 

接下来,在docker中部署了5台app,1台nginx做负载均衡器,1台redis做session缓存。 记录下脚本

docker stop a1
docker stop a2
docker stop a3
docker stop a4
docker stop a5
docker rm a1
docker rm a2
docker rm a3
docker rm a4
docker rm a5
docker run --name a1 --restart=always -d -p 8001:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a2 --restart=always -d -p 8002:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a3 --restart=always -d -p 8003:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a4 --restart=always -d -p 8004:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll
docker run --name a5 --restart=always -d -p 8005:80 -v /ocean/www:/wwwroot -w /wwwroot/ microsoft/aspnetcore dotnet /wwwroot/WebApplication1.dll

测试了一下,果然高兴的太早,试了一下,当server变化的时候,session不会同步。这是个问题,继续研究。

 

session其实是根据cookie的一个值来取的,而这个值得不同是因为.net core对其做了数据保护(data Protection) 

数据保护会调用机器自身的一个key值,该key值每台机器都不一样,因此最终造成cookie的值也不一样

 

为了解决这个问题。.Net Core团队提供了包将秘钥保存到redis中

添加包引用

Microsoft.AspNetCore.DataProtection.Redis

修改startup文件如下

            var redis = ConnectionMultiplexer.Connect("192.168.1.132:6379");
            services.AddDataProtection()
                .SetApplicationName("session_application_name")
                .PersistKeysToRedis(redis, "DataProtection-Keys");
            services.AddDistributedRedisCache(option =>
            {
                //redis 数据库连接字符串
                option.Configuration = "192.168.1.132:6379";
                //redis 实例名
                option.InstanceName = "master";
            });
            services.AddSession();

这次是真的OK了。哈哈哈

 

另外之前有写过一篇类似的 

http://blog.wx6.org/2017/929.htm

written by ocean

4月 03

这个功能很有意思。直接引用dll即可以浏览页面。

方法很简单,要被共享的功能单独写成一个.net core的webapplication

 

在另外一个.net core的webapplication中引用上面的dll,切记不要忘掉PrecompiledViews

之后在使用共享UI的dll应用中,startup中添加如下代码即可

 public void ConfigureServices(IServiceCollection services)
        {
            var assembly = typeof(OceanController).GetTypeInfo().Assembly;
            services.AddMvc()
            .AddApplicationPart(assembly);  
            services.Configure<RazorViewEngineOptions>(options =>
            {
                options.FileProviders.Add(
                    new EmbeddedFileProvider(typeof(OceanController).GetTypeInfo().Assembly));
            });
        }

OceanController即共享UI的Controller

written by ocean

7月 27

我在这里主要用来监控每个页面的执行时间

    public class VisitLogFilter : IActionFilter
    {
        private const string DURATION = "DURATION";

        public void OnActionExecuted(ActionExecutedContext context)
        {

            var stopwach = context.RouteData.Values[DURATION] as Stopwatch;
            stopwach.Stop();
            var time = stopwach.Elapsed;

            var url = context.HttpContext.Request.Host + context.HttpContext.Request.Path;
            var agent = ((FrameRequestHeaders)context.HttpContext.Request.Headers).HeaderUserAgent.ToString();
            var ip = IPHelper.GetUserIp(context.HttpContext);
            LogHelper.Info(time.TotalMilliseconds + "\t\t" + ip + "\t\t" + url + "\t\t\t\t" + agent);

            if (context.Result is ViewResult)
            {
                ((ViewResult)context.Result).ViewData[DURATION] = (time.TotalMilliseconds).ToString("#.##");
            }
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var stopwach = new Stopwatch();
            stopwach.Start();
            context.RouteData.Values.Add(DURATION, stopwach);           
        }

    }

 

注入全局

            services.AddMvc(
                config =>
                {
                    config.Filters.Add(new VisitLogFilter()); 
                });

前台展示的代码

                        @if (ViewData.ContainsKey("DURATION"))
                        {
                            <p>页面执行时间 : @(ViewData["DURATION"]) ms.</p>
                        }

written by ocean

7月 25

当程序出现错误的时候将会调用

    public class ExceptionFilter : IExceptionFilter
    { 
        public void OnException(ExceptionContext context)
        { 
            var url = context.HttpContext.Request.Host + context.HttpContext.Request.Path;

            LogHelper.Error(url);
            LogHelper.Error(context.Exception);
        }
    }

全局注入方法

            services.AddMvc(
                config =>
                { 
                    config.Filters.Add(new ExceptionFilter());
                });

written by ocean

7月 23

测试了好几个以前的类都跑不起来,于是灵机一动,在网页中播放声音

                ProcessStartInfo startInfo = new ProcessStartInfo(@"C:\Program Files\Internet Explorer\iexplore.exe");
                startInfo.Arguments = "http://music.163.com/#/outchain/2/28406526/";
                Process process1 = new Process();
                process1.StartInfo = startInfo;
                process1.Start();

虽然不雅,但是在我的场景下是适用的。

written by ocean

7月 05

Console程序中 中文乱码 

引用包

System.Text.Encoding.CodePages

在程序开头调用

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

 

项目发布到ubuntu上

命令行定位到web项目目录

dotnet  publish -r ubuntu.16.04-x64

written by ocean

6月 19

.net中的session需要添加引用nuget包

Microsoft.AspNetCore.Session

Session 是基于 IDistributedCache 构建的,所以必须引用一种 IDistributedCache 的实现,ASP.NET Core 提供了多种 IDistributedCache 的实现,如内存、数据库、redis等。所以也需要引用对应的nuget包,一种就可以

Microsoft.Extensions.Caching.Memory
Microsoft.Extensions.Caching.Redis
Microsoft.Extensions.Caching.SqlServer

这里先用最简单的Memory举例

 

添加startup->ConfigureServices

services.AddSession();

添加startup->Configure

app.UseSession();

特别注意:UseSession要在UseMvc之前,否则会有问题

然后在Controller中就可以使用session了

保存session

var b = System.Text.Encoding.UTF8.GetBytes("这个是测试.来自 http://blog.wx6.org");
HttpContext.Session.Set("key", b);

获取session

HttpContext.Session.TryGetValue("key", out byte[] xxx);
var s = System.Text.Encoding.UTF8.GetString(xxx);

这里获取和设置都使用的byte[]类型。这是因为.net core下的session可以远程服务器保存,因此就必须支持序列化。好在微软在Microsoft.AspNet.Http命名空间下,为我们添加了几个扩展方法,分别用于设置和保存byte[]类型、int类型、以及string类型

需要引用nuget包

Microsoft.AspNet.Http.Extensions.dll

  

另外,类库中不能直接使用,需要注入才可以调到,定义方法体如下

public static void Set1(ISession session)
{
    session.SetString("kkk", "dddd");
}

调用方式,是在Controller中调用

Class1.Set1(HttpContext.Session);

 

 

 

写了两个帮助类

    public static class SessionHelper
    { 
        public static void Set(this ISession session, string key, object obj)
        { 
            session.Set(key, ByteHelper.Object2Bytes(obj));
        }
        public static T Get<T>(this ISession session, string key)
        {
            var b = new byte[] { };
            session.TryGetValue(key, out b);
            return ByteHelper.Bytes2Object<T>(b); 
        }
    }

用到的ByteHelper

    public class ByteHelper
    {
        /// <summary>
        /// 将对象转换为byte数组
        /// </summary>
        /// <param name="obj">被转换对象</param>
        /// <returns>转换后byte数组</returns>
        public static byte[] Object2Bytes(object obj)
        {
            string json = JsonConvert.SerializeObject(obj);
            byte[] serializedResult = System.Text.Encoding.UTF8.GetBytes(json);
            return serializedResult;
        }

        /// <summary>
        /// 将byte数组转换成对象
        /// </summary>
        /// <param name="buff">被转换byte数组</param>
        /// <returns>转换完成后的对象</returns>
        public static object Bytes2Object(byte[] buff)
        {
            string json = System.Text.Encoding.UTF8.GetString(buff);
            return JsonConvert.DeserializeObject<object>(json);
        } 

        /// <summary>
        /// 将byte数组转换成对象
        /// </summary>
        /// <param name="buff">被转换byte数组</param>
        /// <returns>转换完成后的对象</returns>
        public static T Bytes2Object<T>(byte[] buff)
        {
            string json = System.Text.Encoding.UTF8.GetString(buff);
            return JsonConvert.DeserializeObject<T>(json);
        }
    }

written by ocean \\ tags:

6月 17

我做东西喜欢从最简单开始。这样方便梳理每个细节。今天弄个.net core的站点试试。

开发工具vs2017,新建项目 .net core 下的 asp.net core web application 

.net framework 选择最高的4.6.1 创建一个空的站点

 

空网站已经引用了两个包

Microsoft.ApplicationInsights.AspNetCore
Microsoft.AspNetCore

删除Startup自带的代码文件只保留两个方法体 ConfigureServices和Configure

 

增加mvc框架支持

添加nuget包引用

Microsoft.AspNetCore.Mvc

添加startup->ConfigureServices

services.AddMvc();

添加startup->Configure

app.UseMvc(routes =>
{
    routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}");
});

 按照mvc规则给项目添加Controllers和Home文件夹,并且添加简单的action和view。运行即可看到结果。

 

使用Session

比较多,特地重写了一篇

asp.net core中使用session

written by ocean \\ tags: ,