Extend OutputCacheAttribute in MVC 3 - asp.net-mvc

What caching mechanism does OutputCacheAttribute use and how can i extend it?

It uses ASP.NET WebForm caching, and you can extend it by overriding OnResultExecuting.
Refer to:
http://forums.asp.net/t/1703571.aspx/1?Extending+OutputCache+Attribute+in+asp+net+mvc+3
Code extract from above link.
public override void OnResultExecuting(ResultExecutingContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (!filterContext.IsChildAction) {
// we need to call ProcessRequest() since there's no other way to set the Page.Response intrinsic
using (OutputCachedPage page = new OutputCachedPage(_cacheSettings)) {
page.ProcessRequest(HttpContext.Current);
}
}
}
private sealed class OutputCachedPage : Page {
private OutputCacheParameters _cacheSettings;
public OutputCachedPage(OutputCacheParameters cacheSettings) {
// Tracing requires Page IDs to be unique.
ID = Guid.NewGuid().ToString();
_cacheSettings = cacheSettings;
}
protected override void FrameworkInitialize() {
// when you put the <%# OutputCache %> directive on a page, the generated code calls InitOutputCache() from here
base.FrameworkInitialize();
InitOutputCache(_cacheSettings);
}
}

Related

Why my action filter not work?

I'm new in asp.net mvc, i need detect view page refresh with user and do some thing for that purpose read this:
asp.net mvc - Detecting page refresh
for define action filter right click on controller folder and add RefreshDetectFilter class :
namespace StoreProject.Controllers
{
public class RefreshDetectFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookie = filterContext.HttpContext.Request.Cookies["RefreshFilter"];
filterContext.RouteData.Values["IsRefreshed"] = cookie != null &&
cookie.Value == filterContext.HttpContext.Request.Url.ToString();
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Response.SetCookie(new HttpCookie("RefreshFilter", filterContext.HttpContext.Request.Url.ToString()));
}
}
}
and register that in Global.asax,with this way:
GlobalFilters.Filters.Add(new RefreshDetectFilter());
in my action want to use that action filter with this code:
if (RouteData.Values["IsRefreshed"] == true)
{
// page has been refreshed.
}
but i get this error:
How can i solve that problem?thanks.
When you read values out of RouteData.Values, you are given an Object rather than a bool. You need to cast to the appropriate type before performing your check.
You also don't need to check against equality with true as the value is already true or false:
if ((bool) RouteData.Values["IsRefreshed"])
{
}

asp.net MVC 3 applying AuthorizeAttribute to areas

I'm currently writing an Admin MVC 3 site, and each user only has access to certain parts of the site.
The areas of my site are the same as the user Roles, so what I would like to do is the put the AuthorizeAttribute on each area, using the area's name as the parameter in the Role.
So far I've got this to work when I'm hard coding the checking of each area, but I would like to just loop through all areas and apply the Authorize filter.
(i'm using this as my custom FilterProvider - http://www.dotnetcurry.com/ShowArticle.aspx?ID=578)
My code so far ("Gcm" is one of my areas, and is also a Role) :
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// for all controllers, run AdminAuthorizeAttribute to make sure they're at least logged in
filters.Add(ObjectFactory.GetInstance<AdminAuthorizeAttribute>());
AdminAuthorizeAttribute gcmAuthroizeAttribute = ObjectFactory.GetInstance<AdminAuthorizeAttribute>();
gcmAuthroizeAttribute.Roles = "Gcm";
var provider = new FilterProvider();
provider.Add(
x =>
x.RouteData.DataTokens["area"] != null && x.RouteData.DataTokens["area"].ToString() == "Gcm"
? gcmAuthroizeAttribute
: null);
FilterProviders.Providers.Add(provider);
}
Does anyone know how to get all the areas of my application, so I can just loop through them, rather than hard coding each area?
Or if anyone has a better idea of how to Authorize per area, that would be appreciated to.
Thanks for your help
Saan
You could you make a base controller for each area, and put the authorize attribute over the base class. That way you can pass the area parameter in for each area's base controller.
Here is an example of a Authorize Attribute override i have created. I needed my authorize function to support to types of member ship so you might not want to get too into the inner workings of the functions, but AuthorizeCore is where the main logic occures. In my case i am checking it against a entity datacontext.
Usage:
[AjaxAuthorize(AjaxRole = "Administrators")]
public JsonResult SaveAdministrativeUser(v.... )
Code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
private class HttpAuthorizeFailedResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 403. Membership.Provider.Name == "UnitArchiveMembershipProvider"
context.HttpContext.Response.StatusCode = context.HttpContext. User.Identity is WindowsIdentity ? 401 : 403;
}
}
public string AjaxRole { get; set;}
public AjaxAuthorizeAttribute()
{
AjaxRole = "Users";
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (string.IsNullOrEmpty(MvcApplication.Config.DBSettings.Database))
{
return true;
}
//When authorize parameter is set to false, not authorization should be performed.
UnitArchiveData db = DataContextFactory.GetWebRequestScopedDataContext<UnitArchiveData>(MvcApplication.Config.DBSettings.GetConnectionString());
if (httpContext.User.Identity.IsAuthenticated)
{
login_data user = db.login_datas.Where(n => n.EmailAddress == httpContext.User.Identity.Name).FirstOrDefault();
if (user != null)
{
return user.cd_login_role.RoleName == "Administrators" || user.cd_login_role.RoleName == AjaxRole;
}
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//Ajax request doesn't return to login page, it just returns 403 error.
filterContext.Result = new HttpAuthorizeFailedResult();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
When I was investigating a separate issue, I came across How to pass parameters to a custom ActionFilter in ASP.NET MVC 2?
That attribute example can be altered to check for the current Controller's area.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
RouteData routeData = filterContext.RouteData;
// check if user is allowed on this page
if (SessionFactory.GetSession().Contains(SessionKey.User))
{
User user = (User)SessionFactory.GetSession().Get(SessionKey.User);
string thisArea = routeData.DataTokens["area"].ToString();
// if the user doesn't have access to this area
if (!user.IsInRole(thisArea))
{
HandleUnauthorizedRequest(filterContext);
}
}
// do normal OnAuthorization checks too
base.OnAuthorization(filterContext);
}
}
I then apply my custom authorize attribute to all controllers like this in Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// for all controllers, run CustomAuthorizeAttribute to make sure they're at logged in and have access to area
filters.Add(ObjectFactory.GetInstance<CustomAuthorizeAttribute>());
}
Thanks for all who replied
Saan

TempData are always empty

I want to use TempData to store messages between Post and followed redirect but TempData are always empty.
I have BaseContoller offering some infrastructure for passing TempData. Simplified code looks like:
public abstract class BaseController : Controller
{
public const string AuditMessagesKey = "AuditMessages";
private List<InformationMessage> _informationMessages = new List<InformationMessage>();
protected BaseController()
{
// I also tried this in overriden Initialize
ViewData[AuditMessagesKey] = GetAuditMessages();
}
protected void AddAuditMessage(InformationMessage message)
{
if (message == null)
return;
_informationMessages.Add(message);
}
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
if (filterContext.Result is RedirectToRouteResult)
{
// I see that messages are stored into TempData
TempData[AuditMessagesKey] = _informationMessages;
// This also doesn't help
// TempData.Keep(AuditMessagesKey);
}
}
private ICollection<InformationMessage> GetAuditMessages()
{
// TempData are always empty here
var messages = TempData[AuditMessagesKey] as List<InformationMessage>;
if (messages == null)
{
messages = new List<InformationMessage>();
}
return messages;
}
}
Action method looks like:
[HttpPost]
public ActionResult CancelEdit(RequestSaveModel model)
{
AddAuditMessage(new InformationMessage
{
Message = String.Format(Messages.RequestEditationCanceled, model.Title),
Severity = MessageSeverity.Information
});
return RedirectToAction("Detail", new { Id = model.Id});
}
Application is tested on VS Development web server. There are no Ajax calls and I removed all Html.RenderAction calls from my master page. I can see that TempData are accessed only once per request in GetAuditedMessages and stored only once in OnResultExecuting. Nothing overwrites the data. Session state is allowed.
The code is little bit simplified. We are also using antiforgery token, custom filters for authorization and for action selection but it should not affect TempData behavior.
I don't understand it. I used TempData before in test application and it worked fine.
The problem I see in your code is that you are trying to retrieve the data from TempData in the controller's constructor - which is before it is available.
Move the call to GetAuditMessages() into an OnActionExecuting method, and it will be accessible.
public abstract class BaseController : Controller
{
public const string AuditMessagesKey = "AuditMessages";
private List<InformationMessage> _informationMessages = new List<InformationMessage>();
protected BaseController()
{
// TempData is not available yet
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData[AuditMessagesKey] = GetAuditMessages();
base.OnActionExecuting(filterContext);
}
protected void AddAuditMessage(InformationMessage message)
{
if (message == null)
return;
_informationMessages.Add(message);
}
protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
if (filterContext.Result is RedirectToRouteResult)
{
// I see that messages are stored into TempData
TempData[AuditMessagesKey] = _informationMessages;
// This also doesn't help
// TempData.Keep(AuditMessagesKey);
}
}
private ICollection<InformationMessage> GetAuditMessages()
{
var messages = TempData[AuditMessagesKey] as List<InformationMessage>;
if (messages == null)
{
messages = new List<InformationMessage>();
}
return messages;
}
}
I think this is what's happening:
In CancelEdit, your RedirectToAction is returned, and the framework redirects to "Detail". In your Detail method, the ActionExecuting fires, but it's filterContext.Result is not your RedirectToAction result - it's a new result (actually, no result as of yet).
Do you need the check for "filterContext.Result is RedirectToRouteResult"? It seems that you will only have those messages added before you perform a redirect.
in my Solution I forgot to remove HttpCookies for my development
it just work on published site in Https
<httpCookies httpOnlyCookies="true" requireSSL="true" />

Patching Asp.net Mvc2 AntiForgeryToken exception

Some background to my issue:
It appears that there is a change/bug in Mvc2 concerning ValidateAntiForgeryTokenAttribute.
When upgrading from Mvc1 to Mvc2, users with an active session will receive the following error when they request a page using ValidateAntiForgeryTokenAttribute:
Unable to cast object of type 'System.Web.UI.Triplet' to type 'System.Object[]'.
The issue is documented here.
After upgrading to Mvc2, we anticipate that we will be seriously affected by this issue. I have coded a fix derived from the code in the comments (documented below for posterity). At the moment this code is invoked by creating a subclass of Controller and AsyncController overriding the Initialize method in order to correct the problem. e.g.
public class FixedController:Controller
{
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
this.FixAntiForgeryTokenMvc1ToMvc2(requestContext); //extension
}
}
internal static class ControllerEx
{
public static void FixAntiForgeryTokenMvc1ToMvc2(
this Controller controller,
RequestContext requestContext)
{
var cc = new ControllerContext(requestContext,
controller);
var antiForgeryAttribute = new ValidateAntiForgeryTokenAttribute();
try
{
antiForgeryAttribute.OnAuthorization(new AuthorizationContext(cc));
}
catch (HttpAntiForgeryException forgeryException)
{
var castException = forgeryException.InnerException;
if (castException != null
&& castException is InvalidCastException
&& castException.Message.StartsWith(
"Unable to cast object of type"
+ " 'System.Web.UI.Triplet' to type"
+ " 'System.Object[]'"))
{
var responseTokenCookieNames =
controller
.Response
.Cookies
.Cast<Cookie>()
.Select(c => c.Name)
.Where(n => n.Contains("RequestVerificationToken"));
foreach (var s in responseTokenCookieNames)
{
var cookie = controller.Response.Cookies[s];
if (cookie != null)
{
cookie.Value = "";
}
}
var requestTokenCookieNames =
controller
.Request
.Cookies
.Cast<String>()
.Where(n => n.Contains("RequestVerificationToken"))
.ToList();
foreach (var c in requestTokenCookieNames)
{
controller.Request.Cookies.Remove(c);
}
}
}
}
}
The knock on effect of this is that I must alter all my controller classes to derive from my new, corrected Controller subclasses. This seems quite intrusive for code that intend to deprecate in about a month's time.
So, coming to my question, I'd like to know if there is a less intrusive means of patching existing classes such that downstream users of the class do not have to be altered, perhaps using reflection?
Spender,
rather than code every controller, use a basecontroller and inherit from that along the lines of:
public abstract class BaseController : Controller
{
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
FixAntiForgeryTokenMvc1ToMvc2(this, requestContext);
}
private static void FixAntiForgeryTokenMvc1ToMvc2(
Controller controller, RequestContext requestContext)
{
// stuff ....
}
}
and in your 'normal' controllers, just have:
public class NormalController : BaseController
{
// all the previous stuff
}
give it a go...

ASP.NET MVC: How to automatically disable [RequireHttps] on localhost?

I want my login page to be SSL only:
[RequireHttps]
public ActionResult Login()
{
if (Helper.LoggedIn)
{
Response.Redirect("/account/stats");
}
return View();
}
But obviously it doesn't work on localhost when I develop and debug my application. I don't wanna use IIS 7 with SSL certificates, how can I automatically disable the RequireHttps attribute?
Update
Based on info provided by StackOverflow users and ASP.NET MVC 2 source code I created the following class that solves the problem.
public class RequireSSLAttribute : FilterAttribute, IAuthorizationFilter
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.Request.IsSecureConnection)
{
HandleNonHttpsRequest(filterContext);
}
}
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.Url.Host.Contains("localhost")) return;
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed via SSL");
}
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
And it's used like this:
[RequireSSL]
public ActionResult Login()
{
if (Helper.LoggedIn)
{
Response.Redirect("/account/stats");
}
return View();
}
The easiest thing would be to derive a new attribute from RequireHttps and override HandleNonHttpsRequest
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.Request.Url.Host.Contains("localhost"))
{
base.HandleNonHttpsRequest(filterContext);
}
}
HandleNonHttpsRequest is the method that throws the exception, here all we're doing is not calling it if the host is localhost (and as Jeff says in his comment you could extend this to test environments or in fact any other exceptions you want).
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
if (!HttpContext.Current.IsDebuggingEnabled) {
filters.Add(new RequireHttpsAttribute());
}
}
#if (!DEBUG)
[RequireHttps]
#endif
public ActionResult Login()
{
if (Helper.LoggedIn)
{
Response.Redirect("/account/stats");
}
return View();
}
You can encapsulate this requirement in a derived attribute:
class RequireHttpsNonDebugAttribute : RequireHttpsAttribute {
public override void HandleNonHttpsRequest(AuthorizationContext ctx) {
#if (!DEBUG)
base.HandleNonHttpsRequest(ctx);
#endif
}
}
MVC 6 (ASP.NET Core 1.0):
The proper solution would be to use env.IsProduction() or env.IsDevelopment().
Example:
Startup.cs - AddMvc with a custom filter:
public void ConfigureServices(IServiceCollection services)
{
// TODO: Register other services
services.AddMvc(options =>
{
options.Filters.Add(typeof(RequireHttpsInProductionAttribute));
});
}
Custom filter inherit from RequireHttpsAttribute
public class RequireHttpsInProductionAttribute : RequireHttpsAttribute
{
private bool IsProduction { get; }
public RequireHttpsInProductionAttribute(IHostingEnvironment environment)
{
if (environment == null)
throw new ArgumentNullException(nameof(environment));
this.IsProduction = environment.IsProduction();
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (this.IsProduction)
base.OnAuthorization(filterContext);
}
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if(this.IsProduction)
base.HandleNonHttpsRequest(filterContext);
}
}
Design decisions explained:
Use environment IsProduction() or IsDevelopment() over e.g.
"#if DEBUG". Sometimes we release/publish in DEBUG mode on our test server and don't want to disable this security requirement. This needs to be disabled only in localhost/development (since we are too lazy to setup localhost SSL in IIS Express or whatever we use locally).
Use filter in Startup.cs for global setup (since we want this to apply everywhere). Startup should be responsible for registering and setting up all global rules. If your company employ a new developer, she would expect to find global setup in Startup.cs.
Use RequireHttpsAttribute logic since it's proven (by Microsoft). The only thing we want to change is when logic is applied (production) and when it's not (development/localhost). Never use "magical" strings like "http://" and "https://" when it can be avoided by reusing a Microsoft component created to provide the same logic.
Above I would consider the "proper" solution.
Note:
As an alternative, we could make a "class BaseController : Controller" and make all our controllers inherit from "BaseController" (instead of Controller). Then we only have to set the attribute 1 global place (and don't need to register filter in Startup.cs).
Some people prefer the attribute style. Please note this will eliminate design decision #2's benefits.
Example of usage:
[RequireHttpsInProductionAttribute]
public class BaseController : Controller
{
// Maybe you have other shared controller logic..
}
public class HomeController : BaseController
{
// Add endpoints (GET / POST) for Home controller
}

Resources