用于在记录之前清理请求和响应的数据注释

时间:2016-06-14 18:27:44

标签: asp.net .net logging asp.net-web-api

我正在寻找可靠的解决方案来记录我们的控制器发出的请求和响应的详细信息。但是,一些传递的数据包含不应写入日志的敏感信息。

在控制器中,入站请求被绑定到请求主体中的单个模型,并且当请求被应答时,单个模型将传递给Ok()结果,如此(非常简化):

[HttpGet]
[Route("Some/Route")]
public IHttpActionResult SomeController([FromBody] RequestType requestObj)
{
    ResponseType responseObj = GetResponse(requestObj)
    return this.Ok(responseObj);
}

现在我的目标是分别在控制器的开头和结尾以某种方式记录请求和响应对象的内容。我想要做的是先绑定模型,然后注销它们的属性。 RequestType的一个例子是:

public class RequestType
{
    public string SomeAttribute { get; set; }
    public string AnotherAttribute { get; set; }
    public string Password{ get; set; }
}

日志看起来像是:

[date-time] Request to SomeController:
SomeAttribute: "value_from_request"
AnotherAttribute: "another_value"
Password: "supersecret123"

现在显然我们不想记录密码。所以我想创建一个不会记录某些字段的自定义数据注释。它的使用看起来像这样(更新的RequestType):

public class RequestType
{
    public string SomeAttribute { get; set; }
    public string AnotherAttribute { get; set; }

    [SensitiveData]
    public string Password{ get; set; }
}

我从哪里开始?我并不是非常熟悉.NET,但我知道有许多魔术类可以被子类化以覆盖它们的一些功能。有没有这样的课程可以帮助吗?更好的是,在模型绑定期间有没有办法做到这一点?那么我们也可以捕获模型绑定期间发生的错误吗?

2 个答案:

答案 0 :(得分:1)

我们应该能够通过<?php require_once('TwitterAPIExchange.php'); $settings = array( 'oauth_access_token' => "xxxxxx-asdsaldjasd", 'oauth_access_token_secret' => "xxxxxx", 'consumer_key' => "xxxxxxx", 'consumer_secret' => "xxxxxxxxxx" ); $url = "https://api.twitter.com/1.1/statuses/user_timeline.json"; $requestMethod = "GET"; $getfield = '?screen_name=username'; $twitter = new TwitterAPIExchange($settings); $response = $twitter->setGetfield($getfield) ->buildOauth($url, $requestMethod) ->performRequest(); $tweets = json_decode($response); foreach($tweets as $tweet) { if ($tweet->retweeted) // here is the part where i did var_dump { $url = "https://api.twitter.com/1.1/statuses/destroy/$tweet->id.json"; $requestMethod = 'POST'; $postfields = array( 'id' => '$tweet->id' ); $twitter = new TwitterAPIExchange($settings); $response = $twitter->buildOauth($url, $requestMethod) ->setPostfields($postfields) ->performRequest(); $response; } } 来实现您的目标。

捕获请求属性

ActionFilterAttribute

敏感数据属性

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class CaptureRequestsAttribute : ActionFilterAttribute // *IMPORTANT* This is in the System.Web.Http.Filters namespace, not System.Web.Mvc
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var messages = actionContext.ActionArguments.Select(arg => GetLogMessage(arg.Value));
        var logMessage = $"[{DateTime.Now}] Request to " +
            $"{actionContext.ControllerContext.Controller}]:\n{string.Join("\n", messages)}";
        WriteToLog(logMessage);

        base.OnActionExecuting(actionContext);
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var result = actionExecutedContext.Response.Content as ObjectContent;
        var message = GetLogMessage(result?.Value);
        var logMessage = $"[{DateTime.Now}] Response from " +
            $"{actionExecutedContext.ActionContext.ControllerContext.Controller}:\n{message}";
        WriteToLog(logMessage);

        base.OnActionExecuted(actionExecutedContext);
    }

    private static void WriteToLog(string message)
    {
        // todo: write you logging stuff here
    }

    private static string GetLogMessage(object objectToLog)
    {
        if (objectToLog == null)
        {
            return string.Empty;
        }

        var type = objectToLog.GetType();
        var properties = type.GetProperties();

        if (properties.Length == 0)
        {
            return $"{type}: {objectToLog}";
        }
        else
        {
            var nonSensitiveProperties = type
                .GetProperties()
                .Where(IsNotSensitiveData)
                .Select(property => $"{property.Name}: {property.GetValue(objectToLog)}");

            return string.Join("\n", nonSensitiveProperties);
        }
    }

    private static bool IsNotSensitiveData(PropertyInfo property) =>
        property.GetCustomAttributes<SensitiveDataAttribute>().Count() == 0;
}

然后,您可以将其添加到WebApi控制器(或其中的特定方法):

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class SensitiveDataAttribute : Attribute
{
}

最后你的模型可以添加[CaptureRequests] public class ValuesController : ApiController { // .. methods }

SensitiveDataAttribute

答案 1 :(得分:0)

这不会使用DataAnnotations,但是,想到的一种方法是使用序列化。如果您的有效负载在合理的大小范围内,则可以在读取和写入日志时序列化和反序列化RequestType类。这需要自定义序列化格式或使用默认的xml。

[Seriliazeble()]
public class RequestType
{
    public string SomeAttribute { get; set; }
    public string AnotherAttribute { get; set; }

    [NonSerialized()] 
    public string Password{ get; set; }
}

使用上述属性将省略序列化的密码。然后你继续进行Logger.Log(MySerializer.Serialize(MyRequest));,你的敏感数据将被省略。

link详细介绍了该方法。

对于xml序列化,只需使用XmlSerializer类。

public class MySerializationService
{

    public string SerializeObject(object item)
    {
        XmlSerializer serializer = new XmlSerializer(item.GetType());
        System.IO.MemoryStream aMemStr = new System.IO.MemoryStream();
        System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(aMemStr, null);
        serializer.Serialize(writer, item);
        string strXml = System.Text.Encoding.UTF8.GetString(aMemStr.ToArray());
        return strXml;
    }

    public object DeSerializeObject(Type objectType, string objectString)
    {
        object obj = null;
        XmlSerializer xs = new XmlSerializer(objectType);
        obj = xs.Deserialize(new StringReader(objectString));
        return obj;
    }
}

然后使用上述或类似的方法,您可以自定义格式进行读写。

写:

string logData=new MySerializationService().SerializeObject(myRequest);

阅读:

RequestType loggedRequest= (RequestType)new MySerializationService().DeSerializeObject(new RequestType().GetType(), logData);