ASp.net Core EF ActionFilterAttribute AOP

在項目中常常遇到一些數據的修改,不少時候業務方須要一個修改日誌記錄,這裏咱們計劃用mssql數據庫來存放日誌記錄,用EF來操做,記錄日誌能夠用mvc的ActionFilterAttribute 來完成也能夠用AOP來完成。之前在asp.net的AOP用的是IMessageSink這裏咱們計劃用Castle.DynamicProxy來完成。javascript

準備工做:

建立數據庫表:html

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來遷移數據)java

建立 Asp.netCore項目

這裏咱們以asp.netcore2.2建立一個WebAppLog視圖模型程序jquery

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

 "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文件:json

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文件:cookie

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視圖:mvc

@{
    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方法最後添加以下:app

 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​​​​​​​

相關文章
相關標籤/搜索