Problems running an async method from a controller's constructor - asp.net-mvc

I am working on a project where I want to keep users logged in using access tokens/refresh tokens. I store these values in a cookie and whenever a user visits the site, I want to automatically log him in regardless of the page that he uses to access the site. To do this, I created a BaseController, that all other controllers inherit from. The BaseController looks like this:
public abstract class BaseController : Controller
{
public BaseController()
{
LoginModel.SetUserFromAuthenticationCookie();
}
}
This constructor gets executed every time before an action is executed and is therefore exactly what I want. The problem is that SetUserFromAuthenticationCookie() is an async method, because it has to do calls to other async methods. It looks like this:
public async static Task SetUserFromAuthenticationCookie()
{
// Check if the authentication cookie is set and the User is null
if (AuthenticationRepository != null && User == null)
{
Api api = new Api();
// If a new authentication cookie was successfully created
if (await AuthenticationRepository.CreateNewAuthenticationCookieAsync())
{
var response = await api.Request(HttpMethod.Get, "api/user/mycredentials");
if(response.IsSuccessStatusCode)
{
User = api.serializer.Deserialize<UserViewModel>(await response.Content.ReadAsStringAsync());
}
}
}
}
The problem is that the execution order is not as I anticipated and because of that the user does not get logged in. I tried to work with .Result for the async methods, but that resulted in a deadlock. Besides that I read many threads on SO concerning the issue and eventually also found one that managed to get the login to work: How would I run an async Task<T> method synchronously?. It is somewhat hacky though and works with this helper:
public static class AsyncHelpers
{
/// <summary>
/// Execute's an async Task<T> method which has a void return value synchronously
/// </summary>
/// <param name="task">Task<T> method to execute</param>
public static void RunSync(Func<Task> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
synch.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
/// <summary>
/// Execute's an async Task<T> method which has a T return type synchronously
/// </summary>
/// <typeparam name="T">Return Type</typeparam>
/// <param name="task">Task<T> method to execute</param>
/// <returns></returns>
public static T RunSync<T>(Func<Task<T>> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
T ret = default(T);
synch.Post(async _ =>
{
try
{
ret = await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return ret;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private bool done;
public Exception InnerException { get; set; }
readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
readonly Queue<Tuple<SendOrPostCallback, object>> items =
new Queue<Tuple<SendOrPostCallback, object>>();
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("We cannot send to our same thread");
}
public override void Post(SendOrPostCallback d, object state)
{
lock (items)
{
items.Enqueue(Tuple.Create(d, state));
}
workItemsWaiting.Set();
}
public void EndMessageLoop()
{
Post(_ => done = true, null);
}
public void BeginMessageLoop()
{
while (!done)
{
Tuple<SendOrPostCallback, object> task = null;
lock (items)
{
if (items.Count > 0)
{
task = items.Dequeue();
}
}
if (task != null)
{
task.Item1(task.Item2);
if (InnerException != null) // the method threw an exeption
{
throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
}
}
else
{
workItemsWaiting.WaitOne();
}
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
}
If I then change the content of the BaseController constructor to:
AsyncHelpers.RunSync(() => LoginModel.SetUserFromAuthenticationCookie());
the functionality works as anticipated.
I would like to know though if you have any suggestions on how to do this in a nicer manner. Perhaps I should move the call to the SetUserFromAuthenticationCookie() to another location, but at this time I do not know where that would be.

I found this solution on another stack. Synchronously waiting for an async operation, and why does Wait() freeze the program here
Your constructor would need to look like this.
public BaseController()
{
var task = Task.Run(async () => { await LoginModel.SetUserFromAuthenticationCookie(); });
task.Wait();
}

Related

Passing parameters to a SignalR Hub (ASP NET Core 6)

how can i pass parameters to a asynchronous task of a SignalR Hub?
The paramaeters id, dis and dg have to be passes to the asynchronous task SendResults().
My hub:
public class ResultHub : Hub
{
ResultRepository ResultRepository;
public ResultHub(IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString("DefaultConnection");
ResultRepository = new ResultRepository(connectionString);
}
public async Task SendResults()
{
int id = 2977;
int dis = 3;
int dg = 1;
var Results = ResultRepository.GetResults(id, dis, dg);
await Clients.All.SendAsync("ReceivedResults", Results);
}
}
The asynchronous task SendResults gets the results with ResultRepository.GetResults.
SendResults is called in the Javascript within the chtml file:
function InvokeResults() {
connection.invoke("SendResults").catch(function (err) {
return console.error(err.toString());
});
}
and in the method TableDependency_OnChanged of the class SubscribeResultTableDependency
public class SubscribeResultTableDependency : ISubscribeTableDependency
{
SqlTableDependency<Result> tableDependency;
ResultHub ResultHub;
public SubscribeResultTableDependency(ResultHub resultHub)
{
this.resultHub = resultHub;
}
public void SubscribeTableDependency(string connectionString)
{
tableDependency = new SqlTableDependency<Result>(connectionString);
tableDependency.OnChanged += TableDependency_OnChanged;
tableDependency.OnError += TableDependency_OnError;
tableDependency.Start();
}
private void TableDependency_OnChanged(object sender, TableDependency.SqlClient.Base.EventArgs.RecordChangedEventArgs<Result> e)
{
if (e.ChangeType != TableDependency.SqlClient.Base.Enums.ChangeType.None)
{
resultHub.SendResults();
}
}
private void TableDependency_OnError(object sender, TableDependency.SqlClient.Base.EventArgs.ErrorEventArgs e)
{
Console.WriteLine($"{nameof(Result)} SqlTableDependency error: {e.Error.Message}");
}
}
Passing of parameters in the connection.invoke of the Javascript works, but how can this be done in both calls?
(Microsoft.NETCore.App\6.0.13)
According to your description, if you want to how to pass the parameter from the js to the Hub method, I suggest you could refer to below example:
1.Modify the hub method to add parameter, like below:
public class ChatHub : Hub
{
public async Task SendResults(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
2.Modify the js to add the parameter:
connection.invoke("SendResults", "parameter1", "parameter2").catch(function (err) {
return console.error(err.toString());
});

Implement dependency injection in background services in Xamarin Forms using Prism

I am making use of Prism in my xamarin forms project.I was able to use dependency injection(constructor injection) in my View Model without any problems.I am also making use of background services to push long running tasks in the background.How do I inject dependency in my Background services?When I try to pass the interface object as a paramater to the constructor(SyncingBackgroundingCode) ,the object(SqliteService) is null.I have registered and resolved the objects in the dependency injection container.
How to handle this case?Can anybody provide an example or link to implement this scenario?
This is the piece of code where im trying to implement dependency injection.
This is in Droid :-
public class AndroidSyncBackgroundService : Service
{
CancellationTokenSource _cts;
public override IBinder OnBind (Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand (Intent intent, StartCommandFlags flags, int startId)
{
_cts = new CancellationTokenSource ();
Task.Run (() => {
try {
//INVOKE THE SHARED CODE
var oBackground = new SyncingBackgroundingCode();
oBackground.RunBackgroundingCode(_cts.Token).Wait();
}
catch (OperationCanceledException)
{
}
finally {
if (_cts.IsCancellationRequested)
{
var message = new CancelledTask();
Device.BeginInvokeOnMainThread (
() => MessagingCenter.Send(message, "CancelledTask")
);
}
}
}, _cts.Token);
return StartCommandResult.Sticky;
}
public override void OnDestroy ()
{
if (_cts != null) {
_cts.Token.ThrowIfCancellationRequested ();
_cts.Cancel ();
}
base.OnDestroy ();
}
}
This is in PCL:-
public class SyncingBackgroundingCode
{
public SQLiteConnection _sqlconnection;
SqliteCalls oSQLite = new SqliteCalls();
ISqliteService _SqliteService;
public SyncingBackgroundingCode(ISqliteService SqliteService)
{
//object is null
}
public async Task RunBackgroundingCode(CancellationToken token)
{
DependencyService.Get<ISQLite>().GetConnection();
await Task.Run (async () => {
token.ThrowIfCancellationRequested();
if (App.oSqliteCallsMainLH != null)
{
App.bRunningBackgroundTask = true;
oSQLite = App.oSqliteCallsMainLH;
await Task.Run(async () =>
{
await Task.Delay(1);
oSQLite.ftnSaveOnlineModeXMLFormat("Offline", 0);
oSQLite.SyncEmployeeTableData();
oSQLite.SaveOfflineAppCommentData();
oSQLite.SaveOfflineAdditionToFlowData();
await Task.Delay(500);
var msgStopSyncBackgroundingTask = new StopSyncBackgroundingTask();
MessagingCenter.Send(msgStopSyncBackgroundingTask, "StopSyncBackgroundingTask");
});
}
}, token);
}
}
Unfortunately Xamarin and Xamarin Forms don't give frameworks like Prism anywhere to tie into to handle IoC scenarios. There are a couple of ways you can handle this though.
First the Container is a public property on the PrismApplication in your background service you could do something like:
public class FooBackgroundService
{
private App _app => (App)Xamarin.Forms.Application.Current;
private void DoFoo()
{
var sqlite = _app.Container.Resolve<ISQLite>();
}
}
Another slightly more involved way would be to use the ServiceLocator pattern. You might have something like the following:
public static class Locator
{
private static Func<Type, object> _resolver;
public static T ResolveService<T>() =>
(T)_resolver?.Invoke(typeof(T));
public static void SetResolver(Func<Type, object> resolver) =>
_resolver = resolver;
}
In your app you would then simply set the resolver. Prism actually does something similar to this with the ViewModel locator, which then allows it to inject the correct instance of the NavigationService.
public class App : PrismApplication
{
protected override void OnInitialized()
{
SetServiceLocator();
NavigationService.NavigateAsync("MainPage");
}
protected override void RegisterTypes()
{
// RegisterTypes
}
private void SetServiceLocator()
{
Locator.SetResolver(type => Container.Resolve(type, true));
}
}
Finally your service would simply reference the Service Locator like:
public class BarBackgroundService
{
public void DoBar()
{
var sqlite = Locator.ResolveService<ISQLite>();
// do foo
}
}

MVC 6 How can I include a BaseRepository in my controller class

I am using an ORM to connect to the database it is called dapper. The issue with it is that it's database calls are synchronous and I recently found a way to make it asynchronous by following this short tutorial http://www.joesauve.com/async-dapper-and-async-sql-connection-management/ . My question is how can I bring this BaseRepository into my Controller class ? This is the code on that website and it's the same one I have
BaseRepository- by the way there is no issue in this code
public abstract class BaseRepository
{
private readonly string _ConnectionString;
protected BaseRepository(string connectionString)
{
_ConnectionString = connectionString;
}
protected async Task<T> WithConnection<T>(Func<IDbConnection, Task<T>> getData)
{
try {
using (var connection = new SqlConnection(_ConnectionString)) {
await connection.OpenAsync(); // Asynchronously open a connection to the database
return await getData(connection); // Asynchronously execute getData, which has been passed in as a Func<IDBConnection, Task<T>>
}
}
catch (TimeoutException ex) {
throw new Exception(String.Format("{0}.WithConnection() experienced a SQL timeout", GetType().FullName), ex);
}
catch (SqlException ex) {
throw new Exception(String.Format("{0}.WithConnection() experienced a SQL exception (not a timeout)", GetType().FullName), ex);
}
}
}
and now he brings it in like this
public class PersonRepository : BaseRepository
{
public PersonRepository(string connectionString): base (connectionString) { }
public async Task<Person> GetPersonById(Guid Id)
{
return await WithConnection(async c => {
// Here's all the same data access code,
// albeit now it's async, and nicely wrapped
// in this handy WithConnection() call.
var p = new DynamicParameters();
p.Add("Id", Id, DbType.Guid);
var people = await c.QueryAsync<Person>(
sql: "sp_Person_GetById",
param: p,
commandType: CommandType.StoredProcedure);
return people.FirstOrDefault();
});
}
}
The part I am having a problem with is this public class PersonRepository : BaseRepository because Asp.Net Controllers start with public class HomeController: Controller , I need access to the WithConnection method to get this working. My controller looks like this
public class HomeController : Controller
{
public class ConnectionRepository : BaseRepository
{
public ConnectionRepository(string connectionString) : base(connectionString) { }
}
public async Task<ActionResult> topfive()
{
// I get Error on WithConnection as it can't see the BaseRepository
return await WithConnection(async c =>
{
var topfive = await c.QueryAsync<Streams>("select * from streams ").ToList();
return View(topfive);
});
}
}
I obviously can not cover my ActionResult method with the BaseRepository because it gives all types of errors any suggestions ?
Why are you using inheritance instead of composition? What about something like:
public class PersonRepository : BaseRepository
{
public PersonRepository(string connectionString): base (connectionString) { }
public async Task<Person> GetPersonById(Guid Id)
{
return await WithConnection(async c => {
// Here's all the same data access code,
// albeit now it's async, and nicely wrapped
// in this handy WithConnection() call.
var p = new DynamicParameters();
p.Add("Id", Id, DbType.Guid);
var people = await c.QueryAsync<Person>(
sql: "sp_Person_GetById",
param: p,
commandType: CommandType.StoredProcedure);
return people.FirstOrDefault();
});
}
}
public class ConnectionRepository : BaseRepository
{
public ConnectionRepository(string connectionString) : base(connectionString) { }
}
public async Task<List<TopFileClass>> topfive()
{
// I get Error on WithConnection as it can't see the BaseRepository
return await WithConnection(async c =>
{
var topfive = await c.QueryAsync<Streams>("select * from streams ").ToList();
return topfive;
});
}
public class HomeController : Controller
{
private readonly PersonRepository _repo;
public HomeController(PersonRepository repo)
{
_repo = repo;
}
public async Task<ActionResult> TopFive()
{
var top5 = await _repo.topfive();
return View(top5);
}
}
If you are not familiar how to make the repository automatically get injected into the constructor, read up on dependency injection in MVC 6.
you have to intehirt the "BaseRepository" from "Controller". i think that will work for you. then just go with below code:
public abstract class BaseRepository : Controller
{
// do you work
}
public class PersonRepository : BaseRepository
{
public PersonRepository(string connectionString): base (connectionString) { }
public async Task<Person> GetPersonById(Guid Id)
{
return await WithConnection(async c => {
// Here's all the same data access code,
// albeit now it's async, and nicely wrapped
// in this handy WithConnection() call.
var p = new DynamicParameters();
p.Add("Id", Id, DbType.Guid);
var people = await c.QueryAsync<Person>(
sql: "sp_Person_GetById",
param: p,
commandType: CommandType.StoredProcedure);
return people.FirstOrDefault();
});
}
}

IMobileServiceClient.PullAsync deadlock when trying to sync with Azure Mobile Services

I have the classes below.
public class AzureMobileDataContext : IAsyncInitialization
{
private static readonly Lazy<AzureMobileDataContext> lazy =
new Lazy<AzureMobileDataContext> (() =>
new AzureMobileDataContext(
new MobileServiceClient(
"http://myservice.azure-mobile.net/",
"123456789ABCDEFGHIJKLMNOP")));
public static AzureMobileDataContext Instance { get { return lazy.Value; } }
public Task Initialization { get; private set; }
public IMobileServiceClient Context { get; private set; }
private Object lockObj = new Object ();
private static MobileServiceSQLiteStore store;
public AzureMobileDataContext (IMobileServiceClient context)
{
Context = context;
Initialization = Init ();
Initialization.ContinueWith (async (antecedent) => {
await Context.SyncContext.InitializeAsync (store, new MobileServiceSyncHandler ());
});
}
private Task Init ()
{
return Task.Run (() => {
lock (lockObj) {
if (!Context.SyncContext.IsInitialized) {
try {
store = new MobileServiceSQLiteStore ("mysqlite.db3");
store.DefineTable<Post> ();
store.DefineTable<PostPhotoUrl> ();
store.DefineTable<User> ();
store.DefineTable<Club> ();
store.DefineTable<District> ();
} catch (Exception ex) {
Debug.WriteLine ("Init: {0}", ex.Message);
}
}
}
});
}
public async Task<IMobileServiceSyncTable<TEntity>> GetTableAsync<TEntity> ()
{
await Initialization;
return Context.GetSyncTable<TEntity> ();
}
public async Task PushAsync ()
{
try {
await Initialization;
await Context.SyncContext.PushAsync ();
} catch (MobileServiceInvalidOperationException invalidOperationEx) {
Debug.WriteLine (invalidOperationEx.Message);
} catch (MobileServicePushFailedException pushFailedException) {
Debug.WriteLine (pushFailedException.Message);
}
}
public async Task PullAsync<TEntity> (IMobileServiceTableQuery<TEntity> query)
{
try {
await Initialization;
IMobileServiceSyncTable<TEntity> entityTable = await GetTableAsync<TEntity> ();
await entityTable.PullAsync (typeof(TEntity).ToString (), query); // Never returns, no exception is caught or thrown.
await entityTable.PurgeAsync ();
} catch (MobileServiceInvalidOperationException preconditionFailedEx) {
Debug.WriteLine (preconditionFailedEx.Message);
} catch (Exception ex) {
Debug.WriteLine (ex.Message);
}
}
public async Task SyncAsync<TEntity> ()
{
await PushAsync ();
IMobileServiceSyncTable<TEntity> syncTable = await GetTableAsync<TEntity> ();
await PullAsync (syncTable.CreateQuery ());
}
}
I use this singleton from a BaseRepository I have that is a base class for 5 different entity repositories.
public abstract class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected AzureMobileDataContext MobileServiceContext { get { return AzureMobileDataContext.Instance; } }
protected virtual Task PushAsync ()
{
return MobileServiceContext.PushAsync ();
}
protected virtual Task PullAsync (IMobileServiceTableQuery<TEntity> query)
{
return MobileServiceContext.PullAsync (query);
}
public virtual async Task<DataObjectResponse<IEnumerable<TEntity>>> FindAsync (Expression<Func<TEntity, bool>> predicate)
{
IMobileServiceSyncTable<TEntity> syncTable = await MobileServiceContext.GetTableAsync<TEntity> ();
await PullAsync (syncTable.CreateQuery ());
IEnumerable<TEntity> entities = await syncTable.Where (predicate).ToEnumerableAsync ();
return new DataObjectResponse<IEnumerable<TEntity>> (entities);
}
}
The users repository.
public class UsersAzureRepository : BaseRepository<User>, IUsersRepository
{
public UsersAzureRepository ()
{
}
public async Task<DataObjectResponse<User>> FindByIdAsync (string entityId)
{
DataObjectResponse<IEnumerable<User>> users = await FindAsync (p => p.Id == entityId);
return new DataObjectResponse<User>(users.Data.FirstOrDefault ());
}
}
A DataService Facade class containing the GetUserById method.
public async Task<UserModel> GetUserById (string userId)
{
DataObjectResponse<User> users = await UsersRepository.FindByIdAsync (userId);
UserModel userModel = Mapper.Map<User, UserModel> (users.Data);
return userModel;
}
Users view model method.
public async Task<UserModel> GetUsersAsync() // testing purposes
{
UserModel user = await _dataService.GetUserById("032beb3b-1cbf-4a0d-809c-a25c71139c55");
if (user != null) {
Debug.WriteLine ("User loaded: {0}", user.Id);
}
return user;
}
Call from an iOS UIViewController.
public async override void ViewDidLoad ()
{
base.ViewDidLoad ();
UserModel user = await ViewModel.GetUsersAsync ();
}
The AzureMobileDataContext serves more as a thread safe wrapper to the IMobileServiceClient context operations, making sure not multiple threads will try to initialize the database (I had an exception when using it directly to the BaseRepository<T> before).
I'm not so sure from here where might the problem is. I suspect that the wrapper is not the best solution and any recommendations are welcome.
Any other ways to debug the PullAsync method?
[EDIT]
The local SQLite database syncs the table data from the remote service but still the call doesn't return.
The problem was in the Azure Mobile Service, server side.
I was returning from the TableController an IEnumerable but the SDK uses OData query expressions to do it's own job, returning an IEnumerable is not sufficient, changing to IQueryable fixed this issue of a continuous looping when pulling data.
I strongly believe that the server return type shouldn't be related to the SDK but this is the way it works.

How to turn output caching off for authenticated users in ASP.NET MVC?

I have an ASP.NET MVC application. I need to cache some pages however only for non-authenticated users.
I've tried to use VaryByCustom="user" with the following GetVaryByCustomString implementation:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom == "user")
{
if (context.User.Identity.IsAuthenticated)
{
return context.User.Identity.Name;
}
else
{
return "";
}
}
return base.GetVaryByCustomString(context, custom);
}
However this isn't exactly what I need because pages are still cached. Only difference is that now is cached for each user separately.
One possible solution is to return Guid.NewGuid() each time when user is Authenticated, but it looks like a huge waste of resources to me.
So do you have any tips for me?
So here is what I done:
public class NonAuthenticatedOnlyCacheAttribute : OutputCacheAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
if (httpContext.User.Identity.IsAuthenticated)
{
// it's crucial not to cache Authenticated content
Location = OutputCacheLocation.None;
}
// this smells a little but it works
httpContext.Response.Cache.AddValidationCallback(IgnoreAuthenticated, null);
base.OnResultExecuting(filterContext);
}
// This method is called each time when cached page is going to be
// served and ensures that cache is ignored for authenticated users.
private void IgnoreAuthenticated(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
if (context.User.Identity.IsAuthenticated)
validationStatus = HttpValidationStatus.IgnoreThisRequest;
else
validationStatus = HttpValidationStatus.Valid;
}
}
Many thanks to Craig Stuntz who pointed me to correct direction and whose answer I unwittingly downvoted.
Attributes in general are cached, then you need to store original Location. If you access the page Logged, it set Location to None, then when you access as anonymous, it still None.
public class AuthenticatedOnServerCacheAttribute : OutputCacheAttribute
{
private OutputCacheLocation? originalLocation;
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
if (httpContext.User.Identity.IsAuthenticated)
{
originalLocation = originalLocation ?? Location;
Location = OutputCacheLocation.None;
}
else
{
Location = originalLocation ?? Location;
}
base.OnResultExecuting(filterContext);
}
}
The accepted answer is correct but it doesn't work for caching in this way Partial views.
I have combined both variants:
GetVaryByCustomString and set Duration to the minimum - for Partial Views and AddValidationCallback method for pages. Actually it is possible to use only the first method but the second one looks not such expensive - it doesn't call OnResultExecuting each time but only registered handler.
So the custom cache attribute class
public class CacheAttribute : OutputCacheAttribute
{
public CacheAttribute()
{
Duration = 300; /*default cache time*/
}
private bool _partialView;
/// <summary>
/// Set true if Partial view is cached
/// </summary>
public bool PartialView
{
get { return _partialView; }
set
{
_partialView = value;
if ( _partialView ) {
VaryByCustom = "Auth";
}
}
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if ( PartialView ) OnCachePartialEnabled( filterContext );
else OnCacheEnabled(filterContext);
base.OnResultExecuting( filterContext );
}
private OutputCacheLocation? originalLocation;
private int? _prevDuration;
protected void OnCachePartialEnabled(ResultExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
if ( !_prevDuration.HasValue) _prevDuration = Duration;
Duration = httpContext.User.Identity.IsAuthenticated ? 1 : _prevDuration.Value;
}
protected void OnCacheEnabled(ResultExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
if ( httpContext.User.Identity.IsAuthenticated ) {
// it's crucial not to cache Authenticated content
originalLocation = originalLocation ?? Location;
Location = OutputCacheLocation.None;
}
else {
Location = originalLocation ?? Location;
}
// this smells a little but it works
httpContext.Response.Cache.AddValidationCallback( IgnoreAuthenticated, null );
}
// This method is called each time when cached page is going to be
// served and ensures that cache is ignored for authenticated users.
private void IgnoreAuthenticated(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = context.User.Identity.IsAuthenticated
? HttpValidationStatus.IgnoreThisRequest
: HttpValidationStatus.Valid;
}
}
Override GetVaryByCustomString method in Global.asax.cs
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if ( custom == "Auth" ) {
//do not cache when user is authenticated
if ( context.User.Identity.IsAuthenticated ) {
return base.GetVaryByCustomString( context, custom );
}
return "NotAuth";
}
return base.GetVaryByCustomString( context, custom );
}
Use it like this:
[Cache]
public virtual ActionResult Index()
{
return PartialView();
}
[ChildActionOnly, Cache(PartialView=true)]
public virtual ActionResult IndexPartial()
{
return PartialView();
}
Updated: I have also added Fujiy's fix here

Resources