File uploads via Web Api fail on second upload - asp.net

I am working with Web Api to create a way to upload files via web api. I have found several blog posts on how to accomplish this, and the code is all very similar with a key commonality being the Request.Content.ReadAsMultipartAsync() call. The problem I have is the first upload works fine, but then IIS gets into a faulted state where subsequent uploads fail. The first 32Kb comes in, but then it quits. Debugging shows only a null reference exception that occurs somewhere in the ASP.NET framework.
Here is the ApiController definition I have...
public class FileUploadController : ApiController
{
public void Post()
{
if (Request.Content.IsMimeMultipartContent())
{
var path = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
var task = Request.Content.ReadAsMultipartAsync(provider);
task.ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}
Also, here is the page I am posting from...
<!doctype html>
<head>
<title>File Upload Progress Demo #3</title>
</head>
<body>
<h1>File Upload Progress Demo #3</h1>
<code><input type="file" name="myfile[]"></code><br>
<form action="/api/fileupload" method="post" enctype="multipart/form-data">
<input type="file" name="myfile"><br>
<input type="submit" value="Upload File to Server">
</form>
<div class="progress">
<div class="bar"></div>
<div class="percent">0%</div>
</div>
<div id="status"></div>
</body>
The above code can be downloaded in a default WebApi solution from https://github.com/JohnLivermore/FileUploadTest. Run and navigate to http://localhost:{port}/FormPost.html. The first upload succeeds (uploads to App_Data), but subsequent uploads only upload the first 32 Kb and then fail.

You shouldn't use a void method.
Void and async don't play well together for a number of reasons.
public Task<HttpResponseMessage> Post()
{
var rootUrl = "c:/uploads";
if (Request.Content.IsMimeMultipartContent())
{
var streamProvider = new MultipartFormDataStreamProvider(rootUrl);
var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
//do stuff with files if you wish
return new HttpResponseMessage(HttpStatusCode.OK);
});
return task;
}
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}

The main issue of your code is exiting your method without waiting for all asynchronous tasks to finish. You can use .Wait() for that purpose:
public class FileUploadController : ApiController
{
public void Post()
{
if (Request.Content.IsMimeMultipartContent())
{
var path = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
var readAsMultipartTask = Request.Content.ReadAsMultipartAsync(provider);
var continueWithTask = readAsMultipartTask.ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
continueWithTask.Wait();
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}
This will make the upload work properly, but you should be aware that your method is breaking HTTP protocol because you are not sending back any response in case of proper upload. Your method should be more like this:
public class FileUploadController : ApiController
{
public async Task<HttpResponseMessage> Post()
{
if (Request.Content.IsMimeMultipartContent())
{
var path = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
await Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
//Here you should return a meaningful response
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}
(Thanks to use of async/await the synchronization of asynchronous tasks is handled by framework)

Related

ASP.NET CORE [Razor Pages] real time webAPP with SignalR and Typescript

i am working with SignalR and real time web development atm.
I am little confused because of JavaScript and TypeScript.
There are two scenarios i am working on:
First scenario with TypeScript:
./clients/index.html looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script defer src="/main.509cbdfefce2f16684e8.js"></script></head>
<body>
<h1>Watching connection list</h1>
<p>People watching this page: <span id="viewCounter">0</span></p>
<h1>Passing arguments to the hub with a button</h1>
<p>
<input id="inputFirstName" />
<input id="inputLastName" />
<button id="btnGetFullName">Get Full Name</button>
</p>
</body>
</html>
./clients/index.ts looks like this:
import * as signalR from "#microsoft/signalr";
//COUNTER
var counter = document.getElementById("viewCounter");
//BUTTON - PASSING VALUES FROM WEBSITE TO HUB CONTROLLER
var button = document.getElementById("btnGetFullName");
// create connection
let connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl("/hub/view")
.build();
// client event
connection.on("viewCountUpdate", (value: number) => {
counter.innerText = value.toString();
});
//BUTTON - PASSING VALUES FROM WEBSITE TO HUB CONTROLLER
button.addEventListener("click", function (evt) {
var firstName = (document.getElementById("inputFirstName") as HTMLInputElement).value;
var lastName = (document.getElementById("inputLastName") as HTMLInputElement).value;
connection.invoke("getFullName", firstName, lastName).then((name: string) => { alert(name); });
});
// notify server we're watching
function notify() {
connection.send ("notifyWatching");
}
// start the connection
function startSuccess() {
console.log("Connected.");
//COUNTER
notify();
}
function startFail() {
console.log("Connection failed.");
}
connection.start().then(startSuccess, startFail);
and in Program.cs i am using UseStaticFiles and UseDefaultFiles and it looks like this:
using RealTimeApp.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.UseEndpoints(configure => {
configure.MapHub<DevHub>("/hub/view");
});
app.Run();
Now the thing is:
I want to combine Razor Pages with SignalR to make some functions around the project in real time and some not.
Is this possible to combine razor and make calls to my HUB from in example ./Pages/Account/Details (and vice versa)
[from hub to razor page]
[from razor page to hub]
If yes, please leave me instruction how to..
Second scenario is:
working with JS, but i was trying to go with it, but it doesnt work for me.
i want to try typescript because it fits me more.
Best Regards,
####UPDATE
I see my index.ts is generating file "main.~~~~~~.js
Is this possible to use some references like with section?
#####UPDATE
I can invoke my method from Razor Page with button and it looks like this:
Razor.cshtml:
<form method="post" asp-page-handler="Test">
<button>OnClickTest</button>
</form>
Razor.cshtml.cs
public class SiteModel : PageModel
{
private readonly IHubContext<DevHub> _hub;
public SiteModel(IHubContext<DevHub> hub)
{
_hub = hub;
}
public void OnGet()
{
}
public async Task OnPostTest()
{
await _hub.Clients.All.SendAsync("viewCountUpdate", 66);
}
}
If you have any ideas how to go along with it, please leave a comment or even a solution.
I'm not sure if this is what you need but you can define a render section inside _Layout.cshtml
<body>
...
...
...
#RenderSection("Scripts", required: false)
</body>
Then inside your razor page add:
<div id="counter"></div>
#section Scripts {
<script defer src="/main.509cbdfefce2f16684e8.js"></script>
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/layout?view=aspnetcore-6.0#sections
Edit:
In your Hub you can define a method that pushes the viewCountUpdate event to the clients.
public class DevHub : Hub
{
public async Task CountUpdate()
=> await Clients.All.SendAsync("viewCountUpdate", 66);
}
Then call it from typescript using
await connection.invoke("CountUpdate");
Now, few hours later i am comming here to tell you how i find out of my problem.
I decide to leave TS and i decide to go along with JS.
This is simple explanation:
My Hub class:
public class MainHub : Hub<IMainHub>
{
public static int ViewCount { get; set; } = 0;
//FROM HUB TO CLIENT
public async override Task OnConnectedAsync()
{
ViewCount++;
await Clients.All.ViewCountUpdate(ViewCount);
await base.OnConnectedAsync();
}
public async override Task OnDisconnectedAsync(Exception exception)
{
ViewCount--;
await Clients.All.ViewCountUpdate(ViewCount);
await base.OnDisconnectedAsync(exception);
}
My Hub Interface:
public interface IMainHub
{
Task ViewCountUpdate(int viewCount);
}
Razor Index.cshtml:
<h1>Watching connection list</h1>
<p>People watching this page: <span id="viewCounter">0</span></p>
#section Scripts{
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/index.js"></script>
}
My index.js
const connection = new signalR.HubConnectionBuilder()
.withUrl("/testhub")
.configureLogging(signalR.LogLevel.Trace)
.withAutomaticReconnect([0, 10, 30, 60, 90, 150])
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
//FROM HUB TO CLIENT [EVENTS]
connection.on("ViewCountUpdate", (viewCount) => {
counter.innerHTML = viewCount;
});
// Start the connection.
start();
And now i can add reference to js scripts in everysingle page and it works.

Sending Signalr message from hub to signal has no effect

I have a asp.net net core application, where I want to send a message from the hub to the client (the converse is already working for me).
So I have this controller action, where I am injecting my Hub into:
public IActionResult to()
{
_hub.Clients.All.SendAsync("ReceiveMessage", "user", "message");
return View("~/Views/msg/ClientReceiver.cshtml");
}
So, it's basically just sending a message, and returning a view.
here's the view:
<!DOCTYPE html>
<html>
<body>
<button onclick="receive()">Receive msg from server</button>
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/js/ClientReceiver.js"></script>
</body>
</html>
and the ``ClientReceiver.js` file that is being referenced looks like so:
function receive() {
const connection = new signalR.HubConnectionBuilder()
.withUrl("/NotificationHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on("ReceiveMessage", (user, message) => {
alert(message);
});
}
When looking at the documentation (going to the heading "Call client methods from the hub"), then it seems like this should work.
This does'nt work though, no Alert message appears when it should.
The console in my browser indicates that the correct connection has been established though:
[2021-06-24T23:11:48.359Z] Information: Normalizing '/NotificationHub' to 'https://localhost:44385/NotificationHub'.
When you enter the to method to return to your ClientReceiver.cshtml page, your ClientReceiver is not connected yet, so the page cannot receive your message, you should rewrite a method, and visit the method every time you click the button and send message.
You can try to change your ClientReceiver.js like below:
function receive() {
$.ajax({
url: '/home/send',
type: 'get',
});
}
var connection = new signalR.HubConnectionBuilder()
.withUrl("/NotificationHub")
.build();
connection.on("ReceiveMessage", function (user,message) {
alert(message);
});
connection.start();
Controller:
private readonly IHubContext<NotificationHub> _hub;
public HomeController( IHubContext<NotificationHub> hub)
{
_hub = hub;
}
public IActionResult To()
{
return View("~/Views/msg/ClientReceiver.cshtml");
}
public async Task SendAsync()
{
await _hub.Clients.All.SendAsync("ReceiveMessage", "user", "message");
}
Test result:

How to make a Blazor page update the content of one html tag with incoming data from gRPC service

So i'm testing with Blazor and gRPC and my dificulty at the moment is on how to pass the content of a variable that is on a class, specifically the gRPC GreeterService Class to the Blazor page when new information arrives. Notice that my aplication is a client and a server, and i make an initial comunication for the server and then the server starts to send to the client data(numbers) in unary mode, every time it has new data to send. I have all this working, but now i'm left it that final implementation.
This is my Blazor page
#page "/greeter"
#inject GrpcService1.GreeterService GreeterService1
#using BlazorApp1.Data
<h1>Grpc Connection</h1>
<input type="text" #bind="#myID" />
<button #onclick="#SayHello">SayHello</button>
<p>#Greetmsg</p>
<p></p>
#code {
string Name;
string Greetmsg;
async Task SayHello()
{
this.Greetmsg = await this.GreeterService1.SayHello(this.myID);
}
}
The method that later receives the communication from the server if the hello is accepted there is something like this:
public override async Task<RequestResponse> GiveNumbers(BalconyFullUpdate request, ServerCallContext context)
{
RequestResponse resp = new RequestResponse { RequestAccepted = false };
if (request.Token == publicAuthToken)
{
number = request.Number;
resp = true;
}
return await Task.FromResult(resp);
}
Every time that a new number arrives i want to show it in the UI.
Another way i could do this was, within a while condition, i could do a call to the server requesting a new number just like the SayHello request, that simply awaits for a server response, that only will come when he has a new number to send. When it comes the UI is updated. I'm just reluctant to do it this way because i'm afraid that for some reason the client request is forgotten and the client just sit's there waiting for a response that will never come. I know that i could implement a timeout on the client side to handle that, and on the server maybe i could pause the response, with a thread pause or something like that, and when the method that generates the new number has a new number, it could unpause the response to the client(no clue on how to do that). This last solution looks to me much more difficult to do than the first one.
What are your thoughts about it? And solutions..
##################### UPDATE ##########################
Now i'm trying to use a singleton, grab its instance in the Blazor page, and subcribe to a inner event of his.
This is the singleton:
public class ThreadSafeSingletonString
{
private static ThreadSafeSingletonString _instance;
private static readonly object _padlock = new object();
private ThreadSafeSingletonString()
{
}
public static ThreadSafeSingletonString Instance
{
get
{
if (_instance == null)
{
lock(_padlock)
{
if (_instance == null)
{
_instance = new ThreadSafeSingletonString();
_instance.number="";
}
}
}
return _instance;
}
set
{
_instance.number= value.number;
_instance.NotifyDataChanged();
}
}
public int number{ get; set; }
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
And in Blazor page in code section i have:
protected override void OnInitialized()
{
threadSafeSingleton.OnChange += updateNumber();
}
public System.Action updateNumber()
{
this.fromrefresh = threadSafeSingleton.number + " que vem.";
Console.WriteLine("Passou pelo UpdateNumber");
this.StateHasChanged();
return StateHasChanged;
}
Unfortunatly the updatenumber function never gets executed...
To force a refresh of the ui you can call the StateHasChanged() method on your component:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-3.1
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
Hope this helps
Simple Request
After fully understanding that your problem is just to Update the Page not to get unsyncronous messages from the server with a bi directional connection. So jou just have to change your page like (please not there is no need to change the files generated by gRPC, I called it Number.proto so my service is named NumberService):
async Task SayHello()
{
//Request via gRPC
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
var client = new this.NumberService.NumberServiceClient(channel);
var request = new Number{
identification = "ABC"
};
var result = await client.SendNumber(request).RequestAccepted;
await channel.ShutdownAsync();
//Update page
this.Greetmsg = result;
InvokeAsync(StateHasChanged);//Required to refresh page
}
Bi Directional
For making a continious bi directional connection you need to change the proto file to use streams like:
service ChatService {
rpc chat(stream ChatMessage) returns (stream ChatMessageFromServer);
}
This Chant sample is from the https://github.com/meteatamel/grpc-samples-dotnet
The main challenge on this is do divide the task waiting for the gRPC server from the client. I found out that BackgroundService is good for this. So create a Service inherited from BackgroundService where place the while loop waiting for the server in the ExecuteAsyncmethod. Also define a Action callback to update the page (alternative you can use an event)
public class MyChatService : BackgroundService
{
Random _random = new Random();
public Action<int> Callback { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Replace next lines with the code request and wait for server...
using (_call = _chatService.chat())
{
// Read messages from the response stream
while (await _call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = _call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
if (Callback != null) Callback(displayMessage);
}
// Format and display the message
}
}
}
}
On the page init and the BackgroundService and set the callback:
#page "/greeter"
#using System.Threading
<p>Current Number: #currentNumber</p>
#code {
int currentNumber = 0;
MyChatService myChatService;
protected override async Task OnInitializedAsync()
{
myChatService = new MyChatService();
myChatService.Callback = i =>
{
currentNumber = i;
InvokeAsync(StateHasChanged);
};
await myChatService.StartAsync(new CancellationToken());
}
}
More information on BackgroundService in .net core can be found here: https://gunnarpeipman.com/dotnet-core-worker-service/

Trying to upload a file from controller using HttpWebRequest method to API Action that has an argument of [FromForm] IFormFile file receives null

I am trying to upload a file from Controller to API in asp.net core mvc. The method of the API which should recive the file is given below:
[Route("api/[controller]")]
public class ReservationController : Controller
{
...
[HttpPost]
public void UploadFile ([FromForm] IFormFile file)
{
....
}
}
Now on my other project there is a View that contains file upload control:
<form method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<div class="text-center panel-body">
<button type="submit" class="btn btn-sm btn-primary">Add</button>
</div>
</form>
Now on the submit button the controller action that gets called is :
[HttpPost]
public void AddFile(IFormFile file)
{
HttpWebRequest apiRequest = WebRequest.Create("http://localhost:8888/api/Reservation") as HttpWebRequest;
apiRequest.Method = "Post";
apiRequest.ContentType = "application/octet-stream";
string apiResponse = "";
using (HttpWebResponse response = apiRequest.GetResponse() as HttpWebResponse)
{
StreamReader reader = new StreamReader(response.GetResponseStream());
apiResponse = reader.ReadToEnd();
}
}
The IFormFile file gets the file. Upto that point it is working fine.
Problem
Now I am creating object of HttpWebRequest to upload my file using API method.
The API method gets called but i revived null value i.e. on the below method the 'file' parameter gets null?
[HttpPost]
public void UploadFile ([FromForm] IFormFile file)
{
}
Why it is so? please help?
NOTE
If i just change the view to include action attribute on the form to the URL of my API then it works:
<form method="post" enctype="multipart/form-data" action="http://localhost:8888/api/Reservation/UploadFile">
<input type="file" name="file" />
<div class="text-center panel-body">
<button type="submit" class="btn btn-sm btn-primary">Add</button>
</div>
</form>
Since i want to do it from controller and get back the response therefore I want to make API call from Controller.
IFormFile specifically only works with multipart/form-data encoding, which is why it arrives fine in your first action. However, in the request you're making there, the biggest issue is that you aren't actually even sending the file in the request in the first place. Then, you would also need to use multipart/form-data rather than application/octet-stream.
Handling this via WebRequest is actually fairly complex, and HttpClient is the preferred way to make requests anyways. With HttpClient:
string apiResponse;
using (var httpClient = new HttpClient())
{
var form = new MultipartFormDataContent();
using (var fileStream = file.OpenReadStream())
{
form.Add(new StreamContent(fileStream), "file", file.Filename);
using (var response = await httpClient.PostAsync(url, form))
{
response.EnsureSuccessStatusCode();
apiResponse = await response.Content.ReadAsStringAsync();
}
}
}
I wanted to make the example intentionally simple, but you actually should not use HttpClient like this. It should be treated as a singleton, for the most part, which is thankfully fairly easy via IHttpClientFactory. First, you're going to need some sort of wrapper class, which is a good idea anyways when you're working with an API:
public class ReservationService
{
private readonly HttpClient _client;
public ReservationService(HttpClient client)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task<string> AddFileAsync(IFormFile file)
{
var form = new MultipartFormDataContent();
using (var fileStream = file.OpenReadStream())
{
form.Add(new StreamContent(fileStream), "file", file.Filename);
using (var response = await httpClient.PostAsync("", form))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
}
Then, in ConfigureServices in Startup.cs:
services.AddScoped<ReservationService>();
services.AddHttpClient<ReservationService>(c =>
{
c.BaseAddress = new Uri("http://localhost:8888/api/Reservation");
});
Finally, in your controller:
public class FooController : Controller
{
private readonly ReservationService _service;
public FooController(ReservationService service)
{
_service = service ?? throw new ArgumentNullException(nameof(service));
}
...
[HttpPost]
public async Task<IActionResult> AddFile(IFormFile file)
{
var apiResponse = await _service.AddFileAsync(file);
// whatever else you want to do
}
}
This is how I handle file uploads in .net core using HttpClient
public async Task<string> UploadFile(IFormFile file)
{
Client = new HttpClient() { BaseAddress = new Uri("http://localhost:8888/api/") };
HttpContent fileStreamContent = new StreamContent(file.OpenReadStream());
var multiContent = new MultipartFormDataContent
{
{ fileStreamContent, "file", "file.FileName" }
};
var response = await Client.PostAsync("Reservation", multiContent);
var data = await response.Content.ReadAsStringAsync();;
return data;
}

Async calls in WP7

I have been experimenting with WP7 apps today and have hit a bit of a wall.
I like to have seperation between the UI and the main app code but Ive hit a wall.
I have succesfully implemented a webclient request and gotten a result, but because the call is async I dont know how to pass this backup to the UI level. I cannot seem to hack in a wait for response to complete or anything.
I must be doing something wrong.
(this is the xbox360Voice library that I have for download on my website: http://www.jamesstuddart.co.uk/Projects/ASP.Net/Xbox_Feeds/ which I am porting to WP7 as a test)
here is the backend code snippet:
internal const string BaseUrlFormat = "http://www.360voice.com/api/gamertag-profile.asp?tag={0}";
internal static string ResponseXml { get; set; }
internal static WebClient Client = new WebClient();
public static XboxGamer? GetGamer(string gamerTag)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null);
return SerializeResponse(response);
}
internal static XboxGamer? SerializeResponse(string response)
{
if (string.IsNullOrEmpty(response))
{
return null;
}
var tempGamer = new XboxGamer();
var gamer = (XboxGamer)SerializationMethods.Deserialize(tempGamer, response);
return gamer;
}
internal static string GetResponse(string url, string userName, string password)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
Client.Credentials = new NetworkCredential(userName, password);
}
try
{
Client.DownloadStringCompleted += ClientDownloadStringCompleted;
Client.DownloadStringAsync(new Uri(url));
return ResponseXml;
}
catch (Exception ex)
{
return null;
}
}
internal static void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
ResponseXml = e.Result;
}
}
and this is the front end code:
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
var xboxGamer = xboxManager.GetGamer();
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
Now I understand I need to place something in ClientDownloadStringCompleted but I am unsure what.
The problem you have is that as soon as an asynchronous operation is introduced in to the code path the entire code path needs to become asynchronous.
Because GetResponse calls DownloadStringAsync it must become asynchronous, it can't return a string, it can only do that on a callback
Because GetGamer calls GetResponse which is now asynchronous it can't return a XboxGamer, it can only do that on a callback
Because GetGamerDetails calls GetGamer which is now asynchronous it can't continue with its code following the call, it can only do that after it has received a call back from GetGamer.
Because GetGamerDetails is now asynchronous anything call it must also acknowledge this behaviour.
.... this continues all the way up to the top of the chain where a user event will have occured.
Here is some air code that knocks some asynchronicity in to the code.
public static void GetGamer(string gamerTag, Action<XboxGamer?> completed)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null, (response) =>
{
completed(SerializeResponse(response));
});
}
internal static string GetResponse(string url, string userName, string password, Action<string> completed)
{
WebClient client = new WebClient();
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
client.Credentials = new NetworkCredential(userName, password);
}
try
{
client.DownloadStringCompleted += (s, args) =>
{
// Messy error handling needed here, out of scope
completed(args.Result);
};
client.DownloadStringAsync(new Uri(url));
}
catch
{
completed(null);
}
}
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
xboxManager.GetGamer( (xboxGamer) =>
{
// Need to move to the main UI thread.
Dispatcher.BeginInvoke(new Action<XboxGamer?>(DisplayGamerDetails), xboxGamer);
});
}
void DisplayGamerDetails(XboxGamer? xboxGamer)
{
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
As you can see async programming can get realy messy.
You generally have 2 options. Either you expose your backend code as an async API as well, or you need to wait for the call to complete in GetResponse.
Doing it the async way would mean starting the process one place, then return, and have the UI update when data is available. This is generally the preferred way, since calling a blocking method on the UI thread will make your app seem unresponsive as long as the method is running.
I think the "Silverlight Way" would be to use databinding. Your XboxGamer object should implement the INotifyPropertyChanged interface. When you call GetGamer() it returns immediately with an "empty" XboxGamer object (maybe with GamerTag=="Loading..." or something). In your ClientDownloadStringCompleted handler you should deserialize the returned XML and then fire the INotifyPropertyChanged.PropertyChanged event.
If you look at the "Windows Phone Databound Application" project template in the SDK, the ItemViewModel class is implemented this way.
Here is how you can expose asynchronous features to any type on WP7.

Resources