Forward Hero Card from Skype Chatbot with functional buttons (or deeplinking chatbot and passing a reference) - botframework

Is there a way to create a hero card that has buttons that keep working when it is forwarded to another user?
We want to build a chatbot that enables you to share things with someone else. So first you talk to the chatbot (mainly via buttons) to setup a thing and then you forward that to one of your contacts so that they can participate. With Facebook Messenger we can directly call a share action that pops up the share dialog with which the user can forward a card to a contact. Using a m.me/123?ref=456 URL in a button of the card the receiver can open a conversation with the chatbot in the correct context. I managed to do something similar for Telegram.
I try to replicate this with other services. With Skype there is no way to explicitly call the share action, but I can write a message "Please forward the next card to the user you want to share this with:" [and then I display that card]. Now I give that card a button with a postBack action that should open the correct context, but that button seems to be non functional. When tapping it no post back is sent to the chatbot.
Is there a way to implement something like what I described for Facebook Messenger with Skype? Or is there a way to deep link to a chatbot passing some reference?
(I'd rather have the later for a second use case anyway, where a user puts a share button on their website that then opens Skype in a conversation with the chatbot, passing the reference. I have that for Messenger and Telegram.)
Edit: What I would like to do would basically be the following. I use nodejs, but only the ChatConnector class and not the UniversalBot class.
connector.send([
{
type: 'message',
address,
textFomrat: 'plain',
text: 'Forward the next message to people you want to share THING with:',
},
{
type: 'message',
address,
attachments: [{
contentType: 'application/vnd.microsoft.card.hero',
content: {
title: name,
images: [{
url: image_url
}],
buttons: [{
type: 'postBack',
title: 'Chat with this bot about THING',
value: 'open:' + thing_id,
}]
}
}],
}
], callback);

I'm not sure how you're sending cards to the other user, but here's an example in .net where the buttons sent will function as expected for the other user:
using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.FormFlow;
using System.Collections.Generic;
using System.Collections.Concurrent;
using Microsoft.Bot.Builder.ConnectorEx;
using Newtonsoft.Json;
namespace CardsBot
{
[Serializable]
public class SendToUserDialog : IDialog<object>
{
static ConcurrentDictionary<string, ConversationReference> _cachedConversationReferences = new ConcurrentDictionary<string, ConversationReference>();
private const string HelpString = "Enter 'Send Card' to send a card to another user. 'Send Message' to send a json message to another user. Or 'List Users' to see who is ready to receive messages.";
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
string upperText = message.Text.Replace(" ", "").ToUpper();
if (upperText == "SENDCARD")
{
IFormDialog<SendCardForm> form = new FormDialog<SendCardForm>(new SendCardForm(), SendCardForm.BuildForm, FormOptions.PromptInStart);
context.Call(form, AfterSendCardComplete);
}
else if (upperText == "SENDMESSAGE")
{
IFormDialog<SendMessageForm> form = new FormDialog<SendMessageForm>(new SendMessageForm(), SendMessageForm.BuildForm, FormOptions.PromptInStart);
context.Call(form, AfterSendMessageFormComplete);
}
else if (upperText == "LISTUSERS")
{
var names = String.Join(", ", _cachedConversationReferences.Keys);
await context.PostAsync($"Users : {names}");
}
else
{
if (!context.UserData.ContainsKey("name"))
{
var getNamePrompt = new PromptDialog.PromptString("What is your name?", "Please enter your name.", 3);
context.Call(getNamePrompt, AfterNamePrompt);
}
else
{
await context.PostAsync($"You said: {message.Text} ({HelpString})");
context.Wait(MessageReceivedAsync);
}
}
}
private async Task AfterSendMessageFormComplete(IDialogContext context, IAwaitable<SendMessageForm> result)
{
var sendMessageForm = await result;
if (string.IsNullOrEmpty(sendMessageForm.RecipientName))
{
await context.PostAsync("A recipient name was not provided. Cannot send the message to nobody.");
}
else
{
ConversationReference conversation = null;
if (_cachedConversationReferences.TryGetValue(sendMessageForm.RecipientName, out conversation))
{
var originalReply = conversation.GetPostToBotMessage().CreateReply();
var replyMessage = GetMessage(originalReply, sendMessageForm.MessageJson);
var connector = new ConnectorClient(new Uri(originalReply.ServiceUrl));
await connector.Conversations.ReplyToActivityAsync(replyMessage);
await context.PostAsync($"Message was sent to {sendMessageForm.RecipientName} on {replyMessage.ChannelId} channel.");
}
else
{
await context.PostAsync($"No recipient found matching the name {sendMessageForm.RecipientName}");
}
}
}
private async Task AfterNamePrompt(IDialogContext context, IAwaitable<string> result)
{
var name = await result;
context.UserData.SetValue("name", name);
await context.PostAsync($"Thanks. What would you like to do now {name}? {HelpString}");
var conversationReference = context.Activity.ToConversationReference();
_cachedConversationReferences.AddOrUpdate(name, conversationReference, (oldVal, newVal) => conversationReference);
}
public Activity GetMessage(Activity originalReply, string messageJson)
{
var reply = JsonConvert.DeserializeObject<Activity>(messageJson);
reply.ChannelId = originalReply.ChannelId;
reply.Timestamp = originalReply.Timestamp;
reply.From = originalReply.From;
reply.Conversation = originalReply.Conversation;
reply.Recipient = originalReply.Recipient;
reply.Id = originalReply.Id;
reply.ReplyToId = originalReply.ReplyToId;
reply.ServiceUrl = originalReply.ServiceUrl;
return reply;
}
private async Task AfterSendCardComplete(IDialogContext context, IAwaitable<SendCardForm> result)
{
var SendCardForm = await result;
if (string.IsNullOrEmpty(SendCardForm.RecipientName))
{
await context.PostAsync("A recipient name was not provided. Cannot send a card to nobody.");
}
else
{
ConversationReference conversation = null;
if (_cachedConversationReferences.TryGetValue(SendCardForm.RecipientName, out conversation))
{
var reply = conversation.GetPostToBotMessage().CreateReply();
reply.Attachments.Add(GetCard(SendCardForm.Text, SendCardForm.Buttons.Value));
var connector = new ConnectorClient(new Uri(reply.ServiceUrl));
await connector.Conversations.ReplyToActivityAsync(reply);
}
else
{
await context.PostAsync($"No recipient found matching the name {SendCardForm.RecipientName}");
}
}
}
public Attachment GetCard(string text, int numberOfButtons)
{
var card = new HeroCard(text);
card.Buttons = new List<CardAction>(GetCardActions(numberOfButtons));
return card.ToAttachment();
}
private IEnumerable<CardAction> GetCardActions(int numberOfButtons)
{
for (int counter = 1; counter < numberOfButtons; counter++)
{
yield return new CardAction()
{
Title = $"button{counter}",
Type = ActionTypes.ImBack,
Value = $"button{counter}"
};
}
}
}
[Serializable]
public class SendMessageForm
{
[Prompt("Who would you like to send this message to (enter the name of the recipient)?")]
public string RecipientName { get; set; }
[Prompt("Paste in the .json of the message you would like to Send.")]
public string MessageJson { get; set; }
public static IForm<SendMessageForm> BuildForm()
{
return new FormBuilder<SendMessageForm>()
.AddRemainingFields()
.Confirm("Is this information correct?{*}")
.Build();
}
}
[Serializable]
public class SendCardForm
{
[Prompt("What would you like for the card text?")]
public string Text { get; set; }
[Prompt("How many buttons?")]
public int? Buttons { get; set; }
[Prompt("Who would you like to send the card to?")]
public string RecipientName { get; set; }
public static IForm<SendCardForm> BuildForm()
{
return new FormBuilder<SendCardForm>()
.AddRemainingFields()
.Confirm("Is this information correct?{*}")
.Build();
}
}
}

Related

Xamarin Form - Can't get return value data from Service class method into MainPage method for filtering

I'm new to Xamarin, I have an app in Xamarin-Form that it's fetching data from web api and getting user input from Entry control.
The web api service class is working fine and reaches the deserialization in the getCourses method as seen below in Code Snippet 1.
The Entry control as well is working fine until it retrieves the user input on the MainPage class, OnOkGetCourseButton method as seen below Code Snippet 2.
What I want to achieve is, inside MainPage.xaml.cs, I create a method that takes the user input data and check agaisnt the deseriaized json data (the Id specially),
if it finds the Id in deserialized List of data, then it can send the found data to another ViewPage and display them.
if It cannot find the data, it shows a dialog box.
So far, I tried to call Task<ObservableCollection> getCourses() method from the MainPage class, inside CheckCourseComplete as seen below but it giving me no value/nothing, some kind of null value.
I don't want to filter the user input against web api json response inside getCourses(),
I want to do that in a separate method to follow S-OLID (Single Responsibility Principle).
If it's not possible in a separate method, then I just need to get it worked.
Please what is the best way to achieve it?
Code Snippet 1
public class CourseService : ICourseService
{
string Base_Url = "https://www.test.com/api/TheCourse";
public async Task<ObservableCollection<Course>> getCourses()
{
try
{
string url = Base_Url;
HttpClient client = new HttpClient();
HttpResponseMessage responseMessage = await client.GetAsync(url);
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
var result = await responseMessage.Content.ReadAsStringAsync();
var deserializedClass = JsonConvert.DeserializeObject<ObservableCollection<Course>>(result);
// I don't want to do that here, as it will violate SRP (SOLID)
return deserializedClass;
}
return null;
}
catch (Exception)
{
throw;
}
}
}
Code Snippet 2
namespace CourseMobile
{
public partial class MainPage : ContentPage
{
private string _getEntryText;
private readonly CourseViewModel orderViewModel;
public Course FetchCourse { get; set; }
public MainPage()
{
InitializeComponent();
CheckCourseComplete();
BindingContext = new CourseViewModel();
}
public string GetEntryText
{
get => _getEntryText;
set => _getEntryText = value;
}
public async void OnOkGetCourseButton(object sender, EventArgs e)
{
var inputtedCourseNumber = this.GetEntryText;
if(inputtedCourseNumber == string.Empty)
{
await DisplayAlert("", "Please enter your Course number", "OK 3");
}
else
{
CheckCourseComplete();
this.GetEntryText = inputtedCourseNumber;
await DisplayAlert("New Text", inputtedCourseNumber, "OK 2");
}
}
void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
var newText = e.NewTextValue;
this.GetEntryText = newText;
}
public async void CheckCourseComplete()
{
CourseService myCourse = new CourseService();
await myCourse.getCourses(); // It doesn't return the json data (web api data)
// I need to check user input + (web api data) here
}
}
}
getCourses is async, so you need to use await when calling it
public async void CheckCourseComplete()
{
CourseService myCourse = new CourseService();
var data = await myCourse.getCourses();
// now filter data
}

How to call the Publisher Methods in ASPBoilerplate?

I am exploring the ASPBoilerplate framework and what interests me more is its real time notification capability. However, I am having this error.
Here is my Notification publisher class:
public class Publisher : Hub, ITransientDependency
{
private readonly INotificationPublisher _notificationPublisher;
public Publisher(INotificationPublisher notificationPublisher)
{
_notificationPublisher = notificationPublisher;
}
//Send a general notification to all subscribed users in current tenant (tenant in the session)
public async Task Publish_Announcement(string announcementMessage)
{
//Example "LowDiskWarningMessage" content for English -> "Attention! Only {remainingDiskInMb} MBs left on the disk!"
var data = new MessageNotificationData(announcementMessage);
await _notificationPublisher.PublishAsync("abp.notifications.received", data, severity: NotificationSeverity.Info);
}
}
And I am testing if it will notify all online users whenever a new user was created.
public override async Task<UserDto> Create(CreateUserDto input)
{
CheckCreatePermission();
var user = ObjectMapper.Map<User>(input);
user.TenantId = AbpSession.TenantId;
user.IsEmailConfirmed = true;
await _userManager.InitializeOptionsAsync(AbpSession.TenantId);
CheckErrors(await _userManager.CreateAsync(user, input.Password));
if (input.RoleNames != null)
{
CheckErrors(await _userManager.SetRoles(user, input.RoleNames));
}
CurrentUnitOfWork.SaveChanges();
//I cannot call the publisher class since it has dependencies in its contructor.
new Publisher().Publish_Announcement("Hi");
return MapToEntityDto(user);
}
Or am I just doing it wrong?
Please help. Thanks!
Basically, the Notifications is based on Publisher / Subsriber pattern and while the notifications are published through the system, the users who have subscribed to that notification will receive it.
Publish Notifications
public class MyService : ITransientDependency
{
private readonly INotificationPublisher _notificationPublisher;
public MyService(INotificationPublisher notificationPublisher)
{
_notificationPublisher = notificationPublisher;
}
//Send a general notification to a specific user
public async Task Publish_SentFriendshipRequest(string senderUserName, string friendshipMessage, UserIdentifier targetUserId)
{
await _notificationPublisher.PublishAsync("SentFriendshipRequest", new SentFriendshipRequestNotificationData(senderUserName, friendshipMessage), userIds: new[] { targetUserId });
}
//Send an entity notification to a specific user
public async Task Publish_CommentPhoto(string commenterUserName, string comment, Guid photoId, UserIdentifier photoOwnerUserId)
{
await _notificationPublisher.PublishAsync("CommentPhoto", new CommentPhotoNotificationData(commenterUserName, comment), new EntityIdentifier(typeof(Photo), photoId), userIds: new[] { photoOwnerUserId });
}
//Send a general notification to all subscribed users in current tenant (tenant in the session)
public async Task Publish_LowDisk(int remainingDiskInMb)
{
//Example "LowDiskWarningMessage" content for English -> "Attention! Only {remainingDiskInMb} MBs left on the disk!"
var data = new LocalizableMessageNotificationData(new LocalizableString("LowDiskWarningMessage", "MyLocalizationSourceName"));
data["remainingDiskInMb"] = remainingDiskInMb;
await _notificationPublisher.PublishAsync("System.LowDisk", data, severity: NotificationSeverity.Warn);
}
}
Subscribe to Notifications
public class MyService : ITransientDependency
{
private readonly INotificationSubscriptionManager _notificationSubscriptionManager;
public MyService(INotificationSubscriptionManager notificationSubscriptionManager)
{
_notificationSubscriptionManager = notificationSubscriptionManager;
}
//Subscribe to a general notification
public async Task Subscribe_SentFriendshipRequest(int? tenantId, long userId)
{
await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "SentFriendshipRequest");
}
//Subscribe to an entity notification
public async Task Subscribe_CommentPhoto(int? tenantId, long userId, Guid photoId)
{
await _notificationSubscriptionManager.SubscribeAsync(new UserIdentifier(tenantId, userId), "CommentPhoto", new EntityIdentifier(typeof(Photo), photoId));
}
}
Client-Side
This is done by signalr and you need to capture the event once it's triggered.
abp.event.on('abp.notifications.received', function (userNotification) {
if (userNotification.notification.data.type === 'Abp.Notifications.LocalizableMessageNotificationData') {
var localizedText = abp.localization.localize(
userNotification.notification.data.message.name,
userNotification.notification.data.message.sourceName
);
$.each(userNotification.notification.data.properties, function (key, value) {
localizedText = localizedText.replace('{' + key + '}', value);
});
alert('New localized notification: ' + localizedText);
} else if (userNotification.notification.data.type === 'Abp.Notifications.MessageNotificationData') {
alert('New simple notification: ' + userNotification.notification.data.message);
}
});
You can find more information here

Consuming WEB API Xamarin Forms

I have a WEB API hosted on a server, there I have a Products table with Name and Description.I already checked for the postman and this is ok, when I try to implement the method in xamarin by visual studio to bring a record by its name and display in a listview I receiving the following message
Can not implicitly convert type "void"in
"System.Collections.Generic.List"
public async void GetProductByName(string Name)
{
var client = new HttpClient(handler);
client.Timeout = TimeSpan.FromSeconds(120);
txtTest.Text =
"http://www.ProdutosAPITest6.hostname.com/api/products";
var URI = txtTest.Text + "/" + Name.ToString();
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(URI)
};
var response = await client.SendAsync(requestMessage);
if (response.IsSuccessStatusCode == true)
{
var products = await response.Content.ReadAsStringAsync();
var resultModel = JsonConvert.DeserializeObject<Product>
(products);
return resultModel;
}
}
MY CLASS
namespace XF_ConsumingWebAPI.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
}
MY LISTVIEW
public ListView myListView { get { return ProductsList; }}
protected override async void OnAppearing()
{
var name = //pass name;
List<ProductModel> products = await GetProductByName(Name);
if (products.Count() > 0)
{
myListView.ItemsSource = products;
}
base.OnAppearing();
}
in the line List<Produto> products = await LoadData(Name);
I'm receiving the following message
Can not implicitly convert type "void" in
"System.Collections.Generic.List<XF_ConsumingWebAPI.Models.Product>"
Try to set ItemSource in GetproductByName function instead of returning resultModel i.e.
myListView.ItemSource=resultModel

Validate Model in Pipeline Instead of Controller [duplicate]

I was wondering how I can achieve model validation with ASP.NET Web API. I have my model like so:
public class Enquiry
{
[Key]
public int EnquiryId { get; set; }
[Required]
public DateTime EnquiryDate { get; set; }
[Required]
public string CustomerAccountNumber { get; set; }
[Required]
public string ContactName { get; set; }
}
I then have a Post action in my API Controller:
public void Post(Enquiry enquiry)
{
enquiry.EnquiryDate = DateTime.Now;
context.DaybookEnquiries.Add(enquiry);
context.SaveChanges();
}
How do I add if(ModelState.IsValid) and then handle the error message to pass down to the user?
For separation of concern, I would suggest you use action filter for model validation, so you don't need to care much how to do validation in your api controller:
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace System.Web.Http.Filters
{
public class ValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
}
Maybe not what you were looking for, but perhaps nice for someone to know:
If you are using .net Web Api 2 you could just do the following:
if (!ModelState.IsValid)
return BadRequest();
Depending on the model errors, you get this result:
{
Message: "The request is invalid."
ModelState: {
model.PropertyA: [
"The PropertyA field is required."
],
model.PropertyB: [
"The PropertyB field is required."
]
}
}
Like this, for example:
public HttpResponseMessage Post(Person person)
{
if (ModelState.IsValid)
{
PersonDB.Add(person);
return Request.CreateResponse(HttpStatusCode.Created, person);
}
else
{
// the code below should probably be refactored into a GetModelErrors
// method on your BaseApiController or something like that
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
return Request.CreateResponse(HttpStatusCode.Forbidden, errors);
}
}
This will return a response like this (assuming JSON, but same basic principle for XML):
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
(some headers removed here)
["A value is required.","The field First is required.","Some custom errorm essage."]
You can of course construct your error object/list any way you like, for example adding field names, field id's etc.
Even if it's a "one way" Ajax call like a POST of a new entity, you should still return something to the caller - something that indicates whether or not the request was successful. Imagine a site where your user will add some info about themselves via an AJAX POST request. What if the information they have tried to entered isn't valid - how will they know if their Save action was successful or not?
The best way to do this is using Good Old HTTP Status Codes like 200 OK and so on. That way your JavaScript can properly handle failures using the correct callbacks (error, success etc).
Here's a nice tutorial on a more advanced version of this method, using an ActionFilter and jQuery: http://asp.net/web-api/videos/getting-started/custom-validation
Or, if you are looking for simple collection of errors for your apps.. here is my implementation of this:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
var errors = new List<string>();
foreach (var state in modelState)
{
foreach (var error in state.Value.Errors)
{
errors.Add(error.ErrorMessage);
}
}
var response = new { errors = errors };
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType);
}
}
Error Message Response will look like:
{
"errors": [
"Please enter a valid phone number (7+ more digits)",
"Please enter a valid e-mail address"
]
}
You can use attributes from the System.ComponentModel.DataAnnotations namespace to set validation rules. Refer Model Validation - By Mike Wasson for details.
Also refer video ASP.NET Web API, Part 5: Custom Validation - Jon Galloway
Other References
Take a Walk on the Client Side with WebAPI and WebForms
How ASP.NET Web API binds HTTP messages to domain models, and how to work with media formats in Web API.
Dominick Baier - Securing ASP.NET Web APIs
Hooking AngularJS validation to ASP.NET Web API Validation
Displaying ModelState Errors with AngularJS in ASP.NET MVC
How to render errors to client? AngularJS/WebApi ModelState
Dependency-Injected Validation in Web API
Add below code in startup.cs file
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => new ErrorModel()
{
ErrorCode = ((int)HttpStatusCode.BadRequest).ToString(CultureInfo.CurrentCulture),
ErrorMessage = p.ErrorMessage,
ServerErrorMessage = string.Empty
})).ToList();
var result = new BaseResponse
{
Error = errors,
ResponseCode = (int)HttpStatusCode.BadRequest,
ResponseMessage = ResponseMessageConstants.VALIDATIONFAIL,
};
return new BadRequestObjectResult(result);
};
});
C#
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
...
[ValidateModel]
public HttpResponseMessage Post([FromBody]AnyModel model)
{
Javascript
$.ajax({
type: "POST",
url: "/api/xxxxx",
async: 'false',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
error: function (xhr, status, err) {
if (xhr.status == 400) {
DisplayModelStateErrors(xhr.responseJSON.ModelState);
}
},
....
function DisplayModelStateErrors(modelState) {
var message = "";
var propStrings = Object.keys(modelState);
$.each(propStrings, function (i, propString) {
var propErrors = modelState[propString];
$.each(propErrors, function (j, propError) {
message += propError;
});
message += "\n";
});
alert(message);
};
Here you can check to show the model state error one by one
public HttpResponseMessage CertificateUpload(employeeModel emp)
{
if (!ModelState.IsValid)
{
string errordetails = "";
var errors = new List<string>();
foreach (var state in ModelState)
{
foreach (var error in state.Value.Errors)
{
string p = error.ErrorMessage;
errordetails = errordetails + error.ErrorMessage;
}
}
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("error", errordetails);
return Request.CreateResponse(HttpStatusCode.BadRequest, dict);
}
else
{
//do something
}
}
}
I had an issue implementing the accepted solution pattern where my ModelStateFilter would always return false (and subsequently a 400) for actionContext.ModelState.IsValid for certain model objects:
public class ModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest};
}
}
}
I only accept JSON, so I implemented a custom model binder class:
public class AddressModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
var posted = actionContext.Request.Content.ReadAsStringAsync().Result;
AddressDTO address = JsonConvert.DeserializeObject<AddressDTO>(posted);
if (address != null)
{
// moar val here
bindingContext.Model = address;
return true;
}
return false;
}
}
Which I register directly after my model via
config.BindParameter(typeof(AddressDTO), new AddressModelBinder());
You can also throw exceptions as documented here:
http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx
Note, to do what that article suggests, remember to include System.Net.Http
Put this in the startup.cs file
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p =>p.ErrorMessage)).ToList();
var result = new Response
{
Succeeded = false,
ResponseMessage = string.Join(", ",errors)
};
return new BadRequestObjectResult(result);
};
});

Azure Notification Hub and WP8 Intermitant notifications

This is a fairly long piece of code but I am getting nowhere with this and cannot see any issues, although I am new to using notification hubs. I am trying to register for targeted notifications (the logged on user) using the notification hub in Azure. After the registration, a test notification is sent.
The issue I am having is that sometimes the notification is sent to the device, and sometimes it is not. It mostly isn't but occasionally when I step through the code on the server, i will get the notification on the emulator come through. Once when I deployed the app to my phone the notification came though on the emulator! I cannot discover a pattern.
My Controller class looks like this;
private NotificationHelper hub;
public RegisterController()
{
hub = NotificationHelper.Instance;
}
public async Task<RegistrationDescription> Post([FromBody]JObject registrationCall)
{
var obj = await hub.Post(registrationCall);
return obj;
}
And the helper class (which is used elsewhere so is not directly in the controller) looks like this;
public static NotificationHelper Instance = new NotificationHelper();
public NotificationHubClient Hub { get; set; }
// Create the client in the constructor.
public NotificationHelper()
{
var cn = "<my-cn>";
Hub = NotificationHubClient.CreateClientFromConnectionString(cn, "<my-hub>");
}
public async Task<RegistrationDescription> Post([FromBody] JObject registrationCall)
{
// Get the registration info that we need from the request.
var platform = registrationCall["platform"].ToString();
var installationId = registrationCall["instId"].ToString();
var channelUri = registrationCall["channelUri"] != null
? registrationCall["channelUri"].ToString()
: null;
var deviceToken = registrationCall["deviceToken"] != null
? registrationCall["deviceToken"].ToString()
: null;
var userName = HttpContext.Current.User.Identity.Name;
// Get registrations for the current installation ID.
var regsForInstId = await Hub.GetRegistrationsByTagAsync(installationId, 100);
var updated = false;
var firstRegistration = true;
RegistrationDescription registration = null;
// Check for existing registrations.
foreach (var registrationDescription in regsForInstId)
{
if (firstRegistration)
{
// Update the tags.
registrationDescription.Tags = new HashSet<string>() {installationId, userName};
// We need to handle each platform separately.
switch (platform)
{
case "windows":
var winReg = registrationDescription as MpnsRegistrationDescription;
winReg.ChannelUri = new Uri(channelUri);
registration = await Hub.UpdateRegistrationAsync(winReg);
break;
case "ios":
var iosReg = registrationDescription as AppleRegistrationDescription;
iosReg.DeviceToken = deviceToken;
registration = await Hub.UpdateRegistrationAsync(iosReg);
break;
}
updated = true;
firstRegistration = false;
}
else
{
// We shouldn't have any extra registrations; delete if we do.
await Hub.DeleteRegistrationAsync(registrationDescription);
}
}
// Create a new registration.
if (!updated)
{
switch (platform)
{
case "windows":
registration = await Hub.CreateMpnsNativeRegistrationAsync(channelUri,
new string[] {installationId, userName});
break;
case "ios":
registration = await Hub.CreateAppleNativeRegistrationAsync(deviceToken,
new string[] {installationId, userName});
break;
}
}
// Send out a test notification.
await SendNotification(string.Format("Test notification for {0}", userName), userName);
return registration;
And finally, my SendNotification method is here;
internal async Task SendNotification(string notificationText, string tag)
{
try
{
var toast = PrepareToastPayload("<my-hub>", notificationText);
// Send a notification to the logged-in user on both platforms.
await NotificationHelper.Instance.Hub.SendMpnsNativeNotificationAsync(toast, tag);
//await hubClient.SendAppleNativeNotificationAsync(alert, tag);
}
catch (ArgumentException ex)
{
// This is expected when an APNS registration doesn't exist.
Console.WriteLine(ex.Message);
}
}
I suspect the issue is in my phone client code, which is here and SubscribeToService is called immediately after WebAPI login;
public void SubscribeToService()
{
_channel = HttpNotificationChannel.Find("mychannel");
if (_channel == null)
{
_channel = new HttpNotificationChannel("mychannel");
_channel.Open();
_channel.BindToShellToast();
}
_channel.ChannelUriUpdated += async (o, args) =>
{
var hub = new NotificationHub("<my-hub>", "<my-cn>");
await hub.RegisterNativeAsync(args.ChannelUri.ToString());
await RegisterForMessageNotificationsAsync();
};
}
public async Task RegisterForMessageNotificationsAsync()
{
using (var client = GetNewHttpClient(true))
{
// Get the info that we need to request registration.
var installationId = LocalStorageManager.GetInstallationId(); // a new Guid
var registration = new Dictionary<string, string>()
{
{"platform", "windows"},
{"instId", installationId},
{"channelUri", _channel.ChannelUri.ToString()}
};
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(ApiUrl + "api/Register/RegisterForNotifications"));
request.Content = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json");
string message;
try
{
HttpResponseMessage response = await client.SendAsync(request);
message = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
message = ex.Message;
}
_registrationId = message;
}
}
Any help would be greatly appriciated as I have been stuck on this now for days! I know this is a lot of code to paste up here but it is all relevant.
Thanks,
EDIT: The SubscribeToService() method is called when the user logs in and authenticates with the WebAPI. The method is here;
public async Task<User> SendSubmitLogonAsync(LogonObject lo)
{
_logonObject = lo;
using (var client = GetNewHttpClient(false))
{
var logonString = String.Format("grant_type=password&username={0}&password={1}", lo.username, lo.password);
var sc = new StringContent(logonString, Encoding.UTF8);
var response = await client.PostAsync("Token", sc);
if (response.IsSuccessStatusCode)
{
_logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
var userInfo = await GetUserInfoAsync();
if (_channel == null)
SubscribeToService();
else
await RegisterForMessageNotificationsAsync();
return userInfo;
}
// ...
}
}
I have solved the issue. There are tons of fairly poorly organised howto's for azure notification hubs and only one of them has this note toward the bottom;
NOTE:
You will not receive the notification when you are still in the app.
To receive a toast notification while the app is active, you must
handle the ShellToastNotificationReceived event.
This is why I was experiencing intermittent results, as i assumed you would still get a notification if you were in the app. And this little note is pretty well hidden.
Have you used proper tag / tag expressions while register/send the message. Also, Where are you storing the id back from the notification hub. It should be used when you update the channel uri (it will expire).
I would suggest to start from scratch.
Ref: http://msdn.microsoft.com/en-us/library/dn530749.aspx

Resources