I am using Service Bus Topic trigger azure function, and this function is basically performing below tasks:
Calling external API(Janrain).
After that it is again calling external API(Mailjet) to trigger email.
Then it is publishing message into Service Bus Topic.
In the Azure Application insights I can see there are lot of System.Net.Sockets.SocketException logged.
Exception details:
ExceptionMessage:The SSL connection could not be established, see
inner exception., StackTrace: at
System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream
stream, SslClientAuthenticationOptions sslOptions, CancellationToken
cancellationToken) at
System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage
request, Boolean allowHttp2, CancellationToken cancellationToken)
at
System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage
request, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask,
HttpRequestMessage request, CancellationTokenSource cts, Boolean
disposeCts) at
Mailjet.Client.MailjetClient.PostAsync(MailjetRequest request)
Could you please suggest how can I fix this issue?
Below is my Janrain API client:
public class JanrainApiClient : IJanrainApiClient
{
#region private fields
private readonly ILogger<JanrainApiClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JanrainConfigOption _janrainConfigOption;
#endregion
#region ctors
public JanrainApiClient(ILogger<JanrainApiClient> logger,
IOptions<JanrainConfigOption> janrainConfigOption,
IHttpClientFactory httpClientFactory)
{
this._logger = logger;
this._janrainConfigOption = janrainConfigOption.Value;
this._httpClientFactory = httpClientFactory;
}
#endregion
#region public methods
public async Task<JanrainResultDto> FindAsync(string userUUID)
{
try
{
_logger.LogInformation("{class} -> {method} -> Start",
nameof(JanrainApiClient), nameof(JanrainApiClient.FindAsync));
JanrainResultDto resultDto = new JanrainResultDto();
var request = GetJanrainApiRequest(userUUID);
var httpClient = _httpClientFactory.CreateClient();
httpClient.BaseAddress = new Uri(GetServiceUrl(request));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(_janrainConfigOption.Scheme, _janrainConfigOption.ApiAuth);
var response = await httpClient.GetAsync(GetServiceUrl(request));
var jsonAsString = await response.Content.ReadAsStringAsync();
JanrainApiResponse entityFindResponse = jsonAsString.AsPoco<JanrainApiResponse>();
if (entityFindResponse != null && entityFindResponse.Results != null)
{
resultDto = entityFindResponse.Results.FirstOrDefault();
}
else
{
_logger.LogError($"Error while requesting Janrain info for UserUUId:{userUUID}." +
$"Error:{entityFindResponse?.Error}, ErrorMessage:{entityFindResponse?.ErrorDescription}");
}
_logger.LogInformation("{class} -> {method} -> End",
nameof(JanrainApiClient), nameof(JanrainApiClient.FindAsync));
return resultDto;
}
catch (Exception ex)
{
_logger.LogError($"Exception while calling Janrain entity Find API for UserUUId :{userUUID}, " +
$"Error message:{ex.Message}, StackTrace:{ex.StackTrace}");
throw;
}
}
#endregion
#region private methods
private JanrainApiRequest GetJanrainApiRequest(string userUUId)
{
return new JanrainApiRequest
{
Username = string.Empty,
TypeName = _janrainConfigOption.TypeName,
Attributes = JanrainResultDto.Attributes(),
QueryFilter = $"uuid='{userUUId}'",
ClientId = _janrainConfigOption.ClientId,
};
}
private string GetServiceUrl(JanrainApiRequest request)
{
return $"{_janrainConfigOption.EntityFindApiUrl}{request.ToQueryString()}";
}
#endregion
}
Below is my Mailjet client factory:
public class MailjetClientFactory : IMailjetClientFactory
{
#region private fields
private readonly MailjetConfigOption _mailjetConfigOption;
#endregion
#region ctors
public MailjetClientFactory(IOptions<MailjetConfigOption> mailjetConfigOption)
{
_mailjetConfigOption = mailjetConfigOption.Value;
}
#endregion
#region public methods
/// <summary>
/// Create Mailjet client
/// </summary>
/// <returns>The IMailjetClient instance.</returns>
public IMailjetClient CreateClient()
{
return new MailjetClient(_mailjetConfigOption.PublicKey,
_mailjetConfigOption.SecretKey)
{
Version = ApiVersion.V3_1
};
}
#endregion
}
Sending email with Mailjet API:
public async Task<MailjetResponse> SendEmail(MailjetEmailMessage emailMessage)
{
var mailJetMessage = buildMailJetMessages.BuildMailjetMessageFor(emailMessage);
var mailJetClient = createMailJetClients.CreateClient();
var mailJetResponse = await mailJetClient.PostAsync(new MailjetRequest
{
Resource = Send.Resource,
}.Property(Send.Messages, mailJetMessage));
return mailJetResponse;
}
Service Bus Message Factory:
public class AzureServiceBusFactory : IMessageBusFactory
{
#region private fields
private readonly ServiceBusConfigOption _serviceBusConfigOption;
private readonly object _lockObject = new object();
private readonly ConcurrentDictionary<string, ServiceBusClient> _clients = new ConcurrentDictionary<string, ServiceBusClient>();
private readonly ConcurrentDictionary<string, ServiceBusSender> _senders = new ConcurrentDictionary<string, ServiceBusSender>();
#endregion
public AzureServiceBusFactory(IOptions<ServiceBusConfigOption> serviceBusConfigOption)
{
_serviceBusConfigOption = serviceBusConfigOption.Value;
}
#region public methods
/// <summary>
/// Get ServiceBusClient
/// </summary>
/// <param name="senderName"></param>
/// <returns></returns>
public IMessageBus GetClient(string senderName)
{
var connectionString = _serviceBusConfigOption.ConnectionString;
var key = $"{connectionString}-{senderName}";
if (this._senders.ContainsKey(key) && !this._senders[key].IsClosed)
{
return AzureServiceBus.Create(this._senders[key]);
}
var client = this.GetServiceBusClient(connectionString);
lock (this._lockObject)
{
if (this._senders.ContainsKey(key) && this._senders[key].IsClosed)
{
if (this._senders[key].IsClosed)
{
this._senders[key].DisposeAsync().GetAwaiter().GetResult();
}
return AzureServiceBus.Create(this._senders[key]);
}
var sender = client.CreateSender(senderName);
this._senders[key] = sender;
}
return AzureServiceBus.Create(this._senders[key]);
}
#endregion
#region protected methods
/// <summary>
/// Create ServiceBusClient
/// </summary>
/// <param name="connectionString"></param>
/// <returns></returns>
protected virtual ServiceBusClient GetServiceBusClient(string connectionString)
{
var key = $"{connectionString}";
lock (this._lockObject)
{
if (this.ClientDoesnotExistOrIsClosed(connectionString))
{
var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions
{
TransportType = ServiceBusTransportType.AmqpTcp
});
this._clients[key] = client;
}
return this._clients[key];
}
}
#endregion
#region private methods
/// <summary>
/// Check ClientDoesnotExistOrIsClosed
/// </summary>
/// <param name="connectionString"></param>
/// <returns></returns>
private bool ClientDoesnotExistOrIsClosed(string connectionString)
{
return !this._clients.ContainsKey(connectionString) || this._clients[connectionString].IsClosed;
}
#endregion
}
internal class AzureServiceBus : IMessageBus
{
#region private fields
private const string ContentType = "application/json";
private readonly ServiceBusSender _serviceBusSender;
#endregion
#region ctors
internal AzureServiceBus(ServiceBusSender serviceBusSender)
{
this._serviceBusSender = serviceBusSender;
}
#endregion
#region public methods
/// <summary>
/// Publish message into Service Bus Topic
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message"></param>
/// <returns></returns>
public async Task PublishMessageAsync<T>(T message, string messageId)
{
var serviceBusMessage = new ServiceBusMessage(Encoding.UTF8.GetBytes(message.AsJson()))
{
ContentType = ContentType
};
serviceBusMessage.MessageId = messageId;
await this._serviceBusSender.SendMessageAsync(serviceBusMessage);
}
/// <summary>
/// Publish message into Service Bus Topic
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message"></param>
/// <param name="messageId"></param>
/// <param name="filterPropertyName"></param>
/// <param name="filterPropertyValue"></param>
/// <returns></returns>
public async Task PublishMessageAsync<T>(T message, string messageId, string filterPropertyName, string filterPropertyValue)
{
var serviceBusMessage = new ServiceBusMessage(Encoding.UTF8.GetBytes(message.AsJson()))
{
ContentType = ContentType
};
serviceBusMessage.MessageId = messageId;
serviceBusMessage.ApplicationProperties.Add(filterPropertyName, filterPropertyValue);
serviceBusMessage.CorrelationId = filterPropertyValue;
await this._serviceBusSender.SendMessageAsync(serviceBusMessage);
}
#endregion
#region internal methods
internal static IMessageBus Create(ServiceBusSender sender)
{
return new AzureServiceBus(sender);
}
#endregion
}
DI registrations:
builder.Services.AddSingleton<IJanrainApiClient, JanrainApiClient>();
builder.Services.AddSingleton<IMailjetClientFactory, MailjetClientFactory>();
builder.Services.AddSingleton<IMessageBusFactory, AzureServiceBusFactory>();
builder.Services.AddTransient<IMailjetEmailService, MailjetEmailService>();
I'm writing a .NET Core API which collects data by sending API call to external website and save it to database. I have written three methods, Get_Access_Token, Get_Access and Get_Trips first two works file but the third one returns 404 URL not found, Interesting thing that it works on Postman and Swagger UI.
Here is my code
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace KnsPortal.Models.MediRouteCustom
{
public class MRHttpClient
{
/// <summary>
/// Get and Set the Access Token - Bearer Token
/// </summary>
private static AccessToken? _accessToken;
/// <summary>
/// Get and Set the list of Access Keys and Funding Sources
/// </summary>
private static List<Access.Access>? _accessKey;
/// <summary>
/// Get the MediRoute Username from config
/// </summary>
private readonly string _clientId;
/// <summary>
/// Get MediRoute Password from config
/// </summary>
private readonly string _clientSecret;
/// <summary>
/// Get the GrantType from config, eg. 'Password'
/// </summary>
private readonly string _grantType;
/// <summary>
/// Get the API Host URL of MediRoute eg. https://external.medirouteapi.com
/// </summary>
private readonly string _apiHost;
/// <summary>
/// Set the number of days you want to collect the trips data, Setting 0 will collect the data of current day
/// </summary>
private readonly int _nextDaysSpan;
/// <summary>
/// Get the instance of HTTPClient
/// </summary>
private readonly HttpClient _httpClient;
/// <summary>
/// Either to included Unscheduled trips or not?
/// </summary>
private readonly bool _includeUnScheduledTrips;
/// <summary>
/// Either to include UnPerformed Trips or not?
/// </summary>
private readonly bool _includeUnperformedTrips;
/// <summary>
/// Either to include Breaks or not?
/// </summary>
private readonly bool _includeBreaks;
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();
//readonly HttpClient httpClient = new HttpClient();
static MRHttpClient()
{
_accessToken = null!;
}
[JsonConstructor]
public MRHttpClient() { }
[JsonConstructor]
public MRHttpClient( HttpClient _client)
{
// Initializing the values on first call
_httpClient = _client ?? throw new ArgumentNullException(nameof(_client));
var config = configuration ?? throw new ArgumentNullException(nameof(configuration));
_clientId = config.GetValue<string>("MediRoute:UserName");
_clientSecret = config.GetValue<string>("MediRoute:Password");
_grantType = config.GetValue<string>("MediRoute:GrantType");
_apiHost = config.GetValue<string>("MediRoute:ApiHost");
_nextDaysSpan = config.GetValue<int>("MediRoute:NextDaySpan");
_includeUnScheduledTrips = config.GetValue<bool>("MediRoute:IncludeUnScheduledTrips");
_includeUnperformedTrips = config.GetValue<bool>("MediRoute:IncludeUnPerformedTrips");
_includeBreaks = config.GetValue<bool>("MediRoute:IncludeBreaks");
}
public async Task<List<MediRoute>> GetTrips(List<Access.Access> _acs, AccessToken _tkn)
{
var _totalTrips = new List<MediRoute>();
if (_acs.Count > 0)
{
for (int i = 0; i < _acs.Count; i++)
{
for (int j = 0; j < _nextDaysSpan + 1; j++)
{
DateTime _date = DateTime.Now.AddDays(j);
// set body parameters required by api
var keyValues = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("api_key", _acs[i].APIKey),
new KeyValuePair<string, string>("includeUnScheduled", _includeUnScheduledTrips.ToString()),
new KeyValuePair<string, string>("includeUnperformed", _includeUnperformedTrips.ToString()),
new KeyValuePair<string, string>("includeBreaks", _includeBreaks.ToString()),
new KeyValuePair<string, string>("date", _date.ToString("yyyy-MM-dd"))
};
// initialize a request message
//API Host: https://external.mediroutesapi.com
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(_apiHost + "/api/v1/rides/getRidesByDate"),
Headers =
{
{ HttpRequestHeader.Accept.ToString(), "application/json" },
{ HttpRequestHeader.Authorization.ToString(), "Bearer "+_tkn.access_token }
},
Content = new FormUrlEncodedContent(keyValues)
};
// sent the api call
using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
// collect response
var responseContent = await response.Content.ReadAsStringAsync();
//Deserialize Object from a json format to Object
List<MediRoute> _newTrips = JsonConvert.DeserializeObject<List<MediRoute>>(responseContent);
if (_newTrips.Count > 0)
{
_totalTrips.AddRange(_newTrips);
}
// wait for 2 seconds to send next api call
Thread.Sleep(2000);
}
// wait for 2 seconds to send api calls for another trip provider
Thread.Sleep(2000);
}
return _totalTrips;
}
else return _totalTrips;
}
}
}
Can anyone point me out where I'm doing wrong? I have attached the params snap as well.
any help would be much appreciated.
Let me post the answer to my own question as how did i figure it out. Query String & Post data are two different set of parameters. Server was expecting Query string and I was sending by FormUrlEncodedContent. If the server explicitly requiring data in query string format, you can't use alternative.
I had to replace
var keyValues = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("api_key", _acs[i].APIKey),
new KeyValuePair<string, string>("includeUnScheduled", _includeUnScheduledTrips.ToString()),
new KeyValuePair<string, string>("includeUnperformed", _includeUnperformedTrips.ToString()),
new KeyValuePair<string, string>("includeBreaks", _includeBreaks.ToString()),
new KeyValuePair<string, string>("date", _date.ToString("yyyy-MM-dd"))
};
// initialize a request message
//API Host: https://external.mediroutesapi.com
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(_apiHost + "/api/v1/rides/getRidesByDate"),
Headers =
{
{ HttpRequestHeader.Accept.ToString(), "application/json" },
{ HttpRequestHeader.Authorization.ToString(), "Bearer "+_tkn.access_token }
},
Content = new FormUrlEncodedContent(keyValues)
};
with
var uriBuilder = new UriBuilder(_apiHost+ "/api/v1/rides/getRidesByDate");
var paramValues = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
paramValues.Add("api_key", _acs[i].APIKey);
paramValues.Add("includeUnScheduled", _includeUnScheduledTrips.ToString());
paramValues.Add("includeUnperformed", _includeUnperformedTrips.ToString());
paramValues.Add("includeBreaks", _includeBreaks.ToString());
paramValues.Add("date", _date.ToString("yyyy-MM-dd"));
uriBuilder.Query = paramValues.ToString();
var url = uriBuilder.Uri.ToString();
//
// initialize a request message
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(uriBuilder.Uri.ToString()),
Headers =
{
{ HttpRequestHeader.Accept.ToString(), "application/json" },
{ HttpRequestHeader.Authorization.ToString(), "Bearer "+_tkn.access_token }
}
};
and it worked.
I have an ASP.NET MVC project and one page of it must show my device state. I want get device state with MQTT from a broker (broker.mqttdashboard.com). I am using MQTTnet. I can not connect to broker , I have not username or password and I want to use a public channel to publish messages to broker by pressing a button. the button run PublishMqttMsg method and I want to give messages from broker.
I don't know how to connect to broker. Can anyone help me?
My controller code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Protocol;
using System;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet;
using Microsoft.AspNet.Identity;
namespace WebApplication10.Controllers
{
public class MqttController : Controller
{
private static IManagedMqttClient client;
// GET: Mqtt
public ActionResult Index()
{
var CurrentUser = User.Identity.GetUserId();
ConnectAsync(CurrentUser, "broker.mqttdashboard.com", "", 8000, true);
client.UseConnectedHandler(e =>
{
Console.WriteLine("Connected successfully with MQTT Brokers.");
});
client.UseDisconnectedHandler(e =>
{
Console.WriteLine("Disconnected from MQTT Brokers.");
});
client.UseApplicationMessageReceivedHandler(e =>
{
try
{
string topic = e.ApplicationMessage.Topic;
if (string.IsNullOrWhiteSpace(topic) == false)
{
string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
Console.WriteLine($"Topic: {topic}. Message Received: {payload}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message, ex);
}
});
return View();
}
public ActionResult SubscribeMqttMsg()
{
SubscribeAsync("test_topic/1");
return View("Index");
}
public ActionResult PublishMqttMsg()
{
PublishAsync("test_topic/1", "test message");
return View("Index");
}
/// <summary>
/// Connect to broker.
/// </summary>
/// <returns>Task.</returns>
public static async Task ConnectAsync(string user, string uri, string pass, int port, bool userSSL)
{
string clientId = Guid.NewGuid().ToString();
string mqttURI = uri;
string mqttUser = user;
string mqttPassword = pass;
int mqttPort = port;
bool mqttSecure = userSSL;
var messageBuilder = new MqttClientOptionsBuilder()
//.WithClientId(clientId)
//.WithCredentials(mqttUser, mqttPassword)
.WithTcpServer(mqttURI, mqttPort)
.WithCleanSession();
var options = mqttSecure
? messageBuilder
.WithTls()
.Build()
: messageBuilder
.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(options)
.Build();
client = new MqttFactory().CreateManagedMqttClient();
await client.StartAsync(managedOptions);
}
/// <summary>
/// Publish Message.
/// </summary>
/// <param name="topic">Topic.</param>
/// <param name="payload">Payload.</param>
/// <param name="retainFlag">Retain flag.</param>
/// <param name="qos">Quality of Service.</param>
/// <returns>Task.</returns>
public static async Task PublishAsync(string topic, string payload, bool retainFlag = true, int qos = 1) =>
await client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.WithRetainFlag(retainFlag)
.Build())
;
/// <summary>
/// Subscribe topic.
/// </summary>
/// <param name="topic">Topic.</param>
/// <param name="qos">Quality of Service.</param>
/// <returns>Task.</returns>
public static async Task SubscribeAsync(string topic, int qos = 1) =>
await client.SubscribeAsync(new TopicFilterBuilder()
.WithTopic(topic)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.Build());
}
}
Port 8000 at broker.hivemq.com is the "Websocket Port" (so listening for MQTT over websockets connections). You are attempting to connect using a TCP client. Either change the port (to 1833) or change:
var messageBuilder = new MqttClientOptionsBuilder()
.WithTcpServer(mqttURI, mqttPort)
...
to:
var messageBuilder = new MqttClientOptionsBuilder()
.WithWebSocketServer("broker.hivemq.com:8000/mqtt")
...
(see the documentation).
I have been struggling on this issue for weeks now.
I have an app where i have configured owin backend with web api and autofac DI with background handfire jobs. I have alsmost looked at every question on Stackoveflow regarding this but nothing seems to work.
My app regarding OWIN/Hangfire/WebAPI all seems to work okay. Until it comes to SignalR push messages.
If i call any notification hub endpoint from js client push messages go okay and i can receive push messages on any other connected client. But when i wan to send message from my api controller or hangfire job it never reaches to any client.
Startup.cs
public void Configuration(IAppBuilder app)
{
//var signalRHelper = new SignalRHelper(GlobalHost.ConnectionManager.GetHubContext<NotificationHub>());
var constants = new Constants();
constants.Set(ConstantTypes.AllyHrNoReplyEmailAddress, Util.Constants.AllyHrNoReplyEmailAddress);
constants.Set(ConstantTypes.SendGridKey, Util.Constants.SendGridKey);
constants.Set(ConstantTypes.EncryptionKey, Util.Constants.EncryptionKey);
constants.Set(ConstantTypes.ApiUrl, Util.Constants.ApiUrl);
constants.Set(ConstantTypes.RootFolder, Util.Constants.RootFolder);
constants.Set(ConstantTypes.FrontEndUrl, Util.Constants.FrontEndUrl);
GlobalConfiguration.Configuration
.UseSqlServerStorage("AllyHrDb");
var config = System.Web.Http.GlobalConfiguration.Configuration;
var builder = new ContainerBuilder();
var jobBuilder = new ContainerBuilder();
var signalRBuilder = new ContainerBuilder();
var hubConfig = new HubConfiguration();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.Register(x => constants);
builder.RegisterModule(new ServiceModule());
jobBuilder.Register(x => constants);
jobBuilder.RegisterModule(new HangfireServiceModule());
signalRBuilder.RegisterModule(new SignalRServiceModule());
signalRBuilder.Register(x => constants);
signalRBuilder.RegisterType<AutofacDependencyResolver>().As<IDependencyResolver>().SingleInstance();
signalRBuilder.RegisterType<ConnectionManager>().As<IConnectionManager>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<NotificationHub>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<SignalRHelper>().PropertiesAutowired().ExternallyOwned().SingleInstance();
signalRBuilder.Register(context => context.Resolve<IDependencyResolver>().Resolve<IConnectionManager>().GetHubContext<NotificationHub, INotificationHub>()).ExternallyOwned().SingleInstance();
var hubContainer = signalRBuilder.Build();
builder.RegisterInstance(hubContainer.Resolve<IConnectionManager>());
builder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
builder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
builder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
jobBuilder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
jobBuilder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
jobBuilder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
var container = builder.Build();
var jobContainer = jobBuilder.Build();
var idProvider = new SignalRCustomUserIdProvider();
hubConfig.Resolver = new AutofacDependencyResolver(hubContainer);
hubConfig.Resolver.Register(typeof(IUserIdProvider), () => idProvider);
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider()
});
map.RunSignalR(hubConfig);
});
GlobalConfiguration.Configuration.UseAutofacActivator(jobContainer);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseHangfireServer();
app.UseHangfireDashboard();
ConfigureAuth(app);
app.UseWebApi(config);
}
I had to use different container because i have db set to InstancePerRequest scope.
All my services are being resolved in notification hub class, no problems there. The only issues is when i try and send message from hangfire service or even from api controller using hub context it never reaches to any client.
NotificationHub.cs
public interface INotificationHub
{
/// <summary>
///
/// </summary>
void pushNotification(string message);
/// <summary>
///
/// </summary>
/// <param name="model"></param>
void getNotification(object model);
void getMessage(object model);
}
/// <summary>
/// Notification Hub
/// </summary>
[HubName("NotificationHub")]
[Authorize]
public class NotificationHub : Hub<INotificationHub>
{
/// <summary>
///
/// </summary>
public static IHubContext<INotificationHub> GlobalContext { get; private set; }
private readonly IChatMessagingService _chatMessagingService;
private readonly IUserService _userService;
private Guid LoggedInUserId
{
get
{
var claims = ((ClaimsIdentity)Context.User.Identity).Claims.ToArray();
var userIdClaim = claims.FirstOrDefault(x => x.Type.Equals("UserId"));
if (userIdClaim == null) return Guid.Empty;
return Guid.Parse(userIdClaim.Value);
}
}
/// <summary>
/// Consructor
/// </summary>
/// <param name="lifetimeScope"></param>
/// <param name="context"></param>
public NotificationHub(ILifetimeScope lifetimeScope, IHubContext<INotificationHub> context)
{
GlobalContext = context;
try
{
var childScope = lifetimeScope.BeginLifetimeScope();
_chatMessagingService = childScope.Resolve<IChatMessagingService>();
_userService = childScope.Resolve<IUserService>();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <summary>
/// Notifications
/// </summary>
public void Notifications()
{
Clients.All.pushNotification("AllyHr" + LoggedInUserId);
}
/// <summary>
/// Send Message
/// </summary>
/// <param name="model"></param>
public void SendMessage(SendChatMessageBindingModel model)
{
var chatMessage = _chatMessagingService.SendMessageToGroup(LoggedInUserId, model.GroupId, model.Message);
var recipientIds = _chatMessagingService.GetChatMembersByGroupId(LoggedInUserId, model.GroupId);
var stringUserIds = new List<string>();
var chatGroup = _chatMessagingService.GetChatGroupById(model.GroupId);
foreach (var recipientId in recipientIds)
{
stringUserIds.Add(recipientId.ToString());
}
Clients.Users(stringUserIds).getNotification(new
{
message = "A new Message is Recieved in Chat Group: " + chatGroup.Name,
groupId = chatGroup.Id
});
var chatMessageVm = chatMessage.Map<ChatMessage, ChatMessageViewModel>();
chatMessageVm.Sender = _userService.Get(chatMessageVm.SenderId).Map<User, UserViewModel>();
stringUserIds.Add(LoggedInUserId.ToString());
Clients.Users(stringUserIds).getMessage(chatMessageVm);
}
}
signalRhelper.cs use to call from api or from Hangfire services
public class SignalRHelper
{
public IConnectionManager ConnectionManager { get; set; }
public IHubContext<INotificationHub> HubContext { get; set; }
/// <summary>
/// Send Notifications to Users
/// </summary>
/// <param name="message"></param>
/// <param name="userIds"></param>
public void GetNotification(object message, IList<string> userIds)
{
HubContext.Clients.Users(userIds).getNotification(message);
}
/// <summary>
/// Get LoggedInUser Id for SignalR
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public static Guid GetLoggedInUserId(IPrincipal user)
{
var claim = GetLoggedinUserClaim(user);
if (claim == null) return Guid.Empty;
return Guid.Parse(claim.Value);
}
private static Claim GetLoggedinUserClaim(IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).Claims.ToArray();
return claim.FirstOrDefault(x => x.Type.Equals("UserId"));
}
}
Could this be related to Autofac creating a new lifetimescope for your call, but you were expecting to continue using the existing scope? Maybe check your autofac registrations for singleinstance / instanceperlifetimescope
Just saying, but have you registered any static classes? They can keep your scope alive for far too long.
I see you're using multiple containerbuilders - that's not something we do over here, we have one 'massive' containerbuilder for each app. I'm curious why you're doing that? To satisfy my curiosity, could you try using a single containerbuilder and registering everything on that single builder? (Although it looks like this is a pattern for SignalR and autofac)
The documentation says: " a common error in OWIN integration is the use of GlobalHost."
It looks like you're doing exactly that.
I've developed an mvc 5 application using nopcommerce and i use facebook login using External callback it was working but now it is not working and i can't find out actual problem. And using this below code
this.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri());
and it's returning me always null and authentication status failed i searched on web an do every thing and followed that steps but still i can't login with facebook.
My code is like this in FacebookProviderAuthorizer.cs
private AuthorizeState VerifyAuthentication(string returnUrl)
{
var authResult = DotNetOpenAuth.AspNet.Clients.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri());
if (authResult.IsSuccessful)
{
}
}
And then write Call back method
private Uri GenerateLocalCallbackUri()
{
string url = string.Format("{0}plugins/externalauthFacebook/logincallback/", _webHelper.GetStoreLocation());
return new Uri(url);
}
Then generate service login url
private Uri GenerateServiceLoginUrl()
{
//code copied from DotNetOpenAuth.AspNet.Clients.FacebookClient file
var builder = new UriBuilder("https://www.facebook.com/dialog/oauth");
var args = new Dictionary<string, string>();
args.Add("client_id", _facebookExternalAuthSettings.ClientKeyIdentifier);
args.Add("redirect_uri", GenerateLocalCallbackUri().AbsoluteUri);
args.Add("response_type", "token");
args.Add("scope", "email");
AppendQueryArgs(builder, args);
return builder.Uri;
}
We ran into this same issue on Monday, 3/27/2017, when Facebook discontinued support for their Graph API v2.2.
We are also using DotNetOpenAuth, which was originally installed via Nuget. The source code is available at the link below:
https://github.com/DotNetOpenAuth/DotNetOpenAuth
Specifically, we discovered that our code was utilizing the 4.3 branch which contains the source code for the DotNetOpenAuth.AspNet.DLL. Upon inspecting the source, we discovered the problem is with this snippet of code from DotNetOpenAuth.AspNet\Clients\OAuth2\FacebookClient.cs, located within the QueryAccessToken method:
using (WebClient client = new WebClient()) {
string data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data)) {
return null;
}
var parsedQueryString = HttpUtility.ParseQueryString(data);
return parsedQueryString["access_token"];
}
The issue, specifically, is the ParseQueryString call. Starting with v2.3 of the API, the data is no longer returned as an HTML query string, but in standard JSON format.
To fix this, we created our own custom class inheriting OAuth2Client and imported most of the same code from FacebookClient.cs. We then replaced the above code snippet with code that parses the JSON response to extract the access_token, and returns that instead. You can see an example of how to do this in the same FacebookClient class, within the GetUserData method:
FacebookGraphData graphData;
var request =
WebRequest.Create(
"https://graph.facebook.com/me?access_token=" +
MessagingUtilities.EscapeUriDataStringRfc3986(accessToken));
using (var response = request.GetResponse()) {
using (var responseStream = response.GetResponseStream()) {
graphData = JsonHelper.Deserialize<FacebookGraphData>(responseStream);
}
}
The only other change was to register our custom class in place of the FacebookClient class so the OAuth callback uses it to handle the post from Facebook's API. Once we did this, everything worked smoothly again.
I have used the code shared by #Vishal and got the same working.
The main thing that we have to focus is to override the QueryAccessToken method to use json response.
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var uri = BuildUri(TokenEndpoint, new NameValueCollection
{
{ "code", authorizationCode },
{ "client_id", _appId },
{ "client_secret", _appSecret },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
});
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
string accessToken = null;
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
// handle response from FB
// this will not be a url with params like the first request to get the 'code'
Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);
using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));
Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
accessToken = desirializedJsonObject["access_token"].ToString();
}
return accessToken;
}
Steps to achieve this:
Step 1. What you have to do is add one file named FacebookClientOverride.cs(other than FacebookClient.cs)
Here is the code snippet of the whole file.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using DotNetOpenAuth.AspNet.Clients;
using Newtonsoft.Json;
public class FacebookClient : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The user info endpoint.
/// </summary>
private const string UserInfoEndpoint = "https://graph.facebook.com/me";
/// <summary>
/// The app id.
/// </summary>
private readonly string _appId;
/// <summary>
/// The app secret.
/// </summary>
private readonly string _appSecret;
/// <summary>
/// The requested scopes.
/// </summary>
private readonly string[] _requestedScopes;
#endregion
/// <summary>
/// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
public FacebookClient(string appId, string appSecret)
: this(appId, appSecret, new[] { "email" }) { }
/// <summary>
/// Creates a new Facebook OAuth2 client.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
/// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
: base("facebook")
{
if (string.IsNullOrWhiteSpace(appId))
throw new ArgumentNullException("appId");
if (string.IsNullOrWhiteSpace(appSecret))
throw new ArgumentNullException("appSecret");
if (requestedScopes == null)
throw new ArgumentNullException("requestedScopes");
if (requestedScopes.Length == 0)
throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");
_appId = appId;
_appSecret = appSecret;
_requestedScopes = requestedScopes;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);
return BuildUri(AuthorizationEndpoint, new NameValueCollection
{
{ "client_id", _appId },
{ "scope", string.Join(" ", _requestedScopes) },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
{ "state", state },
});
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
using (var webResponse = webRequest.GetResponse())
using (var stream = webResponse.GetResponseStream())
{
if (stream == null)
return null;
using (var textReader = new StreamReader(stream))
{
var json = textReader.ReadToEnd();
var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());
data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));
return data;
}
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var uri = BuildUri(TokenEndpoint, new NameValueCollection
{
{ "code", authorizationCode },
{ "client_id", _appId },
{ "client_secret", _appSecret },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
});
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
string accessToken = null;
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
// handle response from FB
// this will not be a url with params like the first request to get the 'code'
Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);
using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));
Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
accessToken = desirializedJsonObject["access_token"].ToString();
}
return accessToken;
}
private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
{
var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
var qs = String.Join("&", keyValuePairs);
var builder = new UriBuilder(baseUri) { Query = qs };
return builder.Uri;
}
/// <summary>
/// Facebook works best when return data be packed into a "state" parameter.
/// This should be called before verifying the request, so that the url is rewritten to support this.
/// </summary>
public static void RewriteRequest()
{
var ctx = HttpContext.Current;
var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
if (stateString == null || !stateString.Contains("__provider__=facebook"))
return;
var q = HttpUtility.ParseQueryString(stateString);
q.Add(ctx.Request.QueryString);
q.Remove("state");
ctx.RewritePath(ctx.Request.Path + "?" + q);
}
}
Step 2. Add one reference to System.Web.Extensions
Step 3. In the FacebookProviderAuthorizer.cs (Nopcommerce project)
look for the FacebookClient Property
private FacebookClient _facebookApplication;
This should refer to your new file just added.
Step 4. Now put a break point in the method named VerifyAuthentication in the file FacebookProviderAuthorizer.cs .
The authResult.IsSuccessful must be true now as it successfully parsed the token.
Thanks all.
Please like if the soluutions worked for you.
Building onto Steve's post, I created a "FriendlyFacebookClient" to use in-place of FacebookClient, copied over some of the internal methods, and replaced QueryAccessToken with the following:
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
UriBuilder builder = new UriBuilder("https://graph.facebook.com/oauth/access_token");
AppendQueryArgs(builder, (IEnumerable<KeyValuePair<string, string>>)new Dictionary<string, string>()
{
{ "client_id", this.appId},
{ "redirect_uri", FriendlyFacebookClient.NormalizeHexEncoding(returnUrl.AbsoluteUri)},
{ "client_secret", this.appSecret },
{ "code", authorizationCode },
{ "scope", "email" }
});
using (WebClient webClient = new WebClient())
{
var response = webClient.DownloadString(builder.Uri);
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
return data["access_token"];
}
}
According to Previous Answer I got my solution. In MVC4 everyone write down their AppID and SecurityCode. Due to change of facebook GRAPH API those previous links are broken. Consequently everyone need to change the RegisterFacebookClient class. But this class is a sealed class in the .Net library, so anyone can't extend or overwrite it. As a result we need to use a wrapper class. I am writing this answer because all the previous answer suppliers have missed one thing, for which I have suffered a lot. Because they did not mentioned the system of class warping system in the AuthConfig Class. So it takes too much to solve this problem. Therefore I am going step by step as below. Let us consider my Wrapper class is FacebookClientV2Dot3 therefore my class will be
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using DotNetOpenAuth.AspNet.Clients;
using Newtonsoft.Json;
public class FacebookClientV2Dot3 : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The user info endpoint.
/// </summary>
private const string UserInfoEndpoint = "https://graph.facebook.com/me";
/// <summary>
/// The app id.
/// </summary>
private readonly string _appId;
/// <summary>
/// The app secret.
/// </summary>
private readonly string _appSecret;
/// <summary>
/// The requested scopes.
/// </summary>
private readonly string[] _requestedScopes;
#endregion
/// <summary>
/// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
public FacebookClient(string appId, string appSecret)
: this(appId, appSecret, new[] { "email" }) { }
/// <summary>
/// Creates a new Facebook OAuth2 client.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
/// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
: base("facebook")
{
if (string.IsNullOrWhiteSpace(appId))
throw new ArgumentNullException("appId");
if (string.IsNullOrWhiteSpace(appSecret))
throw new ArgumentNullException("appSecret");
if (requestedScopes == null)
throw new ArgumentNullException("requestedScopes");
if (requestedScopes.Length == 0)
throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");
_appId = appId;
_appSecret = appSecret;
_requestedScopes = requestedScopes;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);
return BuildUri(AuthorizationEndpoint, new NameValueCollection
{
{ "client_id", _appId },
{ "scope", string.Join(" ", _requestedScopes) },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
{ "state", state },
});
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
using (var webResponse = webRequest.GetResponse())
using (var stream = webResponse.GetResponseStream())
{
if (stream == null)
return null;
using (var textReader = new StreamReader(stream))
{
var json = textReader.ReadToEnd();
var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());
data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));
return data;
}
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var uri = BuildUri(TokenEndpoint, new NameValueCollection
{
{ "code", authorizationCode },
{ "client_id", _appId },
{ "client_secret", _appSecret },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
});
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
string accessToken = null;
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
// handle response from FB
// this will not be a url with params like the first request to get the 'code'
Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);
using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));
Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
accessToken = desirializedJsonObject["access_token"].ToString();
}
return accessToken;
}
private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
{
var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
var qs = String.Join("&", keyValuePairs);
var builder = new UriBuilder(baseUri) { Query = qs };
return builder.Uri;
}
/// <summary>
/// Facebook works best when return data be packed into a "state" parameter.
/// This should be called before verifying the request, so that the url is rewritten to support this.
/// </summary>
public static void RewriteRequest()
{
var ctx = HttpContext.Current;
var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
if (stateString == null || !stateString.Contains("__provider__=facebook"))
return;
var q = HttpUtility.ParseQueryString(stateString);
q.Add(ctx.Request.QueryString);
q.Remove("state");
ctx.RewritePath(ctx.Request.Path + "?" + q);
}
}
Look here you I have replaces all the API links by newer version links.
Now you need to modify your
AuthConfig
Just use a wrapper class instead of RegisterFacebookClient. Completely block those portion of code. And add this...
OAuthWebSecurity.RegisterClient(new FacebookClientV2Dot3("AppID", "HassedPassword"));
Then all success. You facebook login will be back in previous state.
However you can face a new issue regarding this new API rather than previous API, the problem is that IP Whitelisting. Like this image. Hope you will need nothing but this. Happy coding.
As suggested by #SteveTerry We need to update QueryAccessToken function in FacebookClient class. unfortunately "FacebookClient" is sealed class so we cannot inherit and override. So whichever way you choose is up to you. Here is what end result should look like:
Old code of this function was:
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) {
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(TokenEndpoint);
builder.AppendQueryArgs(
new Dictionary<string, string> {
{ "client_id", this.appId },
{ "redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri) },
{ "client_secret", this.appSecret },
{ "code", authorizationCode },
{ "scope", "email" },
});
using (webclient client = new webclient()) {
string data = client.downloadstring(builder.uri);
if (string.isnullorempty(data)) {
return null;
}
var parsedquerystring = httputility.parsequerystring(data);
return parsedquerystring["access_token"];
}
}
And to support new version of fb api it should be like this:
/// <summary>
/// Contains access_token of a Facebook user.
/// </summary>
[DataContract]
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Facebook", Justification = "Brand name")]
public class FacebookAccessTokenData
{
#region Public Properties
/// <summary>
///
/// </summary>
[DataMember(Name = "access_token")]
public string AccessToken { get; set; }
/// <summary>
///
/// </summary>
[DataMember(Name = "token_type")]
public string TokenType { get; set; }
/// <summary>
///
/// </summary>
[DataMember(Name = "expires_in")]
public string ExpiresIn { get; set; }
#endregion
}
/// <summary>
/// Obtains an access token given an authorization code and callback URL.
/// </summary>
/// <param name="returnUrl">
/// The return url.
/// </param>
/// <param name="authorizationCode">
/// The authorization code.
/// </param>
/// <returns>
/// The access token.
/// </returns>
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) {
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(TokenEndpoint);
builder.AppendQueryArgs(
new Dictionary<string, string> {
{ "client_id", this.appId },
{ "redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri) },
{ "client_secret", this.appSecret },
{ "code", authorizationCode },
{ "scope", "email" },
});
FacebookAccessTokenData graphData;
var request = WebRequest.Create(builder.Uri);
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
graphData = JsonHelper.Deserialize<FacebookAccessTokenData>(responseStream);
}
}
return graphData.AccessToken;
}
I solve my issue I will do the same thing as #Adam described in his answer, As per the #Adam, #SteveTerry and #Adeem's answer i change my code and create custom FacebookClient class with different name. and replace with original reference of FacebookClient in nopCommerce.
public class FacebookOAuth2Client : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The user info endpoint.
/// </summary>
private const string UserInfoEndpoint = "https://graph.facebook.com/me";
/// <summary>
/// The app id.
/// </summary>
private readonly string _appId;
/// <summary>
/// The app secret.
/// </summary>
private readonly string _appSecret;
/// <summary>
/// The requested scopes.
/// </summary>
private readonly string[] _requestedScopes;
#endregion
/// <summary>
/// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
public FacebookClient(string appId, string appSecret)
: this(appId, appSecret, new[] { "email" }) { }
/// <summary>
/// Creates a new Facebook OAuth2 client.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
/// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
: base("facebook")
{
if (string.IsNullOrWhiteSpace(appId))
throw new ArgumentNullException("appId");
if (string.IsNullOrWhiteSpace(appSecret))
throw new ArgumentNullException("appSecret");
if (requestedScopes == null)
throw new ArgumentNullException("requestedScopes");
if (requestedScopes.Length == 0)
throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");
_appId = appId;
_appSecret = appSecret;
_requestedScopes = requestedScopes;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);
return BuildUri(AuthorizationEndpoint, new NameValueCollection
{
{ "client_id", _appId },
{ "scope", string.Join(" ", _requestedScopes) },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
{ "state", state },
});
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
using (var webResponse = webRequest.GetResponse())
using (var stream = webResponse.GetResponseStream())
{
if (stream == null)
return null;
using (var textReader = new StreamReader(stream))
{
var json = textReader.ReadToEnd();
var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());
data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));
return data;
}
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var uri = BuildUri(TokenEndpoint, new NameValueCollection
{
{ "code", authorizationCode },
{ "client_id", _appId },
{ "client_secret", _appSecret },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
});
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
string accessToken = null;
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
// handle response from FB
// this will not be a url with params like the first request to get the 'code'
Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);
using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));
Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
accessToken = desirializedJsonObject["access_token"].ToString();
}
return accessToken;
}
private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
{
var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
var qs = String.Join("&", keyValuePairs);
var builder = new UriBuilder(baseUri) { Query = qs };
return builder.Uri;
}
/// <summary>
/// Facebook works best when return data be packed into a "state" parameter.
/// This should be called before verifying the request, so that the url is rewritten to support this.
/// </summary>
public static void RewriteRequest()
{
var ctx = HttpContext.Current;
var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
if (stateString == null || !stateString.Contains("__provider__=facebook"))
return;
var q = HttpUtility.ParseQueryString(stateString);
q.Add(ctx.Request.QueryString);
q.Remove("state");
ctx.RewritePath(ctx.Request.Path + "?" + q);
}
}
in this code i have no reference of JsonHelper so i use simple jsonConvert. Thanx again #Adam, #SteveTerry and #Adeem's for help.