2010-09-13 78 views
8

我有一个后台服务正在运行,向我的网站的用户发送电子邮件。我想将电子邮件模板编写为MVC视图,以保持一致(因此可以使用相同的模型发送电子邮件以显示网页)。有没有办法从非web应用程序处理MVC视图(aspx文件)?

不幸的是,当我尝试做一个LoadControl(它只是通过补丁来BuildManager.CreateInstanceFromVirtualPath),我得到如下:

System.NullReferenceException at 
    System.Web.dll!System.Web.VirtualPath.GetCacheKey() + 0x26 bytes 
    System.Web.dll!System.Web.Compilation.BuildManager.GetCacheKeyFromVirtualPath + 0x2a bytes 
    System.Web.dll!System.Web.Compilation.BuildManager.GetVPathBuildResultFromCacheInternal + 0x30 bytes 

看来,如果我是MvcBuildViews设置为true,有应该是一些简单的方法来使用编译视图来建立一个电子邮件模板,但我不知道如何。

我发现从里克施特拉尔以下的博客,这可能做的伎俩: http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.asp

然而,似乎开始了整个ASP.NET服务器处理请求。

有没有简单的方法来加载MVC视图&渲染它?或者是按照Rick Strahl的建议加载ASP.NET运行时的唯一方法?

+0

我已经工作了类似的问题。我花了3个小时搞乱了一些事情,虽然我做了很多事情,但我遇到了很多问题。现在以eglasius的身份推荐并使用spark视图引擎来处理所有电子邮件视图。 – Castrohenge 2010-09-13 17:34:20

+0

我用Spark视图引擎来做到这一点,它工作得很好。 – 2010-09-13 18:05:35

+0

尝试在此处获取与此相关的所有数据的中央存储库,因此添加一个链接到另一个也是相关的问题:http://stackoverflow.com/questions/1661584/compiling-an-aspx-page-into-a -standalone-程序 – marq 2010-09-15 01:01:49

回答

7

端起来回答我的问题:)

public class AspHost : MarshalByRefObject 
{ 
    public string _VirtualDir; 
    public string _PhysicalDir; 

    public string ViewToString<T>(string aspx, Dictionary<string, object> viewData, T model) 
    { 
     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); 

       ViewDataDictionary<T> viewDataDictionary = new ViewDataDictionary<T>(model); 
       foreach (KeyValuePair<string, object> pair in viewData) 
       { 
        viewDataDictionary.Add(pair.Key, pair.Value); 
       } 

       object view = BuildManager.CreateInstanceFromVirtualPath(aspx, typeof(object)); 

       ViewPage viewPage = view as ViewPage; 
       if (viewPage != null) 
       { 
        viewPage.ViewData = viewDataDictionary; 
       } 
       else 
       { 
        ViewUserControl viewUserControl = view as ViewUserControl; 
        if (viewUserControl != null) 
        { 
         viewPage = new ViewPage(); 
         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/MvcApplication", "/"); 
var viewData = new ViewDataDictionary<SomeModelType>(){ Model = myModel }; 
String rendered = host.ViewToString("~/Views/MyView.aspx", new Dictionary<string, object>(viewData), viewData.Model); 
+0

真的很好。 +1 – 2012-04-05 16:26:26

11

默认的asp.net视图引擎绑定到asp.net引擎。它绑定在上下文中,我认为你可以解决它,但它绝对是不是简单的

问题在于默认视图引擎+ asp.net引擎的组合,其他视图引擎不应该有这个问题。至少火花视图引擎不会。


编辑: OP解决了最后的提示,但FWIW我的版本使用默认asp.net的MVC项目模板的控制器入户指标的行动:

public class MyAppHost : MarshalByRefObject 
{ 
    public string RenderHomeIndexAction() 
    { 
     var controller = new HomeController(); 
     using (var writer = new StringWriter()) 
     { 
      var httpContext = new HttpContext(new HttpRequest("", "http://example.com", ""), new HttpResponse(writer)); 
      if (HttpContext.Current != null) throw new NotSupportedException("httpcontext was already set"); 
      HttpContext.Current = httpContext; 
      var controllerName = controller.GetType().Name; 
      var routeData = new RouteData(); 
      routeData.Values.Add("controller", controllerName.Remove(controllerName.LastIndexOf("Controller"))); 
      routeData.Values.Add("action", "index"); 
      var controllerContext = new ControllerContext(new HttpContextWrapper(httpContext), routeData, controller); 
      var res = controller.Index(); 
      res.ExecuteResult(controllerContext); 
      HttpContext.Current = null; 
      return writer.ToString(); 
     } 
    } 
} 

...从一个单独的项目:

[TestMethod] 
    public void TestIndexAction() 
    { 
     var myAppHost = (MyAppHost)ApplicationHost.CreateApplicationHost(
      typeof(MyAppHost), "/", @"c:\full\physical\path\to\the\mvc\project"); 
     var view = myAppHost.RenderHomeIndexAction(); 
     Assert.IsTrue(view.Contains("learn more about")); 

    } 

一些额外的注意事项:

  • 新HttpRequest中的网址无关紧要,但需要成为有效的网址
  • 它并不意味着从一个已有上下文/说,我不确定的asp.net应用程序中使用如果它实际上产生了新的AppDomain并且工作,那么控制器类型的构造函数和特定实例在代码中是显式的,可以用参数中要传递的东西来替换,但需要处理MarshalByRef的限制/最坏情况一些简单的反射可以用于它
+0

嗯,我确定我会后悔问这个,但是......一个人怎么解决它? – marq 2010-09-14 14:38:54

+0

@marq一种方式是托管asp.net运行时,就像在你的问题/启动一个AppDomain的链接中一样。另一个是替换HttpContext.Current,并且当这样做的时候把你控制的输出流挂接到响应---这样你才能真正进入渲染视图。其中一个问题是,即使有一些东西可以让它看起来像写给你提供的作家,但asp.net引擎总是直接去.Response写... – eglasius 2010-09-14 18:49:24

+0

...。部分视图也不会发生同样的情况,但您仍然必须构建一个有点填充的RequestContext或ControllerContext才能检索视图,然后在此答案中执行类似操作http://stackoverflow.com/questions/3700005/ asp-net-mvc-use-controller-view-outside-of-the-mvc-application-context/3700036#3700036 – eglasius 2010-09-14 18:51:50

0

我们在离线状态下为我们的Web应用程序使用了Cassini Web服务器。 可能这种方法也适用于你?看看这里Cassini

0

总之,没有 - ASP.NET视图呈现已结合到Web响应周期。在过去可能有必要获得合理的表现。

现在还有其他一些选项,包括来自Microsoft的新的razor view engine或开放源代码Spark View Engine

+0

+1与网络响应周期结婚。 – 2012-04-05 16:20:54

0

这是我第一次尝试,它失败了。见上面的正确和工作的答案

这是尽可能接近,但我仍然没有工作。现在它抱怨导致NullreferenceException的get_Server。

我以为我会在这里发布我做了什么和我有多远,以防有人想继续研究。

我修改的csproj文件来生成与预编译的ASPX文件的组件,因此:

<PropertyGroup> 
... 
    <MvcBuildViews>true</MvcBuildViews> 
    <AspNetMergePath>C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\aspnet_merge.exe</AspNetMergePath> 
... 
</PropertyGroup> 
<Target Name="AfterBuild" Condition="'$(MvcBuildViews)'=='true'"> 
    <AspNetCompiler PhysicalPath="$(ProjectDir)" TargetPath="$(ProjectDir)..\$(ProjectName)_CompiledAspx" Updateable="false" VirtualPath="$(ProjectName)" Force="true" /> 
    <Exec Command="%22$(AspNetMergePath)%22 %22$(ProjectDir)..\$(ProjectName)_CompiledAspx%22 -o %22$(ProjectName)_views%22" /> 
    <Copy SourceFiles="$(ProjectDir)..\$(ProjectName)_CompiledAspx\bin\$(ProjectName)_views.dll" DestinationFolder="$(TargetDir)CompiledAspx\" /> 
</Target> 

这创造了一个“MyProject_CompiledAspx.dll”,然后我从我的应用程序引用。但是,这导致了一个新的NullReferenceException。

ASPX文件功能强大,与ASP.NET服务器紧密集成是很可惜的。

+0

Woops ...我不小心粘贴了两次。忽略第二组 marq 2010-09-17 15:05:47

+0

我决定从前一段时间试着解决问题的时候开始进一步尝试一下我的发现,并且确定它能够正常工作,我很快就会发布我的解决方案。您仍然需要AppDomain,但您可以根据需要直接使用View和Controller对象。 – eglasius 2010-09-17 17:20:18

+0

甜,感谢eglasius! – marq 2010-09-18 00:59:29

相关问题