I am playing around with .NET MAUI, and I got a problem. I call a rest API endpoint, and it is called, no errors (works 100% because I got response in Postman and on the SwaggerUI). But my mobile app client never receives a response. I probably miss something. Any idea is welcome.
namespace Mobile.UI.Clients;
public abstract class BaseClient
{
private readonly HttpClient httpClient;
private readonly MobileAppSettings settings;
private string BaseURL
{
get
{
return DeviceInfo.Platform == DevicePlatform.Android ?
this.settings.AndroidBaseURL :
this.settings.IosBaseURL;
}
}
protected BaseClient(HttpClient httpClient, MobileAppSettings settings)
{
this.settings = settings;
this.httpClient = BuildHttpClient(httpClient);
}
/// <summary>
/// Creates a simple get request
/// </summary>
/// <typeparam name="T">The return type</typeparam>
/// <param name="route">The route part without the base url</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
protected async Task<T> SendGetRequestAsync<T>(string route)
{
try
{
var uri = BuildUri(route);
var response = await httpClient.GetAsync(uri);
if (!response.IsSuccessStatusCode)
{
throw new Exception("Faild to fetch data.");
}
var content = await SerializeResponse<T>(response.Content);
return content;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// Creates a simple get request
/// </summary>
/// <typeparam name="T">The return type</typeparam>
/// <param name="route">The route part without the base url</param>
/// <param name="routParam">Rout parameter</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
protected async Task<T> SendGetRequestAsync<T>(string route, object routParam)
{
try
{
var uri = BuildUri(route, routParam);
var response = await httpClient.GetAsync(uri);
if (!response.IsSuccessStatusCode)
{
throw new Exception("Faild to fetch data.");
}
var content = await SerializeResponse<T>(response.Content);
return content;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
private HttpClient BuildHttpClient(HttpClient httpClient)
{
#if DEBUG
var handler = new HttpsClientHandlerService();
httpClient = new HttpClient(handler.GetPlatformMessageHandler());
#endif
httpClient.BaseAddress = new Uri(BaseURL);
httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
httpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new("application/json"));
return httpClient;
}
private Uri BuildUri(string route)
{
return new Uri(Path.Combine(BaseURL, settings.ApiVersion, route));
}
private Uri BuildUri(string route, object routParam)
{
return new Uri(Path.Combine(BaseURL, settings.ApiVersion, route, $"{routParam}"));
}
private async Task<T> SerializeResponse<T>(HttpContent content)
{
var stream = await content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<T>(stream);
}
}
public class PlayerClient : BaseClient, IPlayerClient
{
public PlayerClient(HttpClient httpClient, MobileAppSettings settings) : base(httpClient, settings)
{}
public async Task<List<PlayerModel>> GetAllAsync()
{
var path = #"players/get-all";
return await SendGetRequestAsync<List<PlayerModel>>(path);
}
}
public class HttpsClientHandlerService : IHttpsClientHandlerService
{
public HttpMessageHandler GetPlatformMessageHandler()
{
#if ANDROID
var handler = new Xamarin.Android.Net.AndroidMessageHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert != null && cert.Issuer.Equals("CN=localhost"))
return true;
return errors == System.Net.Security.SslPolicyErrors.None;
};
return handler;
#elif IOS
var handler = new NSUrlSessionHandler
{
TrustOverrideForUrl = IsHttpsLocalhost
};
return handler;
#elif WINDOWS || MACCATALYST
return null;
#else
throw new PlatformNotSupportedException("Only Android, iOS, MacCatalyst, and Windows supported.");
#endif
}
#if IOS
public bool IsHttpsLocalhost(NSUrlSessionHandler sender, string url, Security.SecTrust trust)
{
if (url.StartsWith("https://localhost"))
return true;
return false;
}
#endif
}
In the android message handler I captured an error (if it is an error):
System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors | System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch
Related
I am using RestSharp to make requests to an API which uses bearer token authentication. Most requests run as expected, but there is a specific endpoint which always redirects the request to a dynamic location. When this redirect occurs, the Authorization header is lost (by design), thus resulting in a Bad Request.
I've done some looking into the issue and found one similar issue here, but the custom AuthenticationModule I made is never having the Authenticate function called.
Am I missing something obvious in the setup that is preventing the Authentication module from being used, or is something else going on?
Thanks!
My Authenticator class:
public class AdpAuthenticator : IAuthenticator
{
/// <summary>
/// The current access token for making requests to the API.
/// </summary>
private static string AccessToken { get; set; }
/// <summary>
/// When the current access token expires.
/// </summary>
private static DateTime TokenExpiresOn { get; set; }
private static CredentialCache CredentialCache { get; set; }
/// <summary>
/// Singleton instance for making requests for access tokens.
/// </summary>
private static IRestClient AuthenticationClient { get; set; }
/// <summary>
/// Singleton instance of the request for obtaining access tokens.
/// </summary>
private static IRestRequest AuthenticationRequest { get; set; }
/// <summary>
/// Construct a new AdpAuthenticator.
/// </summary>
/// <param name="adpClientId"></param>
/// <param name="adpClientSecret"></param>
/// <param name="adpCertPath"></param>
public AdpAuthenticator(string adpClientId, string adpClientSecret, string adpCertPath)
{
if (string.IsNullOrWhiteSpace(adpClientId)) throw new ArgumentNullException("Passed adpClientId was empty or null.");
if (string.IsNullOrWhiteSpace(adpClientSecret)) throw new ArgumentNullException("Passed adpClientSecret was empty or null.");
if (CredentialCache == null)
{
CredentialCache = new CredentialCache
{
{new Uri("https://api.adp.com"), "Basic", new NetworkCredential(adpClientId, adpClientSecret) }
};
}
if (AuthenticationClient == null)
{
X509Certificate2Collection certificateCollection;
X509Certificate2 certificate = new X509Certificate2(adpCertPath);
certificateCollection = new X509Certificate2Collection
{
certificate
};
AuthenticationClient = new RestClient("https://api.adp.com")
{
ClientCertificates = certificateCollection,
Authenticator = new HttpBasicAuthenticator(adpClientId, adpClientSecret)
};
AuthenticationClient.UseSerializer(new JsonNetSerializer());
}
if (AuthenticationRequest == null)
{
AuthenticationRequest = new RestRequest("auth/oauth/v2/token", Method.POST)
{
Credentials = CredentialCache
};
AuthenticationRequest.AddOrUpdateParameter("grant_type", "client_credentials", ParameterType.QueryString);
}
RegisterAuthenticationModule(new Uri("https://api.adp.com/"));
}
/// <summary>
/// Authenticate a request.
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
public void Authenticate(IRestClient client, IRestRequest request)
{
//If accessToken is null or expired, get a new one.
if (!HasValidToken())
{
RefreshAccessToken();
}
//request.AddOrUpdateParameter("Authorization", AccessToken, ParameterType.HttpHeader);
//var newCache = new CredentialCache
//{
// {new Uri("https://api.adp.com/"), "Bearer", new NetworkCredential(AccessToken, "") }
//};
var newCache = new CredentialCache();
newCache.Add(new Uri("https://api.adp.com/"), AdpAuthenticationModule.TheAuthenticationType, new NetworkCredential(AccessToken, ""));
request.Credentials = newCache;
//request.AddOrUpdateParameter("Authorization", "Bearer " + AccessToken, ParameterType.HttpHeader);
}
private void RefreshAccessToken()
{
try
{
var response = AuthenticationClient.Execute<AuthorizationResponse>(AuthenticationRequest);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new FailedAuthenticationException($"Authentication failed to refresh access token, returned with code {response.StatusCode}. Content: \"{response.Content}\".", null);
}
if (string.IsNullOrWhiteSpace(response.Data.access_token))
{
throw new Exception("Error: response returned during access token refresh gave Status 200 OK, but access_token returned was null or whitespace.");
}
AccessToken = response.Data.access_token;
if (response.Data.expires_in <= 0)
{
throw new Exception("Error: response returned during access token refresh gave Status 200 OK, but expires_in value returned was <=0.");
}
TokenExpiresOn = DateTime.Now.AddSeconds(response.Data.expires_in);
}
catch (FailedAuthenticationException)
{
throw;
}
catch (Exception e)
{
throw new FailedAuthenticationException($"Authentication failed to refresh access token, see inner exception details.", e);
}
}
/// <summary>
/// Returns whether the current access token is valid.
/// </summary>
/// <returns>False if token is null or has 10 or less minutes until expiry; else returns true.</returns>
public bool HasValidToken()
{
return !string.IsNullOrEmpty(AccessToken) && DateTime.Now.CompareTo(TokenExpiresOn.AddMinutes(-10.0)) < 0;
}
private static AdpAuthenticationModule RegisterAuthenticationModule(Uri loginServerUrl)
{
var registeredModules = AuthenticationManager.RegisteredModules;
AdpAuthenticationModule authenticationModule;
while (registeredModules.MoveNext())
{
object current = registeredModules.Current;
if (current is AdpAuthenticationModule)
{
authenticationModule = (AdpAuthenticationModule)current;
if (authenticationModule.LoginServerUrl.Equals(loginServerUrl))
{
return authenticationModule;
}
}
}
authenticationModule = new AdpAuthenticationModule(loginServerUrl);
AuthenticationManager.Register(authenticationModule);
return authenticationModule;
}
}
My Custom Authentication Module:
public class AdpAuthenticationModule : IAuthenticationModule
{
/// <summary>
/// The name of the custom authentication type.
/// </summary>
public string AuthenticationType => TheAuthenticationType;
public static string TheAuthenticationType => "AdpAuthentication";
/// <summary>
/// Returns false, as this IAuthenticationModule cannot pre-authenticate.
/// </summary>
public bool CanPreAuthenticate => false;
private readonly CredentialCache credentialCache = new CredentialCache();
private readonly Uri loginServerUrl;
internal CredentialCache CredentialCache
{
get
{
return credentialCache;
}
}
internal Uri LoginServerUrl
{
get
{
return loginServerUrl;
}
}
internal AdpAuthenticationModule(Uri loginServerUrl)
{
this.loginServerUrl = loginServerUrl ?? throw new ArgumentNullException("AdpAuthenticationModule.loginServerUrl");
}
/// <summary>
/// Builds and returns a <see cref="Authorization"/> object for a request.
/// </summary>
/// <param name="challenge"></param>
/// <param name="request"></param>
/// <param name="credentials"></param>
/// <returns></returns>
public Authorization Authenticate(string challenge, WebRequest request, ICredentials credentials)
{
Authorization result = null;
if (request != null && credentials != null)
{
NetworkCredential creds = credentials.GetCredential(LoginServerUrl, AuthenticationType);
if (creds == null)
{
return null;
}
ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
if (policy != null && !policy.ShouldSendCredential(LoginServerUrl, request, creds, this))
{
return null;
}
string token = Convert.ToBase64String(Encoding.UTF8.GetBytes(creds.UserName));
result = new Authorization(string.Format("Bearer {0}", token));
}
return result;
}
/// <summary>
/// Returns null, since this IAuthenticationModule cannot pre-authenticate.
/// </summary>
/// <param name="request"></param>
/// <param name="credentials"></param>
/// <returns></returns>
public Authorization PreAuthenticate(WebRequest request, ICredentials credentials)
{
return null;
}
}
Implementation of IAuthenticationModule need to be registered in the AuthenticationManager class from System.Net.
Use the following code :
AuthenticationManager.Register(new AdpAuthenticationModule());
Using the following command:
curl -v -X GET -H "Content-Type: application/json" -d {'"mode":"0"'} http://host.domain.abc.com:23423/api/start-trial-api/
I am able to send the JSON data to web request and get the response back.
How can I do the same in C#?
I am able to POST data to the other service and get the response but don't understand how to send the data to GET request.
I tried searching on google and stackoverflow for the same in C#, but did not find anything.
Sample code - Make sure the request method is set to "GET"
string url = "";
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json";
var webResponse = request.GetResponse();
using (var s = webResponse.GetResponseStream())
{
using (TextReader textReader = new StreamReader(s, true))
{
string jsonString = textReader.ReadToEnd();
}
}
Plenty of abstractions here, but hopefully will give a rough guide on how to connect to a service in C#
The Interface
public interface IShopifyAPIGateway
{
HttpResponseMessage Get(string path);
}
Shopify API Gateway, which instatiates HTTPClient()
public sealed class ShopifyAPIGateway : IShopifyAPIGateway
{
/// <summary>
///
/// </summary>
private Identity _identity;
/// <summary>
///
/// </summary>
private HttpClient _httpClient;
/// <summary>
///
/// </summary>
public ShopifyAPIGateway(Identity
identity)
{
_identity = identity;
_httpClient = new HttpClient(ClientHandler());
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public HttpResponseMessage Get(string path)
{
try
{
var response = Connect().GetAsync(path).Result;
return response;
}
catch (CustomHttpResponseException ex)
{
new Email().SendEmail(_identity.ClientName, "Http Response Error - Shopify API Module",
"Http Response Error - Shopify API Module: " + ex.Message,
"error#retain.me");
throw new CustomHttpResponseException(ex.Message);
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
private HttpClient Connect()
{
try
{
_httpClient.BaseAddress = new Uri(_identity.APIURL);
return _httpClient;
}
catch (CustomHttpRequestException ex)
{
throw new CustomHttpRequestException(ex.Message);
}
}
/// <summary>
///
/// </summary>
/// <param name="userKey"></param>
/// <returns></returns>
private HttpClientHandler ClientHandler()
{
try
{
return new HttpClientHandler()
{
Credentials = new NetworkCredential(_identity.APIKey,
_identity.Password),
PreAuthenticate = true
};
}
catch (CustomClientHandlerException ex)
{
throw new CustomClientHandlerException(ex.Message);
}
}
}
Generic repo to return any object(s) where the response object matches T
public sealed class EntityRepository<T> : IEntityRepository<T>
{
private IShopifyAPIGateway _gateWay;
public T Find(string path)
{
try
{
_gateWay = new ShopifyAPIGateway(_identity);
var json = _gateWay.Get(path).Content.ReadAsStringAsync();
T results = JsonConvert.DeserializeObject<T>(json.Result);
return results;
}
catch (System.Exception ex)
{
throw new ApplicationException(ex.Message);
}
}
}
Usage return type must match the Type your passing and also the Type that's being returned in response.
private IEnumerable<Order> Orders()
{
var entityRepo = new EntityRepository<Order>();
return entityRepo.Find("somedomain/api/orders?mode=0", _identity);
}
I have an issue where I can't seem to send new data to the connected Signal R clients from a ChangedEventHandler. The docs says that I can get the hub context by using:-
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
However nothing gets sent to the clients (checked on fiddler) or any errors reported. My onchange event is wired up at the moment from Application_Start as I am creating a proof of concept. I should point out the hub does work on start up and retrieves the data from the initial GetAll call
protected void Application_Start()
{
...
_sqlTableDependency.OnChanged += _sqlTableDependency_OnChanged;
_sqlTableDependency.Start();
...
}
private void _sqlTableDependency_OnChanged(object sender, RecordChangedEventArgs<BiddingText> e)
{
switch (e.ChangeType)
{
case ChangeType.Insert:
foreach (var insertedCustomer in e.ChangedEntities)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
biddingTextList.Add(insertedCustomer);
}
break;
}
}
When I put a breakpoint on the hub context I get my ChatHub back.
My Javascript code:
$.connection.hub.url = "http://localhost:37185/signalr";
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
chat.client.initialText = function(data) {
var index;
//console.log(data.length);
for (index = 0; index < data.List.length; ++index) {
$('#list').append("<li>" + data.List[index].text + "</li>");
}
};
chat.client.addToList = function(data) {
console.log(data);
$('#list').append("<li>" + data.text + "</li>");
};
// Start the connection.
$.connection.hub.start({ jsonp: true }).done(function () {
chat.server.getAll(1831);
});
My Hub code:
public class ChatHub : Microsoft.AspNet.SignalR.Hub
{
private readonly IMediator mediator;
public ChatHub(IMediator mediator)
{
this.mediator = mediator;
}
public void GetAll(int saleId)
{
var model = mediator.Request(new BiddingTextQuery { SaleId = saleId});
Clients.Caller.initialText(model);
}
}
Not sure if this is relevant but the Clients.Connection.Identity is different everytime I use GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
Can anyone help?
I had some similar issues a while back setting up a Nancy API to publish some events to SignalR clients.
The core issue I had was that I had failed to ensure Nancy and SignalR was using the same DI container at the SignalR global level.
SignalR, as Nancy, has a default DependencyResolver that is used to resolve any dependencies in your hubs. When I failed to implement the same source of dependencies for Nancy and SignalR I basically ended up with two separate applications.
Small disclaimer: You have not posted your config code, so my solution here is based on some assumptions (as well as the following twitter answer from David Fowler when you reached out on twitter:
#rippo you have a custom dependency resolver and global has has another. You need to use one container
(https://twitter.com/davidfowl/status/635000470340153344)
Now some code:
First you need to implement a custom SignalR depependency resolver, and ensure it uses the same source of dependencies as the rest of your application.
This is the implementation I used for an Autofac container:
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
namespace LabCommunicator.Server.Configuration
{
internal class AutofacSignalrDependencyResolver : DefaultDependencyResolver, IRegistrationSource
{
private ILifetimeScope LifetimeScope { get; set; }
public AutofacSignalrDependencyResolver(ILifetimeScope lifetimeScope)
{
LifetimeScope = lifetimeScope;
var currentRegistrationSource = LifetimeScope.ComponentRegistry.Sources.FirstOrDefault(s => s.GetType() == GetType());
if (currentRegistrationSource != null)
{
((AutofacSignalrDependencyResolver)currentRegistrationSource).LifetimeScope = lifetimeScope;
}
else
{
LifetimeScope.ComponentRegistry.AddRegistrationSource(this);
}
}
public override object GetService(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetService(serviceType);
}
if (LifetimeScope.TryResolve(serviceType, out result))
{
return result;
}
return null;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetServices(serviceType);
}
if (LifetimeScope.TryResolve(typeof(IEnumerable<>).MakeGenericType(serviceType), out result))
{
return (IEnumerable<object>)result;
}
return Enumerable.Empty<object>();
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var typedService = service as TypedService;
if (typedService != null)
{
var instances = base.GetServices(typedService.ServiceType);
if (instances != null)
{
return instances
.Select(i => RegistrationBuilder.ForDelegate(i.GetType(), (c, p) => i).As(typedService.ServiceType)
.InstancePerLifetimeScope()
.PreserveExistingDefaults()
.CreateRegistration());
}
}
return Enumerable.Empty<IComponentRegistration>();
}
bool IRegistrationSource.IsAdapterForIndividualComponents
{
get { return false; }
}
}
}
Next, in your SignalR config, you need to assign the custom dependency resolver:
Note: My app was using owin, so you may not need the HubConfiguration bit, but you need the GlobalHost bit (which is the one I messed up when my stuff was not working).
var resolver = new AutofacSignalrDependencyResolver(container);
'Owin config options.
var config = new HubConfiguration()
{
Resolver = resolver,
EnableDetailedErrors = true,
EnableCrossDomain = true
};
GlobalHost.DependencyResolver = resolver;
'More owin stuff
app.MapHubs(config);
Hope this will help resolve your issue.
You need to keep track of the clients that connect to the hub, and then send them the new messages, something like this
This is a base class I wrote for my Hubs
/// <summary>
/// base class for Hubs in the system.
/// </summary>
public class HubBase : Hub {
/// <summary>
/// The hub users
/// </summary>
protected static ConcurrentDictionary<Guid, HubUser> Users = new ConcurrentDictionary<Guid, HubUser>();
/// <summary>
/// Called when the connection connects to this hub instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnConnected() {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user = Users.GetOrAdd(userName, _ => new HubUser {
UserId = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}
/// <summary>
/// Called when a connection disconnects from this hub gracefully or due to a timeout.
/// </summary>
/// <param name="stopCalled">true, if stop was called on the client closing the connection gracefully;
/// false, if the connection has been lost for longer than the
/// <see cref="P:Microsoft.AspNet.SignalR.Configuration.IConfigurationManager.DisconnectTimeout" />.
/// Timeouts can be caused by clients reconnecting to another SignalR server in scaleout.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled) {
try {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user;
Users.TryGetValue(userName, out user);
if (user != null) {
lock (user.ConnectionIds) {
user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
if (!user.ConnectionIds.Any()) {
HubUser removedUser;
Users.TryRemove(userName, out removedUser);
}
}
}
} catch {
//Bug in SignalR causing Context.User.Identity.Name to sometime be null
//when user disconnects, thus remove the connection manually.
lock (Users) {
HubUser entry = Users.Values.FirstOrDefault(v => v.ConnectionIds.Contains(Context.ConnectionId));
if (entry != null) entry.ConnectionIds.Remove(Context.ConnectionId);
}
}
return base.OnDisconnected(stopCalled);
}
private Guid RetrieveUserId() {
Cookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket decryptedCookie = FormsAuthentication.Decrypt(authCookie.Value);
var user = JsonConvert.DeserializeObject<User>(decryptedCookie.UserData);
return user.Id;
}
}
Then the Hub code is
/// <summary>
/// A hub for sending alerts to users.
/// </summary>
public class AlertHub : HubBase, IAlertHub {
/// <summary>
/// Sends the alert.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="userId">The user identifier.</param>
public void SendAlert(string message, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).sendAlert(message);
}
}
/// <summary>
/// Send alert to user.
/// </summary>
/// <param name="returnId">The return identifier.</param>
/// <param name="userId">The user identifier.</param>
public void ReturnProcessedAlert(Guid returnId, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).returnProcessedAlert(returnId);
}
}
}
I'm in the process of porting my windows 8.1 app to windows 10 UWP, but calling PostAsync now throws an exception.
This exact code works perfectly when targeting 8.1, but when I target Windows 10 UWP, it throws the following exception:
This IRandomAccessStream does not support the GetInputStreamAt method because it requires cloning and this stream does not support cloning.
Code
public async void TestPost()
{
var parameters = GetParameters();
var formattedData = new FormUrlEncodedContent(parameters);
using (var clientHandler = new HttpClientHandler { Credentials = GetCredentials() })
{
using (var httpClient = new HttpClient(clientHandler))
{
var response = await httpClient.PostAsync(postUrl, formattedData);
}
}
}
private Dictionary<string, string> GetParameters()
{
var parameters = new Dictionary<string, string>();
parameters["grant_type"] = "url";
parameters["device_id"] = "unique key";
parameters["redirect_uri"] = "redirect url";
return parameters;
}
public static NetworkCredential GetCredentials()
{
return new NetworkCredential("<secret key>", "");
}
Stacktrace
at System.IO.NetFxToWinRtStreamAdapter.ThrowCloningNotSuported(String methodName)
at System.IO.NetFxToWinRtStreamAdapter.GetInputStreamAt(UInt64 position)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Net.Http.HttpHandlerToFilter.<SendAsync>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Net.Http.HttpClientHandler.<SendAsync>d__1.MoveNext()
Have you tried using Windows.Web.Http.HttpClient instead?
// using Windows.Web.Http;
// using Windows.Web.Http.Filters;
var parameters = GetParameters();
var formattedData = new HttpFormUrlEncodedContent(parameters);
using (var clientHandler = new HttpBaseProtocolFilter())
{
clientHandler.ServerCredential = GetCredentials();
using (var httpClient = new HttpClient(clientHandler))
{
var response = await httpClient.PostAsync(postUrl, formattedData);
}
}
Its a bug. The workaround is to use Windows.Web
using Windows.Web.Http;
using Windows.Web.Http.Filters;
using Windows.Web.Http.Headers;
/// <summary>
/// Performs the post asynchronous.
/// </summary>
/// <typeparam name="T">The generic type parameter.</typeparam>
/// <param name="uri">The URI.</param>
/// <param name="objectToPost">The object to post.</param>
/// <returns>The response message.</returns>
private static async Task<HttpResponseMessage> PerformPostAsync<T>string uri, object objectToPost)
{
HttpResponseMessage response = null;
// Just add default filter (to enable enterprise authentication)
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
using (HttpClient client = HttpService.CreateHttpClient(filter))
{
// Now create the new request for the post
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri));
if (objectToPost != null)
{
// Frist get the bytes
byte[] bytes = UTF8Encoding.UTF8.GetBytes(JsonHelper.Serialize(objectToPost));
// Now create the HttpBufferContent from the bytes and set the request content
IHttpContent content = new HttpBufferContent(bytes.AsBuffer());
content.Headers.ContentType = HttpMediaTypeHeaderValue.Parse(HttpService.JsonMediaType);
request.Content = content;
}
// Now complete the request
response = await client.SendRequestAsync(request);
}
return response;
}
/// <summary>
/// Creates the HTTP client.
/// </summary>
/// <param name="filter">The filter.</param>
/// <returns>HTTP client.</returns>
private static HttpClient CreateHttpClient(HttpBaseProtocolFilter filter = null)
{
HttpClient client = new HttpClient(filter);
client.DefaultRequestHeaders.Accept.Add(new HttpMediaTypeWithQualityHeaderValue(HttpService.JsonMediaType));
return client;
}
}
We needed to use the PCL System.Net.Http library for cross-platform so we couldn't just swap everything over to use the platform specific library. We ended up using a different HttpMessageHandler for the specific problematic cases. That handler delegates the actual call to the Windows.Web.Http library.
/// <summary>
/// A System.Net.Http message handler that delegates out to Windows.Web.Http.HttpClient.
/// </summary>
public class WindowsHttpMessageHandler : HttpMessageHandler
{
private const string UserAgentHeaderName = "User-Agent";
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Windows.Web.Http.HttpClient client = new Windows.Web.Http.HttpClient();
Windows.Web.Http.HttpRequestMessage webRequest = new Windows.Web.Http.HttpRequestMessage
{
Method = ConvertMethod(request.Method),
RequestUri = request.RequestUri,
Content = await ConvertRequestContentAsync(request.Content).ConfigureAwait(false),
};
CopyHeaders(request.Headers, webRequest.Headers);
Windows.Web.Http.HttpResponseMessage webResponse = await client.SendRequestAsync(webRequest)
.AsTask(cancellationToken)
.ConfigureAwait(false);
HttpResponseMessage response = new HttpResponseMessage
{
StatusCode = ConvertStatusCode(webResponse.StatusCode),
ReasonPhrase = webResponse.ReasonPhrase,
Content = await ConvertResponseContentAsync(webResponse.Content).ConfigureAwait(false),
RequestMessage = request,
};
CopyHeaders(webResponse.Headers, response.Headers);
return response;
}
private static void CopyHeaders(HttpRequestHeaders input, Windows.Web.Http.Headers.HttpRequestHeaderCollection output)
{
foreach (var header in input)
{
output.Add(header.Key, GetHeaderValue(header.Key, header.Value));
}
}
private static void CopyHeaders(HttpContentHeaders input, Windows.Web.Http.Headers.HttpContentHeaderCollection output)
{
foreach (var header in input)
{
output.Add(header.Key, GetHeaderValue(header.Key, header.Value));
}
}
private static void CopyHeaders(Windows.Web.Http.Headers.HttpContentHeaderCollection input, HttpContentHeaders output)
{
foreach (var header in input)
{
if (!string.Equals(header.Key, "Expires", StringComparison.OrdinalIgnoreCase) || header.Value != "-1")
{
output.Add(header.Key, header.Value);
}
}
}
private static void CopyHeaders(Windows.Web.Http.Headers.HttpResponseHeaderCollection input, HttpResponseHeaders output)
{
foreach (var header in input)
{
output.Add(header.Key, header.Value);
}
}
private static string GetHeaderValue(string name, IEnumerable<string> value)
{
return string.Join(string.Equals(name, UserAgentHeaderName, StringComparison.OrdinalIgnoreCase) ? " " : ",", value);
}
private static Windows.Web.Http.HttpMethod ConvertMethod(HttpMethod method)
{
return new Windows.Web.Http.HttpMethod(method.ToString());
}
private static async Task<Windows.Web.Http.IHttpContent> ConvertRequestContentAsync(HttpContent content)
{
if (content == null)
{
return null;
}
Stream contentStream = await content.ReadAsStreamAsync().ConfigureAwait(false);
var result = new Windows.Web.Http.HttpStreamContent(contentStream.AsInputStream());
CopyHeaders(content.Headers, result.Headers);
return result;
}
private static async Task<HttpContent> ConvertResponseContentAsync(Windows.Web.Http.IHttpContent content)
{
var responseStream = await content.ReadAsInputStreamAsync();
var result = new StreamContent(responseStream.AsStreamForRead());
CopyHeaders(content.Headers, result.Headers);
return result;
}
private static HttpStatusCode ConvertStatusCode(Windows.Web.Http.HttpStatusCode statusCode)
{
return (HttpStatusCode)(int)statusCode;
}
}
Though since we only needed it for a couple of calls it's not 100% tested for all use cases.
I am new to writing Unit Test in visual studio. In my web application i have following contents.
1> Interface
public interface IGettProxy
{
Task<List<CityDetails>> getCity();
Task<List<CountryDetails>> getCountry(int cityId);
}
2> Contracts (Implementation of Interface)
public async Task<List<CityDetails>> getCity()
{
try
{
_serviceUrl = String.Format("{0}/Search/getCityinfo", _serviceUrl);
string requestUri = _serviceUrl;
client = new HttpClient();
var response = await client.GetAsync(requestUri);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var Result = new JavaScriptSerializer().Deserialize<List<CityDetails>>(json);
return Result;
}
else
{
throw new Exception("Errorhandling message");
}
}
catch (Exception ex) { throw ex; }
}
public async Task<List<CountryDetails>> getCountry(int cityId)
{
try
{
_serviceUrl = String.Format("{0}/Search/getcountryinfo?cityId={1}", _serviceUrl, cityId);
string requestUri = _serviceUrl;
client = new HttpClient();
var response = await client.GetAsync(requestUri);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var Result = new JavaScriptSerializer().Deserialize<List<CountryDetails>>(json);
return Result;
}
else
{
throw new Exception("Errorhandling message");
}
}
catch (Exception ex) { throw ex; }
}
3> Controller
private IGettProxy igettProxy;
public GettController(IGettProxy gettProxy)
{
igettProxy = gettProxy;
}
/// <summary>
/// Invoked on Page Load
/// </summary>
/// <returns></returns>
public async Task<ActionResult> Getdet()
{
try
{
List<CityDetails> cityDetails = await igettProxy.getCity();
SearchModel viewModel = new SearchModel();
viewModel.cityDetail = cityDetails;
return View(viewModel);
}
catch (Exception ex) { throw ex; }
}
/// <summary>
/// Get Country list based on city information
/// </summary>
/// <param name="cityId"></param>
/// <returns></returns>
public async Task<JsonResult> getCountry (int cityId)
{
try
{
List<CountryDetails> CountryDetails = await iSearchProxy.getCountry(cityId);
return Json(CountryDetails,JsonRequestBehavior.AllowGet);
}
catch (Exception ex) { throw ex; }
}
I have different class libraries for data member.
For injection configuration i am using Unity method.
So in this view i have drop down to bind city, country values.
For this drop down binding i want to write unit test. Please help me with this detail. Thanks in advance.
My Test method
[TestMethod]
public void bookAppointment()
{
List<CityDetails> cityDetails = new List<CityDetails>();
cityDetails.Add(new CityDetails {ID=1,CityName="Delhi"});
// var mockproxy = new StubISearchProxy();
StubISearchProxy searchProxy = new StubISearchProxy();
searchProxy.GetCity = () => cityDetails;
SearchController searchController = new SearchController(searchProxy);
var str = searchController.getCity();
}
In DI Unity will resolve this interface implementation for you. In order to test this you'll have to create a fake class that implements your interface, and inject (on the constructor).
Something like:
public class FakeClass : IGettProxy {
public Task<List<CityDetails>> getCity(){
// here goes your fake implementation, to be injected on your controller.
}
// Remember to implement the other method
}
Then when you instantiate your controller you're going to pass this fake implementation of the interface (that what the constructor requires).
And now you can test it.