ASP。NET - LocalReport.Render() -模拟无效的令牌

时间:2021-07-21 08:10:23

I'm trying to generate a PDF report using local reporting services inside an ASP.NET MVC web application.

我正在尝试使用ASP中的本地报告服务生成PDF报告。净MVC web应用程序。

Problem is, when the call to the Render() method is done inside a separate Task, I get an exception:

问题是,当对Render()方法的调用在单独的任务中完成时,我将得到一个异常:

Microsoft.ReportingServices.ReportProcessing.ReportProcessingException: Failed to load expression host assembly. Details: Invalid token for impersonation - it cannot be duplicated.

Microsoft.ReportingServices.ReportProcessing。报告处理异常:未能加载表达式主机程序集。细节:无效的模拟令牌——它不能被复制。

If the call to Render() is hit by a user request (via controller -> class library) in the same thread, the exception is not thrown.

如果对Render()的调用被同一线程中的用户请求(通过controller ->类库)击中,则不会抛出异常。

I'm out of choices at the moment. Things I tried so far:

我现在别无选择。到目前为止我尝试过的事情:

I've tried wrapping the Task inside a using block.

我尝试过在using块中包装这个任务。

// Doesn't work even with (true) or ()
IntPtr currentUser = WindowsIdentity.GetCurrent(false).Token;
Task.Run(() =>
{
    using (WindowsIdentity.Impersonate(currentUser))
    {
        ProcessStart();
    }
});

public static void ProcessStart()
{
    LocalReport localReport = new LocalReport();

    ...
    getting data for the report
    ...

    // this statement throws the exception
    byte[] pdfStream = localReport.Render(formatString,
                       deviceInfo,
                       out mimetype,
                       out encoding,
                       out fileNameExtension,
                       out streams,
                       out warnings);

    ...
    saving bytestream to file
    ...
}

I checked THIS POST but I can't see how that could help me since I'm currently just debugging in localhost.

我查看了这篇文章,但是我看不出这对我有什么帮助,因为我目前正在localhost中调试。

I checked THIS ANSWER but it suggests to remove the task and do the reporting in the same thread, which I can't do.

我检查了这个答案,但是它建议删除这个任务并在同一个线程中执行报告,这是我做不到的。

Thanks in advance for your time.

提前谢谢你的时间。

1 个解决方案

#1


1  

On my way to the working solution I first started to tinker with the alwaysFlowImpersonationPolicy configuration flag, which seems to exist to deal with passing impersonation tokens between threads in an ASP.NET request lifecycle, there is some useful documentation but it was a dead end.

在我进入工作解决方案的过程中,我首先开始修改alwaysFlowImpersonationPolicy配置标志,它似乎存在,用于处理在ASP线程之间传递模拟令牌。NET请求生命周期中,有一些有用的文档,但这是一个死胡同。

The problem here is the identity, and the fact that LocalReport.Render uses sandboxed app domains to compile and execute expressions.

这里的问题是标识和LocalReport。渲染使用沙盒应用程序域来编译和执行表达式。

If we could prevent the report engine from creating a sandbox app domain, its code would execute in the same domain and execution context as the calling code, avoiding the additional token propagation that throws the inner exception. Inspecting the documentation for the LocalReport class finds us the ExecuteReportInCurrentAppDomain method, promising to do just that:

如果我们可以阻止报表引擎创建沙箱应用程序域,那么它的代码将在与调用代码相同的域和执行上下文中执行,从而避免抛出内部异常的附加令牌传播。检查LocalReport类的文档,找到ExecuteReportInCurrentAppDomain方法,并承诺这样做:

public static void ProcessStart()
{
    var localReport = new LocalReport();

    /* ...
     * getting data for the report
     * ... */

    /* prepare to do all rendering in the same appdomain */
    localReport.ExecuteReportInCurrentAppDomain(Assembly.GetExecutingAssembly().Evidence);
    /* now this fails no more */
    byte[] pdfStream = localReport.Render(
        formatString,
        deviceInfo,
        out mimetype,
        out encoding,
        out fileNameExtension,
        out streams,
        out warnings
    );

    /* ...
     * saving bytestream to file 
     * ... */
}

I tested it, and it actually works! But there is a downside: The method has been deprecated in .NET 4.0 and higher. It is necessary to add two legacy configuration switches to make it work:

我测试了它,它确实有效!但是也有一个缺点:这个方法在。net 4.0或更高版本中已经被弃用了。有必要添加两个遗留配置开关以使其工作:

<configuration>
    <system.web>
        <trust legacyCasModel="true" />
    </system.web>
    <runtime>
         <legacyCasPolicy enabled="true" />    
         <NetFx40_LegacySecurityPolicy enabled="true"/>
    </runtime>
</configuration>

If these are not added, calling ExecuteReportInCurrentAppDomain throws an exception. I am using this code in production now; I am aware that in future versions of .NET this will likely break - perhaps until then the report engine is improved as well. Adding these legacy CAS support stuff is not recommended of course, it it may have an impact on other parts of your application, for example it will prevent the runtime from using NGEN images from GAC.

如果没有添加这些内容,那么调用ExecuteReportInCurrentAppDomain将抛出一个异常。我现在在生产中使用这个代码;我知道,在将来的。net版本中,这很可能会失败——也许在那之前报告引擎也会得到改进。当然不建议添加这些遗留的CAS支持内容,它可能会对应用程序的其他部分产生影响,例如,它将阻止运行时使用GAC的NGEN映像。

To try to plant the request identity on the task context is just as pointless as setting the AlwaysFlowImpersonation flag in the Aspnet.config file.

尝试在任务上下文中植入请求标识与在Aspnet中设置AlwaysFlowImpersonation标志一样没有意义。配置文件。

There are many other problems with this approach overall, for example there is no mechanism in place to prevent the host process to terminate the worker thread and your background task with it. Several other posts on SO and elsewhere deal with these additional complications, anyway the most robust solution still is to have a service process running independently from IIS and assigning background tasks to it using a message queue, named pipes or something like that. Starting from .NET framework version 4.5.2, there is the HostingEnvironment.QueueBackgroundWorkItem which you could use instead.

这种方法还有许多其他问题,例如,没有任何机制可以阻止主机进程终止工作线程和您的后台任务。其他一些关于SO和其他地方的文章处理了这些附加的复杂性,无论如何,最健壮的解决方案仍然是拥有一个独立于IIS的服务进程,并使用消息队列、命名管道或类似的东西为它分配后台任务。从。net framework 4.5.2版本开始,有HostingEnvironment。可以使用的QueueBackgroundWorkItem。

As there is so much advice against this, why would we still want to go for it? IMO there is just one very good reason: whenever we want a request to return really fast, and we do not care if the result is discarded. I don't know about OP's user story, but mine permits it: we trigger the request from a SCORM e-learning package on page navigation, and we need to avoid any delay here. if the users navigate away before they consume their asynchronously delivered report, the worker thread can as well terminate the task prematurely.

既然有这么多的反对意见,我们为什么还要去尝试呢?在我看来,有一个很好的理由:每当我们想要一个请求快速返回时,我们并不关心结果是否被丢弃。我不知道OP的用户故事,但我的允许:我们触发来自页面导航的SCORM电子学习包的请求,我们需要避免任何延迟。如果用户在使用异步提交的报告之前就离开了,那么工作线程也可以提前终止该任务。

#1


1  

On my way to the working solution I first started to tinker with the alwaysFlowImpersonationPolicy configuration flag, which seems to exist to deal with passing impersonation tokens between threads in an ASP.NET request lifecycle, there is some useful documentation but it was a dead end.

在我进入工作解决方案的过程中,我首先开始修改alwaysFlowImpersonationPolicy配置标志,它似乎存在,用于处理在ASP线程之间传递模拟令牌。NET请求生命周期中,有一些有用的文档,但这是一个死胡同。

The problem here is the identity, and the fact that LocalReport.Render uses sandboxed app domains to compile and execute expressions.

这里的问题是标识和LocalReport。渲染使用沙盒应用程序域来编译和执行表达式。

If we could prevent the report engine from creating a sandbox app domain, its code would execute in the same domain and execution context as the calling code, avoiding the additional token propagation that throws the inner exception. Inspecting the documentation for the LocalReport class finds us the ExecuteReportInCurrentAppDomain method, promising to do just that:

如果我们可以阻止报表引擎创建沙箱应用程序域,那么它的代码将在与调用代码相同的域和执行上下文中执行,从而避免抛出内部异常的附加令牌传播。检查LocalReport类的文档,找到ExecuteReportInCurrentAppDomain方法,并承诺这样做:

public static void ProcessStart()
{
    var localReport = new LocalReport();

    /* ...
     * getting data for the report
     * ... */

    /* prepare to do all rendering in the same appdomain */
    localReport.ExecuteReportInCurrentAppDomain(Assembly.GetExecutingAssembly().Evidence);
    /* now this fails no more */
    byte[] pdfStream = localReport.Render(
        formatString,
        deviceInfo,
        out mimetype,
        out encoding,
        out fileNameExtension,
        out streams,
        out warnings
    );

    /* ...
     * saving bytestream to file 
     * ... */
}

I tested it, and it actually works! But there is a downside: The method has been deprecated in .NET 4.0 and higher. It is necessary to add two legacy configuration switches to make it work:

我测试了它,它确实有效!但是也有一个缺点:这个方法在。net 4.0或更高版本中已经被弃用了。有必要添加两个遗留配置开关以使其工作:

<configuration>
    <system.web>
        <trust legacyCasModel="true" />
    </system.web>
    <runtime>
         <legacyCasPolicy enabled="true" />    
         <NetFx40_LegacySecurityPolicy enabled="true"/>
    </runtime>
</configuration>

If these are not added, calling ExecuteReportInCurrentAppDomain throws an exception. I am using this code in production now; I am aware that in future versions of .NET this will likely break - perhaps until then the report engine is improved as well. Adding these legacy CAS support stuff is not recommended of course, it it may have an impact on other parts of your application, for example it will prevent the runtime from using NGEN images from GAC.

如果没有添加这些内容,那么调用ExecuteReportInCurrentAppDomain将抛出一个异常。我现在在生产中使用这个代码;我知道,在将来的。net版本中,这很可能会失败——也许在那之前报告引擎也会得到改进。当然不建议添加这些遗留的CAS支持内容,它可能会对应用程序的其他部分产生影响,例如,它将阻止运行时使用GAC的NGEN映像。

To try to plant the request identity on the task context is just as pointless as setting the AlwaysFlowImpersonation flag in the Aspnet.config file.

尝试在任务上下文中植入请求标识与在Aspnet中设置AlwaysFlowImpersonation标志一样没有意义。配置文件。

There are many other problems with this approach overall, for example there is no mechanism in place to prevent the host process to terminate the worker thread and your background task with it. Several other posts on SO and elsewhere deal with these additional complications, anyway the most robust solution still is to have a service process running independently from IIS and assigning background tasks to it using a message queue, named pipes or something like that. Starting from .NET framework version 4.5.2, there is the HostingEnvironment.QueueBackgroundWorkItem which you could use instead.

这种方法还有许多其他问题,例如,没有任何机制可以阻止主机进程终止工作线程和您的后台任务。其他一些关于SO和其他地方的文章处理了这些附加的复杂性,无论如何,最健壮的解决方案仍然是拥有一个独立于IIS的服务进程,并使用消息队列、命名管道或类似的东西为它分配后台任务。从。net framework 4.5.2版本开始,有HostingEnvironment。可以使用的QueueBackgroundWorkItem。

As there is so much advice against this, why would we still want to go for it? IMO there is just one very good reason: whenever we want a request to return really fast, and we do not care if the result is discarded. I don't know about OP's user story, but mine permits it: we trigger the request from a SCORM e-learning package on page navigation, and we need to avoid any delay here. if the users navigate away before they consume their asynchronously delivered report, the worker thread can as well terminate the task prematurely.

既然有这么多的反对意见,我们为什么还要去尝试呢?在我看来,有一个很好的理由:每当我们想要一个请求快速返回时,我们并不关心结果是否被丢弃。我不知道OP的用户故事,但我的允许:我们触发来自页面导航的SCORM电子学习包的请求,我们需要避免任何延迟。如果用户在使用异步提交的报告之前就离开了,那么工作线程也可以提前终止该任务。