I'm working with the Nest API, which supports REST Streaming via Firebase. I have REST working, however I cannot get it to stream correctly. This is very important for my app, and REST just isn't effective for what I want to do.
I'm using Hammock for the requests, and here's the code:
public class NestAPI
{
private RestClient client { get; set; }
public NestAPI()
{
this.client = new RestClient();
this.client.Authority = "https://developer-api.nest.com/";
this.client.HasElevatedPermissions = true;
}
public void BeginStreaming()
{
RestRequest request = new RestRequest();
request.AddParameter("auth", App.accessToken);
request.RetryPolicy = new RetryPolicy() { RetryCount = 3 };
//Enables streaming
//request.AddHeader("Accept", "text/event-stream");
//request.StreamOptions = new StreamOptions() { Duration = new TimeSpan(96, 0, 0), ResultsPerCallback = 1 };
this.client.BeginRequest<object>(request, new RestCallback<object>(this.StreamCompletedEvent));
}
private void StreamCompletedEvent(RestRequest request, RestResponse<object> response, object userState)
{
//TO DO: check for errors first
string json = response.Content;
}
public void EndStreaming()
{
this.client.CancelStreaming();
}
}
This code works and does return JSON, however I can't seem to enable streaming. When I uncomment the lines below "Enables streaming", the callback event never fires. It's important to note that authentication is done using the uri parameter, "auth".
Unfortunately, there doesn't seem to be Firebase libraries available, and REST is my only option. I want to know when JSON properties change and want to set different values while streaming.
I'm not familiar with Hammock, but can you make sure that it's set to follow redirects? The streaming endpoint typically issues HTTP 307 to get inform the client of the correct server to connect to.
I've never used Hammock, but looking through source code (briefly) it appears you need to set it up as a streaming request with StreamOptions. Twitter has some open source that uses this here https://github.com/camertron/twitter-windows/blob/master/Source/Twitter/Classes/API/Streaming/UserStream.cs.
The way you have Hammock configured here it's waiting for an entire request to complete before calling your callback. This will (almost) never happen with a streaming request as the server keeps the connection open to push new results.
Related
I am building a simple restaurant management system in WPF. I have my backend in Laravel. I needed to setup a web socket to get real-time notifications on WPF app when a customer places an order from mobile app. I configured the web socket in Laravel using beyondcode/laravel-websockets. For ease, I tested the web socket on client side using laravel-echo with Vue. Everything works well there but I couldn't find any solution to replicate laravel-echo in C#.
Here is the code I am using in Vue.js with laravel-echo:
import Echo from "laravel-echo";
import Pusher from "pusher-js";
window.Pusher = Pusher;
const token = "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'laravel_rdb',
wsHost: '127.0.0.1',
authEndpoint: 'http://localhost/may-app/public/broadcasting/auth',
encrypted: false,
forceTLS: false,
wsPort: 6001,
wssPort: 6001,
disableStats: true,
enabledTransports: ['ws', 'wss'],
auth : {
headers : {
Authorization: "Bearer " + token,
Accept: "application/json",
}
},
})
window.Echo.private('customer-order')
.listen('OrderPlaced', (e) => {
console.log(e)
})
I found SocketIOClient is used to implement web socket functionality in .NET. I tried to use a solution I found here but it didn't work for me. Also, I didn't find any way to set up my authentication URL in this package. I read socket.io documentation for anything related to authentication but I couldn't find any.
How do I implement equivalent functionality in C# .NET as in laravel-echo?
There is probably no client like laravel-echo for .NET. However, you will be able to connect to your sockets using pusher client: pusher/pusher-websocket-dotnet and this is probably the highest level of compatibility you can reach. But you will need to parse your messages and subscribe to the channels by yourself, there will be no sweet wrapping like in laravel-echo =(
I was able to implement a solution using the package mentioned by PunyFlash in the answers. The NuGet package is available here and here is the GitHub repo.
My solution might be useful for someone in the future so, my equivalent code for the laravel-echo code above, in .NET is:
internal class OrderSocket
{
public static async void Connect()
{
try
{
//Setting authentication
var authorizer = new CustomAuthorizer("http://localhost/may-app/public/broadcasting/auth")
{
AuthenticationHeader = new System.Net.Http.Headers.AuthenticationHeaderValue("Authorization", "Bearer " + "1|CSaob3KZhU5UHiocBjPgzpazbceUKTLRLJO0ZIV0"),
};
//Creating pusher object with authentication
Pusher pusher = new Pusher("laravel_rdb", new PusherOptions
{
Authorizer = authorizer,
Host = "127.0.0.1:6001",
});
//Connecting to web socket
await pusher.ConnectAsync().ConfigureAwait(false);
//Subscribing to channel
Channel channel = await pusher.SubscribeAsync("private-customer-order").ConfigureAwait(false);
if (channel.IsSubscribed)
{
//Binding to an event
channel.Bind("App\\Events\\OrderPlaced", (PusherEvent eventResponse) =>
{
// Deserialize json if server returns json values
Debug.WriteLine(eventResponse.Data);
});
}
}
catch (Exception)
{
Debug.WriteLine("An exception occurred.");
}
}
}
//HttpAuthorizer child class to set default headers
internal class CustomAuthorizer : HttpAuthorizer
{
public CustomAuthorizer(string authEndpoint) : base(authEndpoint) { }
public override void PreAuthorize(HttpClient httpClient)
{
base.PreAuthorize(httpClient);
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
}
I am trying to port an application from an azure mobile service to an azure web app. (the mobile service was working). I have added microsoft account authentication to the web-app, and the web app api has a MobileAppController attribute. I have a Universal windows app front end that calls the api. The app first checks if a player is in the database, if not I get a not found response. If I call the method using the following code with the MobileServiceClient I get an exception.
private async Task<HttpResponseMessage> GetAZMAsyncP(string apiext, IDictionary<string,string> param )
{
string myuri = String.Format("{0}{1}", urlbase, apiext);
//client is the MobileServiceClient that is correctly logged in
//I do not get response which is 404 not found, I get an exception "The request could not be completed, Not Found"
var response = await client.InvokeApiAsync(myuri, System.Net.Http.HttpMethod.Get, param);
return response;
}
If I call the api from an httpclient and add my own headers, which the mobile client is supposed to do for me, then I get the response as requested. Here is the code:
private async static Task<HttpResponseMessage> GetAZAsync(string apiext)
{
string completeUrl = String.Format("{0}{1}", urlbase, apiext);
// Call out to AZ
using (var http = new HttpClient())
{
// http.BaseAddress = new Uri(completeUrl);
HttpRequestMessage rq = new HttpRequestMessage()
{
RequestUri = new Uri(completeUrl),
Method = HttpMethod.Get
};
addauthheader(rq);
var response = await http.SendAsync(rq);
return response;
}
}
private static void addauthheader(HttpRequestMessage rq)
{
MobileServiceUser user = App.client.CurrentUser;
rq.Headers.Add("X-ZUMO-FEATURES", "AT,QS");
rq.Headers.Add("X-ZUMO-INSTALLATION-ID",
"ff90f37e-0c03-4c52-a343-af711752e383");
rq.Headers.Add("X-ZUMO-AUTH", user.MobileServiceAuthenticationToken);
rq.Headers.Add("Accept", "application/json");
rq.Headers.Add("User-Agent", "ZUMO/2.1");
rq.Headers.Add("User-Agent",
"(lang = Managed; os = Windows Store; os_version = --; arch = X86; version = 2.1.40707.0)");
rq.Headers.Add("X-ZUMO-VERSION",
"ZUMO/2.1(lang = Managed; os = Windows Store; os_version = --; arch = X86; version = 2.1.40707.0)");
rq.Headers.Add("ZUMO-API-VERSION", "2.0.0");
}
You can try this out as it is live (and buggy).
https://gamenote2.azurewebsites.net/api/Players?displayname=Paul Goldschmidt&teamid=arizona-diamondbacks
Should give you a 404,
https://gamenote2.azurewebsites.net/api/Players?displayname=Chase Utley&teamid=los-angeles-dodgers
should give you a chase utley object. (YOu will be asked to log into a Microsoft Account).
So my questions: 1. Can I fix the mobileclient call to get a response instead of an execption
2. Is there any good reason for me to be spending so much time on this.
If you examine the exception, you will note that the status code is in there - it's just in a property that is not serialized. Just surround your InvokeApiAsync() call with a try/catch and test for the StatusCode. It should be a lot easier than writing your own HTTP Client code for the same purpose.
Specifically, MobileServiceInvalidOperationException contains the HttpResponse of the failed request, so you can check exception.Response.StatusCode value.
Suddenly i got reports form user that a list in my app didn't show any data - It worked fine on my device. Later i found out that everything works fine and dandy on all android devices with 6.0 installed - every android version below 6.0(Marshmallow), wont get data transferred! I am at a loss - have no idea what has happened or how to fix this.... Help!
Does anyone recognize this or have possible solution to how this can be fixed?
In my forms app i have a portable library where i have a class handling the SOAP webservice, it is implemented like below:
public class soapwebservice
{
//private Uri baseUri = new Uri("uri");
private static DataConnection _instance = null;
private HttpClient client = null;
//Contructor
private DataConnection()
{
client = new HttpClient(new NativeMessageHandler());
client.BaseAddress = baseUri;
}
public static DataConnection Instance { get { if (_instance == null) _instance = new DataConnection(); return _instance; } }
public async Task<Other.ServiceResponse> RefreshRouteList()
{
try
{
var soapString = this.constructRefreshsoap();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("SOAPAction", "https://trolderuterne.play2know.dk/GetRoutes");
var content = new StringContent(soapString, Encoding.UTF8, "text/xml");
using (var response = await client.PostAsync("/Classes/mobileServices.asmx", content))
{
if (response.IsSuccessStatusCode)
{
var soapResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Other.ServiceResponse>(ParseSoapResponse(soapResponse));
}
return new ServiceResponse { Code = Codes.ServerError, Message = response.StatusCode.ToString() };
}
}
catch (Exception ex)
{
return new ServiceResponse
{
Code = Codes.ServerError,
Message = ex.Message
};
}
finally
{
}
}
The error message i get when running the app is:
"Error: NameResolutionFailure"
I have now tried to consume the webservice directly in the android project instead of the PCL.
Just to mention it i have my webservice going over a proxy, due to security. It still works on 6.0, but when i go to a simulator running 4.4 i still get error: "Error: NameResolutionFailure".
I tried grabbing the original webservice directly from our server and I get the following error message: "Error: ConnectFailure (Network is unreachable)"
Hopefully someone has some insight, and can tell me how to get the data i need from the webservice in devices below Android 6.0!
NameResolutionFailure looks like a DNS error. But you're going through a proxy, so who knows what they are doing. Did they change something recently? Can you try to resolve the name into an IP both with and without the proxy, over WiFi and mobile data too?
ConnectFailure looks like you cannot connect to the server. Can you try to get data directly from the IP address instead? Try both directly and through the proxy, over WiFi and mobile data too.
Android 6 changed some things related to SSL, could that be affecting it?
I was asked by XAMARIN suppport to install the beta version og their software and this "kinda" solved the issue. I can now consume a SOAP webservice, but the service cant be SSL encrypted, if you want to use android below 6.0.
So i removed the SSL encryption from our proxy and now it works with all versions!
public static class HttpRequestHelper
{
public static string RequestBody()
{
var bodyStream = new StreamReader(HttpContext.Current.Request.InputStream);
bodyStream.BaseStream.Seek(0, SeekOrigin.Begin);
var bodyText = bodyStream.ReadToEnd();
return bodyText;
}
}
I plan to call this from ActionFilters to log incoming requests. Of course there could be multiple simultaneous requests.
Is this approach ok?
Is your question from the perspective of concurrency or ASP.NET Web API in general? Every request has its own context and you are okay with multiple requests going on in parallel. But here are two things for you to look at.
(1) Since you are using HttpContext, you are locking yourself to web hosting (IIS), which in many cases should be okay. But I would like you to be aware of this.
(2) Your code HttpRequestHelper.RequestBody() will work when called from an action filter, as you mentioned. However, if you try to call this from other places, say a message handler, this will not work. When I say this will not work, parameter binding that binds request body to action method parameter will not work. You will need to seek to the beginning once you are done. The reason it works from action filter is that binding would have already happened by the time action filter runs in the pipeline. This is another thing you might need to be aware of.
I've needed use InputStream of Http Request. I have a WebApp and IOS App that navigates to a aspx page, if the url request contains some parameters i read the information in database and if i not find any parameters in url request i read the request body and i work fine !
protected void Page_Load(object sender, EventArgs e)
{
try
{
if (string.IsNullOrEmpty(Request.QueryString["AdHoc"]) == false)
{
string v_AdHocParam = Request.QueryString["AdHoc"];
string [] v_ListParam = v_AdHocParam.Split(new char[] {','});
if (v_ListParam.Length < 2)
{
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(WS_DemandeIntervention));
WS_DemandeIntervention response = (WS_DemandeIntervention)jsonSerializer.ReadObject(Request.InputStream);
....
}
if (string.IsNullOrEmpty(Request.QueryString["IdBonDeCommande"])==false)
{
....
Our internal Redmine server only allows me to connect via HTTPS. Here's how I tried to use the REST API via HTTPS from .NET:
As suggested in Using the REST API with .NET, setting the host variable to "https://redmine.company.com/redmine/" and the apiKey to "ffffffffffffffffffffffffffffffffffffffff".
From scratch with the following code:
using System.IO;
using System.Net;
class Program
{
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, error) => true;
var request = (HttpWebRequest)WebRequest.Create(
"https://redmine.company.com/redmine/issues/149.xml?key=ffffffffffffffffffffffffffffffffffffffff");
request.CookieContainer = new CookieContainer();
request.Method = "GET";
using (var response = request.GetResponse()) // Hangs here
using (var responseStream = response.GetResponseStream())
using (var memoryStream = new MemoryStream())
{
responseStream.CopyTo(memoryStream);
}
}
}
Of course, company.com and ffffffffffffffffffffffffffffffffffffffff are just placeholders for my real company and my real API key on my account page. Both attempts hang for some time before timing out with a WebException (see the Hangs here comment in attempt 2). I then tried to download other stuff from the Redmine server (like e.g. time_entries.csv, atom feeds, etc.), each time with exactly the same result.
So far so bad. However, if I copy-paste the URL https://redmine.company.com/redmine/issues/149.xml?key=ffffffffffffffffffffffffffffffffffffffff into my browser, I get exactly the response I would expect. So, it seems as though our Redmine server behaves as it should, but somehow I can't get it to work from .NET.
I have successfully downloaded stuff from other HTTPS sites and have managed to download issue data from http://demo.redmine.org with the code of attempt 2 (of course with adapted URLs, etc.). So, it seems there might be something special about how Redmine communicates over HTTPS.
If anybody is successfully using the Redmine REST API over HTTPS from .NET, I'd be really grateful for some pointers on what I'm doing wrong.
Also, suggestions on how to debug this from the client side would be greatly appreciated. So far I've tried Fiddler2, with no success. As soon as I enable its "Decrypt HTTPS traffic" setting then I no longer get an answer when I make the request in Internet Explorer.
We use redmine-net-api which supports HTTP/S connection and authentication based on API keys.
RedmineManager rm = new RedmineManager("https://<your-address>", <api-key>, "random-password");
IList<Issue> issues = rm.GetObjectList<Issue>(new NameValueCollection() { { "project_id", <project-id> } });
Try this, it works for me:
// Allow every secure connection
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, error) => true;
// Create redmine manager (where URL is "https://10.27.10.10/redmine" for me and redmineKey is my redmine API key
RedmineManager redmineManager = new RedmineManager(redmineURL, redmineKey);
// Create your query parameters
NameValueCollection queryParameters = new NameValueCollection { { "project_id", "4" }, {"tracker_id", "17"}, { "offset", "0" } };
// Perform your query
int issuesFound = 0;
foreach (var issue in redmineManager.GetObjectList<Issue>(queryParameters, out issuesFound))
{
// By default you get the 25 first issues of the project_id and tracker_id specified.
// Play with the offset to get the rest
queryParameters["offset"] = ....
}
Explicit passing SecurityProtocolType.Tls12 value for securityProtocolType parameter solved the problem for my case:
RedmineManager redmineManager = new RedmineManager(_host, _apiKey,
securityProtocolType: SecurityProtocolType.Tls12);