Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web Api Request Content is empty in action filter

I have an attribute named Log that tries to log the content of request and response into a text file. I've put that over my Controller to cover all the actions. In LogAttribute I'm reading content as a string (ReadAsStringAsync) so I don't lose request body.

public class LogAttribute : ActionFilterAttribute {     // ..     public override void OnActionExecuting(HttpActionContext actionContext)     {         // stuff goes here         var content = actionContext.Request.Content.ReadAsStringAsync().Result;          // content is always empty because request body is cleared     }      public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)     {         // other stuff goes here         var content = actionContext.Request.Content.ReadAsStringAsync().Result;         // content is always empty because request body is cleared     }      // .. } 

On the other hand, I've put the FromBody attribute before my action parameter class to take advantage of its benefits.

[Log] public class SomethingController {     public HttpResponseMessage Foo([FromBody] myModel)     {         // something     } } 

The problem is the content is always empty either in ActionExecuting or ActionExecuted.

I think this is because FromBody runs before my Log attribute unlike their order in the code. And again I think its because of finding the best action/controller match for the request according to action parameters (Route Processing). After that my request body is cleared since request body is non-buffered in WebApi.

I want to know if there is any way to change the run time order of the FromBody attribute and my Log attribute? or something else that solves the problem! I should mention that I don't want to remove the FromBody and using HttpRequestMessage instead of my Model or something like that.

like image 343
Reza Ahmadi Avatar asked Jan 25 '14 14:01

Reza Ahmadi


People also ask

Does Net Web API support action filter?

The ASP.NET MVC framework supports four different types of filters: Authorization filters – Implements the IAuthorizationFilter attribute. Action filters – Implements the IActionFilter attribute.

What are action filters in Web API?

Action Filter − Action filters are used to add extra logic before or after action methods execution. The OnActionExecuting and OnActionExecuted methods are used to add our logic before and after an action method is executed.

What is an action filter in the ASP.NET MVC context?

ASP.NET MVC provides Action Filters for executing filtering logic either before or after an action method is called. Action Filters are custom attributes that provide declarative means to add pre-action and post-action behavior to the controller's action methods.


2 Answers

The request body is a non-rewindable stream; it can be read only once. The formatter has already read the stream and populated the model. We're not able to read the stream again in the action filter.

You could try:

public class LogAttribute : ActionFilterAttribute {     public override void OnActionExecuting(HttpActionContext actionContext)     {         var myModel = actionContext.ActionArguments["myModel"];       }      public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)     {         var myModel = actionContext.ActionArguments["myModel"];      } } 

Actually, ActionArguments is just a dictionary, we can loop though it if we need to avoid hardcoded parameter name ("myModel"). When we create a generic action filter that needs to work on a class of similar objects for some specific requirements, we could have our models implement an interface => know which argument is the model we need to work on and we can call the methods though the interface.

Example code:

public class LogAttribute : ActionFilterAttribute     {         public override void OnActionExecuting(HttpActionContext actionContext)         {             foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable)))             {                  ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable                  //do something with it. Maybe call model.log             }         }          public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)         {             foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable)))             {                  ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable                  //do something with it. Maybe call model.log             }         }     } 
like image 147
Khanh TO Avatar answered Sep 22 '22 16:09

Khanh TO


This approach worked for me:

using (var stream = new MemoryStream()) {     var context = (HttpContextBase)Request.Properties["MS_HttpContext"];     context.Request.InputStream.Seek(0, SeekOrigin.Begin);     context.Request.InputStream.CopyTo(stream);     string requestBody = Encoding.UTF8.GetString(stream.ToArray()); } 

Returned for me the json representation of my action parameter object triggering the logging or exception case.

Found as accepted answer here

like image 39
Anytoe Avatar answered Sep 25 '22 16:09

Anytoe