We are in the middle of upgrading our ASP.NET Core 1.0 web app to ASP.NET Core 3.1.
One thing that isn't working for us right now is our export service, which is used to generate PDFs from cshtml files. This code used to work on ASP.NET Core 1 app. Here's the code for rendering the cshtml files into a string:
public async Task<string> Export(object viewModel, string reportName, eExportType exportType, PdfExportOptions options)
{
using (var writer = new StringWriter())
{
var viewEngine = _context.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
// Find the view
var viewResult = viewEngine.GetView($"~/Views/Reports/{reportName}.cshtml", $"~/Views/Reports/{reportName}.cshtml", false);
if (viewResult?.Success == false) // This is the line that's not working: Success equals false
{
throw new Exception($"The report {reportName} could not be found.");
}
// Build ViewContext
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = viewModel
};
var tempData = new TempDataDictionary(_context, new SessionStateTempDataProvider(null));
var routingFeature = _context.Features[typeof(IRoutingFeature)] as RoutingFeature;
var actionContext = new ActionContext(_context, routingFeature?.RouteData, new ActionDescriptor());
var viewContext = new ViewContext(actionContext, viewResult?.View, viewData, tempData, writer, new HtmlHelperOptions());
// Render the view
if (viewResult?.View != null)
await viewResult.View?.RenderAsync(viewContext);
// Invoke the node.js service
var htmlContent = writer.GetStringBuilder().ToString();
var nodeServiceName = $"./NodeServices/{exportType.ToString().ToLower()}";
//var exportHandler = GetExportHandler(exportType, options); // TODO: Currently only works with PDF. Use export handlers for different export types
var result = await _nodeServices.InvokeAsync<byte[]>(nodeServiceName, htmlContent, options);
return Convert.ToBase64String(result);
}
}
For some reason viewResult?.Success is always false. I've tried all kinds of combinations to make it work, like viewEngine.GetView($"~/Views/Reports/{reportName}.cshtml", $"~/Views/Reports/{reportName}.cshtml", false)
,
viewEngine.GetView($"~/Views/Reports/{reportName}.cshtml", $"{reportName}.cshtml", false),
viewEngine.GetView($"Views/Reports/{reportName}.cshtml", $"{reportName}.cshtml", false)
etc. but none of them works, Success is always false, and the proerty viewResult.View is always null.
I've looked at many posts on StackOverflow, like this and many others but none of them solves our problem.
I'm suspecting that we might be doing something wrong at our Startup class, because the variable routingFeature above is also null, but I'm not completely sure about that. But just to make sure that out Startup class is configured correctly, here's the code for it:
public class Startup
{
public Startup(/*IHostingEnvironment env,*/ IConfiguration configuration)
{
Configuration = (IConfigurationRoot)configuration;
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<SmsProvidersSettings>(Configuration.GetSection("SmsProvidersSettings"));
// More configuration sections here, removed for brevity
services.AddControllers().AddNewtonsoftJson(options =>
{
// Use the default property (Pascal) casing
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthorization();
var sessionTimeout = Convert.ToDouble(Configuration.GetSection("GlobalSettings")["SessionTimeout"]);
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(typeof(Startup).Assembly));
services.AddSingleton(manager);
services.AddDistributedMemoryCache();
services.AddSession(options => options.IdleTimeout = TimeSpan.FromSeconds(sessionTimeout));
////services.AddMvc(options =>
////{
//// options.Filters.Add(new UserActionAuditFilterAttribute(new AuditHandler(Configuration["ConnectionStrings:Audit"])));
//// options.Filters.Add(new ApiValidationFilterAttribute());
//// options.Filters.Add(new GlobalExceptionFilter());
////});
//services.AddMemoryCache();
services.AddNodeServices(options => options.InvocationTimeoutMilliseconds = 60000);
services.Configure<RequestLocalizationOptions>(opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("he-IL")
};
opts.DefaultRequestCulture = new RequestCulture("he-IL", "he-IL");
opts.SupportedCultures = supportedCultures;
opts.SupportedUICultures = supportedCultures;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
if (Convert.ToBoolean(Configuration.GetSection("GlobalSettings")["UseScheduler"]))
{
services.AddHangfire(configuration =>
configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(
Configuration["ConnectionStrings:DigitalRural"],
new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
UseRecommendedIsolationLevel = true,
PrepareSchemaIfNecessary = true, // Default value: true
EnableHeavyMigrations = true, // Default value: false
UsePageLocksOnDequeue = true,
DisableGlobalLocks = false
}));
}
services.AddTransient<IRecurringJobManager, RecurringJobManager>();
services.AddTransient<IActionSystemService, ActionSystemService>();
// More serivces here, removed for brevity
services.AddScoped<IUnitOfWork>(sp =>
{
var httpContextAccessor = sp.GetService<IHttpContextAccessor>();
var currentUser = httpContextAccessor.HttpContext?.Session.GetJson<SessionUser>(SessionStateKeys.CurrentUser);
int? userId = null;
if (currentUser != null)
userId = currentUser.UserId;
return new UnitOfWork(new DigitalRuralContext(Configuration["ConnectionStrings:DigitalRural"], userId), httpContextAccessor);
});
//
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime, IGlobalApplicationDataService applicationDataService)
{
//var applicationDataService = app.ApplicationServices.GetService(typeof(IGlobalApplicationDataService)) as IGlobalApplicationDataService;
applicationDataService?.GetApplicationDataVM();
app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
// Don't cache index.html
context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
context.Response.Headers.Add("Pragma", "no-cache");
}
await next();
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
// Rewrite request to use app root
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/"; // Put your Angular root page here
await next();
}
});
app.UseAuthentication();
app.UseAuthorization();
loggerFactory
.AddSerilog();
//// Ensure any buffered events are sent at shutdown
//appLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
//if (env.IsDevelopment())
//{
// app.UseDeveloperExceptionPage();
// app.UseBrowserLink();
//}
//else
//{
// app.UseExceptionHandler("/Home/Error");
//}
////app.UseDefaultFiles();
//app.UseStaticFiles(new StaticFileOptions
//{
// OnPrepareResponse = context => { }
//});
////app.UseMvc();
if (Convert.ToBoolean(Configuration.GetSection("GlobalSettings")["UseScheduler"]))
{
app.UseHangfireDashboard("/scheduler", new DashboardOptions // Will be available at http://localhost:60209/scheduler
{
Authorization = new[] { new HangfireDashbordAuthorizationFilter() }
});
app.UseHangfireServer(new BackgroundJobServerOptions { StopTimeout = TimeSpan.FromSeconds(10) });
}
var applicationServices = app.ApplicationServices;
var httpContextAccessor = applicationServices.GetService<IHttpContextAccessor>();
app.UseFileServer(new FileServerOptions { FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")), RequestPath = "", EnableDefaultFiles = true });
app.UseRouting();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
}
}
So any guess as to why our code doesn't work?