Soru NET Core'da MVC'nin dışında Razor Kullanımı


Razor'u .NET Core'da yazdığım bir .NET konsol uygulamasında bir şablon motoru olarak kullanmak isterim.

Ben (sadece RazorEngine, RazorTemplates) karşı karşıya geldim bağımsız Razor motorları tüm. NET gerektirir. .NET Core ile çalışan bir çözüm arıyorum.


32
2017-07-07 13:42


Menşei


github.com/aspnet/Razor sadece çekirdek çalışma zamanını gerektirir (.NET standart kitaplığını kullanarak) - haim770


Cevaplar:


Yakın zamanda bir kütüphane oluşturdum Razorlight.

ASP.NET MVC parçaları gibi yedek bağımlılıkları yoktur ve konsol uygulamalarında kullanılabilir. Şimdilik sadece .NET Core'u (NetStandard1.6) destekliyor - ama tam da ihtiyacınız olan şey bu.

İşte kısa bir örnek:

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" }); 

22
2017-07-25 13:16



Bu uygulamak oldukça kolaydı, ancak oldukça korkunç bir performansı var. 1000 satırlık html üreten bir döngü oluşturdum. Her seferinde 12 saniye sürdü. Sadece 200 satırlı tek bir sayfa oluşturmak yaklaşık 1-2 saniye sürdü. Bir MVC projesinde 1 sayfa yaklaşık 20 milisaniye sürdü. Yani performans konusunda endişelenmiyorsanız, bu geçerli bir seçenektir. - DeadlyChambers
Eh, ParseString kullanırsanız - şablonlar önbelleğe alınmaz, bu yüzden performans sorunları yaşarsınız. Bunun yerine uygun şablon yöneticisi ile Parse kullanın (dosyalar veya gömülü kaynaklar için) - bu şekilde şablon sadece bir kez derlenecek ve bir dahaki sefere önbellekten alınacaktır. Ve MVC projesinde olduğu gibi aynı sayıları göreceksiniz - Toddams
Güncelleştirme: dizeleri yerleşik 2.0 sürümü önbellek şablonları - Toddams
@Toddams Bu, üretim yayınlanmak üzere önceden derlendiği için üretim üzerinde çalışmayacak. Üretimi (önceden derlenmiş görünümler) ve geliştirme ortamını destekleyen mvc proje örneğini ekleyebilir misiniz? Her iki ortam için birlikte çalışmayı başaramıyorum: (( - Freshblood


.NET Core 1.0 için çalışan bir örnek var. aspnet / Entropi / numune / Mvc.RenderViewToString. Bu değişebileceğinden ya da gideceğinden, kendi uygulamalarında kullanıyorum yaklaşımını burada ayrıntılandırırım.

Tl; Dr. - Razor, MVC'nin dışında gerçekten iyi çalışıyor! Bu yaklaşım, kısmi görüşler gibi daha karmaşık oluşturma senaryoları ve aynı zamanda nesneleri basit bir örnek gösterebilse de, nesnelerin görüşlere enjekte edilmesini de ele alabilir.


Çekirdek servis şu şekildedir:

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());
        }
    }
}

Basit bir test konsolu uygulaması sadece servisi (ve bazı destekleyici servisleri) başlatmalı ve buna çağrı yapmalıdır:

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>();
        }
    }
}

Bu, bir görünüm modeli sınıfınızın olduğunu varsayar:

EmailViewModel.cs

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

        public string SenderName { get; set; }
    }
}

Düzen ve görünüm dosyaları:

Görüntüleme / _Layout.cshtml

<!DOCTYPE html>

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

Görüntüleme / EmailTemplate.cshtml

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

Hello @Model.UserName,

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

15
2017-07-07 19:03



@dustinmoris Doğru hatırlıyorsam, sizin için bazı önbellekleme yapar. Bir süredir denemedim. - Nate Barbettini
Korku veren! Ancak, bu işe yaramaz .net core 2.0 :( Bağımlılıkların yüklenemediği görülüyor: The type 'Attribute' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' - Usturaya ihtiyaç duyduğu tüm bağımlılıkları - herhangi bir fikri - yüklemesini nasıl söyleyeceğimi bilmiyorum. - Matt Roberts
Bu kod, projemizdeki belleği sızdırıyor gibi görünüyor, ancak tam olarak ne suçlu olduğunu bilmiyorum. - Doug
@MehdiDehghani Razor her zaman (ilk kez) anında derlemek için uzun bir zaman alır. ASP.NET Core 2.1'deki yeni Razor kütüphanesi desteğiyle ve derleme derleme özelliğiyle ilgilenebilirsiniz: blogs.msdn.microsoft.com/webdev/2018/02/02/... - Nate Barbettini
@doug Evet, bir bellek sızıntısı görüyoruz ve hafızayı geri almanın bir yolu yok (kullanımda olan tek kullanımlık yok). Büyük bir modelle (bir XML beslemesi için 80.000 satırlık ürün) bu, birkaç çalışmadan sonra sunucuyu öldürür. - Ryan O'Neill


Burada sadece Jilet'e (ayrıştırma ve C # kodu oluşturma için) ve Roslyn'e (C # kod derlemesi için) bağlı olan ancak eski CodeDom'u da kullanabileceğiniz bir örnek kod bulunmaktadır.

Bu kod parçasında MVC yoktur, bu nedenle Görünüm yok, .cshtml dosyaları yok, Denetleyici yok, sadece Razor kaynak ayrıştırma ve derleme çalışma zamanı yürütme. Yine de Model kavramı var.

Aşağıdaki nuget paketlerini eklemeniz gerekecek: Microsoft.AspNetCore.Razor.Language (V2.1.1), Microsoft.AspNetCore.Razor.Runtime (v2.1.1) ve Microsoft.CodeAnalysis.CSharp (v2.8.2) nugetler.

Bu C # kaynak kodu, NETCore, NETStandard 2 ve .NET Framework ile uyumludur. Bunu test etmek için sadece bir .NET framework veya .NET core console uygulaması oluşturun, yapıştırın ve nugetleri ekleyin.

using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Hosting;
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)
        {
            // points to the local path
            var fs = RazorProjectFileSystem.Create(".");

            // customize the default engine a little bit
            var engine = RazorProjectEngine.Create(RazorConfiguration.Default, fs, (builder) =>
            {
                InheritsDirective.Register(builder);
                builder.SetNamespace("MyNamespace"); // define a namespace for the Template class
            });

            // 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 = fs.GetItem("hello.txt");

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

            var codeDocument = engine.Process(item);
            var cs = codeDocument.GetCSharpDocument();

            // 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(typeof(RazorCompiledItemAttribute).Assembly.Location), // include Microsoft.AspNetCore.Razor.Runtime
                    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.GetDirectoryName(typeof(object).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...
        }
    }
}

11
2017-12-11 15:45



Güzel iş, teşekkürler! Netstandard dll için netcore2 uygulamasında çalışan netstandard 2.0 sınıf kütüphanesinde çalışabilmek için netstandard dll'ye ek bir referans eklemek zorunda kaldım: MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location),"netstandard.dll")), - Isantipov
@Isantipov - Tamam, bunu işaretlediğiniz için teşekkürler, bunu Windows'tan başka platformlarda test etmemiştim. Cevabı güncelledim. - Simon Mourier
Jiletli motor otomatik olarak "_ViewImports.cshtml" dosyasını bulur mu, yoksa düzeni mi, ithalatı mı, vb. Ayrıca, 'ViewContext' ve görünümlerdeki diğer ilgili özellikler hakkında - tıraş makinesi motoru bunları nasıl ayarlayacağını nasıl biliyor? Yoksa boş mu? - James Wilkins
@JamesWilkins - Adından da anlaşılacağı gibi, bunlar Görünüm kavramlarıdır, bu yüzden, Razor dilinde değil, ASP.NET MVC'de (View V'den beri). - Simon Mourier
@Dave - Cevabımı güncelledim. Şimdi en yeni sürümleriyle çalışmalı. - Simon Mourier


İşte, Nate'in cevabının bir ASP.NET Core 2.0 projesinde kapsamlı hizmet olarak çalışmasını sağlamak için bir sınıf.

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);
    }
}

Startup.cs içinde

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

Bir denetleyicide

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();
    }
}

4
2017-10-07 00:09



Bunu çalışmaya başlayamıyorum. Hatayı alıyorum: 'Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine' türünde 'Mvc.RenderViewToString.RazorViewToStringRenderer' etkinleştirmeyi denerken hizmet için çözümlenemedi. ' - Kjensen