ASp.net Core EF ActionFilterAttribute AOP


在項目中經常遇到一些數據的修改,很多時候業務方需要一個修改日志記錄,這里我們計划用mssql數據庫來存放日志記錄,用EF來操作,記錄日志可以用mvc的ActionFilterAttribute 來完成也可以用AOP來完成。以前在asp.net的AOP用的是IMessageSink這里我們計划用Castle.DynamicProxy來完成。

准備工作:

創建數據庫表:

CREATE TABLE [dbo].[logs](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Title] [nvarchar](50) NULL,
    [Content] [nvarchar](max) NULL,
    [CreateTime] [datetime] NULL,
 CONSTRAINT [PK_logs] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

這里的Title是根據業務划分的,Content是修改后的內容,實際生產應該還要加上修改人。這里都簡化了(個人並不推薦用EF來遷移數據)

創建 Asp.netCore項目

這里我們以asp.netcore2.2創建一個WebAppLog視圖模型程序

在appsettings.json添加數據庫連接串:

 "ConnectionStrings": {
    "SqlServerConnection": "Server=192.168.100.5;initial catalog=test;UID=sa;PWD=xxxx"
  }

在Models文件夾下新建Log.cs

namespace WebAppLog.Models
{
    public class Log
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Content { set; get; }
        public DateTime CreateTime { set; get; }
    }
}

創建LogContext.cs文件:

namespace WebAppLog
{
    using Microsoft.EntityFrameworkCore;
    using WebAppLog.Models;

    public class LogContext : DbContext
    {
        public LogContext(DbContextOptions<LogContext> options) : base(options)
        {
        }
        public virtual DbSet<Log> Log { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Log>().ToTable("logs");
        }
    }
}

修改HomeController.cs文件:

namespace WebAppLog.Controllers
{
    using Microsoft.AspNetCore.Mvc;
    using System.Linq;
    public class HomeController : Controller
    {
        private LogContext context;
     
        public HomeController(LogContext context)
        {
            this.context = context;

        }
        public IActionResult Index()
        {
            var data = context.Log.ToList();
            return View(data);
        }

    }
}

修改Home的Index.cshtml視圖:

@{
    var list = Model as List<Log>;
}
<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <table border="1">
        @foreach (var item in list)
        {
            <tr>
                <td>Title</td>
                <td>@item.Title</td>
            </tr>
            <tr>
                <td>Content</td>
                <td class="htmlcontent">@item.Content</td>
            </tr>
            <tr>
                <td>CreateTime</td>
                <td>@item.CreateTime.ToString("yyyy-MM-dd HH:mm:ss")</td>
            </tr>
        }
    </table>   
</div>

在Startup.cs的ConfigureServices方法最后添加如下:

 string connection = Configuration["ConnectionStrings:SqlServerConnection"];
 services.AddDbContext<LogContext>(options => options.UseSqlServer(connection));

這時候我們的程序就可以運行了。

ActionFilterAttribute

這里我們首先用ActionFilterAttribute來實現日志記錄,在ActionFilterAttribute里面需要用到LogContext,我這里用 filterContext.HttpContext.RequestServices.GetService(typeof(LogContext))來獲取的。

新建LogAttribute.cs文件:在OnActionExecuting方法我們獲取參數,在OnResultExecuted獲取返回值並記錄到數據庫

namespace WebAppLog
{
    using Microsoft.AspNetCore.Mvc.Filters;
    using Newtonsoft.Json;
    using System;
    using WebAppLog.Models;
    public class LogAttribute : ActionFilterAttribute
    {
        public string Title { get; set; }

        public LogAttribute(string title)
        {
            Title = title;
        }
        private string _arguments = null;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            _arguments = JsonConvert.SerializeObject(filterContext.ActionArguments);
            base.OnActionExecuting(filterContext);
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            var context = filterContext.HttpContext.RequestServices.GetService(typeof(LogContext)) as LogContext;
            string result = JsonConvert.SerializeObject(filterContext.Result);
            var log = new Log
            {
                Title = Title,
                Content = $"{{\"arguments\":{_arguments},\"result\":{result}}}",
                CreateTime = DateTime.Now
            };
            context.Log.Add(log);
            context.SaveChanges();

            base.OnResultExecuted(filterContext);
        }
    }
}

在HomeController.cs中增加一個Action

 [Log("test")]
  public ActionResult Update(int id, string content)
   {
      return Ok();
   }

運行程序用postman發送一個請求:

由於我們的日志是json格式,所以需要修改home的Index.cshtml讓他以表格來顯示

在table結束標簽后追加一下js代碼(目的就是讓Content更加好看)

<script type="text/javascript" src="~/js/jquery.min.js"></script>
    <script type="text/javascript">
        function GetHtml(txt) {
            try {
                var obj = $.parseJSON(txt);
                var html = "<table border='1'>"
                for (var i in obj) {
                    var temp = '';
                    var obj2 = obj[i];
                    if (typeof (obj2) == "object" && Object.prototype.toString.call(obj2).toLowerCase() == "[object object]" && !obj2.length) {
                        temp = GetHtml(JSON.stringify(obj2));
                    }
                    else {
                        temp = obj2;
                    }

                    html += "<tr><td>" + i + "</td><td>" + temp + "</td></tr>";
                }
                html += "</table>";
                return html;
            } catch (e) {
                return txt;
            }
        }
        $(".htmlcontent").each(function () {
            var text = $(this).text();
            console.log(text);
            text = GetHtml(text);
            $(this)[0].innerHTML = text;
        });
    </script>

運行程序:

AOP

首先我們需要安裝相應的nuget包

Autofac.Extensions.DependencyInjection

Autofac.Extras.DynamicProxy

首先我們創建一個LogInterceptor.cs文件來實現AOP,但是不是所有的方法都要記錄日志,所以我們創建了一個UsageAttribute來標記是否記錄日志:

namespace WebAppLog
{
    using Castle.DynamicProxy;
    using Microsoft.EntityFrameworkCore;
    using System;
    using System.Reflection;
    using WebAppLog.Models;
    public class LogInterceptor : IInterceptor
    {
        LogContext context;
        public LogInterceptor(string connstr)
        {
            var optionsBuilder = new DbContextOptionsBuilder<LogContext>();
            optionsBuilder.UseSqlServer(connstr);
            context = new LogContext(optionsBuilder.Options);
        }
        public void Intercept(IInvocation invocation)
        {
            //真正調用方法
            invocation.Proceed();
            var methodAttribute = (UsageAttribute)invocation.Method.GetCustomAttribute(typeof(UsageAttribute));
            if (methodAttribute != null)
            {
                var args = invocation.Arguments;
                var pars = invocation.Method.GetParameters();
                string json = "";
                for (int i = 0; i < args.Length; i++)
                {
                    string tmp = $"\"{pars[i].Name}\":\"{args[i].ToString()}\"";
                    json += tmp + ",";
                }
                string argument = "{" + json.TrimEnd(',') + "}";
                string result = invocation.ReturnValue.ToString();
                string title = methodAttribute.Description;
                var log = new Log
                {
                    Title = title,
                    Content = $"{{\"arguments\":{argument},\"result\":\"{result}\"}}",
                    CreateTime = DateTime.Now
                };
                context.Log.Add(log);
                context.SaveChanges();
            }
        }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class UsageAttribute : Attribute
    {
        public string Description { set; get; }

        public UsageAttribute(string description)
        {
            Description = description;
        }
    }
}

要實現AOP 我們需要創建一個LogService.cs(還有對應的接口,這里必須要有接口不然aop搞不定)

namespace WebAppLog
{
    using Autofac.Extras.DynamicProxy;
    public interface ILogService
    {
        [Usage("update")]
        bool Update(int id, string content);
    }

    [Intercept(typeof(LogInterceptor))]
    public class LogService : ILogService
    {
        public bool Update(int id, string content)
        {
            return true;
        }
    }
}
修改HomeController.cs並增加相應的Action
       private LogContext context;
        public ILogService LogService { get; set; }
      
        public HomeController(LogContext context, ILogService logService)
        {
            this.context = context;
            LogService = logService;
        }
        public ActionResult Modify()
        {
            LogService.Update(123, "test");
            return Ok();
        }

現在修改Startup.cs文件,用Autofac的DI替換asp.netCore 默認的DI。把原先默認的ConfigureServices放注釋,新增ConfigureServices方法如下:

 public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            string connection = Configuration["ConnectionStrings:SqlServerConnection"];
            services.AddDbContext<LogContext>(options => options.UseSqlServer(connection));
            ///上面的是原先ConfigureServices的類容,下面是增加的代碼
            var containerBuilder = new ContainerBuilder();
            containerBuilder.Register(c => new LogInterceptor(connection));
            containerBuilder.RegisterType<LogService>().As<ILogService>().PropertiesAutowired().EnableInterfaceInterceptors();
            containerBuilder.Populate(services);

            var container = containerBuilder.Build();
            return new AutofacServiceProvider(container);
        }

然后運行程序,訪問http://localhost:5000/home/modify

最后回到主頁如下:

源碼下載

參考:

Aspect Oriented Programming (AOP) in .NET Core and C# using AutoFac and DynamicProxy

Type Interceptors

.Net Core 學習之路-AutoFac的使用

asp.net EFcore配置鏈接sqlserver

ASP.NET Core 使用 AutoFac 注入 DbContext​​​​​​​


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM