在項目中常常遇到一些數據的修改,不少時候業務方須要一個修改日誌記錄,這裏咱們計劃用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.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裏面須要用到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>
運行程序:
首先咱們須要安裝相應的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