I'm using ASP .NET Core MVC (views and controllers).
Is there a way to add some additional output to all *.cshtml files using middleware, filter or something similar?
I would like to display the path(s) of all cshtml-files like the view itself, partial views, layout-file or components, that are part of the current page.
This is how it should look like:
Right now, I have to add this line to the *.cshtml files, one by one:
#using WkOne.AuthorizationServer.ViewModels;
#model IEnumerable<UserViewModel>
#{
Layout = "_Layout3Cols";
ViewData["Title"] = "Users";
}
<!-- I need this line in every cshtml file -->
<!-- \ -->
<div style="font-size: small;background-color: #CFC;">Path: #Path.ToString() </div>
<table class="table">
<!-- ... and so on... -->
But what I'm looking for is a way to do this in central place.
Any suggestions?
MVC project returns the html codes(razor codes has already been complied to html,so your codes shouldn't contain razor codes) which contained in response body to browser,
The response body could write but couldn't be read ,if you want to add the html codes somewhere you want ,I think you need to replace the default body
I tried as below and added the text "hi"
public class CusTestMiddleware
{
private readonly RequestDelegate _next;
public CusTestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var response = context.Response;
var responseOriginalBody = response.Body;
using var memStream = new MemoryStream();
response.Body = memStream;
await _next(context);
var targetstr = "<a>hi</a>";
byte[] targetbyte = Encoding.UTF8.GetBytes(targetstr);
memStream.Write(targetbyte);
memStream.Position = 0;
var responseReader = new StreamReader(memStream);
var responseBody = await responseReader.ReadToEndAsync();
memStream.Position = 0;
await memStream.CopyToAsync(responseOriginalBody);
response.Body = responseOriginalBody;
}
}
Related
I have SQL Server image type data that I am trying to retrieve via a Web API (Net6) and display in Blazor web assembly. I can get back of string of data, but I am having no luck getting it into the correct format.
The Web API is
public byte[] SQLGetImageBytes(int Id)
{
string SqlConnectionString = configuration.GetValue<string>("SqlConnection");
IDbConnection db = new SqlConnection(SqlConnectionString);
var data = db.QueryFirstOrDefault<Thumbs>(#"SELECT [DocumentID], CAST([Page1] AS VARBINARY(MAX)) As Page1 FROM[Operations_cache].[dbo].[tblLargeThumbs] where[DocumentId] = " + Id);
return data.Page1;
}
In the API, the endpoint is
app.MapGet("/thumbbytes/{id}", (int id) =>
{
SQLQuery sqlq = new();
byte[] imageData = sqlq.SQLGetImageBytes(id);
string imageDataready = string.Concat("data:image/png;base64,",Convert.ToBase64String(imageData));
return imageDataready;
});
My client calls the api with:
protected override async Task OnInitializedAsync()
{
var response = await Http.GetAsync(#"https://example.com:4439/thumbbytes/47992");
string imgstring = await response.Content.ReadAsStringAsync();
}
And the razor page is
#if (imgstring == null)
{}
else
{
<img src=#imgstring />
}
Most of the "help" I've found online references Windows platform packages (System Drawing, etc) that don't work in cross-platform Blazor.
I was trying to do this in .NET 6 (vs full/classic .NET), and I have been through WAY too many hours of trying different combinations (different types of API responses, bytes/strings/etc, and tried retrieving as png/jpg/bmp - pretty sure it is png), but the best I can get is an image placeholder in Blazor/browser, and a string of characters in Insomnia/api testing.So I hope I am just missing something simple. I'm working read-only with image data that I didn't create (and yes, it is in image format, which I understand is to be deprecated), so I can't just change how it is stored.
Put a break point on API endpoint on this line, and copy string being returned
return imageDataready;
Create an img tag in Blazor page and paste copied string, and see if it is rendering image
<img src="paste here image data" />
Look at this code, you are creating a new imgstring variable here
protected override async Task OnInitializedAsync()
{
var response = await Http.GetAsync(#"https://example.com:4439/thumbbytes/47992");
string imgstring = await response.Content.ReadAsStringAsync();
}
Whereas it should be
#code{
string imgstring
protected override async Task OnInitializedAsync()
{
var response = await Http.GetAsync(#"https://example.com:4439/thumbbytes/47992");
imgstring = await response.Content.ReadAsStringAsync();
}
}
I want to print a rendered modal that opens up with some vehicle information, The button is present on the modal and on click should convert the modal HTML to PDF.
Please advise if there is a C# function or so that I can use, which will extract the current HTML I want to convert to PDF, or steer me in the right direction. I have only been doing C# for about 2 months so lacking experience and expertise.
Edit: Also trying to get the code to use CSS when dumping the PDF.
I have added the following code.
References, etc. added.
#using BidWheels.Configuration;
#using BidWheels.Shared.Controls;
#using BidWheels.Data;
#using BidWheels.CustomProviders;
#using BidWheels.Services;
#using System.Timers;
#using Syncfusion.Pdf;
#using Syncfusion.HtmlConverter;
#using System.Linq;
#using System.Web;
#using Microsoft.AspNetCore.Http;
#inject AppState AppState;
#inject UsersDAL UsersDAL;
#inject MainDAL MainDAL;
#inject NotificationService NotificationService;
#inject GeneralConfiguration GeneralConfiguration;
#inject GlobalVar GlobalVar;
#inject GlobalVarShared GlobalVarShared;
#inject Microsoft.JSInterop.IJSRuntime JS
#inject IHttpContextAccessor httpContextAccessor
Button to proc the function to generate the PDF
</div>
<button class="btn btn-outline-success" #onclick="#CreatePDF">Generate PDF</button>
</div>
This is the function invoked by the button.
#functions {
void CreatePDF()
{
> //Initialize HTML to PDF converter
> //HtmlToPdfConverter htmlConverter = new HtmlToPdfConverter(HtmlRenderingEngine.WebKit);
> //WebKitConverterSettings settings = new WebKitConverterSettings();
> //Set WebKit path
> //settings.WebKitPath = Server.MapPath("~/QtBinaries");
> //Assign WebKit settings to HTML converter
> //htmlConverter.ConverterSettings = settings;
> //Get the current URL
> //string url = HttpContext;
> //Convert URL to PDF
> //PdfDocument document = htmlConverter.Convert(url);
> //Save the document
> //document.Save("Sample.pdf", HttpContext.Current.Response, HttpReadType.Save);
Code updated to below
HtmlToPdfConverter htmlConverter = new HtmlToPdfConverter();
WebKitConverterSettings webKitSettings = new WebKitConverterSettings();
webKitSettings.WebKitPath = Directory.GetCurrentDirectory() + "\\wwwroot" + #"\QtBinariesWindows\";
webKitSettings.MediaType = MediaType.Print;
webKitSettings.Orientation = PdfPageOrientation.Portrait;
htmlConverter.ConverterSettings = webKitSettings;
Convert HTML to PDF.
string baseUrl = #"" + Directory.GetCurrentDirectory() + "/wwwroot/css/";
string HTMLBody = await JS.InvokeAsync<string>("getHTMLtoRender", PDFBody);
PdfDocument pdfDocument = htmlConverter.Convert(HTMLBody, baseUrl);
MemoryStream memoryStream = new MemoryStream();
Save and close the document instance.
pdfDocument.Save(memoryStream);
JS.SaveAs("Sample.pdf", memoryStream.ToArray());
}
}
The following errors are being generated.
Error messages generated by the code for the above:
New errors generated and lib:
I always found programmatic PDF handling very difficult.
If you really need a programmatic way take a look at this: Convert HTML to PDF in .NET.
Otherwise I'd advise you to use the browsers print feature with a print to pdf tool, of wich at least one was usually already installed on every system i saw.
1st render razor component to html string as the following
var host = new TestHost();
var component = host.AddComponent<YourComponent>();
var html = component.GetMarkup();
then pass this string to IronPdf library to generate the pdf for you
using IronPdf;
var Renderer = new IronPdf.ChromePdfRenderer();
var PDF = Renderer.RenderHtmlAsPdf(html);
References
stackoverflow link How to render a Blazor component into an HTML string
IronPdf IronPdf website
Once you created the pdf as explained here you can use Append.Blazor.Printing to have a native print dialog. You can see the blogpost here.
Is it possible, once a Razor View has been rendered in the browser, that the HTML and the contents of the mark up (images, tables, data etc.) can be saved as a string or other type?
I want to be able to generate the Razor View for the customer to check everything is ok in the output, then I want them to click a button that saves all of the HTML (Without all of the razor markup etc.).
How do you pass the HTML back to an Action, if it has to be processed pre-rendering, then how can this also be done.
I can then use this to generate PDF's and also save time on processing, as I will save the string in a database.
BTW, this is not a partial view nor will it use partial views, also I know there are still some things to fix in the Razor View, I am more interested in the saving the HTML at this point.
TIA
HTML Pre rendering
HTML Post Rendering
You can use Middleware to obtain a copy of the HTML that is being sent to the browser. Create a class named ResponseToString with the following content:
public class ResponseToStringMidleware
{
RequestDelegate _next;
public ResponseToStringMidleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var isHtml = context.Response.ContentType?.ToLower().Contains("text/html");
Stream responseBody = context.Response.Body;
using (var memoryStream = new MemoryStream())
{
context.Response.Body = memoryStream;
await _next(context);
if (context.Response.StatusCode == 200 && isHtml.GetValueOrDefault())
{
memoryStream.Position = 0;
string html = new StreamReader(memoryStream).ReadToEnd();
// save the HTML
}
memoryStream.Position = 0;
await memoryStream.CopyToAsync(responseBody);
}
}
}
Replace the // save the HTML with some code to persist the HTML as required. Register the Middleware in your Startup's Configure method early:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseMiddleware<ResponseToStringMidleware>();
...
}
Further infomration: Middleware in Razor Pages
I want to have a SPA that's doing all the work client side and even generating some graphs/visuals.
I'd like to be able to have the user click buttons and save the visuals, tables, and other things from the page (that are both seen and not seen, so right click save or copy/paste is not always an option).
How do I call a function from the webassembly/blazor library, get it's results and save it as a file on the client side?
the idea is something like this...?
cshtml
<input type="file" onchange="#ReadFile">
<input type="file" onchange="#SaveFile">
#functions{
object blazorObject = new blazorLibrary.SomeObject();
void ReadFile(){
blazorObject.someFunction(...selectedFile?...);
}
void SaveFile(){
saveFile(...selectedFile..?)
}
}
Creator of Blazor Steve Sanderson used JavaScript interop for similar task during one of his last presentations.
You can find example on BlazorExcelSpreadsheet
Solution includes three parts:
1) JavaScript
function saveAsFile(filename, bytesBase64) {
var link = document.createElement('a');
link.download = filename;
link.href = "data:application/octet-stream;base64," + bytesBase64;
document.body.appendChild(link); // Needed for Firefox
link.click();
document.body.removeChild(link);
}
2) C# interop wrapper
public static class FileUtil
{
public async static Task SaveAs(IJSRuntime js, string filename, byte[] data)
{
await js.InvokeAsync<object>(
"saveAsFile",
filename,
Convert.ToBase64String(data));
}
}
3) Call from your component
#inject IJSRuntime js
#functions {
void DownloadFile() {
var text = "Hello, world!";
var bytes = System.Text.Encoding.UTF8.GetBytes(text);
FileUtil.SaveAs(js, "HelloWorld.txt", bytes);
}
}
You can see it an action in Blazor Fiddle
Add a link
<a class="form-control btn btn-primary" href="/download?name=test.txt" target="_blank">Download</a>
Add Razor Page with a route
2.1. Create Razor page 'Download.cshtml' or another name... 'PewPew.cshtml'... does not matter
2.2. Put the next code in the created page
#page "/download"
#model MyNamespace.DownloadModel
Edit Download.cshtml.cs file
public class DownloadModel : PageModel
{
public IActionResult OnGet(string name) {
// do your magic here
var content = new byte[] { 1, 2, 3 };
return File(content, "application/octet-stream", name);
}
}
I created a repository and nuget package which solves and simplifies this issue please take a look: https://github.com/arivera12/BlazorDownloadFile
The solution proposed by Eugene work, but have one drawback. If you try to do it with large files the browser hangs while downloading the blob to the client side. Solution I have found is to change that code a bit and store files in temporary directory and let the server use its mechanics for serving files instead of pushing it as a blob.
In the server configuration add :
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(___someTempDirectoryLocation___, "downloads")),
RequestPath = "/downloads"
});
This will add static link to a download folder somewhere on your system. Put any files you want to be available for download in there.
Next You can use either link to that file using http://pathToYourApplication/downloads/yourFileName or use simplified save javascript from main example:
function saveAsFile(filename) {
var link = document.createElement('a');
link.download = filename;
link.href = "/downloads/" + filename;
document.body.appendChild(link); // Needed for Firefox
link.click();
document.body.removeChild(link);
}
which will push it to user browser for you.
I did it thus:
Added a new DownloadController.cs to a folder called Controllers
[Controller, Microsoft.AspNetCore.Mvc.Route("/[controller]/[action]")]
public class DownloadController : Controller
{
private readonly IDataCombinerService DataCombinerService;
private readonly IDataLocatorService DataLocatorService;
public DownloadController(IDataCombinerService dataCombinerService, IDataLocatorService dataLocatorService)
{
DataCombinerService = dataCombinerService;
DataLocatorService = dataLocatorService;
}
[HttpGet]
[ActionName("Accounts")]
public async Task<IActionResult> Accounts()
{
var cts = new CancellationTokenSource();
var Accounts = await DataCombinerService.CombineAccounts(await DataLocatorService.GetDataLocationsAsync(cts.Token), cts.Token);
var json = JsonSerializer.SerializeToUtf8Bytes(Accounts, Accounts.GetType(), new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true });
var stream = new MemoryStream(json);
var fResult = new FileStreamResult(stream, MediaTypeNames.Application.Json)
{
FileDownloadName = $"Account Export {DateTime.Now.ToString("yyyyMMdd")}.json"
};
return fResult;
}
[HttpGet]
public IActionResult Index()
{
return View();
}
}
Strictly speaking async isn't required here as it doesn't need to process anything else, but that method is used to display the same results on screen when it is.
Then inside Startup.cs
app.UseEndpoints(endpoints =>
add:
endpoints.MapControllerRoute(
name: "default",
defaults: new { action = "Index" },
pattern: "{controller}/{action}");
endpoints.MapControllers();
Again the defaults isn't strictly speaking required, it's a standard MVC Controller.
This then functions just like a classic MVC response, so you can send back any files, from any source you like. It may be helpful to have a middleware service to hold temporary data between the view and the downloader controller so the client is downloading the same data.
Eugene's answer didn't work for me for some reason, but there is now official documentation on how to do this, which is very similar and works well in my Blazor Server app.
Add these JavaScript methods to your _Host.cshtml file:
<script type="text/javascript">
async function downloadFileFromStream(fileName, contentStreamReference) {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
triggerFileDownload(fileName, url);
URL.revokeObjectURL(url);
}
function triggerFileDownload(fileName, url) {
const anchorElement = document.createElement('a');
anchorElement.href = url;
if (fileName) {
anchorElement.download = fileName;
}
anchorElement.click();
anchorElement.remove();
}
</script>
In your .razor page file, add:
#using System.IO
#inject IJSRuntime JS
<button #onclick="DownloadFileFromStream">
Download File From Stream
</button>
#code {
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);
return fileStream;
}
private async Task DownloadFileFromStream()
{
var fileStream = GetFileStream();
var fileName = "log.bin";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
}
I have an ASP.NET app. My app has a _ViewStart.cshtml file. That file looks like this:
#using MyCompany.MyApp;
#{
Layout = "/Views/Shared/_Layout.cshtml";
var p = HttpContext.Current.Request.QueryString["parameter"];
ViewBag.QSParameter = p;
}
When I execute this code, I get the following error:
The name 'HttpContext' does not exist in the current context
I don't understand. Isn't _ViewStart.cshtml kind of the "shell" for the views? I'm trying to figure out how to globally read a query string parameter and set a value on the ViewBag for each request. I thought this would be the way to do it.
Thanks
You should have access to Request in your _ViewStart file.
Try this:
#using MyCompany.MyApp;
#{
Layout = "/Views/Shared/_Layout.cshtml";
var p = Request.QueryString["parameter"];
ViewBag.QSParameter = p;
}
EDIT: For ASP.NET 5
I don't have ASP.NET 5 on my machine but have looked at the source code for the framework. It looks like there is a Context property on RazorPage that returns an HttpContext. Alternatively, you can access the HttpContext through the ViewContext. See below:
#{
Layout = "/Views/Shared/_Layout.cshtml";
var p = Context.Request.Query["parameter"];
// or this...
// var p = ViewContext.HttpContext.Request.Query["parameter"];
ViewBag.QSParameter = p;
}
To retrieve it from _ViewStart.cshtml, you can use:
ViewBag.QSParameter = Context.Request.Query["parameter"];
Note: Use Query now (over QueryString) in ASP.NET 5
However, I might ellect to go a different route and take advantage of IResultFilter:
public class QSParameterFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
var QSParameter = context.HttpContext.Request.Query["parameter"];
((Controller)context.Controller).ViewBag.QSParameter = QSParameter;
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
Then, register it within your Startup.cs:
services.AddMvc();
services.Configure<MvcOptions>(options => {
options.Filters.Add(new QSParameterFilter());
});
Make the "Build Action = Content" in the file properties. This will solve the issue.