I have a pretty basic page that has a few images via relative paths that I need to save to a .html file.
I have used this in the past but had problems with the images.
public static class ActionResultExtensions
{
public static string Capture(this ActionResult result, ControllerContext controllerContext)
{
using (var capture = new ResponseCapture(controllerContext.RequestContext.HttpContext.Response))
{
result.ExecuteResult(controllerContext);
return capture.ToString();
}
}
}
public class ResponseCapture : IDisposable
{
private readonly HttpResponseBase _response;
private readonly TextWriter _originalWriter;
private StringWriter _localWriter;
public ResponseCapture(HttpResponseBase response)
{
_response = response;
_originalWriter = response.Output;
_localWriter = new StringWriter();
response.Output = _localWriter;
}
public override string ToString()
{
_localWriter.Flush();
return _localWriter.ToString();
}
public void Dispose()
{
if (_localWriter == null) return;
_localWriter.Dispose();
_localWriter = null;
_response.Output = _originalWriter;
}
}
I'd like to know how to get the result WITH all of the CSS and images downloaded along with the HTML in a nice little package.
If you need it all in an HTML file you will need to do a tonne of nasties to inject css, js and images into the actual document, however you could use something like an MHT file which is a bundle of all resources required for a page.
this does the MHT thing by the looks, probably other options out there as well.
http://www.aspnetmht.com/Examples.aspx
Related
The first one of the following examples is working perfectly. The second one istn working, the pdf is saved to the database but the Onpost Method isnt working
1.( Working Example) I am doing this with _taget="blank and calling an empty Razor-Page.
The code looks like this.
HTML
<a class="btn btn-outline-dark" data-toggleToolTip="tooltip" data-placement="top"
title="Anzeigen" asp-page="/Invoices/DisplayInvoiceAsPdf" asp-route-invoiceId="#item.Nr" target="_blank">
<i class="fa-solid fa-download"></i>
</a>
Code behind of empty Razor-Page:
public class DisplayInvoiceAsPdfModel : PageModel
{
private readonly IDataRepository _DataRepository;
public DisplayInvoiceAsPdfModel(IDataRepository DataRepository)
{
_DataRepository = DataRepository;
}
public Kopfdaten Kopfdaten { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Kopfdaten = await _DataRepository.GetDataById(id);
if(Kopfdaten.Pdf == null)
{
return NotFound();
}
return File(Kopfdaten.Pdf, "application/pdf");
}
}
When i click on the Button the Pdf will be opened in a new tab (In Google Chrome).
2.(Not working Example): Im Creating a Preview Pdf in the OnPostmMethod, The PDf should be opened after the Pdf is created and stored to My database. I want to open Pdf with await OnPostPDf(id)
if (y == "OK")
{
//Ok=> Open Pdf in new Tab
await OnPostPDF(id);
if (testBool == true)
{
//Refresh page ,error
return RedirectToPage("Invoice", new { Id = adrId});
}
else
{
//Post
return RedirectToPage("/Index");
}
}
OnpostPdf looks like this:
public async Task<IActionResult> OnPostPDF(int id)
{
Kopfdaten kopfdaten = new Kopfdaten();
kopfdaten = await _DataRepository.DataById(id);
if (kopfdaten.Pdf == null)
{
return NotFound();
}
return File(kopfdaten.Pdf, "application/pdf");
}
Get Data ById
public async Task<Kopfdaten> GetDataById(int id)
{
try
{
return await _Context.Kopfdaten.FindAsync(id);
}
catch (Exception ex)
{
throw new Exception($"Couldn't retrieve entities: {ex.Message}");
}
}
Kopfdaten model:
public partial class Kopfdaten
{
public int Id { get; set; }
public int InVoiceNumber { get; set; }
public string Text { get; set; }
public int AdressId{ get; set; }
public byte[] Pdf { get; set; }
}
"How can I open PDF (In new window, or download it) in Post Method"
So there are 3 ways to handle this kind of scenario.
Way: 1 When You have an existing PDF file in your application folder
In this scenario, suppose you have PDF file in your application folder like below:
Solution:
public ActionResult DisplayPDFFromExistingFile()
{
string physicalPath = "wwwroot/KironGitHub.pdf";
byte[] pdfBytes = System.IO.File.ReadAllBytes(physicalPath);
MemoryStream ms = new MemoryStream(pdfBytes);
return new FileStreamResult(ms, "application/pdf");
}
Output:
Way: 2 When you want to display PDF on your browser from database Model
Solution:
public ActionResult DisplayPdfOnBrowserFromDatabaseList()
{
var data = _context.Members.ToList();
var pdf = data.ToPdf();
MemoryStream ms = new MemoryStream(pdf);
return new FileStreamResult(ms, "application/pdf");
}
Note: To handle this scenario you need to use 3rd party Nuget package which is ArrayToPdf You can search on Nuget package on your visual studio. You can also check the GitHub Link. Finally need to add using ArrayToPdf; on top.
Output:
Way: 3 When you want to download PDF on your browser from database model or existing file
Solution:
public ActionResult DownloadPDFOnBrowser()
{
var data = _context.Members.ToList();
var byteArray = data.ToPdf();
MemoryStream stream = new MemoryStream(byteArray);
string mimeType = "application/pdf";
return new FileStreamResult(stream, mimeType)
{
FileDownloadName = "DatabaseListToPdf.pdf"
};
}
Note: As described above we need to add using ArrayToPdf; on top. To handle this scenario you need to use 3rd party Nuget package which is ArrayToPdf You can search on Nuget package on your visual studio. See the screenshot below:
You can also check the GitHub Link
Output:
These are the use case scenario of how we could handle (PDF) the issue you are having.
I have an issue with opening JPG file in native application on Android.
I'm using newest release of Xamarin Essentials, there is some functionality called Launcher.
Here is my code
await Launcher.TryOpenAsync("file:///" + localPath);
My local path is file stored in Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);.
Whenever I try to open that file I am getting error:
file:////data/user/0/mypackagename/files/.local/share/Screenshot.jpg exposed beyond app through Intent.getData()
I found several solutions here on stackoverflow, but I don't want to use Intent because my application is designed to be cross-platform (I would like to avoid native platform coding if this possible).
Launcher throws error on iOS also:
canOpenURL: failed for URL: ** -- file:///" - error: "This app is not allowed to query for scheme file
What am I doing wrong here?
Step 1: Download the Image
We can use HttpClient to download the image.
HttpClient.GetByteArrayAsync will retrieve the image data and save it in memory.
In DownloadImage below, we will retrieve the image as a byte[].
static class ImageService
{
static readonly HttpClient _client = new HttpClient();
public static Task<byte[]> DownloadImage(string imageUrl)
{
if (!imageUrl.Trim().StartsWith("https", StringComparison.OrdinalIgnoreCase))
throw new Exception("iOS and Android Require Https");
return _client.GetByteArrayAsync(imageUrl);
}
}
Step 2 Save Image to Disk
Now that we've downloaded the image, we will save it to disk.
Xamarin.Essentials.Preferences allows us to save items to disk using key-value pairs. Since byte[] is just a pointer to memory, we'll have to first convert the byte[] to base64-string before we can save it to disk.
static class ImageService
{
static readonly HttpClient _client = new HttpClient();
public static Task<byte[]> DownloadImage(string imageUrl)
{
if (!imageUrl.Trim().StartsWith("https", StringComparison.OrdinalIgnoreCase))
throw new Exception("iOS and Android Require Https");
return _client.GetByteArrayAsync(imageUrl);
}
public static void SaveToDisk(string imageFileName, byte[] imageAsBase64String)
{
Xamarin.Essentials.Preferences.Set(imageFileName, Convert.ToBase64String(imageAsBase64String));
}
}
Step 3 Retrieve the Image for Display
Now that we've downloaded the image and saved it to disk, we need to be able to retrieve the image from disk to display it on the screen.
GetFromDisk below retrieves the image from disk and converts it to Xamarin.Forms.ImageSource.
static class ImageService
{
static readonly HttpClient _client = new HttpClient();
public static Task<byte[]> DownloadImage(string imageUrl)
{
if (!imageUrl.Trim().StartsWith("https", StringComparison.OrdinalIgnoreCase))
throw new Exception("iOS and Android Require Https");
return _client.GetByteArrayAsync(imageUrl);
}
public static void SaveToDisk(string imageFileName, byte[] imageAsBase64String)
{
Xamarin.Essentials.Preferences.Set(imageFileName, Convert.ToBase64String(imageAsBase64String));
}
public static Xamarin.Forms.ImageSource GetFromDisk(string imageFileName)
{
var imageAsBase64String = Xamarin.Essentials.Preferences.Get(imageFileName, string.Empty);
return ImageSource.FromStream(() => new MemoryStream(Convert.FromBase64String(imageAsBase64String)));
}
}
Example: Using ImageService in a Xamarin.Forms.ContentPage
class App : Application
{
public App() => MainPage = new MyPage();
}
class MyPage : ContentPage
{
readonly Image _downloadedImage = new Image();
public MyPage()
{
Content = _downloadedImage;
}
protected override async void OnAppearing()
{
const string xamrainImageUrl = "https://cdn.dribbble.com/users/3701/screenshots/5557667/xamarin-studio-1_2x_4x.png"
const string xamarinImageName = "XamarinLogo.png";
var downloadedImage = await ImageService.DownloadImage(xamrainImageUrl);
ImageService.SaveToDisk(xamarinImageName, downloadedImage);
_downloadedImage.Source = ImageService.GetFromDisk(xamarinImageName);
}
}
I have a pdf i've added as an AndroidAsset and a BundleResource for my Android and IOS projects using xamarin forms.
I just want to be able to open those files from any device, using whatever pdf viewer the device defaults to.
Essentially, i just want to be able to do something like:
Device.OpenUri("file:///android_asset/filename.pdf");
but this doesn't seem to work. Nothing happens and the user is never prompted to open the pdf. I don't want to use any 3rd party libraries that allow the pdf to open in app, i just want it to redirect the user to a pdf viewer or browser.
Any ideas?
First of all you will need an interface class, since you need to call the dependency service in order to pass your document to the native implementation(s) of your app:
so in your shared code add an interface, called "IDocumentView.cs":
public interface IDocumentView
{
void DocumentView(string file, string title);
}
Android
Now in your android project create the corresponding implementation "DocumentView.cs":
assembly: Dependency(typeof(DocumentView))]
namespace MyApp.Droid.Services
{
public class DocumentView: IDocumentView
{
void IDocumentView.DocumentView(string filepath, string title)
{
try
{
File file = new File(filepath);
String mime = FileTypes.GetMimeTypeByExtension(MimeTypeMap.GetFileExtensionFromUrl(filepath));
File extFile = new File (Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments), file.Name);
File extDir = extFile.ParentFile;
// Copy file to external storage to allow other apps to access ist
if (System.IO.File.Exists(extFile.AbsolutePath))
System.IO.File.Delete(extFile.AbsolutePath);
System.IO.File.Copy(file.AbsolutePath, extFile.AbsolutePath);
file.AbsolutePath, extFile.AbsolutePath);
// if copying was successful, start Intent for opening this file
if (System.IO.File.Exists(extFile.AbsolutePath))
{
Intent intent = new Intent();
intent.SetAction(Android.Content.Intent.ActionView);
intent.SetDataAndType(Android.Net.Uri.FromFile(extFile), mime);
MainApplication.FormsContext.StartActivityForResult(intent, 10);
}
}
catch (ActivityNotFoundException anfe)
{
// android could not find a suitable app for this file
var alert = new AlertDialog.Builder(MainApplication.FormsContext);
alert.SetTitle("Error");
alert.SetMessage("No suitable app found to open this file");
alert.SetCancelable(false);
alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide());
alert.Show();
}
catch (Exception ex)
{
// another exception
var alert = new AlertDialog.Builder(MainApplication.FormsContext);
alert.SetTitle("Error");
alert.SetMessage("Error when opening document");
alert.SetCancelable(false);
alert.SetPositiveButton("Okay", (object sender, DialogClickEventArgs e) => ((AlertDialog)sender).Hide());
alert.Show();
}
}
}
}
Please note that MainApplication.FormsContext is a static variable I added to my MainApplication.cs in order to be able to access the Context of the app quickly.
In your Android manifest, add
In your application resources, add an xml resource (into folder "xml") called file_paths.xml with the following content:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="root" path="/"/>
<external-files-path name="files" path="files" />
</paths>
Also you need to ensure that there are apps installed on the target device, which are able to handle the file in question. (Acrobat Reader, Word, Excel, etc...).
iOS
iOS already comes with a quite nice document preview built in, so you can simply use that (again create a file named "DocumentView.cs" in your iOS Project):
[assembly: Dependency(typeof(DocumentView))]
namespace MyApp.iOS.Services
{
public class DocumentView: IDocumentView
{
void IDocumentView.DocumentView(string file, string title)
{
UIApplication.SharedApplication.InvokeOnMainThread(() =>
{
QLPreviewController previewController = new QLPreviewController();
if (File.Exists(file))
{
previewController.DataSource = new PDFPreviewControllerDataSource(NSUrl.FromFilename(file), title);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(previewController, true, null);
}
});
}
}
public class PDFItem : QLPreviewItem
{
public PDFItem(string title, NSUrl uri)
{
this.Title = title;
this.Url = uri;
}
public string Title { get; set; }
public NSUrl Url { get; set; }
public override NSUrl ItemUrl { get { return Url; } }
public override string ItemTitle { get { return Title; } }
}
public class PDFPreviewControllerDataSource : QLPreviewControllerDataSource
{
PDFItem[] sources;
public PDFPreviewControllerDataSource(NSUrl url, string filename)
{
sources = new PDFItem[1];
sources[0] = new PDFItem(filename, url);
}
public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
{
int idx = int.Parse(index.ToString());
if (idx < sources.Length)
return sources.ElementAt(idx);
return null;
}
public override nint PreviewItemCount(QLPreviewController controller)
{
return (nint)sources.Length;
}
}
}
Finally you can call
DependencyService.Get<IDocumentView>().DocumentView(file.path, "Title of the view");
to display the file in question.
I'm trying to access Shared Resources in a Tag Helper, but it doesn't return the value from the resource file even though I know it exists, only ResourceNotFound. I use IHtmlLocalizer to access shared resources in some of my views and it works fine so everything should be configured correctly.
Tag Helper:
[HtmlTargetElement("lc:buy-button", Attributes = "product", TagStructure = TagStructure.WithoutEndTag)]
public class BuyButtonTagHelper : BaseTagHelper
{
private readonly IStringLocalizer<SharedResources> _localizer;
public BuyButtonTagHelper(AppHelper app, IStringLocalizer<SharedResources> localizer) : base(app)
{
_localizer = localizer;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
.........
base.Process(context, output);
}
private string ProcessHtml(string html)
{
string result = html.Replace("{?productId?}", this.Product.ToString());
result = result.Replace("{?subscribeText?}", _localizer["SubscribeButtonText"].Value);
return result;
}
[HtmlAttributeName("product")]
public int Product { get; set; } = -1;
}
Solved it by installing the nuget package Microsoft.AspNetCore.Mvc.Localization
I'm trying to append som UI tools automatically using an action filter like this:
public override void OnResultExecuted(ResultExecutedContext filterContext) {
if (filterContext.HttpContext.User.Identity.IsAuthenticated && !DisableEditorTools) {
using (var sw = new StringWriter()) {
filterContext.Controller.ViewData.Model = filterContext.RouteData.GetCurrentPage<IPage>();
ViewEngineResult viewResult =
ViewEngines.Engines.FindPartialView(filterContext.Controller.ControllerContext,
"~/Areas/UI/Views/Shared/_UIControls.cshtml");
var viewContext = new ViewContext(filterContext.Controller.ControllerContext, viewResult.View,
filterContext.Controller.ViewData, filterContext.Controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(filterContext.Controller.ControllerContext, viewResult.View);
HttpResponseBase response = filterContext.HttpContext.Response;
response.Filter = new AddUiToolsFilter(response.Filter, sw.GetStringBuilder().ToString());
}
}
else {
base.OnResultExecuted(filterContext);
}
}
And then I'm trying to rewrite the response stream like this
public class AddUiToolsFilter : MemoryStream
{
private readonly Stream _response;
private readonly string _htmlToAppend;
public AddUiToolsFilter(Stream response, string htmlToAppend)
{
_response = response;
_htmlToAppend = htmlToAppend;
}
public override void Write(byte[] buffer, int offset, int count)
{
var html = Encoding.UTF8.GetString(buffer);
html = AddUiTools(html, _htmlToAppend);
buffer = Encoding.UTF8.GetBytes(html);
_response.Write(buffer, offset, buffer.Length);
}
private string AddUiTools(string html, string htmlToAppend)
{
string newHtmlDocument = html.Replace("</body>", htmlToAppend + "</body>");
return newHtmlDocument;
}
}
This works pretty good when I not using a browser on my dev machine and this is because Visual studio has something they call browser link. I know it is possible to turn off this but is it possible to make sure that my action filter doesn't interfere with Visual Studio?
I know this is old but I've had the same issue for ages and I finally fixed it and the solution might help someone out.
The problem seems to stem from the fact the buffer length is not the same as the count parameter. Instead of doing the html manipulation in the write method, I instead write the buffer into the base memory stream and then make my changes when the stream is closing:
public class AddUiToolsFilter : MemoryStream
{
private readonly Stream _response;
private readonly string _htmlToAppend;
public AddUiToolsFilter(Stream response, string htmlToAppend)
{
_response = response;
_htmlToAppend = htmlToAppend;
}
public override void Close()
{
var html = Encoding.UTF8.GetString(base.ToArray());
// Add your UI Tools
string newHtmlDocument = html.Replace("</body>", _htmlToAppend + "</body>");
byte[] rawResult = Encoding.UTF8.GetBytes(newHtmlDocument);
_response.Write(rawResult, 0, rawResult.Length);
_response.Close();
}
}
Thinking about it, Flush() might be a technically better place to put it, but you'd have to potentially deal with an early flush.
I think you want to run the base.OnResultExecution always when you are not returning a custom response.
public override void OnResultExecuted(ResultExecutedContext filterContext) {
if (!DisableUiTools) {
using (var sw = new StringWriter()) {
filterContext.Controller.ViewData.Model = filterContext.RouteData.GetCurrentPage<IPage>();
var viewResult = ViewEngines.Engines.FindPartialView(filterContext.Controller.ControllerContext, "~/Areas/UI/Views/Shared/_UIControls.cshtml");
var viewContext = new ViewContext(filterContext.Controller.ControllerContext, viewResult.View, filterContext.Controller.ViewData, filterContext.Controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(filterContext.Controller.ControllerContext, viewResult.View);
var response = filterContext.HttpContext.Response;
response.Filter = new AddUiToolsFilter(response.Filter, sw.GetStringBuilder().ToString());
}
}
}else
base.OnResultExecuted(filterContext);
}