Test and use chat bot without emulator - asp.net

It may seems very basic question but any guidance is appreciated .Where can i start to learn?.
I have used bot framework and made some code that gives total number of cities in particular country .I integrated and used LUIS .All went good but now to test,i use bot emulator of microsoft.
In real time ,i want to use this chat application on my asp.net application .How can i use this code in Asp.net and test without bot emulator.
Working Code(in case it may help someone) -
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using Microsoft.Bot.Connector;
using Newtonsoft.Json;
namespace YahooBot
{
[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
string StockRateString;
LUIS StLUIS = await GetEntityFromLUIS(activity.Text);
if (StLUIS.intents.Count() > 0)
{
switch (StLUIS.intents[0].intent)
{
case "StockPrice":
StockRateString = await GetStock(StLUIS.entities[0].entity);
break;
case "StockPrice2":
StockRateString = await GetStock(StLUIS.entities[0].entity);
break;
case "Getcity":
StockRateString = await GetCityDetails(StLUIS.entities[0].entity);
break;
default:
StockRateString = "Sorry, I am not getting you...";
break;
}
}
else
{
StockRateString = "Sorry, I am not getting you...";
}
Activity reply = activity.CreateReply(StockRateString);
await connector.Conversations.ReplyToActivityAsync(reply);
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task<string> GetStock(string StockSymbol)
{
double? dblStockValue = await YahooBot.GetStockRateAsync(StockSymbol);
if(dblStockValue==null)
{
return string.Format("This \"{0}\" is not an valid stock symbol", StockSymbol);
}
else
{
return string.Format("Stock Price of {0} is {1}", StockSymbol, dblStockValue);
}
}
private async Task<string> GetCityDetails(string citychar)
{
string dblcityValue = await YahooBot.GetCityAsync(citychar);
if (dblcityValue == "")
{
return string.Format("This \"{0}\" is not an valid city ", citychar);
}
else
{
return string.Format("number of cities beginning with {0} is {1}", citychar, dblcityValue);
}
}
private static async Task<LUIS> GetEntityFromLUIS(string Query)
{
Query = Uri.EscapeDataString(Query);
LUIS Data = new LUIS();
using (HttpClient client = new HttpClient())
{
string RequestURI = "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/f2a85791-c9fe-44f8-b23c-2580581dc383?subscription-key=8b4566ea897c4c87960995755aa8881d&verbose=true&timezoneOffset=0&q=" + Query;
HttpResponseMessage msg = await client.GetAsync(RequestURI);
if (msg.IsSuccessStatusCode)
{
var JsonDataResponse = await msg.Content.ReadAsStringAsync();
Data = JsonConvert.DeserializeObject<LUIS>(JsonDataResponse);
}
}
return Data;
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
}

A few steps must be followed:
Publish your bot on Azure (can be done from Visual Studio directly), see documentation
Register your bot on dev.botframework.com (see documentation)
Integrate the chat in your Asp.net site, for example using iFrame: see the tutorial on the documentation . There are also other possibilities described here. An important note is that you should use a token instead of secret in the iframe src, see details in the documentation provided before (option 1 vs option 2)
In a nutshell: everything you need is on the official documentation, in the How-to guides

Related

Is there a way for a UWP app to know if Recurring Billing is turned on for a subscription in the Microsoft store

I have a Xamarin Forms UWP app. I am trying to provide more specific information to users of my app regarding their subscription expiration / renewal. I would like to be able to tell if they have turned off recurring billing for the subscription. Below are the details I get back from the store regarding the current subscription. I assume the isActive will turn to false after they turn off recurring billing AND cancel the subscription after Expiration date. However, in my testing I have found that if they turn off recurring billing and are still within the the subscription period of their last recurring purchase (i.e. the expiration date is still in the future), everything seems to look exactly as if they have recurring billing turned on and the subscription will auto-renew. I am trying find a way to let them know that their current subscription will expire on a specific date versus will auto-renew on a specific date. Am I missing something?
Here is the code -- Everything works fine I just don't see anything in the object returned from the Microsoft Store API that indicates if recurring billing is on for the subscription.
using FCISharedAll;
using FCISuite.UWP;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.Services.Store;
using static FCISharedAll.FCIEnums.FlexEnums;
[assembly: Xamarin.Forms.Dependency(typeof(UWPSubscriptionManager))]
namespace FCISuite.UWP
{
public class UWPSubscriptionManager : IFCISubcriptionManager, IDisposable
{
private StoreContext context = null;
private static string[] ia_productKinds = { "Durable", "Consumable", "UnmanagedConsumable" };
private List<String> iobj_filterList = null;
//We need a constructor to load the list
public UWPSubscriptionManager()
{
iobj_filterList = new List<string>(ia_productKinds);
}
/// <summary>
/// This will run at the start of the app to get the active subscription info for the user.
/// </summary>
/// <returns></returns>
public async Task<ActiveSubscriptionDetails> GetActiveSubscriptionInformation(ActiveSubscriptionDetails pobj_SubscriptionData)
{
string ls_Result = string.Empty;
try
{
if (context == null)
{
context = StoreContext.GetDefault();
// If your app is a desktop app that uses the Desktop Bridge, you
// may need additional code to configure the StoreContext object.
// For more info, see https://aka.ms/storecontext-for-desktop.
}
StoreAppLicense appLicense = await context.GetAppLicenseAsync();
// Check if the customer has the rights to the subscription.
foreach (var addOnLicense in appLicense.AddOnLicenses)
{
StoreLicense license = addOnLicense.Value;
if (license.IsActive)
{
pobj_SubscriptionData.ActiveSubscriptionID = license.SkuStoreId;
pobj_SubscriptionData.SubscriptionEndDate = license.ExpirationDate.DateTime;
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
//Debug.WriteLine("End of Subscription Info");
return pobj_SubscriptionData;
}
#region get available subscriptions
public async Task<List<SubscriptionDetail>> GetAvailableSubscriptions(string ps_ProductNotFoundMessage,
string ps_StoreAddOnsNotFound, string ps_EnvironmentPrefix)
{
string ls_Result = "";
List<SubscriptionDetail> lobj_AvailableSubscriptions = new List<SubscriptionDetail>();
try
{
if (context == null)
{
context = StoreContext.GetDefault();
// If your app is a desktop app that uses the Desktop Bridge, you
// may need additional code to configure the StoreContext object.
// For more info, see https://aka.ms/storecontext-for-desktop.
}
// Get app store product details. Because this might take several moments,
// display a ProgressRing during the operation.
StoreProductResult product_queryResult = await context.GetStoreProductForCurrentAppAsync();
if (product_queryResult.Product == null)
{
// Show additional error info if it is available.
if (product_queryResult.ExtendedError != null)
{
ls_Result += $"\nExtendedError: {product_queryResult.ExtendedError.Message}";
}
// The Store catalog returned an unexpected result.
throw new Exception(ps_ProductNotFoundMessage + " " + ls_Result);
}
else
{
StoreProductQueryResult addon_queryResult = await context.GetAssociatedStoreProductsAsync(iobj_filterList);
if (addon_queryResult.ExtendedError != null)
{
// The user may be offline or there might be some other server failure.
ls_Result = $"ExtendedError: {addon_queryResult.ExtendedError.Message}";
// The Store catalog returned an unexpected result for the Add-ons.
throw new Exception(ps_StoreAddOnsNotFound + " " + ls_Result);
}
List<KeyValuePair<string, StoreProduct>> lobj_ProductsforEnvironment = new List<KeyValuePair<string, StoreProduct>>();
//We are in test - do not show the production subscriptions
lobj_ProductsforEnvironment = (from tobj_Product in addon_queryResult.Products
where tobj_Product.Value.Title.ToUpper().StartsWith(ps_EnvironmentPrefix.ToUpper())
select tobj_Product).ToList();
//foreach (KeyValuePair<string, StoreProduct> item in addon_queryResult.Products)
foreach (KeyValuePair<string, StoreProduct> item in lobj_ProductsforEnvironment)
{
// Add the store product's skus to the list
foreach (StoreSku lobj_Sku in item.Value.Skus)
{
if (lobj_Sku.IsSubscription)
{
lobj_AvailableSubscriptions.Add(new SubscriptionDetail()
{
SubscriptionID = item.Value.StoreId,
SubscriptionName = lobj_Sku.Title,
SubscriptionFormattedPrice = lobj_Sku.Price.FormattedPrice,
// Use the sku.SubscriptionInfo property to get info about the subscription.
// For example, the following code gets the units and duration of the
// subscription billing period.
SubscriptionPeriodUnit = lobj_Sku.SubscriptionInfo.BillingPeriodUnit.ToString(),
SubscriptionPeriod = lobj_Sku.SubscriptionInfo.BillingPeriod,
});
}
}
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return lobj_AvailableSubscriptions;
}
#endregion
#region Subscription Purchase area
/// <summary>
/// This will get the product for the ID from the store, then request to purchase it for the user.
/// </summary>
/// <param name="ps_SubscriptionStoreId">The ID of the store subscription to be purchased</param>
/// <returns>boolean that indicates if the purchase succeeded or failed.</returns>
public async Task<SubscriptionPurchaseDetail> PurchaseSubscription(string ps_SubscriptionStoreId)
{
SubscriptionPurchaseDetail lobj_SubscriptionPurchaseDetail = new SubscriptionPurchaseDetail();
try
{
lobj_SubscriptionPurchaseDetail.PurchaseStatus = FCIStorePurchaseStatus.Succeeded;
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage = string.Empty;
lobj_SubscriptionPurchaseDetail.ProductToPurchaseWasFound = true;
//This gets the product to purchase
var tobj_StoreProduct = await GetSubscriptionProductAsync(ps_SubscriptionStoreId);
if (tobj_StoreProduct == null)
{
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage = "The add-on to purchase was not found. Product ID: " + ps_SubscriptionStoreId;
lobj_SubscriptionPurchaseDetail.ProductToPurchaseWasFound = false;
}
else
{
//Get the store product to purchase
StorePurchaseResult result = await tobj_StoreProduct.RequestPurchaseAsync();
//Convert the store namespace string to our string.
lobj_SubscriptionPurchaseDetail.PurchaseStatus = (FCIStorePurchaseStatus)Enum.Parse(typeof(FCIStorePurchaseStatus), result.Status.ToString());
// Capture the error message for the operation, if any.
if (result.ExtendedError != null)
{
lobj_SubscriptionPurchaseDetail.ExtendedErrorMessage += " " + result.ExtendedError.Message;
}
}
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return lobj_SubscriptionPurchaseDetail;
}
/// <summary>
/// This returns the product we will use to call the RequestPurchaseAsync function on
/// </summary>
/// <param name="ps_SubscriptionStoreId">The ID if the subscription to purchase</param>
/// <returns>StoreProduct which represents subscritpion to purchase.</returns>
private async Task<StoreProduct> GetSubscriptionProductAsync(string ps_SubscriptionStoreId)
{
try
{
if (context == null)
{
context = StoreContext.GetDefault();
// If your app is a desktop app that uses the Desktop Bridge, you
// may need additional code to configure the StoreContext object.
// For more info, see https://aka.ms/storecontext-for-desktop.
}
// Load the sellable add-ons for this app and check if the trial is still
// available for this customer. If they previously acquired a trial they won't
// be able to get a trial again, and the StoreProduct.Skus property will
// only contain one SKU.
StoreProductQueryResult result = await context.GetAssociatedStoreProductsAsync(iobj_filterList);
if (result.ExtendedError != null)
{
throw new Exception("Something went wrong while getting the add-ons from the store. ExtendedError:" + result.ExtendedError);
}
// Look for the product that represents the subscription and return it if found - this is what we
//will purchase
foreach (var item in result.Products)
{
StoreProduct product = item.Value;
if (product.StoreId == ps_SubscriptionStoreId)
{
return product;
}
}
//If we get here the subscription was not found. We will return null and the calling function will know the
//subscription was not found.
}
catch (Exception ex)
{
SharedErrorHandler.ProcessException(ex);
}
return null;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
context = null;
}
}
#endregion
}
}
Currently, there is no such API from StoreContext that could get the information about whether the user disables auto-renew for the subscription.
When users have purchased a subscription add-on, it will renew automatically by default. Detailed messages are listed here: Subscription renewals and grace periods. If users need to check subscription mode, he still need to check it from the account service site.

PushSharp 4.0.10.0: HTTP/2-based Apple Push Notification service (APNs)

We use PushSharp 4.0.10 to send iOS Push Notifications:
https://github.com/Redth/PushSharp
Recently we recieved this email from Apple Developer:
"If you still send push notifications with the legacy binary protocol, it's time to update to the HTTP/2-based Apple Push Notification service (APNs) provider API. You'll be able to take advantage of great features, such as authentication with a JSON Web Token, improved error messaging, and per-notification feedback.
To give you additional time to prepare, the deadline to upgrade to the APNs provider API has been extended to March 31, 2021. We recommend upgrading as soon as possible, as APNs will no longer support the legacy binary protocol after this date."
My question is: Will PushSharp 4.0.10 still work after March 31, 2021?
There is a discussion about this but the thread was closed. But there are still some suggestions on this thread that you might want to try.
The Apple Push Notification service (APNs) will no longer support the legacy binary protocol as of November 2020
https://github.com/Redth/PushSharp/issues/923
**
EDIT - 25th March 2021
The deadline is close and #Ashita Shah asked some code snippet so I hope the following can save your time.
Add the following class dotAPNSService to your project. You can customise this structure according to your needs. Also I didn't focus the best of best coding C# standards when implementing my own push notification service. You can implement LINQ, Tasks async etc. I tested this dotAPNS library and it works perfectly fine. For Android you can still use PushSharp.
Before you implement the dotAPNSService helper class, get the following from your Apple developer account. The ApnsJwtOptions values should be:
BundleId - your app’s bundle ID. Should not include specific topics (i.e. com.myapp but not com.myapp.voip).
CertFilePath - path to the .p8 certificate you have downloaded from the Developer Center.
KeyId - The 10-character Key ID you obtained from your developer account
TeamId - The 10-character Team ID you use for developing your company’s apps. Obtain this value from your developer account.
public class dotAPNSService : IDisposable
{
public event EventHandler OnTokenExpiredHandler;
private ApnsJwtOptions options = null;
public dotAPNSService()
{
options = new ApnsJwtOptions()
{
BundleId = "com.xx.xxxx",
CertFilePath = "../../certificate.p8",
KeyId = "The_Key_Id",
TeamId = "The_Team_Id"
};
}
public void SendNotifications(String[] deviceTokens, String title, String body)
{
if (deviceTokens == null || deviceTokens.Length <= 0)
{
return;
}
if (String.IsNullOrEmpty(title))
{
return;
}
if (String.IsNullOrEmpty(body))
{
return;
}
// once you've gathered all the information needed and created an options instance, it's time to call
var apns = ApnsClient.CreateUsingJwt(new HttpClient(), options);
// start the process
foreach (String deviceToken in deviceTokens)
{
var push = new ApplePush(ApplePushType.Alert)
.AddAlert(title, body)
.AddToken(deviceToken);
Send(apns, push, deviceToken);
}
}
public void SendSilentNotifications(String[] deviceTokens)
{
try
{
if (deviceTokens == null || deviceTokens.Length <= 0)
{
return;
}
// once you've gathered all the information needed and created an options instance, it's time to call
var apns = ApnsClient.CreateUsingJwt(new HttpClient(), options);
// start the process
foreach (String deviceToken in deviceTokens)
{
var push = new ApplePush(ApplePushType.Background)
.AddContentAvailable()
.AddToken(deviceToken);
Send(apns, push, deviceToken);
}
}
finally
{
}
}
private void Send(ApnsClient apns, ApplePush push, String deviceToken)
{
try
{
var response = apns.SendAsync(push);
if (response.Result.Reason == ApnsResponseReason.Success)
{
// the notification has been sent!
}
else
{
Boolean removeToken = false;
switch (response.Result.Reason)
{
case ApnsResponseReason.BadDeviceToken:
removeToken = true;
break;
case ApnsResponseReason.TooManyRequests:
break;
}
// remove the token from database?
if (removeToken)
OnTokenExpired(new ExpiredTokenEventArgs(deviceToken));
}
}
catch (TaskCanceledException)
{
// ERROR - HTTP request timed out, you can use the deviceToken to log the error
}
catch (HttpRequestException ex)
{
// ERROR - HTTP request failed, you can use the deviceToken to log the error
}
}
protected virtual void OnTokenExpired(ExpiredTokenEventArgs args)
{
try
{
EventHandler handler = OnTokenExpiredHandler;
if (handler != null)
{
ISynchronizeInvoke target = handler.Target as ISynchronizeInvoke;
if (target != null && target.InvokeRequired)
target.Invoke(handler, new object[] { this, args });
else
handler(this, args);
}
}
catch (Exception ex)
{
}
}
}
These are the namespaces of the dotAPNSService helper class:
using System;
using System.ComponentModel;
using System.Net.Http;
using System.Threading.Tasks;
using dotAPNS;
In order to use the dotAPNSService helper on your project just pull the tokens from the database and then pass them to it. For instance, to send silent notifications:
public void SendScheduledSilentNotifications()
{
try
{
IList<User> users = _accountService.GetUsers(true);
if (users != null && users.Count > 0)
{
List<String> deviceTokens = new List<String>();
foreach (User user in users)
{
if (!String.IsNullOrEmpty(user.DeviceToken))
deviceTokens.Add(user.DeviceToken);
}
if (deviceTokens.Count > 0)
{
using (dotAPNSService service = new dotAPNSService())
{
service.OnTokenExpiredHandler += new EventHandler(OnTokenExpired);
service.SendSilentNotifications(deviceTokens.ToArray());
}
}
}
}
finally
{
}
}
To remove the expired tokens from the database you can use the following:
private void OnTokenExpired(object sender, EventArgs e)
{
if (e == null)
return;
if (e.GetType() == typeof(ExpiredTokenEventArgs))
{
var args = (ExpiredTokenEventArgs)e;
User user = _accountService.GetUserByDeviceToken(args.Token);
if (user != null)
{
user.DeviceToken = String.Empty;
Boolean success = !(_accountService.SaveUser(user) == null);
if (success)
// INFO - expired device token has been removed from database
else
// INFO - something went wrong
}
}
}
You can download the source code from here:
https://github.com/alexalok/dotAPNS
The API is now sending thousands of silent notifications at one time and there are no delays, crashes etc. Hope this code snippet helps and saves your time!

Get Owin IIdentity from IHttpHandler

Accepted answer note:
Although I have appreciated the help of creating my own OwinMiddleware to send images after doing some checks instead of IHttpModule, that doesn't solve the issue entirely.
The thing is I have added an Authorization header to the ajax requests, and inside that header I am sending my Bearer's Token so that I can get logged user information from Owin. So I have to add this header to the image requests either, to be able to get logged user information from image handler middleware.
Original Question:
I am following this blog post to create token based authentication for my web project. Because some resources of my Web API will be used by native mobile clients. And I have heard that token based authentication is the way to go for that. And in my own project I have a custom image request handler. And need the logged user information inside this handler. But when i try to extract user information from ticket I get null. And I am not sure about this but, I think I have 2 different IIdentity objects here, and I need the one stored inside Owin Context.
Here let me show you some codes;
My GrantResourceOwnerCredentials which is storing claims into ClaimsIdentity,
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
....
// checking user credentials and get user information into 'usr' variable
....
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Sid, usr.UserId.ToString()));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userId", usr.UserId.ToString()
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
Helper function to extract user id from the given IIdentity object
public class utils {
public Guid? GetUserIdFromTicket(IIdentity identity)
{
var cId = (ClaimsIdentity)identity;
var uid = cId.FindFirst(ClaimTypes.Sid);
if (uid != null && Comb.IsComb(uid.Value))
return new Guid(uid.Value);
else
return null;
}
....
}
Now I can get the loggedUserId from my controller like,
var loggedUserId = utils.GetUserIdFromTicket(User.Identity);
but if I call it from my IHttpHandler I get null,
public class ImageHandler : IHttpHandler
{
public ImageHandler()
{
}
public ImageHandler(RequestContext requestContext)
{
RequestContext = requestContext;
}
protected RequestContext RequestContext { get; set; }
public utils utils = new utils(); // changed name for simplicity.
public void ProcessRequest(HttpContext context)
{
var strUserId = RequestContext.RouteData.Values["userid"].ToString();
var strContentId = RequestContext.RouteData.Values["contentid"].ToString();
var fileName = RequestContext.RouteData.Values["filename"].ToString();
var size = RequestContext.RouteData.Values["size"].ToString();
var loggedUserId = utils.GetUserIdFromTicket(context.User.Identity);
....
image processing
....
context.Response.End();
}
}
Hope I didn't messed this up for good...
Solution:
I have implemented my own middleware to serv images to my users after doing some checks. Here is my Invoke task implementation. Everything else is just like as recommended in accepted answer. But as stated above, for this to work I have to send images with the Authorization header, or the loggedUserId will be null again.
public async override Task Invoke(IOwinContext context)
{
// need to interrupt image requests having src format : http://www.mywebsite.com/myapp-img/{userid}/{contentId}/{fileName}/{size}/
if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("myapp-img") > -1)
{
// get values from url.
var pathValues = context.Request.Path.Value.Split('/');
var strUserId = pathValues[2].ToString();
var strContentId = pathValues[3].ToString();
var fileName = pathValues[4].ToString();
var size = pathValues[5].ToString();
// check if code returned a notfound or unauthorized image as response.
var hasError = false;
// get userId from static utils class providing current owin identity object
var loggedUserId = ChildOnBlogUtils.GetUserIdFromTicket(context.Request.User.Identity);
// save root path of application to provide error images.
var rootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
// assign content type of response to requested file type
context.Response.ContentType = ChildOnBlogUtils.GetContentType(context.Request.Path.Value.ToString());
// if user requested thumbnail send it without doing checks
if (size == "thumb")
{
imgPath = "images/" + strUserId.ToLower() + "/thumbnail/" + fileName;
}
else
{
var canSee = false;
// check if user can see the content and put the result into canSee variable
// I am using loggedUserId inside these checks
...
...
// end checks
if (canSee)
{
// removed some more checks here for simplicity
imgPath = "images/" + strUserId.ToLower() + "/" + fileName;
}
else
{
context.Response.ContentType = "Image/png";
var imgData = File.ReadAllBytes(rootPath + "/images/unauthorized.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
hasError = true;
}
}
if (!hasError) // if no errors have been risen until this point. try to provide the requested image to user.
{
try
{
var imgData = UserMediaContainer.GetFileContent(imgPath); // get file from storage account (azure)
if (imgData.Length == 0)
{
context.Response.ContentType = "Image/png";
imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
else
{
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
}
catch (Exception ex)
{
context.Response.ContentType = "Image/png";
var imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
}
}
else if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("profile-img") > -1)
{
// profile image provider. Same code as providing thumbnails.
}
else
{
// if it is not an image request to be handled. move to the next middleware.
await Next.Invoke(context);
}
}
I guess your ImageHandler is processed before everything else in the owin pipeline, which means it is processed before the authorization comes into place.
Since you're using owin I would advise you to drop the IHttpHandler and use some custom owin middleware.
Following this path will allow you to inject your module in the right place in the pipeline.
Creating the middleware is quite easy:
public class ImageProcessingMiddleware : OwinMiddleware
{
public ImageProcessingMiddleware(OwinMiddleware next): base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
string username = context.Request.User.Identity.Name;
Console.WriteLine("Begin Request");
await Next.Invoke(context);
Console.WriteLine("End Request");
}
}
Once you have defined your middleware you can create an extension method for the instantiation:
public static class ImageProcessingExtensions
{
public static IAppBuilder UseImageProcessing(this IAppBuilder app)
{
return app.Use<ImageProcessingMiddleware>();
}
}
Now you can plug-in your middleware in the pipeline:
app.UseImageProcessing();
If you have followed Taiseer sample, you would do that after you have configured the authorization module:
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
Going back to the middleware, you might have noticed there's a method called Invoke:
public async override Task Invoke(IOwinContext context)
{
string username = context.Request.User.Identity.Name;
Console.WriteLine("Begin Request");
await Next.Invoke(context);
Console.WriteLine("End Request");
}
This is the entry-point of each middleware. As you can see I am reading the user's name authorized right after the authorization token has been verified and authorized.
There's an interesting article about owin middleware which is worth reading.

Adding username to groups in SignalR

Is it possible to use the IUserIDProvider instead of ConnectionID when working with Groups? I have already found an answer here, but that concerns the SignalR 1.0 version. I wonder, whether things have changed in 2.0.
So far, I was using the conventional
Groups.Add(Context.ConnectionId, "groupName");
However, it was difficult to keep track of the connected users when their connectionID was changed (the client is a Xamarin Android app and somehow, reconnection always resulted in creation of a new ConnectionID). Thus, when the client is connecting, I have added a header:
public async Task<bool> Login(int waitMilis, string name)
{
var cts = new CancellationTokenSource();
try
{
cts.CancelAfter(waitMilis);
_connection.Headers.Add("userName", name);
await _connection.Start();
return true;
}
catch(Exception ex)
{
CallFailure(ex);
return false;
}
}
And on server side, implemented the IUserIdProvider:
public class MyUserProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
if (request == null)
throw new ArgumentNullException("request");
else if (request.Headers != null && request.Headers["userName"] != null)
return request.Headers["userName"].ToString();
else return null;
}
}
Now, I would like to do something like
Groups.Add("userName", "groupName");
but the Add method does not have an overload for IUserIdProvider. So, is there a possibility to combine the IUserIdProvider and working with Groups, or am I stuck to creating a ConcurrentDictionary and then calling this?
foreach(User user in group.Users)
{
Clients.User(user.Name).SendMessage(message,
group.LastUpdateIndex
);
}
It ruins the whole beauty and simplicity of the SignalR code :-/
Unfortunately, there isn't currently a method like Groups.Add("userName", "groupName"); in SignalR.
I suggest adding users to their appropriate group(s) in OnConnected:
public class MyHub : Hub
{
public override async Task OnConnected()
{
var userName = MyUserHelper.GetUserId(Context.Request);
foreach (var groupName in GroupManager.GetJoinedGroups(userName))
{
await Groups.Add(Context.ConnectionId, groupName);
}
}
// ...
}
If you need to add an already connected user to a group, then you will likely need to send a message to the user using something like Clients.User(userName).joinGroup(groupName). Each client with userName could then call the appropriate hub method to join groupName.

Async calls in WP7

I have been experimenting with WP7 apps today and have hit a bit of a wall.
I like to have seperation between the UI and the main app code but Ive hit a wall.
I have succesfully implemented a webclient request and gotten a result, but because the call is async I dont know how to pass this backup to the UI level. I cannot seem to hack in a wait for response to complete or anything.
I must be doing something wrong.
(this is the xbox360Voice library that I have for download on my website: http://www.jamesstuddart.co.uk/Projects/ASP.Net/Xbox_Feeds/ which I am porting to WP7 as a test)
here is the backend code snippet:
internal const string BaseUrlFormat = "http://www.360voice.com/api/gamertag-profile.asp?tag={0}";
internal static string ResponseXml { get; set; }
internal static WebClient Client = new WebClient();
public static XboxGamer? GetGamer(string gamerTag)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null);
return SerializeResponse(response);
}
internal static XboxGamer? SerializeResponse(string response)
{
if (string.IsNullOrEmpty(response))
{
return null;
}
var tempGamer = new XboxGamer();
var gamer = (XboxGamer)SerializationMethods.Deserialize(tempGamer, response);
return gamer;
}
internal static string GetResponse(string url, string userName, string password)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
Client.Credentials = new NetworkCredential(userName, password);
}
try
{
Client.DownloadStringCompleted += ClientDownloadStringCompleted;
Client.DownloadStringAsync(new Uri(url));
return ResponseXml;
}
catch (Exception ex)
{
return null;
}
}
internal static void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
ResponseXml = e.Result;
}
}
and this is the front end code:
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
var xboxGamer = xboxManager.GetGamer();
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
Now I understand I need to place something in ClientDownloadStringCompleted but I am unsure what.
The problem you have is that as soon as an asynchronous operation is introduced in to the code path the entire code path needs to become asynchronous.
Because GetResponse calls DownloadStringAsync it must become asynchronous, it can't return a string, it can only do that on a callback
Because GetGamer calls GetResponse which is now asynchronous it can't return a XboxGamer, it can only do that on a callback
Because GetGamerDetails calls GetGamer which is now asynchronous it can't continue with its code following the call, it can only do that after it has received a call back from GetGamer.
Because GetGamerDetails is now asynchronous anything call it must also acknowledge this behaviour.
.... this continues all the way up to the top of the chain where a user event will have occured.
Here is some air code that knocks some asynchronicity in to the code.
public static void GetGamer(string gamerTag, Action<XboxGamer?> completed)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null, (response) =>
{
completed(SerializeResponse(response));
});
}
internal static string GetResponse(string url, string userName, string password, Action<string> completed)
{
WebClient client = new WebClient();
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
client.Credentials = new NetworkCredential(userName, password);
}
try
{
client.DownloadStringCompleted += (s, args) =>
{
// Messy error handling needed here, out of scope
completed(args.Result);
};
client.DownloadStringAsync(new Uri(url));
}
catch
{
completed(null);
}
}
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
xboxManager.GetGamer( (xboxGamer) =>
{
// Need to move to the main UI thread.
Dispatcher.BeginInvoke(new Action<XboxGamer?>(DisplayGamerDetails), xboxGamer);
});
}
void DisplayGamerDetails(XboxGamer? xboxGamer)
{
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
As you can see async programming can get realy messy.
You generally have 2 options. Either you expose your backend code as an async API as well, or you need to wait for the call to complete in GetResponse.
Doing it the async way would mean starting the process one place, then return, and have the UI update when data is available. This is generally the preferred way, since calling a blocking method on the UI thread will make your app seem unresponsive as long as the method is running.
I think the "Silverlight Way" would be to use databinding. Your XboxGamer object should implement the INotifyPropertyChanged interface. When you call GetGamer() it returns immediately with an "empty" XboxGamer object (maybe with GamerTag=="Loading..." or something). In your ClientDownloadStringCompleted handler you should deserialize the returned XML and then fire the INotifyPropertyChanged.PropertyChanged event.
If you look at the "Windows Phone Databound Application" project template in the SDK, the ItemViewModel class is implemented this way.
Here is how you can expose asynchronous features to any type on WP7.

Resources