在項目中經常遇到一些數據的修改,很多時候業務方需要一個修改日志記錄,這里我們計划用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




