2016-07-07 8 views
25

Я хотел бы использовать Razor как механизм шаблонов в консольном приложении .NET, который я пишу в .NET Core.Использование Razor вне MVC в .NET Core

Отдельные двигатели Razor, с которыми я столкнулся (RazorEngine, RazorTemplates), требуют полного .NET. Я ищу решение, которое работает с .NET Core.

+2

https://github.com/aspnet/Razor требует только основной среды выполнения (с использованием стандартной библиотеки .NET) – haim770

ответ

17

Недавно я создал библиотеку под названием RazorLight.

Он не имеет избыточных зависимостей, таких как детали ASP.NET MVC и может использоваться в консольных приложениях. Пока что он поддерживает только .NET Core (NetStandard1.6), но это именно то, что вам нужно.

Вот краткий пример:

IRazorLightEngine engine = EngineFactory.CreatePhysical("Path-to-your-views"); 

// Files and strong models 
string resultFromFile = engine.Parse("Test.cshtml", new Model("SomeData")); 

// Strings and anonymous models 
string stringResult = engine.ParseString("Hello @Model.Name", new { Name = "John" }); 
+2

Это было довольно легко реализовать, но оно имеет довольно ужасную производительность. Я создал цикл, созданный вокруг 1000 строк html. Это заняло около 12 секунд каждый раз. Просто создание одной страницы с 200 линиями заняло около 1-2 секунд. В проекте MVC 1 страница заняла около 20 миллисекунд. Поэтому, если вас не беспокоит производительность, это жизнеспособный вариант. – DeadlyChambers

+0

Ну, если вы используете ParseString - шаблоны не кэшируются, поэтому возникают проблемы с производительностью. Вместо этого используйте Parse с соответствующим менеджером шаблонов (для файлов или встроенных ресурсов) - таким образом шаблон будет только компилироваться один раз и в следующий раз из кэша. И вы увидите те же номера, что и в MVC-проекте – Toddams

+2

Обновление: 2.0 версии кеша шаблонов, построенных из строк – Toddams

13

Для .NET Core 1.0 есть рабочий пример: aspnet/Entropy/samples/Mvc.RenderViewToString. Поскольку это может измениться или исчезнуть, я подробно опишу подход, который я использую в своих приложениях.

Tl; dr - Бритва работает очень хорошо вне MVC! Этот подход может обрабатывать более сложные сценарии рендеринга, такие как частичные представления и впрыскивание объектов в представления, хотя я просто продемонстрирую простой пример ниже.


Служба ядра выглядит следующим образом:

RazorViewToStringRenderer.cs

using System; 
using System.IO; 
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; 

namespace RenderRazorToString 
{ 
    public class RazorViewToStringRenderer 
    { 
     private readonly IRazorViewEngine _viewEngine; 
     private readonly ITempDataProvider _tempDataProvider; 
     private readonly IServiceProvider _serviceProvider; 

     public RazorViewToStringRenderer(
      IRazorViewEngine viewEngine, 
      ITempDataProvider tempDataProvider, 
      IServiceProvider serviceProvider) 
     { 
      _viewEngine = viewEngine; 
      _tempDataProvider = tempDataProvider; 
      _serviceProvider = serviceProvider; 
     } 

     public async Task<string> RenderViewToString<TModel>(string name, TModel model) 
     { 
      var actionContext = GetActionContext(); 

      var viewEngineResult = _viewEngine.FindView(actionContext, name, false); 

      if (!viewEngineResult.Success) 
      { 
       throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", name)); 
      } 

      var view = viewEngineResult.View; 

      using (var output = new StringWriter()) 
      { 
       var viewContext = new ViewContext(
        actionContext, 
        view, 
        new ViewDataDictionary<TModel>(
         metadataProvider: new EmptyModelMetadataProvider(), 
         modelState: new ModelStateDictionary()) 
        { 
         Model = model 
        }, 
        new TempDataDictionary(
         actionContext.HttpContext, 
         _tempDataProvider), 
        output, 
        new HtmlHelperOptions()); 

       await view.RenderAsync(viewContext); 

       return output.ToString(); 
      } 
     } 

     private ActionContext GetActionContext() 
     { 
      var httpContext = new DefaultHttpContext 
      { 
       RequestServices = _serviceProvider 
      }; 

      return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); 
     } 
    } 
} 

Простой тест консольного приложения просто необходимо инициализировать службу (и некоторые вспомогательные услуги), и вызов его:

Program.cs

using System; 
using System.Diagnostics; 
using System.IO; 
using Microsoft.AspNetCore.Hosting; 
using Microsoft.AspNetCore.Hosting.Internal; 
using Microsoft.AspNetCore.Mvc.Razor; 
using Microsoft.Extensions.DependencyInjection; 
using Microsoft.Extensions.FileProviders; 
using Microsoft.Extensions.ObjectPool; 
using Microsoft.Extensions.PlatformAbstractions; 

namespace RenderRazorToString 
{ 
    public class Program 
    { 
     public static void Main() 
     { 
      // Initialize the necessary services 
      var services = new ServiceCollection(); 
      ConfigureDefaultServices(services); 
      var provider = services.BuildServiceProvider(); 

      var renderer = provider.GetRequiredService<RazorViewToStringRenderer>(); 

      // Build a model and render a view 
      var model = new EmailViewModel 
      { 
       UserName = "User", 
       SenderName = "Sender" 
      }; 
      var emailContent = renderer.RenderViewToString("EmailTemplate", model).GetAwaiter().GetResult(); 

      Console.WriteLine(emailContent); 
      Console.ReadLine(); 
     } 

     private static void ConfigureDefaultServices(IServiceCollection services) 
     { 
      var applicationEnvironment = PlatformServices.Default.Application; 
      services.AddSingleton(applicationEnvironment); 

      var appDirectory = Directory.GetCurrentDirectory(); 

      var environment = new HostingEnvironment 
      { 
       WebRootFileProvider = new PhysicalFileProvider(appDirectory), 
       ApplicationName = "RenderRazorToString" 
      }; 
      services.AddSingleton<IHostingEnvironment>(environment); 

      services.Configure<RazorViewEngineOptions>(options => 
      { 
       options.FileProviders.Clear(); 
       options.FileProviders.Add(new PhysicalFileProvider(appDirectory)); 
      }); 

      services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); 

      var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); 
      services.AddSingleton<DiagnosticSource>(diagnosticSource); 

      services.AddLogging(); 
      services.AddMvc(); 
      services.AddSingleton<RazorViewToStringRenderer>(); 
     } 
    } 
} 

Это предполагает, что у вас есть вид модели класс:

EmailViewModel.cs

namespace RenderRazorToString 
{ 
    public class EmailViewModel 
    { 
     public string UserName { get; set; } 

     public string SenderName { get; set; } 
    } 
} 

И макета и просмотра файлов:

Просмотров/_Layout.cshtml

<!DOCTYPE html> 

<html> 
<body> 
    <div> 
     @RenderBody() 
    </div> 
    <footer> 
Thanks,<br /> 
@Model.SenderName 
    </footer> 
</body> 
</html> 

просмотров/EmailTemplate.cshtml

@model RenderRazorToString.EmailViewModel 
@{ 
    Layout = "_EmailLayout"; 
} 

Hello @Model.UserName, 

<p> 
    This is a generic email about something.<br /> 
    <br /> 
</p> 
+0

Hi Nate, быстрый вопрос, я думал, что 'view.RenderAsync (ViewContext) .GetAwaiter () .GetResult(); 'следует избегать, и следует использовать async/await или было ли обоснование, почему вы написали этот пример синхронно? – dustinmoris

+0

@dustinmoris Спасибо, что указали, что это был надзор с моей стороны. Вы абсолютно правы. Я обновил код. Демонстрация консоли по-прежнему вызывает функцию GetAwaiter(). GetResult() 'из-за отсутствия надлежащей поддержки консоли async. :) –

+0

ok cool, просто хотел спросить :) – dustinmoris

1

Вот класс, чтобы получить ответ Нейта работает как контекстные службы в проекте ASP.NET 2.0 ядра.

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; 

namespace YourNamespace.Services 
{ 
    public class ViewRender : IViewRender 
    { 
     private readonly IRazorViewEngine _viewEngine; 
     private readonly ITempDataProvider _tempDataProvider; 
     private readonly IServiceProvider _serviceProvider; 

     public ViewRender(
      IRazorViewEngine viewEngine, 
      ITempDataProvider tempDataProvider, 
      IServiceProvider serviceProvider) 
     { 
      _viewEngine = viewEngine; 
      _tempDataProvider = tempDataProvider; 
      _serviceProvider = serviceProvider; 
     } 

     public async Task<string> RenderAsync(string name) 
     { 
      return await RenderAsync<object>(name, null); 
     } 

     public async Task<string> RenderAsync<TModel>(string name, TModel model) 
     { 
      var actionContext = GetActionContext(); 

      var viewEngineResult = _viewEngine.FindView(actionContext, name, false); 

      if (!viewEngineResult.Success) 
      { 
       throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", name)); 
      } 

      var view = viewEngineResult.View; 

      using (var output = new StringWriter()) 
      { 
       var viewContext = new ViewContext(
        actionContext, 
        view, 
        new ViewDataDictionary<TModel>(
         metadataProvider: new EmptyModelMetadataProvider(), 
         modelState: new ModelStateDictionary()) 
        { 
         Model = model 
        }, 
        new TempDataDictionary(
         actionContext.HttpContext, 
         _tempDataProvider), 
        output, 
        new HtmlHelperOptions()); 

       await view.RenderAsync(viewContext); 

       return output.ToString(); 
      } 
     } 

     private ActionContext GetActionContext() 
     { 
      var httpContext = new DefaultHttpContext {RequestServices = _serviceProvider}; 
      return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); 
     } 
    } 

    public interface IViewRender 
    { 
     Task<string> RenderAsync(string name); 

     Task<string> RenderAsync<TModel>(string name, TModel model); 
    } 
} 

В запуске.CS

public void ConfigureServices(IServiceCollection services) 
{ 
    services.AddScoped<IViewRender, ViewRender>(); 
} 

В контроллере

public class VenuesController : Controller 
{ 
    private readonly IViewRender _viewRender; 

    public VenuesController(IViewRender viewRender) 
    { 
     _viewRender = viewRender; 
    } 

    public async Task<IActionResult> Edit() 
    { 
     string html = await _viewRender.RenderAsync("Emails/VenuePublished", venue.Name); 
     return Ok(); 
    } 
} 
3

Вот пример кода, который зависит только от Razor (для синтаксического анализа и C поколения # код) и Рослин (для C# компиляции кода, но вы можете использовать старый CodeDom).

Вам понадобятся только Microsoft.AspNetCore.Razor.Language (v2.0.1) и Microsoft.CodeAnalysis.CSharp (v2.6.0) nugets.

Помимо этого, источник совместим с NETCore, NETStandard 2 и .NET Framework. Чтобы протестировать его, просто создайте .NET-платформу или консольное приложение .NET, вставьте его и добавьте nugets.

using System; 
using System.Collections.Immutable; 
using System.IO; 
using System.Reflection; 
using System.Threading.Tasks; 
using Microsoft.AspNetCore.Razor.Language; 
using Microsoft.AspNetCore.Razor.Language.Extensions; 
using Microsoft.CodeAnalysis; 
using Microsoft.CodeAnalysis.CSharp; 

namespace RazorTemplate 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      // customize the default engine a little bit 
      var engine = RazorEngine.Create(b => 
      { 
       InheritsDirective.Register(b); // make sure the engine understand the @inherits directive in the input templates 
       b.SetNamespace("MyNamespace"); // define a namespace for the Template class 
       b.Build(); 
      }); 

      // points to the local path 
      var project = RazorProject.Create("."); 
      var te = new RazorTemplateEngine(engine, project); 

      // get a razor-templated file. My "hello.txt" template file is defined like this: 
      // 
      // @inherits RazorTemplate.MyTemplate 
      // Hello @Model.Name, welcome to Razor World! 
      // 

      var item = project.GetItem("hello.txt"); 

      // parse and generate C# code, outputs it on the console 
      var cs = te.GenerateCode(item); 
      Console.WriteLine(cs.GeneratedCode); 

      // now, use roslyn, parse the C# code 
      var tree = CSharpSyntaxTree.ParseText(cs.GeneratedCode); 

      // define the dll 
      const string dllName = "hello"; 
      var compilation = CSharpCompilation.Create(dllName, new[] { tree }, 
       new[] 
       { 
        MetadataReference.CreateFromFile(typeof(object).Assembly.Location), // include corlib 
        MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location), // this file (that contains the MyTemplate base class) 

        // for some reason on .NET core, I need to add this... this is not needed with .NET framework 
        MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.dll")), 

        // as found out by @Isantipov, for some other reason on .NET Core for Mac and Linux, we need to add this... this is not needed with .NET framework 
        MetadataReference.CreateFromFile(Path.Combine(Path.GetDirect‌​oryName(typeof(objec‌​t).Assembly.Location‌​), "netstandard.dll")‌​) 
       }, 
       new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); // we want a dll 


      // compile the dll 
      string path = Path.Combine(Path.GetFullPath("."), dllName + ".dll"); 
      var result = compilation.Emit(path); 
      if (!result.Success) 
      { 
       Console.WriteLine(string.Join(Environment.NewLine, result.Diagnostics)); 
       return; 
      } 

      // load the built dll 
      Console.WriteLine(path); 
      var asm = Assembly.LoadFile(path); 

      // the generated type is defined in our custom namespace, as we asked. "Template" is the type name that razor uses by default. 
      var template = (MyTemplate)Activator.CreateInstance(asm.GetType("MyNamespace.Template")); 

      // run the code. 
      // should display "Hello Killroy, welcome to Razor World!" 
      template.ExecuteAsync().Wait(); 
     } 
    } 

    // the model class. this is 100% specific to your context 
    public class MyModel 
    { 
     // this will map to @Model.Name 
     public string Name => "Killroy"; 
    } 

    // the sample base template class. It's not mandatory but I think it's much easier. 
    public abstract class MyTemplate 
    { 
     // this will map to @Model (property name) 
     public MyModel Model => new MyModel(); 

     public void WriteLiteral(string literal) 
     { 
      // replace that by a text writer for example 
      Console.Write(literal); 
     } 

     public void Write(object obj) 
     { 
      // replace that by a text writer for example 
      Console.Write(obj); 
     } 

     public async virtual Task ExecuteAsync() 
     { 
      await Task.Yield(); // whatever, we just need something that compiles... 
     } 
    } 
} 
+0

Хорошая работа, спасибо! Чтобы заставить его работать в библиотеке классов netstandard 2.0, работающей в приложении netcore2 на mac и linux, мне пришлось добавить дополнительную ссылку на netstandard dll: 'MetadataReference.CreateFromFile (Path.Combine (Path.GetDirectoryName (typeof (object) .Assembly. Location), «netstandard.dll»)), ' – Isantipov

+1

@ Исантипов - хорошо, спасибо, что указали, что я не тестировал это на других платформах, кроме Windows. Я обновил ответ. –

+0

Может ли двигатель бритвы автоматически найти файл «_ViewImports.cshtml», или мне нужно найти макет, просмотреть импорт и т. Д., Сам файл, идущий по этому маршруту? Кроме того, как насчет «ViewContext» и других связанных свойств в представлениях - как движок бритвы знает, как их установить? Или они ноль? –

Смежные вопросы