UIImage loaded from URL in Xamarin / C# - c#

It has been 4 years since this question has been answered with this blog post.
Is there a standard way to create a UIImage with an image from a URL? Something like:
UIImage image = UIImage.FromFile("http://foo.com/bar.jpg");
I feel like I'm probably missing something really simple.

Not a one-liner, but with very few lines you can roll your own. E.g.
static UIImage FromUrl (string uri)
{
using (var url = new NSUrl (uri))
using (var data = NSData.FromUrl (url))
return UIImage.LoadFromData (data);
}
The calls, including the one from UIImage, are thread-safe.

With new await/async support you can do:
public async Task<UIImage> LoadImage (string imageUrl)
{
var httpClient = new HttpClient();
Task<byte[]> contentsTask = httpClient.GetByteArrayAsync (imageUrl);
// await! control returns to the caller and the task continues to run on another thread
var contents = await contentsTask;
// load from bytes
return UIImage.LoadFromData (NSData.FromArray (contents));
}
and you call this with:
someYourUIImageObjectOnUI.Image = await this.LoadImage ("some image url");

You want to be sure that you load the image async so that you do not block your UI thread. MonoTouch.Dialog includes an ImageLoader (see sec 5.3) class that you could use.
There are also a couple of variations of UrlImageStore out there to help with async loading images.
Finally, if you want to do it manually, there is a Xamarin Recipe you can use.

I tried the above, it looks like a great idea, but I get:
Cannot implicitly convert type System.Threading.Tasks.Task<MonoTouch.UIKit.UIImage>' toMonotouch.UIKit.UIImage'
[found a solution]
The problem was because the
obj.Image = await this.LoadImage (imageUrl)
must also be in a method marked async.
Then it works!
Thanks

Below code should work,
public static async Task<UIImage> LoadImage(string imageUrl)
{
var httpClient = new HttpClient();
var contents = await httpClient.GetByteArrayAsync(imageUrl);
return UIImage.LoadFromData(NSData.FromArray(contents));
}

Related

Add BackgroundImage with EPPlus only allows path but cannot get path in Blazor WASM

This may not be 100% an EPPlus issue, but since it is Blazor WASM it appears I cannot get the file path to a static image in the wwwroot/images folder. I can get the url and paste it into a browser and that works, even adding that same path to the src attribute of an img works, neither of those helps me.
FYI "background" in this context means a watermark.
It appears that the EPPlus dev team only wants a drive path the file (ex. C:\SomeFolder\SomeFile.png), and I am not seeing how to get that within Blazor WASM. I can get the bytes of the file in c# and even a stream, but no direct path.
My code is the following:
using (var package = new ExcelPackage(fileName))
{
var sheet = package.Workbook.Worksheets.Add(exportModel.OSCode);
sheet.BackgroundImage.SetFromFile("https://localhost:44303/images/Draft.png");
...
}
This returns an exception:
Unhandled exception rendering component: Can't find file /https:/localhost:44303/images/Draft.png
Noticing that leading / I even tried:
sheet.BackgroundImage.SetFromFile("images/Draft.png");
Which returned the same error:
Unhandled exception rendering component: Can't find file /images/Draft.png
So, I am perhaps needing 1 of 2 possible answers:
A way to get a local drive path to the file so the .SetFromFile method is not going to error.
To have a way to set that BackgroundImage property with a byte array or stream of the image. There is this property BackgroundImage.Image but it is readonly.
Thanks to a slap in the face from #Panagiotis-Kanavos I wound up taking the processing out of the client and moving it to the server. With that, I was able to use Static Files to add the watermark with relatively little pain.
In case anyone may need the full solution (which I always find helpful) here it is:
Here is the code within the button click on the Blazor component or page:
private async Task GenerateFile(bool isFinal)
{
...
var fileStream = await excelExportService.ProgramMap(exportModel);
var fileName = "SomeFileName.xlsx";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await jsRuntime.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
That calls a client-side service that really just passes control over to the server:
public class ExcelExportService : IExcelExportService
{
private const string baseUri = "api/excel-export";
private readonly IHttpService httpService;
public ExcelExportService(IHttpService httpService)
{
this.httpService = httpService;
}
public async Task<Stream> ProgramMap(ProgramMapExportModel exportModel)
{
return await httpService.PostAsJsonForStreamAsync<ProgramMapExportModel>($"{baseUri}/program-map", exportModel);
}
}
Here is the server-side controller that catches the call from the client:
[Route("api/excel-export")]
[ApiController]
public class ExcelExportController : ControllerBase
{
private readonly ExcelExportService excelExportService;
public ExcelExportController(ExcelExportService excelExportService)
{
this.excelExportService = excelExportService;
}
[HttpPost]
[Route("program-map")]
public async Task<Stream> ProgramMap([FromBody] ProgramMapExportModel exportModel)
{
return await excelExportService.ProgramMap(exportModel);
}
}
And that in-turn calls the server-side service where the magic happens:
public async Task<Stream> ProgramMap(ProgramMapExportModel exportModel)
{
var result = new MemoryStream();
ExcelPackage.LicenseContext = LicenseContext.Commercial;
var fileName = #$"Gets Overwritten";
using (var package = new ExcelPackage(fileName))
{
var sheet = package.Workbook.Worksheets.Add(exportModel.OSCode);
if (!exportModel.IsFinal)
{
var pathToDraftImage = #$"{Directory.GetCurrentDirectory()}\StaticFiles\Images\Draft.png";
sheet.BackgroundImage.SetFromFile(pathToDraftImage);
}
...
sheet.Cells.AutoFitColumns();
package.SaveAs(result);
}
result.Position = 0; // Without this, data does not get written
return result;
}
For some reason, this next method was not needed when doing this on the client-side but now that it is back here, I had to add a method that returned a stream specifically and used the ReadAsStreamAsync instead of ReadAsJsonAsync:
public async Task<Stream> PostAsJsonForStreamAsync<TValue>(string requestUri, TValue value, CancellationToken cancellationToken = default)
{
Stream result = default;
var responseMessage = await httpClient.PostAsJsonAsync(requestUri, value, cancellationToken);
try
{
result = await responseMessage.Content.ReadAsStreamAsync(cancellationToken: cancellationToken);
}
catch (HttpRequestException e)
{
...
}
return result;
}
Lastly, in order for it to give the end-user a download link, this was used (taken from the Microsoft Docs):
window.downloadFileFromStream = async (fileName, contentStreamReference) => {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement("a");
anchorElement.href = url;
anchorElement.download = fileName ?? "";
anchorElement.click();
anchorElement.remove();
URL.revokeObjectURL(url);
}

I have a class library in which I added UwpDesktop nuget package. Same nuget works fine in Windows form but doesn't work in class library

I have integrated this function in my class library. Everything works fine in function but when it comes to GetFileFromPathAsync(), it just seems to not move forward and does not throw any exception, either, although I have added try catch.
My class library function works in such a way that its output path of .dlls is in separate folder. I test this class library with GUI Application that outputs its resources in same folder as in class library. Any help would be appreciated
private static async Task<string> WindowsMediaOCR(string ImagePath, LanguageEnum language)
{
try
{
// ... Some code not related to below code
var file = await StorageFile.GetFileFromPathAsync(Directory.GetCurrentDirectory() +"\\tempImage.bmp")
var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
var decoder = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(stream);
var softwareBitmap = await decoder.GetSoftwareBitmapAsync();
var ocrResult = await engine.RecognizeAsync(softwareBitmap);
string readText = ocrResult.Text;
}
catch(Exception ex)
{
throw;
}
}
it just seems to not move forward and does not throw any exception, either, although I have added try catch.
The problem is that WindowsMediaOCR is async method and it's return type is Task<string>, so you need to place await key word before calling the method. and please correct above method that need a string return type. For more info please refer this document.
var str = await CorLib.WindowsMediaOCR();

Error when attaching file to System.Net.Mail. How do I fix this error?

Trying to send an email with an attachment. However I am getting an error:
"Cannot convert from
'System.Threading.Tasks.Task' to
'System.Net.Mail.Attachment'
My error occurs in the line Attachments.Add(GetAttachment(attachmentFileName));
I have tried various conversions (see code) but I dont quite see what the issue is. I know the solution is right in front of me but I dont see it.
public class NonFERosterEmail : BaseNotificationEmail<OfferViewModel>
{
public NonFERosterEmail(OfferViewModel vm, string emailList, string attachmentFileName) : base(vm)
{
To.AddRange(GetTo(emailList));
Body = GetBody();
Subject = GetSubject();
//Attachments.Add(new Attachment(GetAttachment(attachmentFileName)));
Attachments.Add(GetAttachment(attachmentFileName));
From = new MailAddress(ConfigurationManager.AppSettings["RedirectEmailTo"]);
}
//public async Task<List<Attachment>> GetAttachment(string attachmentFileName)
public async Task<Attachment> GetAttachment(string attachmentFileName)
{
//var ret = new List<Attachment>();
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["azureStorageAccount"]);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("attachments");
CloudBlockBlob blob = container.GetBlockBlobReference(attachmentFileName);
var contentType = MimeMapping.GetMimeMapping(attachmentFileName);
Stream target = new MemoryStream();
await blob.DownloadToStreamAsync(target);
target.Position = 0;
//ret.Add(new Attachment(target, attachmentFileName, contentType));
Attachment ret = new Attachment(target, attachmentFileName, contentType);
return ret;
}
//remainder of code left out for brevity
}
I expect the GetAttachment to return a correct Attachment object which would be added to the Mail object and sent successfully.
I believe the answers from #SLaks and #Roman Marusyk are correct, but it looks like you are calling GetAttachment from the constructor, which is not asynchronous. As such you would not be able to use await without using an async method. Try using the result property of GetAttachment as shown below.
Attachments.Add(GetAttachment(attachmentFileName).Result);
A better solution would be to use .GetAwaiter().GetResult(), which as #Roman Marusyk pointed out and shown in this post, if the method fails it will throw the exception directly rather than throwing an AggregateException.
A better solution would be to use the following
Attachments.Add(GetAttachment(attachmentFileName).GetAwaiter().GetResult());
To get the value from a Task<T>, you must make your method async and await the task.
You need to await when call method that returns Task, so instead of this
Attachments.Add(GetAttachment(attachmentFileName));
Use:
Attachments.Add(await GetAttachment(attachmentFileName));
or
Attachments.Add(GetAttachment(attachmentFileName).GetAwaiter().GetResult());

How do I create an IMediaPlaybackSource from a Stream, so as to set a MediaPlayer source without using the obsolete SetStreamSource method?

I'm trying to develop a UWP application that will speak text to the user via a Windows.Media.Playback.MediaPlayer. I have this code that currently works:
private async Task Speak(string text)
{
var audio = await _Speech.SynthesizeTextToStreamAsync(text);
player.SetStreamSource(audio);
player.Play();
}
However, this causes a compiler warning: 'MediaPlayer.SetStreamSource(IRandomAccessStream)' is obsolete: 'Use Source instead of SetStreamSource. For more info, see MSDN.
However, I can't find on MSDN how to convert the SpeechSynthesisStream that SynthesizeTextToStreamAsync generates to a IMediaPlaybackSource that the MediaPlayer wants. The Windows.Media.Core.MediaStreamSource class looks promising, but it wants a IMediaStreamDescriptor which I have no idea how to get...
How do I replicate the functionality of this simple three-liner without using deprecated methods?
you can use the MediaSource.CreateFromStream() method for this purpose.
private async Task Speak(string text)
{
var audio = await _Speech.SynthesizeTextToStreamAsync(text);
player.Source = MediaSource.CreateFromStream(audio);
player.Play();
}
SynthesizeTextToStreamAsync returns a SpeechSynthesisStream object that you can use. This example from the MSDN documentation should lead you in the right direction
SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync("Hello World");
mediaElement.SetSource(stream, stream.ContentType);
https://learn.microsoft.com/en-us/uwp/api/windows.media.speechsynthesis.speechsynthesizer

Portable Class Library HttpClient

For one of my projects I want to develop a library that can be used in different platforms (Desktop, Mobile, Surface, etc). Hence have opted Porable Class Library.
I am developing a class for calling different API calls' using HttpClient. I am stuck with how to call the method, response and work around. This is my code :-
public static async Task<JObject> ExecuteGet(string uri)
{
using (HttpClient client = new HttpClient())
{
// TODO - Send HTTP requests
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Get, uri);
reqMsg.Headers.Add(apiIdTag, apiIdKey);
reqMsg.Headers.Add(apiSecretTag, ApiSecret);
reqMsg.Headers.Add("Content-Type", "text/json");
reqMsg.Headers.Add("Accept", "application/json");
//response = await client.SendAsync(reqMsg);
//return response;
//if (response.IsSuccessStatusCode)
//{
string content = await response.Content.ReadAsStringAsync();
return (JObject.Parse(content));
//}
}
}
// Perform AGENT LOGIN Process
public static bool agentStatus() {
bool loginSuccess = false;
try
{
API_Utility.ExecuteGet("http://api.mintchat.com/agent/autoonline").Wait();
// ACCESS Response, JObject ???
}
catch
{
}
finally
{
}
Like ExecuteGet, I will also create for ExecutePost. My query is from ExecuteGet, if (1) I pass JObject on parsing when IsSuccessStatusCode only, then how can I know about any other errors or messages to inform the user. (2) If I pass response, then how do I assign it here
response = API_Utility.ExecuteGet("http://api.mintchat.com/agent/autoonline").Wait();
that is giving error.
What would be the best approach to handle this situation ? And I got to call multiple API's, so different API will have different result sets.
Also, can you confirm that designing this way and adding PCL reference I will be able to access in multiple projects.
UPDATE :-
As mentioned in below 2 answers I have updated my code. As mentioned in the provided link I am calling the from the other project. This is my code :-
Portable Class Library :-
private static HttpRequestMessage getGetRequest(string url)
{
HttpRequestMessage reqMsg = new HttpRequestMessage(HttpMethod.Get, url);
reqMsg.Headers.Add(apiIdTag, apiIdKey);
reqMsg.Headers.Add(apiSecretTag, ApiSecret);
reqMsg.Headers.Add("Content-Type", "text/json");
reqMsg.Headers.Add("Accept", "application/json");
return reqMsg;
}
// Perform AGENT LOGIN Process
public static async Task<bool> agentStatus() {
bool loginSuccess = false;
HttpClient client = null;
HttpRequestMessage request = null;
try
{
client = new HttpClient();
request = getGetRequest("http://api.mintchat.com/agent/autoonline");
response = await client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
JObject o = JObject.Parse(content);
bool stat = bool.Parse(o["status"].ToString());
///[MainAppDataObject sharedAppDataObject].authLogin.chatStatus = str;
o = null;
}
loginSuccess = true;
}
catch
{
}
finally
{
request = null;
client = null;
response = null;
}
return loginSuccess;
}
From the other WPF project, in a btn click event I am calling this as follows :-
private async void btnSignin_Click(object sender, RoutedEventArgs e)
{
/// Other code goes here
// ..........
agent = doLogin(emailid, encPswd);
if (agent != null)
{
//agent.OnlineStatus = getAgentStatus();
// Compile Error at this line
bool stat = await MintWinLib.Helpers.API_Utility.agentStatus();
...
I get these 4 errors :-
Error 1 Predefined type 'System.Runtime.CompilerServices.IAsyncStateMachine' is not defined or imported D:\...\MiveChat\CSC
Error 2 The type 'System.Threading.Tasks.Task`1<T0>' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Threading.Tasks, Version=1.5.11.0, Culture=neutral, PublicKeyToken=b03f5f7f89d50a3a'. D:\...\Login Form.xaml.cs 97 21
Error 3 Cannot find all types required by the 'async' modifier. Are you targeting the wrong framework version, or missing a reference to an assembly? D:\...\Login Form.xaml.cs 97 33
Error 4 Cannot find all types required by the 'async' modifier. Are you targeting the wrong framework version, or missing a reference to an assembly? D:\...\Login Form.xaml.cs 47 28
I tried adding System.Threading.Tasks from the PCL library only, that gave 7 different errors. Where am I going wrong ? What to do to make this working ?
Please guide me on this. Have spend lots of hours figuring the best to develop a library accessible to desktop app & Win Phone app.
Any help is highly appreciative. Thanks.
If you call an async api when making the http calls, you should also expose that async endpoint to the user, and not block the request using Task.Wait.
Also, when creating a third party library, it is recommanded to use ConfigureAwait(false) to avoid deadlocks when the calling code tries to access the Result property or the Wait method. You should also follow guidelines and mark any async method with Async, so the method should be called ExecuteStatusAsync
public static Task<bool> AgentStatusAsync()
{
bool loginSuccess = false;
try
{
// awaiting the task will unwrap it and return the JObject
var jObject = await API_Utility.ExecuteGet("http://api.mintchat.com/agent/autoonline").ConfigureAwait(false);
}
catch
{
}
}
And inside ExecuteGet:
response = await client.SendAsync(reqMsg).ConfigureAwait(false);
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
In case IsSuccessStatusCode is false, you may throw an exception to the calling code to show something went wrong. To do that, you can use the HttpResponseMessage.EnsureSuccessStatusCode which throws an exception if the status code != 200 OK.
Personally, if ExecuteGet is a public API method i would definitely not expose it as a JObject but a strongly typed type.
If you want the result of the task, you need to use the Result property:
var obj = API_Utility.ExecuteGet("http://api.mintchat.com/agent/autoonline").Result;
However, it's usually not a good idea to wait synchronously for an async method to complete, because it can cause deadlocks. The better approach is to await the method:
var obj = await API_Utility.ExecuteGet("http://api.mintchat.com/agent/autoonline");
Note that you need to make the calling method async as well:
public static async Task<bool> agentStatus()
Sync and async code don't play together very well, so async tends to propagate across the whole code base.

Categories