I'm working on a .NET Core chat room using gRPC (BI Directional streaming).
client, for now, is a console app (.net core 3.1, server is 5.0).
client-server communication was working alright and suddenly, I started having RpcExceptions thrown.
the problem is, the exceptions thrown have StatusCode=OK and no details.
here is a code sample (simplified)
SERVER Stream RPC
if (!await requestStream.MoveNext())
return;
//Open Connection - set user stream to server's output
client.Stream = responseStream;
while(await requestStream.MoveNext())
{
var chatMessage = requestStream.Current;
chatMessage.Msg = $"{requestStream.Current.ClientName}: {requestStream.Current.Msg}";
foreach (var user in loggedClients)
{
user.Stream.WriteAsync(chatMessage);
}
}
Client Code
using (var streaming = client.RPC_BroadcastChatMessage(metaData))
{
//Read stream and display on console
var readTask = Task.Run(async () =>
{
while (await streaming.ResponseStream.MoveNext())
{
Console.WriteLine($"{streaming.ResponseStream.Current.Msg}");
}
});
//Send first message (empty string) - for a welcome message
Console.WriteLine("Sending empty msg");
await streaming.RequestStream.WriteAsync(emptyMsg);
//Wait for messages from user and send to server
Console.WriteLine("Type message and press Enter to send...");
string line = Console.ReadLine();
while (!string.Equals(line, "exit", StringComparison.OrdinalIgnoreCase))
{
var stream = streaming.RequestStream;
var msg = new ChatMessage()
{
ClientId = clientId,
ClientName = $"User{userNumber}",
Msg = line,
RoomId = roomId
};
await stream.WriteAsync(msg);
line = Console.ReadLine();
}
}
Exception
Grpc.Core.RpcException: Status(StatusCode="OK", Detail="")
both client and server run locally.
I've tried googling the exception but haven't found anything yet (not statusCode=OK related, anyways).
does anyone has any leads? I would very much appreciate it.
Related
The following code works with UWP but not WASM. I do have the Uno.Wasm.WebSockets package installed on the WASM head.
_webSocketConnectionCTS = new CancellationTokenSource();
using (_webSocket = new ClientWebSocket())
{
try
{
await _webSocket.ConnectAsync(new Uri(address), _webSocketConnectionCTS.Token);
await Receive(_webSocket, _webSocketConnectionCTS.Token);
}
catch (Exception ex)
{
Console.WriteLine("Failed to connect to WebSocket server: " + ex.Message);
}
}
The exception is thrown on the connect: "Value cannot be null" but neither parameter is null.
The ClientWebSocket class is not available in current builds of Uno, as the underlying class is not providing an implementation for it. You can use the Uno.Wasm.WebSockets package instead, which uses the same APIs as ClientWebSocket.
Here's an example from the documentation:
var ws = new Uno.Wasm.WebSockets.WasmWebSocket();
// Connect to a simple echo WebSocket server
await ws.ConnectAsync(new Uri("wss://echo.websocket.org"), CancellationToken.None);
Console.WriteLine("Program: Connected!");
// Send some data
var data = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello websocket !"));
await ws.SendAsync(data, System.Net.WebSockets.WebSocketMessageType.Binary, false, CancellationToken.None);
Console.WriteLine("Program: Sent!");
// Read the echo back
var buffer = new byte[1024];
var received = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
var receivedString = Encoding.UTF8.GetString(buffer, 0, received.Count);
Console.WriteLine($"Received {received.Count} bytes: {receivedString}");
I have been stuck all day on a stupid problem with registering a user to my application.
Here is my code once the 'Register' button is clicked:
public ICommand RegisterCommand
{
get
{
return new Command(async() =>
{
var isSuccess = await _apiServices.RegisterAsync(Email, Password, ConfirmPassword);
if (isSuccess){
Message = "Registered Successfully";
}
else
{
Message = "Retry later";
}
});
}
}
Api services Register Async method:
public async Task<bool> RegisterAsync(string email, string password, string confirmPassword)
{
try
{
System.Diagnostics.Debug.WriteLine("Email: "+email);
var client = new HttpClient();
var model = new RegisterBindingModel
{
Email = email,
Password = password,
ConfirmPassword = confirmPassword
};
var json = JsonConvert.SerializeObject(model);
HttpContent content = new StringContent(json);
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:63724/api/Account/Register", content);
if (response.IsSuccessStatusCode)
{
return true;
}
return false;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("Error: "+e);
throw;
}
}
}
The Error that I get is:
System.Net.Http.HttpRequestException: An error occurred while sending the request ---> System.Net.WebException: Error: ConnectFailure (Connection refused) ---> System.Net.Sockets.SocketException: Connection refused
at System.Net.Sockets.Socket.Connect (System.Net.EndPoint remoteEP) [0x000b6] in <6c708cf596db438ebfc6b7e012659eee>:0
at System.Net.WebConnection.Connect (System.Net.HttpWebRequest request) [0x0016d] in <6c708cf596db438ebfc6b7e012659eee>:0
--- End of inner exception stack trace ---
To me this is very frustrating as I can register a use using Postman with the exact same localhost address. I am following Houssem Dellai's Xamarin.Forms mvc web api tutorials which can be found here
I had an issue with httpclient during the development of my app. I believe there was an issue with the cross-platform implementation of the httpclient class. iOS didn't know how to handle it.
Instead I implemented a very simple httpclient library called flurl: http://tmenier.github.io/Flurl/
First, you will need to install flurl in all project directories (iOS, Android, and the PCL) then the implementation is very simple.
using Flurl;
using Flurl.Http;
public async Task<User> CreateUserAsync(RegisterUserModel userModel)
{
string url = "your/backend/here";
//resp is a user object received and automatically converted into a c# object through the use of .ReceiveJson<typeofobject>();
var resp = await (url).PostJsonAsync(userModel)
.ReceiveJson<User>();
if (resp.LoginSession != null)
{
//Raise my registered event to let other classes know to proceed
OnUserRegistered(resp);
}
return resp;
}
As you can see it makes httpclient implementation very simple. Hopefully this helps.
I'm following the example of the aspnet-snippets-sample
and OneDriveTest.cs to upload a large file through the Microsoft Graph API into a shared folder with app only credentials
and following exception is thrown:
"Access denied. You do not have permission to perform this action or access this resource."
To initialize a large file upload session I do following:
var uploadSession = await _graph
.Drives[DRIVEID]
.Items[ITEMID]
.ItemWithPath("newFile.docx")
.CreateUploadSession()
.Request().PostAsync();
Both the driveId and itemId are valid.
I'm creating my GraphClient like following:
public MicrosoftGraphHelper()
{
_graph = new GraphServiceClient("https://graph.microsoft.com/beta",
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", AccessToken);
return Task.FromResult(0);
}));
}
The access token is acquired through my app credentials and secret. In Azure, my app has read/write permissions for files through Microsoft Graph.
Also, creating a new folder like
POST https://graph.microsoft.com/beta/drives/DRIVEID/items/ITEMID/children
with JSON
{
"name": "NewFolderTest",
"folder": { }
}
works as well.
here is the Upload Task
internal async Task<string> UploadLargeFile(HttpPostedFileBase file)
{
try
{
using (Stream fileStream = file.InputStream)
{
// Create the upload session. The access token is no longer required as you have session established for the upload.
// POST /v1.0/drive/root:/UploadLargeFile.bmp:/microsoft.graph.createUploadSession
var driveItems = await _graph.Drives[DRIVEID]
.Items[ITEMID]
.Children
.Request()
.GetAsync();
var uploadSession = await _graph.Drives[DRIVEID]
.Items[ITEMID].ItemWithPath("test.docx").CreateUploadSession()
.Request().PostAsync();
var maxChunkSize = 320 * 1024; // 320 KB - Change this to your chunk size. 5MB is the default.
var provider = new ChunkedUploadProvider(uploadSession, _graph, fileStream, maxChunkSize);
// Setup the chunk request necessities
var chunkRequests = provider.GetUploadChunkRequests();
var readBuffer = new byte[maxChunkSize];
var trackedExceptions = new List<Exception>();
DriveItem itemResult = null;
//upload the chunks
foreach (var request in chunkRequests)
{
// Do your updates here: update progress bar, etc.
// ...
// Send chunk request
var result = await provider.GetChunkRequestResponseAsync(request, readBuffer, trackedExceptions);
if (result.UploadSucceeded)
{
itemResult = result.ItemResponse;
}
}
// Check that upload succeeded
if (itemResult == null)
{
// Retry the upload
// ...
}
return "Success";
}
}
catch (ServiceException e)
{
// Debug.WriteLine("We could not upload the file: " + e.Error.Message);
return null;
}
}
As soon as the code hits:
var result = await provider.GetChunkRequestResponseAsync(request, readBuffer, trackedExceptions);
trying to request:
"https://myapp.sharepoint.com:443/sites/dev/_api/v2.0/drives/DRIVEID/items/ITEMID/uploadSession"
is thrown.
What and where do I have to apply required permissions? The Documentation refers to Files.ReadWrite Does Sharepoint need additional permissions to Microsoft Graph API?
Within the shared folder I can see a new created tmp file with 0 bytes.
So far i have this.
public static async Task<OutlookServicesClient> CreateOutlookClientAsync(string capability)
{
try
{
string authority = CommonAuthority;
// Create an AuthenticationContext using this authority.
_authenticationContext = new AuthenticationContext(authority);
//See the Discovery Service Sample (https://github.com/OfficeDev/Office365-Discovery-Service-Sample)
//for an approach that improves performance by storing the discovery service information in a cache.
DiscoveryClient discoveryClient = new DiscoveryClient(
async () => await GetTokenHelperAsync(_authenticationContext, DiscoveryResourceId));
// Get the specified capability ("Contacts").
CapabilityDiscoveryResult result =
await discoveryClient.DiscoverCapabilityAsync(capability);
var client = new OutlookServicesClient(
result.ServiceEndpointUri,
async () =>
await GetTokenHelperAsync(_authenticationContext, result.ServiceResourceId));
return client;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
if (_authenticationContext != null && _authenticationContext.TokenCache != null)
_authenticationContext.TokenCache.Clear();
return null;
}
}
}
private static async Task<string> GetTokenHelperAsync(AuthenticationContext context, string resourceId)
{
string accessToken = null;
AuthenticationResult result = null;
string myId = WebConfigurationManager.AppSettings["ida:ClientID"];
string myKey = WebConfigurationManager.AppSettings["ida:Password"];
ClientCredential client = new ClientCredential(myId,myKey);
result = await context.AcquireTokenAsync(resourceId, client);
//result =context.AcquireToken(resourceId, ClientID,_returnUri);
accessToken = result.AccessToken;
return accessToken;
}
When i get to result one of two things happen if i user AcquireTokenAsync i get an error stating Application with identifier XXXX was not found in directory api.office.com otherwise if i run AcquireToken i get the login modal to pop but an error occurs indicating the request must contain client_secret .
I have no idea how to resolve this issue i suspect it may have something to do with the actual app configuration i have tried both creating my own app in Azure AD and using VS Connected Service, Has Anyone Else ran into a similar issues?
Based on the errors you're seeing, there seems to be an issue with how your app is registered. The first error usually happens when the app is not marked as multi-tenant, and you login to the app with a tenant other than the one where the app is registered.
The second error is odd. Client secret is what you're reading out of the ida:Password element and passing in the ClientCredential object.
I just put a .NET tutorial up yesterday that walks through setting this stuff up. Take a look and see if that helps get you unblocked.
I'm using Web API to stream large files to clients, but I'd like to log if the download was successful or not. That is, if the server sent the entire content of the file.
Is there some way to get a a callback or event when the HttpResponseMessage completes sending data?
Perhaps something like this:
var stream = GetMyStream();
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
// This doesn't exist, but it illustrates what I'm trying to do.
response.OnComplete(context =>
{
if (context.Success)
Log.Info("File downloaded successfully.");
else
Log.Warn("File download was terminated by client.");
});
EDIT: I've now tested this using a real connection (via fiddler).
I inherited StreamContent and added my own OnComplete action which checks for an exception:
public class StreamContentWithCompletion : StreamContent
{
public StreamContentWithCompletion(Stream stream) : base (stream) { }
public StreamContentWithCompletion(Stream stream, Action<Exception> onComplete) : base(stream)
{
this.OnComplete = onComplete;
}
public Action<Exception> OnComplete { get; set; }
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var t = base.SerializeToStreamAsync(stream, context);
t.ContinueWith(x =>
{
if (this.OnComplete != null)
{
// The task will be in a faulted state if something went wrong.
// I observed the following exception when I aborted the fiddler session:
// 'System.Web.HttpException (0x800704CD): The remote host closed the connection.'
if (x.IsFaulted)
this.OnComplete(x.Exception.GetBaseException());
else
this.OnComplete(null);
}
}, TaskContinuationOptions.ExecuteSynchronously);
return t;
}
}
Then I use it like so:
var stream = GetMyStream();
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContentWithCompletion(stream, ex =>
{
if (ex == null)
Log.Info("File downloaded successfully.");
else
Log.Warn("File download was terminated by client.");
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return response;
I am not sure if there is direct signaling that all is ok, but you can use a trick to find out that the connection is exist just before you end it up, and right after you fully send the file.
For example the Response.IsClientConnected is return true if the client is still connected, so you can check something like:
// send the file, make a flush
Response.Flush();
// and now the file is fully sended check if the client is still connected
if(Response.IsClientConnected)
{
// log that all looks ok until the last byte.
}
else
{
// the client is not connected, so maybe have lost some data
}
// and now close the connection.
Response.End();
if the server sent the entire content of the file
Actually there is nothing to do :)
This might sound very simplistic but you will know if an exception is raised - if you care about server delivering and not client cancelling halfway. IsClientConnected is based on ASP.NET HttpResponse not the WebApi.