I need to add a header to the response.
The header value is based on the response body.
When I try to add the header I get an error: 'Headers are read-only, response has already started.'
public class SecurityFilter : ActionFilterAttribute
{
public override async void OnActionExecuting(ActionExecutingContext context)
{
var body = await new StreamReader(context.HttpContext.Request.Body).ReadToEndAsync();
}
public override void OnResultExecuted(ResultExecutedContext context)
{
var objectResult = context.Result as ObjectResult;
var resultValue = objectResult.Value;
Console.WriteLine(resultValue);
context.HttpContext.Response.Headers.Add("foo", "bar");
base.OnResultExecuted(context);
}
}
OnResultExecuted method is called after the action result executes. Response headers can't be set/modified if the result has been done.
You can use OnActionExecuted method which is called after the action executes, before the action result. Or use OnResultExecuting method which is called before the action result executes.
Here is a simple demo you could follow:
public class SecurityFilter : ActionFilterAttribute
{
public override async void OnActionExecuting(ActionExecutingContext context)
{
}
public override void OnActionExecuted(ActionExecutedContext context)
{
var objectResult = context.Result as ObjectResult;
var resultValue = objectResult.Value;
Console.WriteLine(resultValue);
context.HttpContext.Response.Headers.Add("foo", "bar");
base.OnActionExecuted(context);
}
}
Related
I use following code to deserialize an instance of MyModel
public async Task<IHttpActionResult> DoSomething([FromBody] MyModel model)
class MyModel {
string a;
int b;
}
Is there away to enforce stricter model binding so that following input won't work?
{
"a":"someString",
"b": 4,
"c": "somethingIWantToCauseAnErrorWhenPresent"
}
You can write your own ActionFilter to lookup the request body and basing on your logic throw an exception
public class CustomValidationFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var bodyLength = context.HttpContext.Request.Body.Length;
var buffer = new byte[bodyLength];
context.HttpContext.Request.EnableRewind();
context.HttpContext.Request.Body.Position = 0;
var streamReader = new StreamReader(context.HttpContext.Request.Body);//do not dispose this streamReader
var requestBody = streamReader.ReadToEnd();
var jsonBody = JsonConvert.DeserializeObject<JObject>(requestBody);
if (jsonBody.Property("c") != null) // your custom validation
{
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
Either you apply this filter globaly, in whole API like this (.net core 2.2):
services.AddMvc(options => options.Filters.Add(typeof(CustomValidationFilter))).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
or inherit from Attribute class and put it on certain action/controller
Using ASP.NET WebApi 2,
Why can't I catch HttpRequestValidationException in my Global ExceptionHandler or Global ExceptionLogger?
Error is: [HttpRequestValidationException (0x80004005): A potentially dangerous Request.QueryString value was detected from the client...]
Using Application_Error works fine, HttpRequestValidationException can be caught fine.
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
//...stuffs
//Global exception handler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
//add global error logger
config.Services.Add(typeof(IExceptionLogger), new GlobalExceptionLogger());
}
public class GlobalExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
//This does not handle HttpRequestValidationException
Exception exception = context.ExceptionContext.Exception;
//.....
}
}
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
//This does not handle HttpRequestValidationException either ...
var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("An unexpected error occured. Please notify your administrator"),
ReasonPhrase = "Unexpected Error"
};
context.Result = new UnhandledExceptionResult(context.Request, result);
}
public class UnhandledExceptionResult : IHttpActionResult
{
private HttpRequestMessage _request;
private HttpResponseMessage _httpResponseMessage;
public UnhandledExceptionResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
{
_request = request;
_httpResponseMessage = httpResponseMessage;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_httpResponseMessage);
}
}
}
I used to believe that the following method would be invoked after all controller methods are done at the end:
protected override void EndExecute(IAsyncResult asyncResult)
Now all override methods are called and then, controller methods are invoked. Do you know what override method is invoked after all methods?
You can use action filter it will be called every time any method execute. You can filter it by matching method name.
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Log("OnActionExecuting", filterContext.RouteData);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
Log("OnActionExecuted", filterContext.RouteData);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
Log("OnResultExecuting", filterContext.RouteData);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
Log("OnResultExecuted", filterContext.RouteData);
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName);
Debug.WriteLine(message, "Action Filter Log");
}
}
For more details please visit : https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
Can I create an ActionFilterAttribute that bypasses the actual execution of the action and returns a value for it?
Yes, it is possible. You can set the result of the filter context provided to you when overriding OnActionExecuting in ActionFilterAttribute.
using System.Web.Mvc;
public sealed class SampleFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext filterContext )
{
filterContext.Result = new RedirectResult( "http://google.com" );
}
}
In the source, you can see that setting the Result property of the filter context changes the flow.
From System.Web.Mvc.ControllerActionInvoker:
internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation)
{
filter.OnActionExecuting(preContext);
if (preContext.Result != null)
{
return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */)
{
Result = preContext.Result
};
}
// other code ommitted
}
You can, like this:
1) Redirects to some action and then return some value:
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/*If something happens*/
if (/*Condition*/)
{
/*You can use Redirect or RedirectToRoute*/
filterContext.HttpContext.Response.Redirect("Redirecto to somewhere");
}
base.OnActionExecuting(filterContext);
}
}
2) Write some value direcly into the request and Ends it sending to the client:
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/*If something happens*/
if (/*Condition*/)
{
filterContext.HttpContext.Response.Write("some value here");
filterContext.HttpContext.Response.End();
}
base.OnActionExecuting(filterContext);
}
}
I have this piece of code:
public class Authenticate : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.Redirect("/");
}
}
}
I was wondering if it is possible to make it redirect to the view for action="Login" controller="AdminLogin"? And how do I pass some message to the login view that tells "you need to login to access that" or similar?
/M
Here is how I solved the redirect-part:
public class Authenticate : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
RedirectToRoute(filterContext,
new
{
controller = "AdminLogin",
action = "AdminLogin"
});
}
}
private void RedirectToRoute(ActionExecutingContext context, object routeValues)
{
var rc = new RequestContext(context.HttpContext, context.RouteData);
string url = RouteTable.Routes.GetVirtualPath(rc,
new RouteValueDictionary(routeValues)).VirtualPath;
context.HttpContext.Response.Redirect(url, true);
}
}
Not sure if it is optimal but seems to do the job correctly