title: How to have razor find views from another assembly in a template service
body: <p>I am trying to load a razor view from another project. I have gone down a few different rabbit holes but so far I have not found a way to make this work. However, from my research there seems to be two main ways to get this working. </p>
  
  <p>One option would be to use embedded resources and then hook up an embedded file provider into razor. I might be wrong but I think this is a pre .net core 2.1 approach. My understanding is that, in 2.1 Razor views are compiled at build time. Setting it as embedded would save the actual file which would be useful for the older runtime compiling.</p>
  
  <pre><code>// Add the embedded file provider to be used with razor view templates
  var viewAssembly = typeof(CoreStartup).GetTypeInfo().Assembly;
  var fileProvider = new EmbeddedFileProvider(viewAssembly);
  services.Configure&lt;RazorViewEngineOptions&gt;(o =&gt; o.FileProviders.Add(fileProvider));
  services.AddTransient&lt;ITemplateService, TemplateService&gt;();
  </code></pre>
  
  <p>The other approach would be to put the views in an areas folder. I even found a <a href="https://github.com/aspnet/Docs/tree/master/aspnetcore/razor-pages/ui-class/samples" rel="noreferrer">sample project</a> which shows you can do exactly that! However, I have been unable to find a way to get the same results.</p>
  
  <p>For reference here is the service I am trying to use to find and render the razor views. I need to get the html output from it to help create an html email template.</p>
  
  <pre><code>namespace TestApp.Services
  {
      using System;
      using System.IO;
      using System.Threading.Tasks;
  
      using Microsoft.AspNetCore.Http;
      using Microsoft.AspNetCore.Mvc;
      using Microsoft.AspNetCore.Mvc.Abstractions;
      using Microsoft.AspNetCore.Mvc.ModelBinding;
      using Microsoft.AspNetCore.Mvc.Razor;
      using Microsoft.AspNetCore.Mvc.Rendering;
      using Microsoft.AspNetCore.Mvc.ViewFeatures;
      using Microsoft.AspNetCore.Routing;
      using Microsoft.Extensions.Options;
  
      using Serilog;
  
      public class TemplateService : ITemplateService
      {
          private readonly IServiceProvider _serviceProvider;
          private readonly ITempDataProvider _tempDataProvider;
          private readonly IRazorViewEngine _viewEngine;
  
          public TemplateService(
              IServiceProvider serviceProvider,
              ITempDataProvider tempDataProvider,
              IRazorViewEngine viewEngine)
          {
              this._serviceProvider = serviceProvider;
              this._tempDataProvider = tempDataProvider;
              this._viewEngine = viewEngine;
          }
  
          public async Task&lt;string&gt; RenderTemplateAsync&lt;TViewModel&gt;(string viewPath, TViewModel viewModel)
          {
              var httpContext = new DefaultHttpContext
                                    {
                                        RequestServices = this._serviceProvider
                                    };
  
              return await RenderTemplateAsync(httpContext, viewPath, viewModel);
          }
  
          public async Task&lt;string&gt; RenderTemplateAsync&lt;TViewModel&gt;(HttpContext httpContext, string viewPath, TViewModel viewModel)
          {
              var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
  
              var viewResult = this._viewEngine.FindView(actionContext, viewPath, false);
              if (!viewResult.Success)
              {
                  Log.Error("Failed to render template {@viewPath} because it was not found.", viewPath);
                  throw new FileNotFoundException($"Failed to render template {viewPath} because it was not found.");
              }
  
              var viewDictionary = new ViewDataDictionary&lt;TViewModel&gt;(new EmptyModelMetadataProvider(), new ModelStateDictionary());
              var tempDataDictionary = new TempDataDictionary(httpContext, this._tempDataProvider);
  
              using (var outputWriter = new StringWriter())
              {
                  try
                  {
                      var viewContext = new ViewContext(
                          actionContext, 
                          viewResult.View, 
                          viewDictionary,
                          tempDataDictionary, 
                          outputWriter, 
                          new HtmlHelperOptions());
                      viewContext.ViewData.Model = viewModel;
  
                      await viewResult.View.RenderAsync(viewContext);
                  }
                  catch (Exception ex)
                  {
                      Log.Error(ex, "Failed to render template due to a razor engine failure");
                      throw;
                  }
  
                  return outputWriter.ToString().Replace("\r\n", string.Empty);
              }
          }
      }
  }
  </code></pre>
  
  <p>I am calling the service like this. I have tried every possible string I can think of in the route to try and get it to work. The problem is I'm not sure the exact format to use for finding an area view. So I'm not sure if the route is wrong or if it's not hooked up right.</p>
  
  <pre><code>var body = await _templateService.RenderTemplateAsync(HttpContext, "Common/EmailDetails", emailModel);
  </code></pre>
  
  <p>Right now I'm trying to get the EmailDetails.cshtml shared view.</p>
  
  <pre><code>/Areas
      /Common
          /Views
              /Shared
                  -EmailDetails.cshtml
  </code></pre>