I'm trying to create a handler that will produce a popup when a message is received from SignalR which is running in a service. I've got this working in a non-service, but it won't work in a service.
This code works from a non-service:
client.OnMessageReceived +=
(sender2, message) =>
RunOnUiThread(() =>
showMessage(message));
Where client is the SignalR client and showMessage is the method called when a message is received from client. No problem.
Now I want to run the client in/as a service and I need to wire up a handler to do basically the same thing. I've tried several methods I found on StackOverflow and other sites, but all the stuff out there is java, not c# for Xamarin for Visual Studio (2017) and does not translate well. I'm at a loss as to how to proceed.
* Update *
This is my forground service code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Acr.UserDialogs;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Views;
using Android.Widget;
using ChatClient.Shared;
using Java.Lang;
namespace OML_Android
{
public class SignalRService : Service
{
public const int SERVICE_RUNNING_NOTIFICATION_ID = 10000;
public const string ACTION_MAIN_ACTIVITY = "OML_Android.action.MainActivity";
public const string SERVICE_STARTED_KEY = "has_service_been_started";
bool isStarted;
Handler handler;
Action runnable;
// This information will eventually be pulled from the intent, this is just for testing
public string firstname = "";
public string lastname = "";
public string username = "";
public string name = "";
private Client mInstance;
public override IBinder OnBind(Intent intent)
{
return null;
// throw new NotImplementedException();
}
public override void OnCreate()
{
base.OnCreate();
mInstance = Client.Getinstance(name, username, firstname, lastname);
mInstance.Connect();
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
return StartCommandResult.Sticky;
}
public override void OnDestroy()
{
// Not sure what to do here yet, got to get the service working first
}
void RegisterForegroundService()
{
var notification = new NotificationCompat.Builder(this)
.SetContentTitle(Resources.GetString(Resource.String.app_name))
.SetContentText(Resources.GetString(Resource.String.notification_text))
.SetSmallIcon(Resource.Drawable.alert_box)
.SetContentIntent(BuildIntentToShowMainActivity())
.SetOngoing(true)
//.AddAction(BuildRestartTimerAction())
//.AddAction(BuildStopServiceAction())
.Build();
// Enlist this instance of the service as a foreground service
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
}
PendingIntent BuildIntentToShowMainActivity()
{
var notificationIntent = new Intent(this, typeof(MainActivity));
notificationIntent.SetAction(ACTION_MAIN_ACTIVITY);
notificationIntent.SetFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTask);
notificationIntent.PutExtra(SERVICE_STARTED_KEY, true);
var pendingIntent = PendingIntent.GetActivity(this, 0, notificationIntent, PendingIntentFlags.UpdateCurrent);
return pendingIntent;
}
public async void showMessage(string message)
{
var result = await UserDialogs.Instance.ConfirmAsync(new ConfirmConfig
{
Message = "Text Message from Company: " + System.Environment.NewLine + message,
OkText = "Ok",
});
if (result)
{
// do something
var x = message;
}
}
}
}
This service sets the client to run as a foreground service (I assume), the client code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Client;
namespace ChatClient.Shared
{
public sealed class Client
{
//public string username;
private string username = "";
private string _platform = "";
private readonly HubConnection _connection;
private readonly IHubProxy _proxy;
public string _Username
{
get { return username; }
set { username = value; }
}
public string _Platform
{
get { return _platform; }
set { _platform = value; }
}
public event EventHandler<string> OnMessageReceived;
public static Client instance = null;
public Client(string name, string username, string firstname, string lastname, string company, string department, string section)
{
_Username = username;
_Platform = name;
_platform = _Platform;
Dictionary<string, string> queryString = new Dictionary<string, string>();
queryString.Add("username", username);
queryString.Add("firstname", firstname);
queryString.Add("lastname", lastname);
queryString.Add("company", company);
queryString.Add("department", department);
queryString.Add("section", section);
_connection = new HubConnection("https://www.example.com/SignalRhub",queryString );
_proxy = _connection.CreateHubProxy("chathub");
}
public static Client Getinstance(string name, string username, string firstname, string lastname)
{
// create the instance only if the instance is null
if (instance == null)
{
// The username and user's name are set before instantiation
instance = new Client(name, username, firstname, lastname,"","","");
}
// Otherwise return the already existing instance
return instance;
}
public async Task Connect()
{
await _connection.Start(); //.ContinueWith._connection.server.registerMeAs("");
_proxy.On("broadcastMessage", (string platform, string message) =>
{
if (OnMessageReceived != null)
OnMessageReceived(this, string.Format("{0}: {1}", platform, message));
});
// Send("Connected");
}
public async Task<List<string>> ConnectedUsers()
{
List<string> Users = await _proxy.Invoke<List<string>>("getConnectedUsers");
return Users;
}
public async Task<List<string>> ConnectedSectionUsers(string company, string department, string section, string username)
{
List<string> Users = await _proxy.Invoke<List<string>>("getConnectedSectionUsers",company, department, section, username);
return Users;
}
public Task Send(string message)
{
return _proxy.Invoke("Send", _platform, message);
}
public Task SendSectionMessage(string company, string department, string section, string name, string message)
{
return _proxy.Invoke("messageSection", company, department, section, name, message);
}
public Task SendCompanyMessage(string company, string department, string section, string name, string message)
{
return _proxy.Invoke("messageCompany", company, name, message);
}
}
}
This is the code I plan to use to start the service (not working yet), I will be adding information to the intent via intent.PutExtras, namely Firstname, lastname, username, name, company, department and section. For now I just set them to null string in the service for testing purposes.:
public static void StartForegroundServiceComapt<SignalRService>(this Context context, Bundle args = null) where SignalRService : Service
{
var intent = new Intent(context, typeof(SignalRService));
if (args != null)
{
intent.PutExtras(args);
}
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
context.StartForegroundService(intent);
}
else
{
context.StartService(intent);
}
}
This all works as expected, I still need to do some work to clean it up but it is working. I am able to connect to the server hub and send messages to the correct groups of people. Now I need to get this to run as a foreground service.
Is your app creating a notification channel? You should be passing the notification channel ID to the NotificationCompat.Builder constructor.
It doesn't look like you're calling the RegisterForegroundService method to promote the service to a foreground service. You'll want to call RegisterForegroundService early in the OnCreate override. Modern versions of Android require a foreground service to show a notification within a few seconds or an exception will be thrown.
You may want to add the android.permission.FOREGROUND_SERVICE permission to your Android Manifest because it's required on Android P and later.
I don't think ACR.UserDialogs will work if your app has no current top activity. The service outlives the activity so it's very possible to run into this scenario. You can simply have the service update the existing foreground notification to show the user a new message is available.
Related
How do I send mobile push notification without using Business Event ?
It is possible to send mobile push notification without using Business Events to open screen in mobile application when user taps the notification.
Below example describes approach :
using System;
using System.Collections;
using System.Linq;
using System.Threading;
using CommonServiceLocator;
using PX.Api.Mobile.PushNotifications;
using PX.Common;
using PX.Data;
using PX.Objects.SO;
namespace PX.PushNotificatioinSample.Ext
{
public class SOOrderEntryPXExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> ViewOnMobileApp;
[PXUIField(DisplayName = Messages.ViewActionDisplayName,
MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Default)]
public virtual IEnumerable viewOnMobileApp(PXAdapter adapter)
{
if (Base.Document.Cache.GetStatus(Base.Document.Current) == PXEntryStatus.Inserted ||
Base.Document.Cache.GetStatus(Base.Document.Current) == PXEntryStatus.InsertedDeleted) { return adapter.Get(); }
//Get instance of PushNotification service
var pushNotificationSender = ServiceLocator.Current.GetInstance<IPushNotificationSender>();
//Users to whom message will be sent
var userIds = new[] { PXAccess.GetUserID() };
//Check if User is using Acumatica Mobile App
var activeTokens = pushNotificationSender.CountActiveTokens(userIds);
if (activeTokens == 0)
{
throw new PXException(Messages.NoDeviceError);
}
string sOrderNbr = Base.Document.Current.OrderNbr;
string sScreenID = Base.Accessinfo.ScreenID.Replace(".", "");
Guid noteID = Base.Document.Current.NoteID.Value;
PXLongOperation.StartOperation(Base, () =>
{
try
{
pushNotificationSender.SendNotificationAsync(
userIds: userIds,
// Push Notification Title
title: Messages.PushNotificationTitle,
// Push Notification Message Body
text: $"{ Messages.PushNotificationMessageBody } { sOrderNbr }.",
// Link to Screen to open upon tap with Sales Order data associated to NoteID
link: (sScreenID, noteID),
cancellation: CancellationToken.None);
}
catch (AggregateException ex)
{
var message = string.Join(";", ex.InnerExceptions.Select(c => c.Message));
throw new InvalidOperationException(message);
}
});
return adapter.Get();
}
}
[PXLocalizable]
public static class Messages
{
public const string ViewActionDisplayName = "View On Mobile App";
public const string NoDeviceError = "You need to set up the Acumatica mobile app.";
public const string PushNotificationTitle = "View Sales Order";
public const string PushNotificationMessageBody = "Tap to view Sales Order # ";
}
}
I have created a chat using SignalR2. The client and server itself works fine. Now, I'm trying to implement a 'users online' function. The server code seems about right, but I'm struggling to make the client receive the data that the server pushes back to the client.
This is the server code below:
public static List<string> Users = new List<string>();
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(name, message);
Clients.All.addMessage(name, message);
}
public void SendUserList(List<string> users)
{
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.updateUserList(users);
}
public override Task OnConnected()
{
string clientId = GetClientId();
//if (Users.IndexOf(clientId) == -1)
//{
Users.Add(clientId);
//}
SendCount(Users.Count);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
System.Diagnostics.Debug.WriteLine("Disconnected");
SendCount(Users.Count);
return base.OnDisconnected(stopCalled);
}
private string GetClientId()
{
string clientId = "";
if (Context.QueryString["clientId"] != null)
{
// clientId passed from application
clientId = this.Context.QueryString["clientId"];
}
if (string.IsNullOrEmpty(clientId.Trim()))
{
clientId = Context.ConnectionId;
}
return clientId;
}
public void SendCount(int count)
{
// Call the addNewMessageToPage method to update clients.
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.updateUsersOnlineCount(count);
}
Below is the client code for connecting / receiving messages:
public static async void ConnectAsync(RadChat ChatInternal)
{
ChatInternal.Author = new Author(null, Varribles.Agent);
var querystringData = new Dictionary<string, string>();
querystringData.Add("clientId", Varribles.Agent);
Connection = new HubConnection(ServerURI, querystringData);
HubProxy = Connection.CreateHubProxy("chatHub");
//Handle incoming event from server: use Invoke to write to console from SignalR's thread
HubProxy.On<string, string>("AddMessage", (name, message) =>
ChatInternal.Invoke((Action)(() =>
Backend.GET.Messages(ChatInternal)
)));
try
{
await Connection.Start();
Backend.GET.Messages(ChatInternal);
}
catch (System.Net.Http.HttpRequestException)
{
//No connection: Don't enable Send button or show chat UI
return;
}
}
Now, my question is, how can I retrieve the 'Users' list from the server?
Thanks in advance
I am setting up push notifications in my Xamarin Forms app for my Android project. I have been following the online documentation (https://developer.xamarin.com/guides/xamarin-forms/cloud-services/push-notifications/azure/) to get it working but I am having some problems
1 - The notifications don't arrive when I send a test from my Azure Notification Hub.
2 - The app crashes during debug after a certain amount of time during the Register method.
I believe that the problem is being caused by its registration, I don't think the registration is working and hence the app times out which causes it to crash and also the Azure Notification Hub sends the message successfully but no device picks it up (possibly because it's not registred)
To start off, here is what I have done so far.
I have setup a project in console.firebase.google.com and taken note of the sender ID and the Server Key as instructed. I have given the Project ID a name of myapp-android-app.
I have added all the code from the documtation to my Android project.
I have added a GCM notification service to my Notification Hub in Azure and supplied it with the Server Key documented from my Firebase app registration.
I have added my google account to my Andoird Emulator
Here is the source code from my Android project.
MainActivity.cs
using System;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Gcm.Client;
namespace MyApp.Droid
{
[Activity(Label = "#string/app_name", Theme = "#style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
//public static MainActivity CurrentActivity { get; private set; }
// Create a new instance field for this activity.
static MainActivity instance = null;
// Return the current activity instance.
public static MainActivity CurrentActivity
{
get
{
return instance;
}
}
protected override void OnCreate(Bundle bundle)
{
instance = this;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
LoadApplication(new App());
try
{
// Check to ensure everything's setup right
GcmClient.CheckDevice(this);
GcmClient.CheckManifest(this);
// Register for push notifications
System.Diagnostics.Debug.WriteLine("Registering...");
GcmClient.Register(this, PushHandlerBroadcastReceiver.SENDER_IDS);
}
catch (Java.Net.MalformedURLException)
{
CreateAndShowDialog("There was an error creating the client. Verify the URL.", "Error");
}
catch (Exception e)
{
CreateAndShowDialog(e.Message, "Error");
}
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
}
void CreateAndShowDialog(String message, String title)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle(title);
builder.Create().Show();
}
}
}
GcmService.cs
using Android.App;
using Android.Content;
using Android.Media;
using Android.Support.V4.App;
using Android.Util;
using Gcm.Client;
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
[assembly: Permission(Name = "#PACKAGE_NAME#.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "#PACKAGE_NAME#.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]
[assembly: UsesPermission(Name = "android.permission.INTERNET")]
[assembly: UsesPermission(Name = "android.permission.WAKE_LOCK")]
//GET_ACCOUNTS is only needed for android versions 4.0.3 and below
[assembly: UsesPermission(Name = "android.permission.GET_ACCOUNTS")]
namespace MyApp.Droid
{
[BroadcastReceiver(Permission = Gcm.Client.Constants.PERMISSION_GCM_INTENTS)]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "#PACKAGE_NAME#" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "#PACKAGE_NAME#" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "#PACKAGE_NAME#" })]
public class PushHandlerBroadcastReceiver : GcmBroadcastReceiverBase<GcmService>
{
public static string[] SENDER_IDS = new string[] { "xxxxxxxxxx" };
}
[Service]
public class GcmService : GcmServiceBase
{
public static string RegistrationToken { get; private set; }
public GcmService()
: base(PushHandlerBroadcastReceiver.SENDER_IDS) { }
protected override void OnRegistered(Context context, string registrationToken)
{
Log.Verbose("PushHandlerBroadcastReceiver", "GCM Registered: " + registrationToken);
RegistrationToken = registrationToken;
var push = AzureService.DefaultManager.CurrentClient.GetPush();
MainActivity.CurrentActivity.RunOnUiThread(() => Register(push, null));
}
protected override void OnUnRegistered(Context context, string registrationToken)
{
Log.Error("PushHandlerBroadcastReceiver", "Unregistered RegisterationToken: " + registrationToken);
}
protected override void OnError(Context context, string errorId)
{
Log.Error("PushHandlerBroadcastReceiver", "GCM Error: " + errorId);
}
public async void Register(Microsoft.WindowsAzure.MobileServices.Push push, IEnumerable<string> tags)
{
try
{
const string templateBodyGCM = "{\"data\":{\"message\":\"$(messageParam)\"}}";
JObject templates = new JObject();
templates["genericMessage"] = new JObject
{
{"body", templateBodyGCM}
};
await push.RegisterAsync(RegistrationToken, templates);
Log.Info("Push Installation Id", push.InstallationId.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
Debugger.Break();
}
}
protected override void OnMessage(Context context, Intent intent)
{
Log.Info("PushHandlerBroadcastReceiver", "GCM Message Received!");
var msg = new StringBuilder();
if (intent != null && intent.Extras != null)
{
foreach (var key in intent.Extras.KeySet())
msg.AppendLine(key + "=" + intent.Extras.Get(key).ToString());
}
// Retrieve the message
var prefs = GetSharedPreferences(context.PackageName, FileCreationMode.Private);
var edit = prefs.Edit();
edit.PutString("last_msg", msg.ToString());
edit.Commit();
string message = intent.Extras.GetString("message");
if (!string.IsNullOrEmpty(message))
{
CreateNotification("New todo item!", "Todo item: " + message);
}
string msg2 = intent.Extras.GetString("msg");
if (!string.IsNullOrEmpty(msg2))
{
CreateNotification("New hub message!", msg2);
return;
}
CreateNotification("Unknown message details", msg.ToString());
}
void CreateNotification(string title, string desc)
{
// Create notification
var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
// Create an intent to show the UI
var uiIntent = new Intent(this, typeof(MainActivity));
// Create the notification
// we use the pending intent, passing our ui intent over which will get called
// when the notification is tapped.
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
var notification = builder.SetContentIntent(PendingIntent.GetActivity(this, 0, uiIntent, 0))
.SetSmallIcon(Android.Resource.Drawable.SymActionEmail)
.SetTicker(title)
.SetContentTitle(title)
.SetContentText(desc)
.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification)) // set the sound
.SetAutoCancel(true).Build(); // remove the notification once the user touches it
// Show the notification
notificationManager.Notify(1, notification);
}
}
}
I replaced the string in: public static string[] SENDER_IDS = new string[] { "xxxxxxxx" }; with the Sender id I recorded from my Firebase console app.
When I run the app through my Android Emulator I get the following error after about 3 minutes.
Error in GcmService.cs:
Exception: System.Threading.Tasks.TaskCanceledException: A task was canceled.
seen at Line 71:
await push.RegisterAsync(RegisteToken, templates);
Does anyone know what I have done wrong? Am I correct in thinking that it's not registering correctly?
I was able to integrate Firebase Cloud Messaging in Xamarin.Forms (both Android and iOS) following this official Xamarin guide that is well explained:
https://developer.xamarin.com/guides/android/application_fundamentals/notifications/remote-notifications-with-fcm/
Looking at your code, it seems to me that you use the old Gcm service (that is deprecated) and not the new Fcm.
You should have a google-services.json file and a FirebaseMessagingService class
Then...
Note
As of now, Xamarin Forms does not handle very well the deployment of the app while developing, making a dirty reinstallation that seems to invalidate the previously obtained Notification Token and sometimes raising IllegalStateException when calling Fcm library's methods.
That's a known bug that can be resolved by cleaning the solution and
relaunching
I am trying to write a simple chat application with iOS and Android clients. I am following the tutorial here http://blogs.msdn.com/b/youssefm/archive/2012/07/17/building-real-time-web-apps-with-asp-net-webapi-and-websockets.aspx to use WebSockets. However, I need to be able to send messages to a single "Chatroom" rather than broadcast to all the users (in reality there will only ever be 2 users, me and the person I'm chatting with). Currently I am doing this:
using Microsoft.Web.WebSockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
namespace ChatPractice.Controllers
{
public class ChatController : ApiController
{
public HttpResponseMessage Get(string username, int roomId)
{
HttpContext.Current.AcceptWebSocketRequest(new ChatWebSocketHandler(username, roomId));
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
}
class ChatWebSocketHandler : WebSocketHandler
{
private static WebSocketCollection _chatClients = new WebSocketCollection();
private string _username;
private int _roomId;
public ChatWebSocketHandler(string username, int roomId)
{
_username = username;
_roomId = roomId;
}
public override void OnOpen()
{
_chatClients.Add(this);
}
public override void OnMessage(string message)
{
var room = _chatClients.Where(x => ((ChatWebSocketHandler)x)._roomId == _roomId);
message = _username + ": " + message;
foreach (var user in room)
user.Send(message);
//_chatClients.Broadcast(_username + ": " + message);
}
}
}
Is using linq to get the correct room here correct or is there a better way to get to it?
This is an example of a chat server using WebSockets: Chat application using Reactive Extennsions.
It uses another WebSocket framework, but the idea should be pretty much the same.
It's better to use SignalR. There's a lot of tutorials online.
i am trying to search in amazon product database with the following code posted in amazon webservice sample codes page
AWSECommerceService ecs = new AWSECommerceService();
// Create ItemSearch wrapper
ItemSearch search = new ItemSearch();
search.AssociateTag = "ABC";
search.AWSAccessKeyId = "XYZ";
// Create a request object
ItemSearchRequest request = new ItemSearchRequest();
// Fill request object with request parameters
request.ResponseGroup = new string[] { "ItemAttributes" };
// Set SearchIndex and Keywords
request.SearchIndex = "All";
request.Keywords = "The Shawshank Redemption";
// Set the request on the search wrapper
search.Request = new ItemSearchRequest[] { request };
try
{
//Send the request and store the response
//in response
ItemSearchResponse response = ecs.ItemSearch(search);
gvRes.DataSource = response.Items;
}
catch (Exception ex)
{
divContent.InnerText = ex.Message;
}
and getting the following error
The request must contain the parameter
Signature.
and amazon documentation is not clear about how to sign the requests.
any idea how to make it work???
thx
i transcribed this vb code and it works for me
add the service reference and name it Amazon
http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl
go into the folder where your project is hosted, open the service reference folder and open the Reference.cs, then replace all the occurrences of [][] with [], next open AWSECommerceService.wsdl and find
<xs:element minOccurs="0" maxOccurs="unbounded" name="ImageSets">
and replace with
<xs:element minOccurs="0" maxOccurs="1" name="ImageSets">
add the following, and you'll need to manually reference some dlls
using System.Security.Cryptography;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.Text.RegularExpressions;
using System.Xml;
using System.IO;
using System.Runtime.Serialization;
using AmazonApiTest.Amazon; //instead of AmazonApiTest use your project name
first various interface implementations
public class AmazonSigningMessageInspector : IClientMessageInspector
{
private string accessKeyId = "";
private string secretKey = "";
public AmazonSigningMessageInspector(string accessKeyId, string secretKey)
{
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
}
public Object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
{
string operation = Regex.Match(request.Headers.Action, "[^/]+$").ToString();
DateTime now = DateTime.UtcNow;
String timestamp = now.ToString("yyyy-MM-ddTHH:mm:ssZ");
String signMe = operation + timestamp;
Byte[] bytesToSign = Encoding.UTF8.GetBytes(signMe);
Byte[] secretKeyBytes = Encoding.UTF8.GetBytes(secretKey);
HMAC hmacSha256 = new HMACSHA256(secretKeyBytes);
Byte[] hashBytes = hmacSha256.ComputeHash(bytesToSign);
String signature = Convert.ToBase64String(hashBytes);
request.Headers.Add(new AmazonHeader("AWSAccessKeyId", accessKeyId));
request.Headers.Add(new AmazonHeader("Timestamp", timestamp));
request.Headers.Add(new AmazonHeader("Signature", signature));
return null;
}
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message Message, Object correlationState)
{
}
}
public class AmazonSigningEndpointBehavior : IEndpointBehavior
{
private string accessKeyId = "";
private string secretKey = "";
public AmazonSigningEndpointBehavior(string accessKeyId, string secretKey)
{
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
}
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new AmazonSigningMessageInspector(accessKeyId, secretKey));
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatched)
{
}
public void Validate(ServiceEndpoint serviceEndpoint)
{
}
public void AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParemeters)
{
}
}
public class AmazonHeader : MessageHeader
{
private string m_name;
private string value;
public AmazonHeader(string name, string value)
{
this.m_name = name;
this.value = value;
}
public override string Name
{
get { return m_name; }
}
public override string Namespace
{
get { return "http://security.amazonaws.com/doc/2007-01-01/"; }
}
protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteString(value);
}
}
now you use the generated code in this way
ItemSearch search = new ItemSearch();
search.AssociateTag = "YOUR ASSOCIATE TAG";
search.AWSAccessKeyId = "YOUR AWS ACCESS KEY ID";
ItemSearchRequest req = new ItemSearchRequest();
req.ResponseGroup = new string[] { "ItemAttributes" };
req.SearchIndex = "Books";
req.Author = "Lansdale";
req.Availability = ItemSearchRequestAvailability.Available;
search.Request = new ItemSearchRequest[]{req};
Amazon.AWSECommerceServicePortTypeClient amzwc = new Amazon.AWSECommerceServicePortTypeClient();
amzwc.ChannelFactory.Endpoint.EndpointBehaviors.Add(new AmazonSigningEndpointBehavior("ACCESS KEY", "SECRET KEY"));
ItemSearchResponse resp = amzwc.ItemSearch(search);
foreach (Item item in resp.Items[0].Item)
Console.WriteLine(item.ItemAttributes.Author[0] + " - " + item.ItemAttributes.Title);
There's a helper class for REST called SignedRequestHelper.
You call it like so:
SignedRequestHelper helper =
new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION);
requestUrl = helper.Sign(querystring);
There must be a similar one for SOAP calls in the above links.
try this one.. i hope it'll help.. i try and it works.. please share it with others.
download the sample code on http://www.falconwebtech.com/post/Using-WCF-and-SOAP-to-Send-Amazon-Product-Advertising-API-Signed-Requests
we need to update service references, make little change at app.config, program.cs, and reference.cs.
app.config:
(1.) appSettings tag;
assign accessKeyId and secretKey value,
add .
(2.) behaviours tag -> endpointBehaviors tag -> behaviour tag -> signingBehavior tag;
assign accessKeyId and secretKey value.
(3.) bindings tag -> basicHttpBinding tag; (optional)
delete binding tag except AWSECommerceServiceBindingNoTransport
and AWSECommerceServiceBindingTransport.
(4.) client tag;
delete endpoint tag except AWSECommerceServiceBindingTransport.
program.cs:
add itemSearch.AssociateTag = ConfigurationManager.AppSettings["associateTag"]; before ItemSearchResponse response = amazonClient.ItemSearch(itemSearch);
reference.cs: (open file in service references folder using visual studio)
change private ImageSet[][] imageSetsField; to private ImageSet[] imageSetsField;
change public ImageSet[][] ImageSets {...} to public ImageSet[] ImageSets {...}
finally we can run our program and it will work. good luck..
nb: there will be 1 warning (invalid child element signing behaviour), i think we can ignore it, or if you have any solution please share.. ^^v..