66using System . IO ;
77using System . Linq ;
88using System . Net ;
9+ using System . Reflection ;
910using System . Threading . Tasks ;
1011using Microsoft . AspNetCore . Html ;
1112using Microsoft . AspNetCore . Http ;
12- using Microsoft . AspNetCore . Mvc ;
1313using Microsoft . AspNetCore . Mvc . Abstractions ;
1414using Microsoft . AspNetCore . Mvc . ModelBinding ;
1515using Microsoft . AspNetCore . Mvc . Razor ;
2323using ServiceStack . Auth ;
2424using ServiceStack . Caching ;
2525using ServiceStack . Configuration ;
26+ using ServiceStack . Host ;
2627using ServiceStack . Host . Handlers ;
2728using ServiceStack . Html ;
2829using ServiceStack . IO ;
3435using ServiceStack . VirtualPath ;
3536using ServiceStack . Web ;
3637using ServiceStack . Text ;
38+ using ActionContext = Microsoft . AspNetCore . Mvc . ActionContext ;
3739
3840namespace ServiceStack . Mvc
3941{
@@ -345,7 +347,23 @@ public async Task<bool> ProcessRequestAsync(IRequest req, object dto, Stream out
345347 return true ;
346348 }
347349
348- private ViewEngineResult FindView ( IEnumerable < string > viewNames , out Dictionary < string , object > routingArgs )
350+ public IView GetViewPage ( string path )
351+ {
352+ var viewEngineResult = FindView ( new [ ] { path } , out _ ) ;
353+ if ( viewEngineResult ? . Success == true )
354+ return viewEngineResult . View ;
355+ return null ;
356+ }
357+
358+ public IView GetContentPage ( string path )
359+ {
360+ var viewEngineResult = GetRoutingPage ( path , out _ ) ;
361+ if ( viewEngineResult ? . Success == true )
362+ return viewEngineResult . View ;
363+ return null ;
364+ }
365+
366+ public ViewEngineResult FindView ( IEnumerable < string > viewNames , out Dictionary < string , object > routingArgs )
349367 {
350368 routingArgs = null ;
351369 const string execPath = "" ;
@@ -378,10 +396,21 @@ private ViewEngineResult FindView(IEnumerable<string> viewNames, out Dictionary<
378396
379397 internal static ViewDataDictionary CreateViewData < T > ( T model )
380398 {
399+ if ( model is ViewDataDictionary viewData )
400+ return viewData ;
401+
402+ if ( model != null && model . GetType ( ) . IsAnonymousType ( ) )
403+ {
404+ return new ViewDataDictionary (
405+ metadataProvider : new EmptyModelMetadataProvider ( ) ,
406+ modelState : new ModelStateDictionary ( ) ) {
407+ Model = new DictionaryDynamicObject ( model . ToObjectDictionary ( ) )
408+ } ;
409+ }
410+
381411 return new ViewDataDictionary < T > (
382412 metadataProvider : new EmptyModelMetadataProvider ( ) ,
383- modelState : new ModelStateDictionary ( ) )
384- {
413+ modelState : new ModelStateDictionary ( ) ) {
385414 Model = model
386415 } ;
387416 }
@@ -396,7 +425,7 @@ internal async Task RenderView(IRequest req, Stream stream, ViewDataDictionary v
396425 new RouteData ( ) ,
397426 new ActionDescriptor ( ) ) ;
398427
399- var sw = new StreamWriter ( stream ) ;
428+ var sw = new StreamWriter ( stream ) ; // don't dispose of stream so other middleware can re-read / filter it
400429 {
401430 if ( viewData == null )
402431 viewData = CreateViewData ( ( object ) null ) ;
@@ -445,6 +474,83 @@ internal async Task RenderView(IRequest req, Stream stream, ViewDataDictionary v
445474 await req . Response . WriteErrorBody ( ex ) ;
446475 }
447476 }
477+
478+ public async Task < ReadOnlyMemory < char > > RenderToHtmlAsync ( IView view , object model = null , string layout = null )
479+ {
480+ using var ms = MemoryStreamFactory . GetStream ( ) ;
481+ await WriteHtmlAsync ( ms , view , model , layout ) ;
482+ return MemoryProvider . Instance . FromUtf8 ( ms . GetBufferAsSpan ( ) ) ;
483+ }
484+
485+ public async Task WriteHtmlAsync ( Stream stream , IView view , object model = null , string layout = null , HttpContext ctx = null , IRequest req = null )
486+ {
487+ if ( view == null )
488+ throw new ArgumentNullException ( nameof ( view ) ) ;
489+
490+ var razorView = view as RazorView ;
491+
492+ try
493+ {
494+ if ( ctx == null )
495+ {
496+ ctx = new DefaultHttpContext {
497+ RequestServices = HostContext . Container
498+ } ;
499+ }
500+
501+ var actionContext = new ActionContext (
502+ ctx ,
503+ new RouteData ( ) ,
504+ new ActionDescriptor ( ) ) ;
505+
506+ var sw = new StreamWriter ( stream ) ;
507+ {
508+ var viewData = CreateViewData ( model ) ;
509+
510+ // Use "_Layout" if unspecified
511+ if ( razorView != null )
512+ razorView . RazorPage . Layout = layout ?? DefaultLayout ;
513+
514+ // Allows Layout from being overridden in page with: Layout = Html.ResolveLayout("LayoutUnlessOverridden")
515+ if ( layout != null )
516+ viewData [ "Layout" ] = layout ;
517+
518+ viewData [ Keywords . IRequest ] = req ?? new BasicRequest { PathInfo = view . Path } ;
519+
520+ var viewContext = new ViewContext (
521+ actionContext ,
522+ view ,
523+ viewData ,
524+ new TempDataDictionary ( actionContext . HttpContext , tempDataProvider ) ,
525+ sw ,
526+ new HtmlHelperOptions ( ) ) ;
527+
528+ await view . RenderAsync ( viewContext ) ;
529+
530+ await sw . FlushAsync ( ) ;
531+
532+ try
533+ {
534+ using ( razorView ? . RazorPage as IDisposable ) { }
535+ }
536+ catch ( Exception ex )
537+ {
538+ log . Warn ( "Error trying to dispose Razor View: " + ex . Message , ex ) ;
539+ }
540+ }
541+ }
542+ catch ( StopExecutionException ) { }
543+ catch ( Exception origEx )
544+ {
545+ var ex = origEx . UnwrapIfSingleException ( ) ;
546+ if ( ex is StopExecutionException )
547+ return ;
548+ if ( ex == origEx )
549+ throw ;
550+ throw ex ;
551+ }
552+ }
553+
448554 }
449555
450556 public class RazorHandler : ServiceStackHandlerBase
0 commit comments