ASP MVC 3 OutputCache avoid cacheing current request - asp.net-mvc

I started using OutputCache for my website.
The problem that I encounter is that when a user update an item I need to reset the cache for that item.
I did that using:
var urlToRemove = Url.Action("Details", "Dress", new {id = model.Id});
Response.RemoveOutputCacheItem(urlToRemove);
In Edit action I also set to TempData the update success message and I display it on the next request. The problem is that the message remains in the cached response.
Do you know how can I avoid caching in an action. Something like:
[OutputCache(Duration = 3600, VaryByParam = "id")]
public ViewResult Details(int id)
{
if(NotificationHelper.HasNotifications)
Response.DoNotCache();
.....
I cannot use the same trick ... because the page is added to the cache after its rendered. So I cannot exclude an action from cache in its body.

What you are describing is sometimes referred as "Donut Hole Caching" because you want to cache everything except some bit of dynamic content in the middle.
Here is are a couple resources you might want to look at:
http://www.devtrends.co.uk/blog/donut-output-caching-in-asp.net-mvc-3
https://github.com/janjongboom/Moth/wiki/Server-side-caching

I don't know if there is an easy way to do what you are asking. However, my experience with messages and OutputCache is don't put them in the response. I ended up making sure there were no messages that are ever displayed on a cached page. If I absolutely had to have a message on a cached page I would set up an ajax call that would grab the messages once the response reached the client.

what about this?
class CustomOutputCacheAttribute : OutputCacheAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.HttpContext.Response.StatusCode >= 400)
{
this.Location = System.Web.UI.OutputCacheLocation.None;
this.Duration = -1;
}
filterContext.HttpContext.Response.Cache.SetOmitVaryStar(true);
base.OnActionExecuted(filterContext);
}
}

Related

ValidateInputAttribute not working in Post Request of ASP.NET MVC controller

My understanding was OOTB, MVC will validate input to prevent XSS Attack and SQL Injection.
For example, In one of my app, the "a dangerous input has been detected" error will be received when I put in HTTP Get request. However, the post actions can let these values posted successfully through html input element without error. Even after I marked the controller action as [ValidateInput(true)]. How can I make them validate those post input?
Any advice will be appreciated!
Without seeing your GET handler, or what you're sending to it, it's tough to say why it behaves that way. However, OOTB MVC guards against SQL injection through the use of Entity Framework, and against XSS through ModelState validation.
Inside the body of your POST action that handles this forms submission you'll want to use code much like the following:
if (ModelState.IsValid)
{
//do the stuff I want to do when things are valid and free of XSS
}
else
{
//something went wrong. Probably shouldn't process this one. Have the user try again
}
Update: please disregard my filthy lies. ValidateInput(true) is not necessary because it is on by default. So, the only things I can think of would be that you have the AllowHtml attribute on your class or properties, or you are not posting back a model for modelBinding, and therefore input validation, to occur. At this point, you're probably going to need to put up some code for further help. There's too many unknowns right now.
I ran into a similar issue - we had JQuery using $.ajax to post JSON to the MVC action. The default model binder does not validate posted JSON allowing unsafe XSS to be posted against our action.
To solve this, I found the RequestValidator has a static method InvokeIsValidRequestString that allowed
public class ValidateJsonXssAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext?.Request;
if (request != null && "application/json".Equals(request.ContentType, StringComparison.OrdinalIgnoreCase))
{
if (request.ContentLength > 0 && request.Form.Count == 0) //
{
if (request.InputStream.Position > 0)
request.InputStream.Position = 0; // InputStream has already been read once from "ProcessRequest"
using (var reader = new StreamReader(request.InputStream))
{
var postedContent = reader.ReadToEnd(); // Get posted JSON content
var isValid = RequestValidator.Current.InvokeIsValidRequestString(HttpContext.Current, postedContent,
RequestValidationSource.Form, "postedJson", out var failureIndex); // Invoke XSS validation
if (!isValid) // Not valid, so throw request validation exception
throw new HttpRequestValidationException("Potentially unsafe input detected");
}
}
}
}
}
Then, you can just decorate relevant MVC actions expecting JSON-posted data that might bypass the standard XSS prevention:
[HttpPost]
[ValidateJsonXss]
public ActionResult PublishRecord(RecordViewModel vm) { ... }
You can see other options for customizing request validation with OWASP .NET recommendations by extending the RequestValidator object, which exposes the string validation done by the ValidateInput automatically utilized by MVC for other scenarios of query string, form collection, and cookie values.
For more info: https://www.owasp.org/index.php/ASP.NET_Request_Validation

MVC3: Session_Start Fires twice when testing for Roles

I need to do some authentication for a web app with MVC3. The customer would like there to be a generic page to show if they do not have any of the role groups in windows AD that are allowed to use the app. I found a pretty simple way to do it, but just curious if it is a valid way or if there is something better out there.
Basically in the Session_Start in the global I am checking for User.IsInRole() and if that returns false then I do a Response.Redirect(). This question is: after it his the code in the IF statement and hits the Response.Redirect() code then it hits the session one more time before it goes to the AccessDenied page in the root of the app. Is this okay? Will it cause any issues If they are valid and does not enter the If to do the response.redirect?
//if (!User.IsInRole("test_user"))
//{
// Response.Redirect("~/AccessDenied.aspx", true);
//}
I would recommend you to write your Authorization filter for MVC3 and do this type of logic there:
public class RoleFilter: AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext)
{
if (!User.IsInRole("test_user"))
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new RedirectResult("~/AcessDenied.aspx");
}
}
}
Also I wouldn't recommend you to use Response.Redirect because it aborts current thread.

In Asp.Net MVC 2 is there a better way to return 401 status codes without getting an auth redirect

I have a portion of my site that has a lightweight xml/json REST API. Most of my site is behind forms auth but only some of my API actions require authentication.
I have a custom AuthorizeAttribute for my API that I use to check for certain permissions and when it fails it results in a 401. All is good, except since I'm using forms auth, Asp.net conveniently converts that into a 302 redirect to my login page.
I've seen some previous questions that seem a bit hackish to either return a 403 instead or to put some logic in the global.asax protected void Application_EndRequest()
that will essentially convert 302 to 401 where it meets whatever criteria.
Previous Question
Previous Question 2
What I'm doing now is sort of like one of the questions, but instead of checking the Application_EndRequest() for a 302 I make my authorize attribute return 666 which indicates to me that I need to set this to a 401.
Here is my code:
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == MyAuthAttribute.AUTHORIZATION_FAILED_STATUS)
{
//check for 666 - status code of hidden 401
Context.Response.StatusCode = 401;
}
}
Even though this works, my question is there something in Asp.net MVC 2 that would prevent me from having to do this? Or, in general is there a better way? I would think this would come up a lot for anyone doing REST api's or just people that do ajax requests in their controllers. The last thing you want is to do a request and get the content of a login page instead of json.
How about decorating your controller/actions with a custom filter:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class RequiresAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var user = filterContext.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.End();
}
}
}
and in your controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[RequiresAuthentication]
public ActionResult AuthenticatedIndex()
{
return View();
}
}
Another way of doing this is to implement a custom ActionResult. In my case, I wanted one anyway, since I wanted a simple way of sending data with custom headers and response codes (for a REST API.) I found the idea of doing a DelegatingActionResult and simply added to it a call to Response.End(). Here's the result:
public class DelegatingActionResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
Command(context);
// prevent ASP.Net from hijacking our headers
context.HttpContext.Response.End();
}
private readonly Action<ControllerContext> Command;
public DelegatingActionResult(Action<ControllerContext> command)
{
if (command == null)
throw new ArgumentNullException("command");
Command = command;
}
}
The simplest and cleanest solution I've found for this is to register a callback with the jQuery.ajaxSuccess() event and check for the "X-AspNetMvc-Version" response header.
Every jQuery Ajax request in my app is handled by Mvc so if the header is missing I know my request has been redirected to the login page, and I simply reload the page for a top-level redirect:
$(document).ajaxSuccess(function(event, XMLHttpRequest, ajaxOptions) {
// if request returns non MVC page reload because this means the user
// session has expired
var mvcHeaderName = "X-AspNetMvc-Version";
var mvcHeaderValue = XMLHttpRequest.getResponseHeader(mvcHeaderName);
if (!mvcHeaderValue) {
location.reload();
}
});
The page reload may cause some Javascript errors (depending on what you're doing with the Ajax response) but in most cases where debugging is off the user will never see these.
If you don't want to use the built-in header I'm sure you could easily add a custom one and follow the same pattern.
TurnOffTheRedirectionAtIIS
From MSDN, This article explains how to avoid the redirection of 401 responses : ).
Citing:
Using the IIS Manager, right-click the
WinLogin.aspx file, click Properties,
and then go to the Custom Errors tab
to Edit the various 401 errors and
assign a custom redirection.
Unfortunately, this redirection must
be a static fileā€”it will not process
an ASP.NET page. My solution is to
redirect to a static Redirect401.htm
file, with the full physical path,
which contains javascript, or a
meta-tag, to redirect to the real
ASP.NET logon form, named
WebLogin.aspx. Note that you lose the
original ReturnUrl in these
redirections, since the IIS error
redirection required a static html
file with nothing dynamic, so you will
have to handle this later.
Hope it helps you.
I'm still using the end request technique, so I thought I would make that the answer, but really
either of the options listed here are generally what I would say are the best answers so far.
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == MyAuthAttribute.AUTHORIZATION_FAILED_STATUS)
{
//check for 666 - status code of hidden 401
Context.Response.StatusCode = 401;
}
}

With ASP.NET membership, how can I show a 403?

By default, ASP.NET's membership provider redirects to a loginUrl when a user is not authorized to access a protected page.
Is there a way to display a custom 403 error page without redirecting the user?
I'd like to avoid sending users to the login page and having the ReturnUrl query string in the address bar.
I'm using MVC (and the Authorize attribute) if anyone has any MVC-specific advice.
Thanks!
I ended up just creating a custom Authorize class that returns my Forbidden view.
It works perfectly.
public class ForbiddenAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext))
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
// auth failed, display 403 page
filterContext.HttpContext.Response.StatusCode = 403;
ViewResult forbiddenView = new ViewResult();
forbiddenView.ViewName = "Forbidden";
filterContext.Result = forbiddenView;
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Asp.net has had what I consider a bug in the formsauth handling of unauthenticated vs underauthenticated requests since 2.0.
After hacking around like everyone else for years I finally got fed up and fixed it. You may be able to use it out of the box but if not I am certain that with minor mods it will suit your needs.
be sure to report success or failure if you do decide to use it and I will update the article.
http://www.codeproject.com/Articles/39062/Salient-Web-Security-AccessControlModule.aspx

How to clear/expire browser cache on log off?

In my ASP.net MVC application, I've got several views that I'd like to set to save in the browser's cache. I've got the methods built to do it, but here's my issue.
The menu in my site is different between logged in and logged off visitors. If the logged in page is cached, then even when the user logs off the menu remains in the logged in mode. It's actually not, but on that visitor's browser it is.
How can I go about clearing/expiring that cache so the visitor's browser updates when I need it to, yet still be able to make use of browser cache?
Thanks in advance!
For HTML pages it's difficult. I turned off client caching for that same reason, and tried to make the server caching as efficient as possible. I now use OutputCache with VaryByCustom set to the login status.
We ran some load tests on that system and the only bottleneck is the bandwidth that this generates.
And on a side note: I used donut-caching for the login status. But I was not able to get it to work with dynamic compression (to reduce the bandwidth bottleneck mentioned above)
See also this question
You can do it with an AutoRefresh attribute on your action method. Here are some examples:
[AutoRefresh(ControllerName = "Home", ActionName = "About", DurationInSeconds = 10)]
public ActionResult Index1()
{
}
AutoRefresh(ActionName = "About", DurationInSeconds = 15)]
public ActionResult Index2()
{
}
[AutoRefresh(RouteName = "ByFavoriteRoute", DurationInSeconds = 30)]
public ActionResult Index3()
{
}
[AutoRefresh(DurationInSeconds = 45)]
public ActionResult Index4()
{
}

Resources