In an ASP.Net project I recently changed some lengthy functions to async. This has turned out to cause some unexpacted problems.
Basically the functions are working when using await, but having to call it from another async function end up being an issue in the form. So far I set page directive Async to true and used RegisterAsyncTask to initiate calls to async functions. The call works. But it turns out when I have Async page directive enabled, the user context sometimes changes.
To imposonate the calling user for all code in certain pages, I have inherited the class System.Web.UI.Page to ImpersonatedPage. In my class I use OnLoad to imporsonate the calling user. This way I am certain user can never see or do any more then he/she has access to already, and audit logs shows the correct user.
When Async page directive enabled it still impersonates the user. But for example in the code for a button click, user context is suddently back to the application pools context. In Page_Load the user context is the calling user as expected. What is causing this, and is it possible to avoid switching back to the application pool user?
Alternatively I tried removing the Async page directive, and calling the async function by using Task.Run. Then the user context does not switch back, but instead HttpContext.Current is null and the async function is not able to do it's work.
Any other ways to call async function from ASP.Net without having to enable Async page directive?
Target Framework: 4.6.1 AFAIK no quirks enabled
ImpersonatedPage class:
public class ImpersonatedPage : System.Web.UI.Page
{
WindowsImpersonationContext m_wic = null;
protected override void OnLoad(EventArgs e)
{
WindowsIdentity wi = (WindowsIdentity)System.Web.HttpContext.Current.User.Identity;
m_wic = wi.Impersonate();
HttpContext.Current.Response.Write("Impersonate<br />");
base.OnLoad(e);
}
protected override void OnUnload(EventArgs e)
{
base.OnUnload(e);
_ImpersonationUndo();
}
protected void _ImpersonationUndo()
{
if (m_wic != null)
{
m_wic.Undo();
HttpContext.Current.Response.Write("Impersonate undo<br />");
}
m_wic = null;
}
}
Form page directives:
<%@ Page Async="true" Language="C#" AutoEventWireup="true" Inherits="ProxyAddresses" Codebehind="ProxyAddresses.aspx.cs" %>
Class declaration:
public partial class ProxyAddresses : ImpersonatedPage
Page_Load:
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("Page_Load: " + WindowsIdentity.GetCurrent().Name + "<br />");
if (!IsPostBack)
{
RegisterAsyncTask(new PageAsyncTask(InitialPageLoadAsync));
}
...
Async func:
private async Task InitialPageLoadAsync()
{
Response.Write("InitialPageLoadAsync: " + WindowsIdentity.GetCurrent().Name + "<br />");
await fnDoSomeAsyncWork();
...
Button click:
protected void idBtnSaveSrv_Click(object sender, EventArgs e)
{
Response.Write("idBtnSaveSrv_Click: " + WindowsIdentity.GetCurrent().Name + "<br />");
...
Debug output - Initial request:
- Impersonate
- Page_Load: DOMAIN\myuser
- InitialPageLoadAsync: IIS APPPOOL\appuser (Why?)
- [page content]
- Impersonate undo
Debug output - Button click:
- Impersonate
- Page_Load: DOMAIN\myuser
- idBtnSaveSrv_Click: IIS APPPOOL\appuser (Why?)
- [page content]
- Impersonate undo
async void, not use RegisterAsyncTask. RegisterAsyncTask is meant to notify IIS about a running task that shouldn't be aborted when the current request ends