在后端类中创建 Ascx 控件的实例 Aspx 页面而不加载 FilePath

发布于 2024-08-03 15:17:07 字数 1165 浏览 2 评论 0原文

问题:是否可以在后端代码中(不是在后面的代码中,而是在实际的后端类中)加载并呈现页面或在 .aspx 或 .ascx 中定义的控件,而无需使用 Load(path) 而只需创建页面/控件类的实例?

我希望能够做到这一点(从后端类而不是后面的代码):

MyControl myCtl = new MyApp.Views.Shared.MyControl();
String html = Util.ControlToString(myCtl); //I get an empty string & hidden errors

而不是这个

public static string ControlToString(string path)
{
    Page pageHolder = new Page();
    MyControl myCtl = (MyControl)pageHolder.LoadControl(path);
    pageHolder.Controls.Add(myCtl);
    StringWriter output = new StringWriter();
    HttpContext.Current.Server.Execute(pageHolder, output, false);
    return output.ToString();
}

详细信息: 在 Asp.net WebApp 中,我偶尔需要将用户控件 (.ascx) 或页面 (.aspx) 呈现为 HTML 字符串。当页面或控件从后面的代码继承时,其类会显示在我的后端代码中的智能感知中,我可以创建实例并设置属性,而不会出现编译时或运行时错误。但是,当我尝试渲染页面或控件时,我总是得到一个空字符串,并且经过检查,页面或控件显示受抑制的内部渲染错误,除非我使用其物理文件路径加载页面或控件。

我认为关键问题与何时& .aspx / .ascx 文件如何运行时编译。我不想创建用户控件的预编译类库,因为这会使设计过程变得尴尬,而且我真的很喜欢 .aspx / .ascx 页面提供的设计器功能,所以我很想找到一种方法使页面在解决方案中进行编译,以便它们可以像任何其他后端类一样使用,但仍然可以使用设计器创建。我希望两全其美(1)能够在设计器中编辑页面和控件,(2)创建实例并使用后端类设置其属性。

Question: Is it possible in back end code (not in the code behind but in an actual back end class) to load and render a page or control defined in a .aspx or .ascx without having to use Load(path) and instead just create an instance of the page/control class?

I want to be able to do this (from a back end class NOT a code behind):

MyControl myCtl = new MyApp.Views.Shared.MyControl();
String html = Util.ControlToString(myCtl); //I get an empty string & hidden errors

instead of this

public static string ControlToString(string path)
{
    Page pageHolder = new Page();
    MyControl myCtl = (MyControl)pageHolder.LoadControl(path);
    pageHolder.Controls.Add(myCtl);
    StringWriter output = new StringWriter();
    HttpContext.Current.Server.Execute(pageHolder, output, false);
    return output.ToString();
}

Details:
In a Asp.net WebApp I occasionally need to render a user control (.ascx) or page (.aspx) as a HTML string. When a page or control inherits from a code behind, its class shows up in intellisense in my back end code and I can create an instance and set properties without getting compile time or run time errors. However, when I try to render the page or control I always get an empty string and upon inspection the page or control shows suppressed internal rendering errors unless I load the page or control using its physical file path.

I think the key issue has to do with when & how the .aspx / .ascx files are runtime compiled. I don't want to create a pre compiled class library of user controls because that would make the design process awkward and I really like the designer features offered by the .aspx / .ascx pages and so I'd love to find a way to make the pages compile in the solution so that they are usable like any other back end class but can still be created using the designer. I want the best of both worlds (1) to be able to edit pages and controls in the designer and (2) create instances and set their properties using back end classes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

忆梦 2024-08-10 15:17:07

这是一种在这种情况下可能会有所帮助的方法。

“后端”代码可能不知道用户控件位于哪里,但用户控件确实知道它在哪里。

因此,在用户控件中,添加一个如下所示的静态方法:(

public partial class MyControl : UserControl
{
  ...
  public static MyControl LoadControl(CustomDto initialData)
  {
    var myControl = 
        (MyControl) 
        ((Page) HttpContext.Current.Handler)
        .LoadControl("~\\UserControlsSecretPath\\MyControl.ascx");
    myControl._initialData = initialData;
    return myControl;
  }
  ...
  private CustomDto _initialData;
}

包含 CustomDto 是为了说明如何将初始数据传递到用户控件。如果不需要这样做,请采取出来!)

有了这个,加载用户控件的代码不需要需要知道用户控件物理位置的路径。如果该位置发生变化,则更新这一位置。使用此 UserControl 的所有其他代码均保持不变。

在您的后端代码或其他任何地方,您可以执行以下操作:

var control = MyControl.LoadControl(customDto);
PlaceHolder1.Controls.Add(control);

Here is an approach that may help in situations like this.

The "back-end" code may not know where the user control is located, but the User Control does know where it is.

So, in the User Control, add a static method like this:

public partial class MyControl : UserControl
{
  ...
  public static MyControl LoadControl(CustomDto initialData)
  {
    var myControl = 
        (MyControl) 
        ((Page) HttpContext.Current.Handler)
        .LoadControl("~\\UserControlsSecretPath\\MyControl.ascx");
    myControl._initialData = initialData;
    return myControl;
  }
  ...
  private CustomDto _initialData;
}

(The CustomDto is included to illustrate how initial data can be passed to the User Control. If you don't need to do that, take it out!)

With this, the code that loads the user control does not need to know the path to where the user control is physically located. If that location ever changes, then update this one location. All other code that uses this UserControl is unchanged.

In your back-end code, or anywhere else, you can do something this:

var control = MyControl.LoadControl(customDto);
PlaceHolder1.Controls.Add(control);
画尸师 2024-08-10 15:17:07

一般来说:没有。

据我所知,ASP.NET从您的类继承,以将 .aspx/.ascx 模板与您的代码结合起来。这就是您的控件显示为空的原因:缺少将模板与您的代码结合起来的代码。这通常是由 ASP.NET 在您第一次访问页面或用户控件时完成的(这正是第一次点击有点慢的原因:它实际上是在生成和编译连接代码)。

对于预编译网站,ASP.NET 会提前生成此代码作为预编译网站 .dll 的一部分,这就是此类网站加载速度更快的原因。但是,IIRC 您仍然需要实例化生成的类而不是原始类。

这是一个非常常见的请求,但到目前为止微软还没有提供执行此操作的工具。

编辑:虽然我不明白为什么您想要将控件渲染到内存中的字符串,但我可能有解决构建问题的方法。

如果您坚持使用非编译的 .ascx 文件(使用网站模型而不是 Web 应用程序模型),您实际上可以通过将它们物理地放置在主项目的子文件夹中来单独开发它们,并仅将它们视为内容文件。然后,您可以使用该子文件夹作为根文件夹创建一个单独的项目。您只需将此子文件夹中的文件视为网站文件即可,主项目仍然可以是Web应用程序。 (实际上是推荐的,因为你不希望 .csproj 文件包含在主项目中。)

但是,共享代码(即在控件项目和主项目之间共享)应该放在单独的库项目中,因此您可以单独编译每个项目,而无需相互依赖。

在主项目中使用LoadControl将动态编译它们(代码隐藏是可能的);如果您需要设置属性,则必须在共享项目中定义接口,在适当的用户控件上实现它们,并将 LoadControl 创建的控件强制转换为适当的接口。

Generally speaking: no.

As far as I know, ASP.NET inherits from your classes to combine the .aspx/.ascx template with your code. This is why your controls show up empty: the code to combine the template with your code is missing. This is usually done by ASP.NET the first time you access a page or user control (that's precisely why the first hit is a little slow: it's actually generating and compiling the hookup-code).

For precompiled websites ASP.NET generates this code as part of your precompiled website's .dll in advance, which is why such sites load quicker. However, IIRC you'll still need to instantiate the generated classes rather than your original classes.

It's a pretty common request, but so far MS has not provided the tools to do this.

Edit: Although I fail to see why you'd want to render a control to an in-memory string, I might have a solution to the build problems.

If you stick to non-compiled .ascx files (using the web site model rather than the web application model), you can actually develop them separately by placing them physically in subfolder of your main project, and treat them as content files only. Then, you can make a separate project with this subfolder as the root folder. You only need to treat the files in this subfolder as web site files, the main project can still be a web application. (Actually recommended, 'cause you don't want the .csproj files included in the main project.)

However, shared code (that is, shared between the controls project and the main project) should be put in a separate library project, so you can compile each project separately without interdependencies.

Using LoadControl within the main project will compile them on the fly (code behind is possible); if you need to set properties, you must however define interfaces in the shared project, implement them on the appropriate user controls and cast the control created by LoadControl to the appropriate interface.

野鹿林 2024-08-10 15:17:07

我开发了一个解决方案来解决 VS 2008 中的问题:

  1. 创建主站点解决方案:创建一个 MVC 1 网站解决方案
    VS 2008
  2. 创建模型类库:为模型代码添加类库
  3. 创建视图代码:添加一个“空网站”来保存.ascx页面,并添加引用模型库
  4. 创建部署站点:添加一个编译“空网站”的部署项目,转到“属性页面”并检查:“将所有输出合并到单个程序集中”和“视为库组件”,并确保取消选中:“允许此预编译站点可更新”
  5. 参考部署输出:在主项目中添加对输出的引用部署站点的。
  6. ASP。 - 编译控件:控件显示在 ASP 下。命名空间和命名方式有两种
    A. 如果 .ascx / aspx 页面没有声明“ClassName”,那么它们将使用带有下划线的文件夹和文件名来命名。 <%@ 控制语言="C#" ClassName="Admin_Index" %>
    B. 如果他们确实声明了一个类名,那么这就是他们的名字

  7. List item

用法: 示例代码如下

这是一个示例用法

public ActionResult Index()
{
    var ctl = new ASP.Directory_FirmProfile();  //create an instance
    ctl.Setup(new MyDataModel);                 //assign data

    //string test = CompiledControl.Render(ctl); //output to string
    return new HtmlCtl.StrongView(ctl);         //output to response
}    



   public class CompiledControl
    {
        public static string Render(Control c)
        {
            Page pageHolder = new Page();
            pageHolder.Controls.Add(c);
            StringWriter output = new StringWriter();
            HttpContext.Current.Server.Execute(pageHolder, output, false);
            return output.ToString();
        }

        public static void Render(Control c, StringWriter output)
        {
            Page pageHolder = new Page();
            pageHolder.Controls.Add(c);
            HttpContext.Current.Server.Execute(pageHolder, output, false);
        }

        public static void Render(Control c, HttpResponseBase r)
        {
            Page pageHolder = new Page();
            pageHolder.Controls.Add(c);
            HttpContext.Current.Server.Execute(pageHolder, r.Output, false);
        }


    }


    public class StrongView : ActionResult
    {
        private Control ctl;
        public StrongView(Control ctl)
        {
            this.ctl = ctl;
        }

        public string VirtualPath{get;set;}


        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            HtmlCtl.CompiledControl.Render(ctl, context.HttpContext.Response);

        }
    }

I developed a solution that solves my problem in VS 2008:

  1. Create Main Site Solution: Create a MVC 1 Website solution in
    VS 2008
  2. Create Model Class Library: Add a Class Library for the Model Code
  3. Create View Code: Add an "Empty Website" to hold the .ascx pages, and add a reference the model library
  4. Create Deployment Site: Add a deployment project that compiles the "Empty Website" goto the "properties page" and Check: "Merge All outputs into a single assembly" and "Treat as library component" and be sure to UnCheck: "Allow this precompiled site to be updatable"
  5. Reference Deployment Output: In the main project add a reference to the output of the Deployment site.
  6. ASP. - Compiled Controls: Controls show up under the ASP. namespace and are named in two ways
    A. if the .ascx / aspx page did not declare a "ClassName" then they are named using their folder and file name with underscores ex. <%@ Control Language="C#" ClassName="Admin_Index" %>
    B. if they did declare a class name then that is their name

  7. List item

Usage: Example code is below

Here is an example usage

public ActionResult Index()
{
    var ctl = new ASP.Directory_FirmProfile();  //create an instance
    ctl.Setup(new MyDataModel);                 //assign data

    //string test = CompiledControl.Render(ctl); //output to string
    return new HtmlCtl.StrongView(ctl);         //output to response
}    



   public class CompiledControl
    {
        public static string Render(Control c)
        {
            Page pageHolder = new Page();
            pageHolder.Controls.Add(c);
            StringWriter output = new StringWriter();
            HttpContext.Current.Server.Execute(pageHolder, output, false);
            return output.ToString();
        }

        public static void Render(Control c, StringWriter output)
        {
            Page pageHolder = new Page();
            pageHolder.Controls.Add(c);
            HttpContext.Current.Server.Execute(pageHolder, output, false);
        }

        public static void Render(Control c, HttpResponseBase r)
        {
            Page pageHolder = new Page();
            pageHolder.Controls.Add(c);
            HttpContext.Current.Server.Execute(pageHolder, r.Output, false);
        }


    }


    public class StrongView : ActionResult
    {
        private Control ctl;
        public StrongView(Control ctl)
        {
            this.ctl = ctl;
        }

        public string VirtualPath{get;set;}


        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            HtmlCtl.CompiledControl.Render(ctl, context.HttpContext.Response);

        }
    }
心头的小情儿 2024-08-10 15:17:07

我按照鲁本的建议提出了一个更简单的解决方案。
它已经运行了大约一个月,没有出现任何问题:

//Example usage

//reference the control
var emailCTL = new HtmlCtl.ControlOnDisk<MyControlType>(@"~\Views\EmailTemplates\MyControlType.ascx");

//if you have a code behind you will get intellisense allowing you to set these properties
// and re-factoring support works most places except the template file. 
emailCTL.c.title = "Hello World "; //title is a property in the code behind
emailCTL.c.data = data; //data is a property in the code behind

string emailBody = emailCTL.RenderStateless(); 



//Helper Class
    public class ControlOnDisk<ControlType> where ControlType : UserControl
    {
        public ControlType c;
        Page pageHolder = new Page();
        public ControlOnDisk(string path)
        {
            var o = pageHolder.LoadControl(path);
            c = (ControlType)o;
            pageHolder.Controls.Add(c);
        }

        public string RenderStateless()
        {

            StringWriter output = new StringWriter();

            // set up dumby context for use in rendering to email stream
            StringBuilder emailMessage = new StringBuilder();
            TextWriter tw = new StringWriter(emailMessage);
            HttpResponse dumbyResponse = new HttpResponse(tw);
            HttpRequest dumbyRequest = new HttpRequest("", "http://InsertURL.com/", ""); //dummy url requierd for context but not used
            HttpContext dumbyContext = new HttpContext(dumbyRequest, dumbyResponse);
            //HttpContextBase dumbyContextBase = new HttpContextWrapper2(dumbyContext);

            dumbyContext.Server.Execute(pageHolder, output, false);
            return output.ToString();

        }
    }

I've come up with a simpler solution along the lines of Ruben's advice.
It has worked without problems for about a month:

//Example usage

//reference the control
var emailCTL = new HtmlCtl.ControlOnDisk<MyControlType>(@"~\Views\EmailTemplates\MyControlType.ascx");

//if you have a code behind you will get intellisense allowing you to set these properties
// and re-factoring support works most places except the template file. 
emailCTL.c.title = "Hello World "; //title is a property in the code behind
emailCTL.c.data = data; //data is a property in the code behind

string emailBody = emailCTL.RenderStateless(); 



//Helper Class
    public class ControlOnDisk<ControlType> where ControlType : UserControl
    {
        public ControlType c;
        Page pageHolder = new Page();
        public ControlOnDisk(string path)
        {
            var o = pageHolder.LoadControl(path);
            c = (ControlType)o;
            pageHolder.Controls.Add(c);
        }

        public string RenderStateless()
        {

            StringWriter output = new StringWriter();

            // set up dumby context for use in rendering to email stream
            StringBuilder emailMessage = new StringBuilder();
            TextWriter tw = new StringWriter(emailMessage);
            HttpResponse dumbyResponse = new HttpResponse(tw);
            HttpRequest dumbyRequest = new HttpRequest("", "http://InsertURL.com/", ""); //dummy url requierd for context but not used
            HttpContext dumbyContext = new HttpContext(dumbyRequest, dumbyResponse);
            //HttpContextBase dumbyContextBase = new HttpContextWrapper2(dumbyContext);

            dumbyContext.Server.Execute(pageHolder, output, false);
            return output.ToString();

        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文