How to download Blazor server page html result - blazor-server-side

Under Asp.Net MVC I used to build the body of my mailing messages on a MVC view which the system downloaded through System.Net.HttpWebRequest.
Now that I am migrating to Blazor server, I can browse the page but if I try to download it to fill the body of the message I always get next body:
Loading... An unhandled exception has occurred. See browser dev
tools for details. Reload X
I tried both through a Blazor page and through a Cshtml razor page. My browser can see both successfully but I always get the same exception.
This is the code I use to download the page:
HttpResponseMessage response = await http.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var contentType = response.Content?.Headers?.ContentType?.MediaType;
if (contentType == "application/json" | contentType == "text/html")
{
string responseText = await response.Content?.ReadAsStringAsync() ?? "";
if (typeof(T) == typeof(string))
retval.Value = (T)(object)responseText;
else
retval.Value = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText);
}
else
{
byte[] result = await response.Content.ReadAsByteArrayAsync();
retval.Value = (T)Convert.ChangeType(result, typeof(T));
}
}
}

I finally discovered the problem was on Program.cs from my .Net7 Server side Blazor app.
I was registering HttpClient as follows:
var http = new HttpClient();
builder.Services.AddScoped(sp => http);
This was Ok to access API data, but for some reason if you try to download a Html page source it throws a System.ObjectDisposedException: Cannot access a disposed object.
The right way to register the service to avoid this exception is:
builder.Services.AddHttpClient();
The problem is I no longer have a http variable in Program.cs, which I used to preload data from Api before the index page was ready.

Need a bit more detail:
What line threw the exception?
What was the exception?
What was value of string responseText or byte[] result?
I suspect either the DeserializeObject or Convert.ChangeType call failed. You should debug this; the answer will probably become apparent as you step through the code.

Related

Unable to send modified HttpResponse back to Response.Body

I am creating a proxy using middleware in ASP.NET Core 2.1 that makes 3rd party API (OData endpoint) call to
Get data
Do some changes
Send response to Response.Body
I took a reference from here
Below is the code snippet that works fine as whatever response I am getting from API, I am sending it further
using (var responseMessage = await _httpClient.SendAsync(targetRequestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
{
context.Response.StatusCode = (int)responseMessage.StatusCode;
CopyFromTargetResponseHeaders(context, responseMessage);
await responseMessage.Content.CopyToAsync(context.Response.Body);
}
However, If I modify the response here, for example, like this, it does not work and it shows blank page without any error.
using (var responseMessage = await _httpClient.SendAsync(targetRequestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
{
context.Response.StatusCode = (int)responseMessage.StatusCode;
CopyFromTargetResponseHeaders(context, responseMessage);
var output = new StringContent("some sample string or may be JSON", Encoding.UTF8, "application/json");
await output.CopyToAsync(context.Response.Body);
}
It looks like we are not allowed to make any change in the response received from API call. Can anyone please tell me how can send modified content back to Response.Body?
I am able to solve the problem by updating "Content-Length" response header before rendering modified response to context.Response.Body something like this:
context.Response.Headers.Remove("Content-Length");
context.Response.Headers.Add("Content-Length", modifiedResponseStream.Length.ToString());
You might run into a System.InvalidOperationException: Response Content-Length mismatch: too few bytes written or similar exception (which you should see in the Output window). So do not use the Content-Length and maybe Content-Type headers from the response, because they probably don't match with the Content-Length and Content-Type of your modified content, e.g.:
private void CopyFromTargetResponseHeaders(HttpContext context, HttpResponseMessage responseMessage)
{
...
foreach (var header in responseMessage.Content.Headers)
{
// do not use the content headers from the response because the content will be modified
// context.Response.Headers[header.Key] = header.Value.ToArray();
}
...
}

How to read the html out of spa website with asp.net.core

As there are no API for this I need to get the HTML of the following website with WebClient response method.
HttpClient client = new HttpClient();
try
{
HttpResponseMessage response = await client.GetAsync("https://www.datawrapper.de/_/UPFwh/");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
}
catch(HttpRequestException e)
{
}
client.Dispose(true);
The problem is, when I do that I get only the source code of normal javascripts of this single page application and not the real HTML.
Anybody know how to grab the real html with
I profiled the traffic a bit and it looks like the response from that URL you're using is indeed mainly a script, which eventually will load the rest of the website.
Looking through the details the HTML part of the main data seems to be available under a different URL:
https://datawrapper.dwcdn.net/UPFwh/34/
Consider using that instead. Hope this helps!

App stops working when calling Http.PostAsync when returning a string

I have a Blazor app that's calling a WebAPI method that returns a string, however when it runs, it just stops. The app remains open but nothing appears to happen.
When i put a breakpoint in the controller it does run.
var ms = new System.IO.MemoryStream();
await file.Data.CopyToAsync(ms);
status = $"Finished loading {file.Size} bytes from {file.Name}";
var content = new MultipartFormDataContent {
{ new ByteArrayContent(ms.GetBuffer()), "\"upload\"", file.Name }
};
string featureImage = await Http.PostAsync("api/Blog/ReturnString", content).Result.Content.ReadAsStringAsync();
Above is the code that calls the WebAPI method. Below is the Controller that returns a very simple string.
[Authorize]
[HttpPost("[action]")]
public async Task<string> ReturnString()
{
return "123456";
}
Apart from the comment by #enet, there're likely two more bugs:
There's a risk of dead lock in the way you get Result by using .Result.Content. You need change the code to await ...
Also, if you're using Blazor Server side, be aware that invoking Http in Blazor ServerSide App is different from the Blazor WebAssembly App. You need create your own HttpClient or inject one and then provide base uri for it.
Hope the below helps:
(if you're using Blazor ServerSide) register HttpClient related services in your startup:
services.AddHttpClient(); // only if you're using Blazor ServerSide
And within your Blazor component/page:
#using System.Net.Http
#inject IHttpClientFactory HttpClientFactory #*if you're using Blazor ServerSide*#
#inject NavigationManager navMgr #*if you're using Blazor ServerSide*#
#code{
var Http = HttpClientFactory.CreateClient(); //if you're using Blazor ServerSide
Http.BaseAddress = new Uri(navMgr.BaseUri); //if you're using Blazor ServerSide
// ... add credentials if you're using Blazor ServerSide
//... your code
// don't block it by access the`.Result` property, await it!
await Http.PostAsync("api/Blog/ReturnString", content).Result.Content.ReadAsStringAsync();
var resp= await Http.PostAsync("api/Blog/ReturnString", content);
var featureImage = await resp.Content.ReadAsStringAsync();
Finally, if above code doesn't address the problem, could you please Press F12 within the browser and show us the error message ?

Exception thrown when WebAuthenticationBroker receives an OAuth2 callback

The WebAuthenticationBroker doesn't seem to be able to handle navigation to my ms-app://. Just throws this ugly error as you will see below.
Steps
Call AuthenticateAsync(), including callback uri obtained at runtime: WebAuthenticationBroker.GetCurrentApplicationCallbackUri()
Go through authorize process, hit Allow.
Instead of returning, the broker shows the page Can't connect to service. We can't connect to the service you need right now. Unable to do anything, so I hit the Back button visible.
Debugger breaks on catch: "The specified protocol is unknown. (Exception from HRESULT: 0x800C000D)"
The callback for WebAuthenticationBroker.AuthenticateAsync() is received (according to Fiddler4 & the Event Viewer) but it throws the aforementioned exception as if it doesn't know how to interpret the ms-app:// protocol.
All examples imply my code should work but I think there's something less obvious causing an issue.
Code
private static string authorizeString =
"https://api.imgur.com/oauth2/authorize?client_id=---------&response_type=token";
private Uri startUri = new Uri(authorizeString);
public async void RequestToken() {
try {
var war = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.UseTitle
, startUri);
// Imgur knows my redirect URI, so I am not passing it through here
if (war.ResponseStatus == WebAuthenticationStatus.Success) {
var token = war.ResponseData;
}
} catch (Exception e) { throw e; }
}
Event Viewer log excerpts (chronological order)
For information on how I obtained this, read the following MSDN: Web authentication problems (Windows). Unfortunately this is the only search result when querying authhost.exe navigation error.
Information: AuthHost redirected to URL: <ms-app://s-1-15-2-504558873-2277781482-774653033-676865894-877042302-1411577334-1137525427/#access_token=------&expires_in=3600&token_type=bearer&refresh_token=------&account_username=------> from URL: <https://api.imgur.com/oauth2/authorize?client_id=------&response_type=token> with HttpStatusCode: 302.
Error: AuthHost encountered a navigation error at URL: <https://api.imgur.com/oauth2/authorize?client_id=------&response_type=token> with StatusCode: 0x800C000D.
Information: AuthHost encountered Meta Tag: mswebdialog-title with content: <Can't connect to the service>.
Thanks for reading, Stack. Don't fail me now!
Afaik, you need to pass the end URL to AuthenticateAsync even if you assume that the remote service knows it.
The way WebAuthenticationBroker works is like the following: you specify an "endpoint" URL and when it encounters a link that starts with this URL, it will consider the authentication process complete and doesn't even try navigating to this URL anymore.
So if you specify "foo://bar" as callback URI, navigating to "foo://bar" will finish the authentication, as will "foo://barbaz", but not "foo://baz".
Resolved! #ma_il helped me understand how the broker actually evaluates the redirect callback and it led me back to square one where I realized I assumed WebAuthenticationOptions.UseTitle was the proper usage. Not so. Up against Imgur's API using a token, it requires WebAuthenticationOptions.None and it worked immediately.
As an example to future answer-seekers, here's my code.
private const string clientId = "---------";
private static Uri endUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
private static string authorizeString = "https://api.imgur.com/oauth2/authorize?"
+ "client_id="
+ clientId
+ "&response_type=token"
+ "&state=somestateyouwant"
+ "&redirect_uri="
+ endUri;
private Uri startUri = new Uri(authorizeString);
public async void RequestToken() {
try {
WebAuthenticationResult webAuthenticationResult =
await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None
, startUri
, endUri);
if (webAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success) {
string token = webAuthenticationResult.ResponseData;
// now you have the token
}
} catch { throw; }
}

HttpWebResponse displays siteminder login even though URLs are configured to be by passed in Siteminder

I am stumped on this problem and have come humbled to the experts on advice for my problem.
I have an ASP.NET MVC app that is Siteminder enabled. In addition, this app has a section of URLS that are web services which provide data to another application. Those URLS have been configured for "bypass" Siteminder authentication in the Siteminder setup. I've double checked the bypass to make sure the Siteminder configuration is correct. I can enter those URLs in a browser and the JSON data is displayed "without" Siteminder authentication. However....
The problem is when I use HttpWebResponse, Stream and StreamReader to retrieve the JSON data when siteminder is enabled, I get the Siteminder "login page HTML" as the string when StreamReader.ReadToEnd() is evoked instead of the JSON formatted data???
This is baffling because I another developer here can access the same web service and get the "correct" JSON formatted data in a PYTHON app. Also, I put it in a regular ASP.NET app so it's not an MVC issue. I get the same result.
Is there another class or library I should use? Is there a configuration setting I need to pass to the web service call? Any help would be greatly appreciated.
Here is the code of one of the web service calls.
public static string GetData()
{
string host = (string)System.Configuration.ConfigurationManager.AppSettings["WEBSERVICE_GET"];
string URL = host + "Api/GetData";
var end = string.Empty;
try
{
HttpWebRequest request = WebRequest.Create(URL) as HttpWebRequest;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
using (Stream responseStream = response.GetResponseStream())
{
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
{
throw new HttpException((int)response.StatusCode, response.StatusDescription);
}
using (StreamReader reader = new StreamReader(responseStream))
{
end = reader.ReadToEnd();
reader.Close();
}
responseStream.Close();
response.Close();
}
}
catch (Exception ex)
{
EmailNotification.SendErrorEmail("Could not get Data from WEBSERVICE + ex);
}
return end;
}