有没有办法从非Web应用程序处理MVC视图(aspx文件)?

时间:2010-09-13 16:44:44

标签: c# asp.net-mvc templates

我有一个运行后台服务,它会向我网站的用户发送电子邮件。我想将电子邮件模板编写为MVC视图,以保持一致(因此可以使用相同的模型发送电子邮件以显示网页)。

不幸的是,当我尝试执行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,那么应该有一些简单的方法来使用编译的视图来构建电子邮件模板,但我无法弄清楚如何。

我从Rick Strahl找到了以下博客,可能会有这样的伎俩: http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.asp

但是,它似乎启动了一个完整的ASP.NET服务器来处理请求。

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

5 个答案:

答案 0 :(得分: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中的
  • url并不重要,但需要是一个有效的URL
  • 它并不打算从已经有上下文的asp.net应用程序中使用,或者说,我不确定它是否实际上产生了新的AppDomain并且正在工作
  • 控制器类型的构造函数和具体实例在代码中是显式的,可以替换为要在参数中传递的东西,但是需要处理MarshalByRef /最坏情况的限制,可以使用一些简单的反射来实现它

答案 1 :(得分: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);

答案 2 :(得分:0)

我们在Web应用程序离线时使用了Cassini Web服务器。 可能这种方法对你也有用吗?看看Cassini

答案 3 :(得分:0)

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

现在,还存在其他一些选项,包括Microsoft的新razor view engine或开源Spark View Engine

答案 4 :(得分:0)

这是我的第一次尝试,但失败了。请参阅上面的正确和有效的答案

这是我能够得到的尽可能接近,但它仍然无法正常工作。现在它抱怨get_Server导致NullreferenceException。

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

我修改了我的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服务器紧密集成,这是一个很小的问题。