Blazor - Drag and Drop uploads file - c#

I'm facing an issue with uploading a file via the Drag'n Drop API.
Here the following blazor component:
<InputTextArea
#ondrop="HandleDrop"
#ondragenter="HandleDragEnter"
#ondragleave="HandleDragLeave"/>
</InputTextArea>
#code {
private async Task HandleDrop(DragEventArgs args)
{
var files = args.DataTransfer.Files;
// Do something to upload the file and get the content
}
I want to upload the file and display it in the textarea. Now since .NET6 the DragEventArgs will list all files (if any) associated with the Drag'n Drop event.
Natively there seems to be no way to get the content of those files.
Therefore I tried to achieve this with JavaScript interop:
private async Task HandleDrop(DragEventArgs args)
{
var content = await jsRuntime.InvokeAsync<string>("getContentFromFile", args);
}
With the following JavaScript function (which is referenced in the _Layout.cshtml):
async function getContentFromFile(args) {
// Use some API here? File Upload API? Didn't work as args is only a data object and not the "real" DragEventArgs from JavaScript
// I also tried FileReader
const fileName = args.Files[0]; // Let's assume we always have one file
let content = await new Promise((resolve) => {
let fileReader = new FileReader();
fileReader.onload = (e) => resolve(fileReader.result);
fileReader.readAsText(fileName);
});
console.log(content);
return content;
}
This approach let to other issues and exceptions, like that the FileReader threw:
parameter is not of type 'Blob'
Is this with this approach with the current version of blazor (.net6) possible at all? Any tips are welcome.

Related

How to use a custom image upload handler in TinyMCE Blazor?

I'm trying to use a custom image handler for TinyMCE Blazor Component within a razor page, without success. The reason I need to use a custom upload handler rather than just allowing TinyMCE to post the request is that I need to add a JWT to the request for authentication.
TinyMCE configuration is done via a dictionary of <string, object>
#code {
private Dictionary<string, object> editorConf = new Dictionary<string, object>
{
{"plugins", "autolink media link image emoticons table paste"},
{"toolbar", "undo redo | styles | bold italic underline | table | link image paste "},
{"paste_data_images", "true"},
{"width", "100%"},
{"automatic_uploads", true },
{"images_upload_url", "/UploadImage/"} // works fine if no JWT required
};
/// other code
}
I cannot use a C# method for the handler because I do not know the parameter types, the only examples I've found are written in PHP (which I am not familiar with) and js and so the parameters are not typed.
I have tried following an approach similar to what is suggested here https://github.com/tinymce/tinymce-blazor/issues/19 by creating a js script that invokes a C# method that would then add the JWT and do the required work before returning the file path of the image.
export function upload_handler(blobInfo, success, failure, progress)
{
DotNet.invokeMethodAsync('MyApp', "UploadHandler", "this is a test!")
.then((data) => {
success(data);
});
};
private static IJSObjectReference? js_imagesupload;
private Dictionary<string, object> editorConf = new Dictionary<string, object>
{
{"plugins", "autolink media link image emoticons table paste"},
{"toolbar", "undo redo | styles | bold italic underline | table | link image paste "},
{"paste_data_images", "true"},
{"width", "100%"},
{"automatic_uploads", true },
{"images_upload_handler", (async () => await js_imagesupload.InvokeVoidAsync("upload_handler", null))}
};
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
js_imagesupload = await JS.InvokeAsync<IJSObjectReference>("import", "./scripts/imagesupload.js"); // js script
}
}
[JSInvokable]
public static Task<string> UploadHandler(string value)
{
// add JWT to request and do image upload work here
}
The problem with this is I get an error complaining about JSON serialization.
rit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Serialization and deserialization of 'System.Func`1[[System.Threading.Tasks.Task, System.Private.CoreLib...
I can see why this would happen, of course Tiny MCE cannot serialize the lambda expression. I'd really appreciate it if someone knows a way I can get around this issue. I'm kind of new to Blazor so it's entirely possible I'm missing something simple! Many thanks.
It turns out that the answer to this is that you can use a javascript file to store the config and this in turn allows you to set the images_upload_handler to a javascript function. Thanks to the TinyMCE contributor jscasca! https://github.com/tinymce/tinymce-blazor/issues/60
So I created a file called tinyMceConf.js and stored this in wwwroot/js/
The file contains the config (tinyMceConf) and also the upload handler which calls a C# method.
The SetDotNetHelper is called from the razor component so that js has a reference to the dotnet instance. See this helpful article for more information Calling .NET Instance Methods in ASP.NET Core Blazor Directly from JavaScript I had to do this so because I needed my method to be an instance method rather than static.
tinyMceConf.js
tinyMceConf = {
height: 400,
toolbar: 'undo redo | styles | bold italic underline | table | link image paste | emoticons',
plugins: 'autolink media link image emoticons table paste',
paste_data_images: true,
automatic_uploads: true,
images_upload_handler: js_upload_handler
}
function SetDotNetHelper(dotNetHelper) {
window.dotNetHelper = dotNetHelper;
}
function js_upload_handler(blobInfo, success, failure, progress) {
console.log(blobInfo.filename());
window.dotNetHelper.invokeMethodAsync('UploadHandler', blobInfo.base64(), blobInfo.filename())
.then((data) => {
success(data);
});
}
Then I added this line to index.html (within the head)
<script src="/js/tinyMceConf.js" type="text/javascript"></script>
Then within my razor component I set JsConfSrc to tinyMceConf (in tinyMceConf.js).
<TinyMCE.Blazor.Editor Id="uuid"
Inline=false
CloudChannel="5"
Disable=false
ClassName="tinymce-wrapper"
JsConfSrc="tinyMceConf"
Field="() => loadedTicket.TechNotes" #bind-Value="#loadedTicket.TechNotes" />
Within my razor component I have a method using the JSInvokable attribute that allows my js to invoke it.
[JSInvokable]
public async Task<string> UploadHandler(string base64string, string filename)
{
// Within my method I use the base 64 string and filename passed from js
// to do what I need and return the url of the uploaded image
}
That's it!

CefSharp Search Engine Implamentation

I am working on a cefsharp based browser and i am trying to implement a search engine into the browser, but the code I have tried docent work, it doesn't really have any errors but when i star the project and type something i the text field nothing happens and it dosent load the search engine i entered into the code, the only time the textbox loads anything is when a url is typed.
This is the code used in the browser that docent work
private void LoadUrl(string url)
{
if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
{
WebUI.Load(url);
}
else
{
var searchUrl = "https://www.google.com/search?q=" + WebUtility.HtmlEncode(url);
WebUI.Load(searchUrl);
}
}
i have also tried
void LoadURl(String url)
{
if (url.StartsWith("http"))
{
WebUI.Load(url);
}
else
{
WebUI.Load(url);
}
}
i was also suggested to try
private void LoadUrl(string url)
{
if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
{
WebUI.LoadUrl(url);
}
else
{
var searchUrl = "https://www.google.com/search?q=" + Uri.EscapeDataString(url);
WebUI.LoadUrl(searchUrl);
}
}
We have here really few Information on how your code works. But what I notice is that you use WebUtility.HtmlEncode for the search query. WebUtility has also a WebUtility.UrlEncode Method, that how I understand your question makes more sense it the context. This is the documentation for the method: https://learn.microsoft.com/de-de/dotnet/api/system.net.webutility.urlencode
The Url you are generating is invalid. You need to use Uri.EscapeDataString to convert the url param into a string that can be appended to a url.
// For this example we check if a well formed absolute Uri was provided
// and load that Url, all others will be loaded using the search engine
// e.g. https://github.com will load directly, attempting to load
// github.com will load the search engine with github.com as the query.
//
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
{
chromiumWebBrowser.LoadUrl(url);
}
else
{
var searchUrl = "https://www.google.com/search?q=" + Uri.EscapeDataString(url);
chromiumWebBrowser.LoadUrl(searchUrl);
}
nothing happens and it dosent load the search engine
You need to subscribe to the LoadError event to get actual error messages. It's up to you to display errors to the user. The following is a basic example:
chromiumWebBrowser.LoadError += OnChromiumWebBrowserLoadError;
private void OnChromiumWebBrowserLoadError(object sender, LoadErrorEventArgs e)
{
//Actions that trigger a download will raise an aborted error.
//Aborted is generally safe to ignore
if (e.ErrorCode == CefErrorCode.Aborted)
{
return;
}
var errorHtml = string.Format("<html><body><h2>Failed to load URL {0} with error {1} ({2}).</h2></body></html>",
e.FailedUrl, e.ErrorText, e.ErrorCode);
_ = e.Browser.SetMainFrameDocumentContentAsync(errorHtml);
}
For testing purposes you can also copy and paste the searchUrl string you've generated and try loading it in Chrome to see what happens, you should also get an error.

Can't load a local javascript file into the webview

I have a webview inside a uwp application and i need to inject some javascript files into it. I can't include them in the source code of the html page.
This is what I have so far.
private async void WebView_DOMContentLoaded(WebView sender, WebViewDOMContentLoadedEventArgs args)
{
await Webview.InvokeScriptAsync("eval", new string[]
{
"var script = document.createElement('script'); script.type='text/javascript'; script.charset='UTF-8'; script.src ="
+ "'ms-appx://Apps.Webapp/JS/jquery-3.2.1.min.js'"
+ ";document.body.appendChild(script);"
});
}
But when i try with the direct invocation the files are simple not loaded
Anyone has any thoughts that can help me?
Edit: I have tried with no luck
"'ms-appx-web://Apps.Webapp/JS/jquery-3.2.1.min.js'"
To load file from the your app package in the WebView you need to use ms-appx-web scheme, not ms-appx. For example:
<script type="text/javascript" src="ms-appx-web:///JS/jquery-3.2.1.min.js"></script>
More details please reference the "Navigating to content section" of WebView class.
After some trial and error I got a working solution:
private async void WebView_DOMContentLoaded(WebView sender, WebViewDOMContentLoadedEventArgs args)
{
string scriptToLoad = File.ReadAllText("Apps.Webapp/JS/jquery-3.2.1.min.js");
string[] arguments = { scriptToLoad };
await WebBrowserWizzio.InvokeScriptAsync("eval", arguments);
}
This way i'm injecting the whole script into the page and not only the path.

WPF Awesomium - custom ResourceInterceptor wont allow local files to be loaded from disk

i have added my own custom Resource Interceptor to Awesomiums Webcore. I use it to load a local html file, manipulate it and then return it.
However this HTML file references other files on disk and I always get the error - something like
Not allowed to load local resource: file:///C:/awesomium project/bin/Debug/Resources/Html/css/myCss.css
This happens whether I return a file or a byte[] as my ResourceResponse. I have the WebPreferences set up as follows
return new WebPreferences()
{
UniversalAccessFromFileURL = true,
FileAccessFromFileURL = true,
SmoothScrolling = true,
};
I had this working using Awesomiums custom data sources but I need to provide our own prefix i.e. I can't use asset://. i'm not sure why this isn't working from the IResourceInterceptor. Any ideas?
Here is my imlementation of the Resource interceptor
public bool OnFilterNavigation(NavigationRequest request)
{
return false;//not handled
}
public ResourceResponse OnRequest(ResourceRequest request)
{
var path = request.Url.ToString();
ResourceResponse response = null;
if (path.StartsWith(#"myCustomPrefix://"))
{
response = _firstHandler.HandleRequest(path.Replace(#"myCustomPrefix://", string.Empty));
}
return response;
}
Edit: _firstHandler is a chain of command pattern. I could potentially do many things with the request so I have a handler for each one. I would like to say the handlers work. If I set them to create a file on disk they create that file - if I load up the html file from disk directly in the awesomium browser it is loaded correctly. It is only an issue when I return it as a ResourceResponse(filepath) or ResourceResponse(byte[], intPtr, mimetype) that it says it can't load the other files the HTML references locally.

2 asynchronus download requests, but the second is dependent on the result of the first - how to sync

Edit: Sorry - now that I've understood the problem a bit better, I think my problem lies elsewhere
I have 2 asynchronus requests.
The first is this:
public void DownloadWebData(Uri apiUrl)
{
WebClient client = new WebClient();
client.DownloadDataCompleted += DownloadDataCompleted;
client.DownloadDataAsync(apiUrl);
}
public void DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
string result = System.Text.Encoding.UTF8.GetString (e.Result);
Uri downloadLink = (GetUri(result));
}
Basically it makes a simple url based API request to a remote webserver which returns some basic textual data over http. GetUri() just parses that data to extract an address from the data for an image to download.
I'm then using imageLoader in monotouch.dialog to download the image. All code is in the same class.
Edit: added the imageLoader code (I left the Console lines in because they serve reasonably well as comments).
public void downloadImage (Uri imageUri)
{
var tmp_img = ImageLoader.DefaultRequestImage (imageUri, this);
if (tmp_img != null)
{
adView.Image = tmp_img;
Console.WriteLine ("Image already cached, displaying");
}
else
{
adView.Image = UIImage.FromFile ("Images/downloading.jpg");
Console.WriteLine ("Image not cached. Using placeholder.");
}
}
public void UpdatedImage (System.Uri uri)
{
adView.Image = ImageLoader.DefaultRequestImage(uri, this);
}
You missed to check if e.Result actually contains something. The download might as well have failed and e.Result is null. Add some basic error handling to your code.
if you are using DownloadWebData inside a for loop, it will be better you generate seperate functions for DownloadDataCompleted event.
You can use anonymous function inside DownloadWebData().
client.DownloadDataCompleted +=(s,e)=>{
string result = System.Text.Encoding.UTF8.GetString (e.Result);
Uri downloadLink = (GetUri(result));
};
After realizing I was asking the wrong question, I finally figured it out here:
Hand back control to main UI thread to update UI after asynchronus image download

Categories

Resources