【问题标题】:Compiling an ASPX page into a standalone program将 ASPX 页面编译为独立程序
【发布时间】:2009-11-02 14:22:51
【问题描述】:

正如我提到的here,我正在尝试从 WinForms 中的 ASPX 页面生成 HTML。

我正在尝试将 ASPX 页面直接编译到 EXE 中;我希望能够写出这样的东西:

var page = new ASP.MyPageName();
var stringWriter = new StringWriter();
using(var htmlWriter = new HtmlTextWriter(stringWriter))
    page.RenderControl(htmlWriter);

我添加了一个 ASPX 页面,将 Build Action 设置为 Compile,并放入以下 ​​Page 声明:

<%@ Page Language="C#" ClassName="MyPageName" %>

代码编译,我在 ASPX 中定义的属性可从调用代码中使用,但 StringWriter 保持为空。我试着打电话给htmlWriter.Flush,但没有帮助。

page 实例的 Controls 集合是空的,它可能不应该是空的。
我在 Reflector 中查看了 EXE,但在任何地方都找不到页面内容。因此,我假设该页面未正确编译。

这样做的正确方法是什么?

【问题讨论】:

  • 现在它甚至无法编译:There is a circular dependency in the target dependency graph involving target "Build".
  • 哇,从没想过采用这种方法。玩得开心

标签: c# .net asp.net


【解决方案1】:

我最终使用ApplicationHost.CreateApplicationHost 在 ASP.Net AppDomain 中运行整个应用程序。这比我伪造 ASP.Net AppDomain 的尝试更简单、更可靠。

注意:为此,您必须将 EXE 文件(或任何包含传递给 CreateApplicationHost 的类型的程序集)的副本放在 ASP.Net 文件夹的 Bin 目录中。这可以在构建后步骤中完成。然后,您可以处理 AssemblyResolve 以在原始目录中找到其他程序集。

或者,您可以将程序本身和所有 DLL 放在 ASP.Net 的 Bin 目录中。

注意:WinForms 的设置功能在 ASP.Net AppDomain 中不起作用。

【讨论】:

    【解决方案2】:

    我相信您要使用的是SimpleWorkerRequest

    不幸的是,它要求资源(我相信)存在于磁盘上。根据您的描述,您似乎更喜欢将整个应用程序驻留在您的 DLL 中。如果是这种情况,您很可能需要实现自己的HttpWorkerRequest

    【讨论】:

      【解决方案3】:

      警告

      这并不可靠,我已经放弃了。


      我最终将文件复制到输出文件夹并使用以下代码在同一个 AppDomain 中初始化 ASP.Net:(我对其进行了测试;它有时可以工作)

      static class PageBuilder {
          public static readonly string PageDirectory = Path.Combine(Path.GetDirectoryName(typeof(PageBuilder).Assembly.Location), "EmailPages");
      
          static bool inited;
          public static void InitDomain() {
              if (inited) return;
              var domain = AppDomain.CurrentDomain;
      
              domain.SetData(".appDomain", "*");
              domain.SetData(".appPath", PageDirectory);
              domain.SetData(".appVPath", "/");
              domain.SetData(".domainId", "MyProduct Domain");
              domain.SetData(".appId", "MyProduct App");
              domain.SetData(".hostingVirtualPath", "/");
      
              var hostEnv = new HostingEnvironment();//The ctor registers the instance
      
              //Ordinarily, the following method is called from app manager right after app domain (and hosting env) is created
              //Since CreateAppDomainWithHostingEnvironment is never called here, I need to call Initialize myself.
              //Here is the signaature of the method.
              //internal void Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters) { 
      
              var cmp = Activator.CreateInstance(typeof(HttpRuntime).Assembly.GetType("System.Web.Hosting.SimpleConfigMapPathFactory"));
              typeof(HostingEnvironment).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(hostEnv, new[] { ApplicationManager.GetApplicationManager(), null, cmp, null });
      
              //This must be done after initializing the HostingEnvironment or it will initialize the config system.
              SetDefaultCompilerVersion("v3.5");
      
              inited = true;
          }
      
          static void SetDefaultCompilerVersion(string version) {
              var info = CodeDomProvider.GetCompilerInfo("c#");
              var options = (IDictionary<string, string>)typeof(CompilerInfo).GetProperty("ProviderOptions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(info, null);
      
              options["CompilerVersion"] = version;
          }
      
      
          public static TPage CreatePage<TPage>(string virtualPath) where TPage : Page {
              return BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(TPage)) as TPage;
          }
      
      //In a base class that inherits Page:
      
          internal string RenderPage() {
              var request = new SimpleWorkerRequest("", null, null);
      
              ProcessRequest(new HttpContext(request));
              using (var writer = new StringWriter(CultureInfo.InvariantCulture)) {
                  using (var htmlWriter = new HtmlTextWriter(writer))
                      RenderControl(htmlWriter);
                  return writer.ToString();
              }
          }
      

      InitDomain必须在程序启动时正确调用;否则,它会抛出一个关于配置系统已经初始化的异常。

      如果不调用ProcessRequest,则页面的Controls 集合为空。


      更新:页面在调用ProcessRequest 期间呈现,因此必须在操作Page 实例后完成。

      如果程序有.config 文件,此代码将不起作用;我做了一个方法来设置默认的 C# 编译器版本,没有使用反射的.config 文件。

      【讨论】:

      • 这是个好主意...我正在尝试在这里进行相同的工作。当我调用“var page = PageBuilder.CreatePage(someVirtualPath);”时我不知道“someVirtualPath”使用什么值。我认为这将与我的 aspx 文件的名称有关,但我无法获得有效的值。
      • virtualPath 是相对于PageDirectory 的绝对路径。例如,如果SomePage.aspx 直接在PageDirectory 中,则应该是/SomePage.aspx。如果它在子文件夹中,则虚拟路径为/SubFolder/SomePage.aspx
      • 顺便说一下,调试这个的时候,一定要开启 Break on All Exceptions (Debug, Exceptions),否则你可能会遇到神秘的问题。 (HostingEnvironment.Initialize 吞下异常)
      • 感谢 SLaks,两个 cmets 都很有帮助。我需要在某处添加 Global.asax 吗?我现在在 BuildManager.CreateInstanceFromVirtualPath 有一个 NullRef 异常——内部异常导致我:System.Web.Compilation.ApplicationBuildProvider.GetGlobalAsaxBuildResult(Boolean isPrecompiledApp),这就是为什么我认为我必须停止它担心全局。 asax。
      • 我也遇到了同样的问题,放弃了。根本问题是AppDomain.CurrentDomain.DynamicDirectory 为空,导致httpRuntime.SetUpCodegenDirectoryInitialize 期间抛出异常,该异常被hostingInit 吞没。由于此异常,ASP.Net 运行时未完全初始化,导致GetGlobalAsaxBuildResult 使用的属性之一为null。要查看这一点,请在所有异常上启用 Break,并查看 SetUpCodegenDirectory 的源代码。有关替代解决方案,请参阅我的第二个答案。
      【解决方案4】:

      为什么不直接考虑在应用中托管 ASP.NET 运行时?

      网上有几个sn-ps教你怎么做。

      这里是one

      【讨论】:

      • 正如我在链接问题中解释的那样,我实际上并没有制作 Web 服务器。另外,我想避免托管单独的 AppDomain。
      【解决方案5】:

      很可能您使用了错误的页面类。您不需要在后面的代码中使用实际命名良好的类。在编译过程中,ASP.NET 生成页面类,该类继承自代码中定义的类,并且在此类中发生所有控件的初始化。因此,您应该使用生成的类(使用 Reflector 检查其名称)。

      【讨论】:

      • 我在自动生成的ASP 命名空间中使用生成的类。 (我使用反射器检查)我将一些属性放在&lt;script runat="server&gt; 块中(页面没有代码隐藏文件)并且属性工作正常。
      • @Page中的ClassName属性控制生成类的名称)
      【解决方案6】:

      如果您正在寻找此答案的 MVC 版本,请参阅: Is there a way to process an MVC view (aspx file) from a non-web application?

      代码使用单独的 AppDomain,但据我所知,这是必需的,因为从 ASPX 文件生成的所有代码都依赖于 HttpContext 和 HostingEnvironment.VirtualPathProvider。

      public class AspHost : MarshalByRefObject
      {
          public string _VirtualDir;
          public string _PhysicalDir;
      
          public string AspxToString(string aspx)
          {
              StringBuilder sb = new StringBuilder();
              using (StringWriter sw = new StringWriter(sb))
              {
                  using (HtmlTextWriter tw = new HtmlTextWriter(sw))
                  {
                      var workerRequest = new SimpleWorkerRequest(aspx, "", tw);
                      HttpContext.Current = new HttpContext(workerRequest);
      
                      object view = BuildManager.CreateInstanceFromVirtualPath(aspx, typeof(object));
      
                      Page viewPage = view as Page;
                      if (viewPage == null)
                      {
                          UserControl viewUserControl = view as UserControl;
                          if (viewUserControl != null)
                          {
                              viewPage = new Page();
                              viewPage.Controls.Add(viewUserControl);
                          }
                      }
      
                      if (viewPage != null)
                      {
                          HttpContext.Current.Server.Execute(viewPage, tw, true);
      
                          return sb.ToString();
                      }
      
                      throw new InvalidOperationException();
                  }
              }
          }
      
          public static AspHost SetupFakeHttpContext(string physicalDir, string virtualDir)
          {
              return (AspHost)ApplicationHost.CreateApplicationHost(
                  typeof(AspHost), virtualDir, physicalDir);
          }
      }
      

      然后,渲染一个文件:

      var host = AspHost.SetupFakeHttpContext("Path/To/Your/AspNetApplication", "/");
      String rendered = host.AspxToString("~/Views/MyView.aspx");
      

      【讨论】:

        【解决方案7】:

        您可以使用ClienBuildManager 类来编译 ASPX 文件。

        【讨论】:

        • AFAIK,这需要一个单独的 AppDomain,我想避免。
        • 我不是专家,但据我所知,它会在编译 ASPX 页面时生成一个新的 AppDomain,但它会返回一个您可以在自己的 AppDomain 中实例化的 Type 对象。跨度>
        • 是的,但是为了渲染页面,它必须通过HttpRuntime.ProcessRequest运行,这需要在它运行的AppDomain中初始化ASP.Net tp。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-28
        • 1970-01-01
        • 2013-11-24
        相关资源
        最近更新 更多