Generate detailed Audit Logs for MVC Controller actions.
$ dotnet add package Audit.MvcMVC Actions Audit Extension for Audit.NET library. (An extensible framework to audit executing operations in .NET).
Generate Audit Trails for MVC actions. Supporting AspNetCore Mvc.
Audit.Mvc provides the infrastructure to log interactions with MVC applications. It can record action methods calls to controllers and razor pages.
NuGet Package To install the package run the following command on the Package Manager Console:
PM> Install-Package Audit.Mvc
If your project targets the full .NET framework, but you are using AspNet Core (Microsoft.AspNetCore.Mvc.*)
you should install and reference the Audit.Mvc.Core package instead, otherwise it will assume you are targeting
the old generation of ASP.NET:
PM> Install-Package Audit.Mvc.Core
If your project targets the NET Core framework (NetStandard >= 1.6), there is no difference between using Audit.Mvc or Audit.Mvc.Core
since both assumes AspNet Core.
Decorate the MVC Actions or Controllers you want to audit with [Audit] action filter.
For example:
public class HomeController : Controller
{
[Audit]
public ActionResult Index(int id, string name)
{
//...
return View(new SomeViewModel() { Id = id, Name = name });
}
[Audit(EventType = "InsertOrderAction", IncludeHeaders = true, IncludeModel = true)]
[HttpPost]
public ActionResult TestPost(SomeViewModel model)
{
//...
}
}
The
[Audit]attribute cannot be used on razor pages, because action filters are not supported on razor pages.
To audit razor pages, include the AuditPageFilter on the filters collection on your startup code, for example:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new AuditPageFilter()
{
IncludeHeaders = true
});
});
}
Or you can apply the filter only to certain pages, for example for pages under /Movies path:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.AddFolderApplicationModelConvention("/Movies", model => model.Filters.Add(new AuditPageFilter()
{
IncludeResponseBody = true
}));
});
}
Alternatively, if you want to setup the audit on a particular page and/or don't want to add the filter as a global filter,
you can override the OnPageHandlerExecutionAsync on your page model and manually call the same method
on an AuditPageFilter instance:
public class YourPageModel : PageModel
{
private readonly AuditPageFilter _pageFilter = new AuditPageFilter() { IncludeHeaders = true };
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
await _pageFilter.OnPageHandlerExecutionAsync(context, next);
}
// ...
}
The MVC audit events are stored using a Data Provider. You can use one of the available data providers or implement your own. Please refer to the data providers section on Audit.NET documentation.
The AuditAttribute can be configured with the following properties:
To configure the output persistence mechanism please see Event Output Configuration.
When IncludeRequestBody is set to true you may need to enable rewind on the request body stream, otherwise the controller won't be able to read the request body more than once (by default it's a forwand-only stream that can be read only once). You can enable rewind on your startup logic with the following startup code:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (context, next) => {
context.Request.EnableBuffering();
await next();
});
}
To selectively exclude certain controllers, action methods, action parameters or return values, you can decorate them with AuditIgnore attribute.
For example:
[Audit(IncludeHeaders = true, IncludeModel = true)]
public class AccountController : Controller
{
[HttpGet]
[AuditIgnore]
public IEnumerable<string> GetAccounts()
{
// this action will not be audited
}
[HttpPost]
public IEnumerable<string> PostAccount(string user, [AuditIgnore]string password)
{
// password argument will not be audited
}
[HttpGet]
[return:AuditIgnore]
public IEnumerable<string> GetSecrets(string user)
{
// the response body of this action will not be audited
}
}
You can also decorate the razor pages classes, methods or arguments to be ignored on the audits:
public class IndexModel : PageModel
{
[return:AuditIgnore]
public IActionResult OnGet(string user)
{
// the response of this action will not be audited
}
[AuditIgnore]
public void OnDelete(string user)
{
// this action will not be audited
}
public async Task<IActionResult> OnPostAsync([AuditIgnore]string password)
{
// password argument will not be audited
}
}
The following table describes the Audit.Mvc output fields:
| Field Name | Type | Description |
|---|---|---|
| TraceId | string | A unique identifier per request |
| HttpMethod | string | HTTP method (GET, POST, etc) |
| ControllerName | string | The controller name (or the area name for razor pages) |
| ActionName | string | The action name (or the display name for razor pages) |
| ViewName | string | The view name (if any) |
| ViewPath | string | View physical path (if any) |
| FormVariables | Object | Form-data input variables passed to the action |
| ActionParameters | Object | The action parameters passed |
| RequestBody | BodyContent | The request body (optional) |
| ResponseBody | BodyContent | The response body (optional) |
| UserName | string | Username on the HttpContext Identity |
| RequestUrl | string | URL of the request |
| IpAddress | string | Client IP address |
| ResponseStatusCode | integer | HTTP response status code |
| ResponseStatus | string | Response status description |
| Headers | Object | HTTP Headers (optional) |
| Model | Object | The model object returned by the controller (if any) (optional) |
| ModelStateValid | boolean | Boolean to indicate if the model is valid |
| ModelStateErrors | string | Error description when the model is invalid |
| RedirectLocation | string | The redirect location (if any) |
| Exception | string | The exception thrown details (if any) |
| Field Name | Type | Description |
|---|---|---|
| Type | string | The body type reported |
| Length | long? | The length of the body if reported |
| Value | Object | The body content |
You can access the Audit Scope from the controller action by calling the Controller extension method GetCurrentAuditScope().
For example:
public class HomeController : Controller
{
[Audit]
public ActionResult Index(int id, string name)
{
//...
var auditScope = this.GetCurrentAuditScope();
auditScope.Comment("New comment from controller");
auditScope.SetCustomField("TestField", Guid.NewGuid());
//...
}
}
See Audit.NET documentation about Custom Field and Comments for more information.
HomeController.Index(GET) with params: id=1234567&name=test
{
"EventType": "Home/Index (GET)",
"Environment": {
...
},
"StartDate": "2016-08-22T18:31:14.6550924-05:00",
"EndDate": "2016-08-22T18:31:23.1834012-05:00",
"Duration": 8529,
"Action": {
"TraceId": "0HLFLQP4HGFAG_00000001",
"HttpMethod": "GET",
"ControllerName": "Home",
"ActionName": "Index",
"ViewName": "Index",
"ViewPath": "~/Views/Home/Index.cshtml",
"FormVariables": {},
"ActionParameters": {
"id": 1234567,
"name": "test",
},
"UserName": "federico@mycompany.com",
"RequestUrl": "/",
"IpAddress": "127.0.0.1",
"ResponseStatus": "200 OK",
"ResponseStatusCode": 200,
"ModelStateValid": true,
"RedirectLocation": null
}
}
HomeController.TestPost(POST) with body: id=1234567&name=test
{
"EventType": "InsertOrderAction",
"Environment": {
...
},
"StartDate": "2016-08-22T18:31:00.0020036-05:00",
"EndDate": "2016-08-22T18:31:15.1705128-05:00",
"Duration": 15000,
"Action": {
"TraceId": "0HLFLQP4HGFAG_00000002",
"HttpMethod": "POST",
"ControllerName": "Home",
"ActionName": "TestPost",
"FormVariables": {
"id": "1234567",
"name": "test"
},
"ActionParameters": {
"model": {
"id": 1234567,
"name": "test"
}
},
"UserName": "federico@mycompany.com",
"RequestUrl": "/Home/TestPost",
"IpAddress": "::1",
"ResponseStatus": "200 OK",
"ResponseStatusCode": 200,
"Headers": {
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Content-Length": "24",
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "es-419,es;q=0.8",
"Host": "localhost:37341",
"Referer": "http://localhost:37341/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743",
"Origin": "http://localhost:37341",
"Upgrade-Insecure-Requests": "1"
},
"ModelStateValid": false,
"ModelStateErrors": {
"Id": "The field Id must be between 0 and 9999."
},
"RedirectLocation": null
}
}
If you are creating an ASP.NET Core MVC project from scratch, you can use the dotnet new template provided on the library Audit.Mvc.Template. This allows to quickly generate an audit-enabled MVC project that can be used as a starting point for your project or as a working example.
To install the template on your system, just type:
dotnet new -i Audit.Mvc.Template
Once you install the template, you should see it on the dotnet new templates list with the name mvcaudit as follows:

You can now create a new project on the current folder by running:
dotnet new mvcaudit
This will create a new Asp.NET Core 2.1 project.
To get help about the options:
dotnet new mvcaudit -h
If you like this project please contribute in any of the following ways:
