IIS托管模块AddOnBeginRequestAsync不需要结果

f1tvaqid  于 2023-04-06  发布在  其他
关注(0)|答案(1)|浏览(163)

尝试将搜索日志保存到本地文件中。我需要异步处理程序,但AddOnBeginRequestAsync需要从BeginRequest,EndRequest返回IAsyncResult。如何在没有它的情况下完成此操作?返回null -不起作用。
这是IIS管理的模块。

public void Dispose()
{
}

public bool IsReusable
{ get { return false; } }

public void Init(HttpApplication app)
{
    app.AddOnBeginRequestAsync(BeginRequest, EndRequest);
}
        
private IAsyncResult BeginRequest(object sender, EventArgs e, AsyncCallback cb, object extraData)
{
    string reqPath = HttpContext.Current.Request.Url.PathAndQuery;
    bool correctString = reqPath.Contains("/?search=");

    if (HttpContext.Current.Request.HttpMethod == "POST" && correctString)
    {
        using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
        {
            string searchData = HttpUtility.UrlDecode(reader.ReadToEnd());
        }
        File.AppendAllText(workDir + "search_log.txt", searchData);
    }
}

private void EndRequest(IAsyncResult ar)
{
    return;
}

当返回null添加到BeginRequest时,则发生错误“System.NullReferenceException”。
还尝试:

public class NullAsyncResult : IAsyncResult
{
public object AsyncState
{
get { return null; }
}

public System.Threading.WaitHandle AsyncWaitHandle
{
get { return null; }
}

public bool CompletedSynchronously
{
get { return true; }
}

public bool IsCompleted
{
get { return true; }
}
}

然后:

private IAsyncResult BeginRequest(object sender, EventArgs e, AsyncCallback cb, object extraData)
{
    string reqPath = HttpContext.Current.Request.Url.PathAndQuery;
    bool correctString = reqPath.Contains("/?search=");

    if (HttpContext.Current.Request.HttpMethod == "POST" && correctString)
    {
        using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
        {
            string searchData = HttpUtility.UrlDecode(reader.ReadToEnd());
        }
        File.AppendAllText(workDir + "search_log.txt", searchData);
    }
return NullAsyncResult();
}

出现错误:

CS1955 Non-callable member 'NullAsyncResult' cannot be used as a method.
ruoxqz4g

ruoxqz4g1#

使用AddOnBeginRequestAsync的目的是当你做一些异步的事情时,现在,你没有,你可以在Application_BeginRequest中做同样的事情。
然而,我认为你可以通过改变你的代码来异步写入文件。这将加快你的应用程序的速度,因为它允许你的应用程序在数据被写入文件的同时开始处理请求。这个异步代码可以通过使用EventHandlerTaskAsyncHelper来简化,这在this answer中有简要的描述。
而且,当你反复使用File.AppendAllText()时,它的开销会很大。它必须打开文件,写入数据,关闭文件,所以你可以做的另一个改进是为文件使用一个静态变量,并在应用程序的生命周期内保持打开状态。这样你就不会经常打开和关闭文件了。您需要使用TextWriter.Synchronized Package 文件流,以允许多个线程写入文件。但是,这仅适用于应用池只有一个工作进程的情况。
另外,如果你将InputStream封装在StreamWriter中并处理它(using将执行此操作),那么它也会处理InputStream,ASP.NET将无法再使用它。因此,你需要以不同的方式读取InputStream,并将Position返回到0,以便ASP.NET可以重新读取流。
您也不需要使用HttpUtility.UrlDecode(),因为您正在阅读请求的主体,而不是URL。
为了简化代码,您可以只使用Request.InputStream而不是HttpContext.Current.Request.InputStream,因为HttpApplication类确实公开了Request属性。
我认为你甚至不需要一个模块来完成这个任务,因为你是在应用程序级别使用AddOnBeginRequestAsync的,你可以在你的Global.asax.cs文件中完成这一切。
我以前做过一些这样的事情,所以我可以分享我的代码。根据您的用例调整它,它看起来像这样:

private static TextWriter logFile;

public override void Init() {
    var wrapper = new EventHandlerTaskAsyncHelper(LogRequestData);
    AddOnBeginRequestAsync(wrapper.BeginEventHandler, wrapper.EndEventHandler);
}

protected void Application_Start() {
    // Whatever else you might have already had in your start event

    // We only use FileShare.ReadWrite to accommodate the short overlap from app pool recycling.
    // The app pool must have only one worker process.
    var fs = new FileStream(workDir + "search_log.txt", FileMode.Append, FileSystemRights.AppendData, FileShare.ReadWrite, 4096, FileOptions.None);
    logFile = TextWriter.Synchronized(new StreamWriter(fs) {
        AutoFlush = true
    });
}

protected void Application_End() {
    logFile.Dispose();
}

private Task LogRequestData(object sender, EventArgs e) {
    string reqPath = Request.Url.PathAndQuery;
    bool correctString = reqPath.Contains("/?search=");

    if (Request.HttpMethod == "POST" && correctString && Request.InputStream.Length > 0) {
        var bytes = new byte[Request.InputStream.Length];
        Request.InputStream.Read(bytes, 0, bytes.Length);
        var searchData = Request.ContentEncoding.GetString(bytes);
        Request.InputStream.Position = 0;

        return logFile.WriteLineAsync(searchData);
    }
    return Task.CompletedTask;
}

我们在Application_Start中初始化logFile,因为它在应用程序的生命周期中只运行一次,而Init在每次示例化新的HttpApplication类时都会运行,这在应用程序的生命周期中可能会发生很多次。我们希望它只运行一次。你也可以在声明中初始化logFile,如果你能让workDir成为一个静态值:

private static TextWriter logFile = TextWriter.Synchronized(
    new StreamWriter(new FileStream(workDir + "search_log.txt", FileMode.Append, FileSystemRights.AppendData, FileShare.ReadWrite, 4096, FileOptions.None)) {
        AutoFlush = true
    });

你会注意到,如果你不需要记录任何东西,它会返回Task.CompletedTask,这就是你在必须返回Task的方法中所做的,但是你没有做任何异步的事情。
我还向if添加了一个条件,因此它只在实际存在数据时读取数据(Request.InputStream.Length > 0)。

相关问题