Loading Assemblies into a separate AppDomain in a WindowsAzure WorkerRole - c#

I'm developing an Azure Worker Role that will need to periodically perform actions based on work pulled from a WCF service. Due to the nature of Azure deployments, a decision was made to implement a plugin framework in Azure to allow quick updates to our product while minimizing the downtime by removing the need for full azure deployments, as well as supporting multiple versions of various assemblies.
A plugin is downloaded from Cloud storage and loaded into a class that contains all relevant info needed to load the assembly. (See PluginAssemblyInfo below)
In other development work I've developed the below ReflectionHelper class. This class works in other applications and I've verified it works in a console application.
However when running in the Azure emulator I get the following error: (I've added a comment on the line the exception is being thrown on.)
Type is not resolved for member 'MyCompanyName.ReflectionHelper,MyCompanyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
The ReflectionHelper class is added as a source file to the same assembly that is calling it. (I did edit the name of the assembly/namespaces for anonymity reasons)
Given the knowledge that this class works in other cases, my only guess is that it may be a trust issue, but I've not found much info online relating to this. Any help would be greatly appreciated.
This is the code for the ReflectionHelper class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Serialization;
using System.Web;
namespace MyCompanyName
{
[Serializable]
public class ReflectionHelper
{
public AppDomain appDomain { get; set; }
public byte[] PluginBytes { get; set; }
public string PluginClassName { get; set; }
public string PluginAssemblyName { get; set; }
public Dictionary<string, byte[]> AssemblyArchive { get; set; }
public object GetInstance(AppDomain NewDomain, byte[] PluginBytes, string PluginClassName, string PluginAssemblyName, Dictionary<string, byte[]> AssemblyArchive)
{
try
{
this.appDomain = NewDomain;
this.PluginBytes = PluginBytes;
this.PluginClassName = PluginClassName;
this.AssemblyArchive = AssemblyArchive;
this.PluginAssemblyName = PluginAssemblyName;
//this is the line that throws the serializationexception
appDomain.AssemblyResolve += new ResolveEventHandler((sender, args) =>
{
AssemblyName x = new AssemblyName(args.Name);
string Name = x.Name;
if (System.IO.Path.GetExtension(x.Name) != ".dll")
Name += ".dll";
Assembly Ret = appDomain.Load(this.AssemblyArchive[Name]);
return Ret;
});
appDomain.DoCallBack(LoaderCallBack);
return appDomain.GetData("Plugin");
}
catch (Exception ex)
{
throw ex;
}
}
public void LoaderCallBack()
{
ObjectHandle PluginObject = appDomain.CreateInstance(string.Format(this.PluginAssemblyName), PluginClassName);
appDomain.SetData("Plugin", PluginObject.Unwrap());
}
}
}
This is the code snippet that invokes it
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomainSetup domainSetup = new AppDomainSetup() { };
AppDomain BrokerJobDomain = AppDomain.CreateDomain("WorkerPluginDomain" + Guid.NewGuid().ToString());//, null, new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase });
ReflectionHelper helper = new ReflectionHelper();
object Instance = helper.GetInstance(BrokerJobDomain, pluginAssembly.PluginBytes, pluginAssembly.ClassName, pluginAssembly.AssemblyName, pluginAssembly.AssemblyArchive);
IWorkPlugin executor = (IWorkPlugin)Instance;
This is the assembly info class that is populated when the plugin is downloaded from cloud storage
public class PluginAssemblyInfo
{
public string ClassName { get; set; }
public byte[] PluginBytes { get; set; }
public Dictionary<string, byte[]> AssemblyArchive { get; set; }
public string AssemblyName { get; set; }
public DateTime LoadDate { get; set; }
}

The issue seems to be related to the RelativeSearchPath not being set up on the child domain. Ensuring the child domain had the same setup credentials seemed to fix this.
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomain BrokerJobDomain = AppDomain.CreateDomain("WorkerPluginDomain" + Guid.NewGuid().ToString(), null, AppDomain.CurrentDomain.SetupInformation );

Related

Load assembly and its dependencies all in memory

I need load an assembly and its dependecies dynamically. It's because I need that the assemblies are not locked for any process.
For example, I've created a solution with three projects.
First project is called Entities, that has one class called Person
namespace Entities
{
public class Person
{
public string Name { get; set; }
}
}
Second project is called Business, tah has on class called PersonBusiness. This project referencies the project Entities
using Entities;
namespace Business
{
public class PersonBusiness
{
public string GetPersonName(string name)
{
return name;
}
public Person GetPerson(string name)
{
Person person = new Person();
person.Name = name;
return person;
}
}
}
The last project is called Runner. This project loads the assembly of project Businnes dynamically
using System.Runtime.Loader;
namespace Runner
{
public class AssemblyLoader : AssemblyLoadContext
{
internal AssemblyLoader() : base(isCollectible: true)
{
}
}
internal class Assembly
{
private dynamic Instance { get; set; }
private AssemblyLoader AssemblyLoader { get; set; }
private Type AssemblyType { get; set; }
public void Load()
{
AssemblyLoader = new AssemblyLoader();
using (var fs = new FileStream(#"C:\temp\assemblies\business.dll", FileMode.Open, FileAccess.Read))
{
System.Reflection.Assembly assembly = AssemblyLoader.LoadFromStream(fs);
AssemblyType = assembly.GetType("Business.PersonBusiness");
Instance = Activator.CreateInstance(AssemblyType);
Console.WriteLine(Instance.GetPersonName("Person name"));
// With next line works, but lock tem assembly in memory
//AssemblyLoadContext.Default.LoadFromAssemblyPath(#"C:\temp\assemblies\Entities.dll");
Console.WriteLine(Instance.GetPerson("Person name 2").Name);
}
}
public void Unload()
{
AssemblyLoader.Unload();
}
}
}
It returns the following error:
System.IO.FileNotFoundException: 'Could not load file or assembly 'Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I tried using AssemblyLoadContext.Default.LoadFromAssemblyPath. It works, but the assembly is locked.
Project in github
You could read the complete assembly dll file into memory, close the file stream (so the file is no longer locked) and then create the Assembly object from the file data in memory? Would that solve your issue?
byte[] dllAssemblyData = null;
Assembly dllAssembly = null;
// Read assembly file into byte array
using (FileStream fs = File.Open("test.dll", FileMode.Open))
{
dllAssemblyData = new byte[fs.Length];
if (fs.Read(dllAssemblyData, 0, dllAssemblyData.Length) != fs.Length)
{
// error - count of bytes read does not match file/stream length
dllAssemblyData = null;
}
}
// Create Assembly from memory
if (dllAssemblyData != null)
{
dllAssembly = Assembly.Load(dllAssemblyData);
}
// Use assembly
if (dllAssembly != null)
{
// TODO: use assembly
}

uri class not shown in system namespace

I'm trying to compile the following code using the .NET 4.5 Framework:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Discussion.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
class CodeReview
{
public List<CodeReviewComment> GetCodeReviewComments(int workItemId)
{
List<CodeReviewComment> comments = new List<CodeReviewComment>();
Uri tfsuri = new Uri(MYURL);
TeamFoundationDiscussionService service = new TeamFoundationDiscussionService();
service.Initialize(new Microsoft.TeamFoundation.Client.TfsTeamProjectCollection(tfsuri));
IDiscussionManager discussionManager = service.CreateDiscussionManager();
IAsyncResult result = discussionManager.BeginQueryByCodeReviewRequest(workItemId, QueryStoreOptions.ServerAndLocal, new AsyncCallback(CallCompletedCallback), null);
var output = discussionManager.EndQueryByCodeReviewRequest(result);
foreach (DiscussionThread thread in output)
{
if (thread.RootComment != null)
{
CodeReviewComment comment = new CodeReviewComment();
comment.Author = thread.RootComment.Author.DisplayName;
comment.Comment = thread.RootComment.Content;
comment.PublishDate = thread.RootComment.PublishedDate.ToShortDateString();
comment.ItemName = thread.ItemPath;
comments.Add(comment);
}
}
return comments;
}
static void CallCompletedCallback(IAsyncResult result)
{
// Handle error conditions here
}
public class CodeReviewComment
{
public string Author { get; set; }
public string Comment { get; set; }
public string PublishDate { get; set; }
public string ItemName { get; set; }
}
}
The Visual Studio compiler is complaining that "the type or namespace 'Uri' could not be found" and when I expand the System namespace in the Object Browser it doesnt list any of the Uri classes. I've tried a few other versions of .NET and still have the same problem although it does appear if I select the .NET 4.0 Client Profile but then none of the TeamFoundation classes are present in the Microsoft namespace.
I solved my own problem but I don't understand why it worked. In desperation I created another new Project using .NET 4.5 and copied and pasted the code over. It compiled and Uri shows up as a Class in the Object Browser. I have no idea what the difference between the 2 projects is.

Azure Functions binding redirect

Is it possible to include a web.config or app.config file in the azure functions folder structure to allow assembly binding redirects?
Assuming you are using the latest (June'17) Visual Studio 2017 Function Tooling, I derived a somewhat-reasonable config-based solution for this following a snippet of code posted by npiasecki over on Issue #992.
It would be ideal if this were managed through the framework, but at least being configuration-driven you have a bit more change isolation. I suppose you could also use some pre-build steps or T4 templating that reconciles the versions of the nugets in the project (and their dependencies) before writing out this config or generating code.
So the downside..
.. becomes having to remember to update the BindingRedirects config when you update the NuGet package (this is often a problem in app.configs anyway). You may also have an issue with the config-driven solution if you need to redirect Newtonsoft.
In our case, we were using the new Azure Fluent NuGet that had a dependency on an older version of Microsoft.IdentityModel.Clients.ActiveDirectory than the version of the normal ARM management libraries which are used side-by-side in a particular Function.
local.settings.json
{
"IsEncrypted": false,
"Values": {
"BindingRedirects": "[ { \"ShortName\": \"Microsoft.IdentityModel.Clients.ActiveDirectory\", \"RedirectToVersion\": \"3.13.9.1126\", \"PublicKeyToken\": \"31bf3856ad364e35\" } ]"
}
}
FunctionUtilities.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
namespace Utilities.AzureFunctions
{
public static class FunctionUtilities
{
public class BindingRedirect
{
public string ShortName { get; set; }
public string PublicKeyToken { get; set; }
public string RedirectToVersion { get; set; }
}
public static void ConfigureBindingRedirects()
{
var config = Environment.GetEnvironmentVariable("BindingRedirects");
var redirects = JsonConvert.DeserializeObject<List<BindingRedirect>>(config);
redirects.ForEach(RedirectAssembly);
}
public static void RedirectAssembly(BindingRedirect bindingRedirect)
{
ResolveEventHandler handler = null;
handler = (sender, args) =>
{
var requestedAssembly = new AssemblyName(args.Name);
if (requestedAssembly.Name != bindingRedirect.ShortName)
{
return null;
}
var targetPublicKeyToken = new AssemblyName("x, PublicKeyToken=" + bindingRedirect.PublicKeyToken)
.GetPublicKeyToken();
requestedAssembly.Version = new Version(bindingRedirect.RedirectToVersion);
requestedAssembly.SetPublicKeyToken(targetPublicKeyToken);
requestedAssembly.CultureInfo = CultureInfo.InvariantCulture;
AppDomain.CurrentDomain.AssemblyResolve -= handler;
return Assembly.Load(requestedAssembly);
};
AppDomain.CurrentDomain.AssemblyResolve += handler;
}
}
}
Just posted a new blog post explaining how to fix the problem, have a look:
https://codopia.wordpress.com/2017/07/21/how-to-fix-the-assembly-binding-redirect-problem-in-azure-functions/
It's actually a tweaked version of the JoeBrockhaus's code, that works well even for Newtonsoft.Json.dll
Inspired by the accepted answer I figured I'd do a more generic one which takes into account upgrades as well.
It fetches all assemblies, orders them descending to get the newest version on top, then returns the newest version on resolve. I call this in a static constructor myself.
public static void RedirectAssembly()
{
var list = AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetName())
.OrderByDescending(a => a.Name)
.ThenByDescending(a => a.Version)
.Select(a => a.FullName)
.ToList();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var requestedAssembly = new AssemblyName(args.Name);
foreach (string asmName in list)
{
if (asmName.StartsWith(requestedAssembly.Name + ","))
{
return Assembly.Load(asmName);
}
}
return null;
};
}
It is not directly possible today, but we are thinking about ways to achieve this. Can you please open an issue on https://github.com/Azure/azure-webjobs-sdk-script/issues to make sure your specific scenario is looked at? Thanks!
First SO post, so apologies if formatting's a bit off.
We've hit this issue a couple of times and managed to find a better way of getting the required redirects by forcing MSBUILD to generate a binding redirects file and then parsing that to be used with the previously suggested answer.
Modify the project settings and add in a couple of targets:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
<AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
...
</PropertyGroup>
</Project>
These classes apply the binding redirects using the same idea that was posted earlier (link) except instead of using the host.json file it reads from the generated binding redirects file. The filename to use is from reflection using the ExecutingAssembly.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
public static class AssemblyBindingRedirectHelper
{
private static FunctionRedirectBindings _redirects;
public static void ConfigureBindingRedirects()
{
// Only load the binding redirects once
if (_redirects != null)
return;
_redirects = new FunctionRedirectBindings();
foreach (var redirect in _redirects.BindingRedirects)
{
RedirectAssembly(redirect);
}
}
public static void RedirectAssembly(BindingRedirect bindingRedirect)
{
ResolveEventHandler handler = null;
handler = (sender, args) =>
{
var requestedAssembly = new AssemblyName(args.Name);
if (requestedAssembly.Name != bindingRedirect.ShortName)
{
return null;
}
var targetPublicKeyToken = new AssemblyName("x, PublicKeyToken=" + bindingRedirect.PublicKeyToken).GetPublicKeyToken();
requestedAssembly.Version = new Version(bindingRedirect.RedirectToVersion);
requestedAssembly.SetPublicKeyToken(targetPublicKeyToken);
requestedAssembly.CultureInfo = CultureInfo.InvariantCulture;
AppDomain.CurrentDomain.AssemblyResolve -= handler;
return Assembly.Load(requestedAssembly);
};
AppDomain.CurrentDomain.AssemblyResolve += handler;
}
}
public class FunctionRedirectBindings
{
public HashSet<BindingRedirect> BindingRedirects { get; } = new HashSet<BindingRedirect>();
public FunctionRedirectBindings()
{
var assm = Assembly.GetExecutingAssembly();
var bindingRedirectFileName = $"{assm.GetName().Name}.dll.config";
var dir = Path.Combine(Environment.GetEnvironmentVariable("HOME"), #"site\wwwroot");
var fullPath = Path.Combine(dir, bindingRedirectFileName);
if(!File.Exists(fullPath))
throw new ArgumentException($"Could not find binding redirect file. Path:{fullPath}");
var xml = ReadFile<configuration>(fullPath);
TransformData(xml);
}
private T ReadFile<T>(string path)
{
using (StreamReader reader = new StreamReader(path))
{
var serializer = new XmlSerializer(typeof(T));
var obj = (T)serializer.Deserialize(reader);
reader.Close();
return obj;
}
}
private void TransformData(configuration xml)
{
foreach(var item in xml.runtime)
{
var br = new BindingRedirect
{
ShortName = item.dependentAssembly.assemblyIdentity.name,
PublicKeyToken = item.dependentAssembly.assemblyIdentity.publicKeyToken,
RedirectToVersion = item.dependentAssembly.bindingRedirect.newVersion
};
BindingRedirects.Add(br);
}
}
}
public class BindingRedirect
{
public string ShortName { get; set; }
public string PublicKeyToken { get; set; }
public string RedirectToVersion { get; set; }
}
Xml classes to use to deserialise the generated binding redirect file into something easier to use. These were generated from the binding redirects file by using VS2017 "paste special -> paste xml as classes" so feel free to roll your own if needed.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0.
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class configuration
{
[System.Xml.Serialization.XmlArrayItemAttribute("assemblyBinding", Namespace = "urn:schemas-microsoft-com:asm.v1", IsNullable = false)]
public assemblyBinding[] runtime { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "urn:schemas-microsoft-com:asm.v1", IsNullable = false)]
public partial class assemblyBinding
{
public assemblyBindingDependentAssembly dependentAssembly { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
public partial class assemblyBindingDependentAssembly
{
public assemblyBindingDependentAssemblyAssemblyIdentity assemblyIdentity { get; set; }
public assemblyBindingDependentAssemblyBindingRedirect bindingRedirect { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
public partial class assemblyBindingDependentAssemblyAssemblyIdentity
{
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string publicKeyToken { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string culture { get; set; }
}
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")]
public partial class assemblyBindingDependentAssemblyBindingRedirect
{
[System.Xml.Serialization.XmlAttributeAttribute()]
public string oldVersion { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string newVersion { get; set; }
}
Here's an alternate solution for when you want the exact version of a particular assembly. With this code, you can easily deploy the assemblies that are missing:
public static class AssemblyHelper
{
//--------------------------------------------------------------------------------
/// <summary>
/// Redirection hack because Azure functions don't support it.
/// How to use:
/// If you get an error that a certain version of a dll can't be found:
/// 1) deploy that particular dll in any project subfolder
/// 2) In your azure function static constructor, Call
/// AssemblyHelper.IncludeSupplementalDllsWhenBinding()
///
/// This will hook the binding calls and look for a matching dll anywhere
/// in the $HOME folder tree.
/// </summary>
//--------------------------------------------------------------------------------
public static void IncludeSupplementalDllsWhenBinding()
{
var searching = false;
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
// This prevents a stack overflow
if(searching) return null;
var requestedAssembly = new AssemblyName(args.Name);
searching = true;
Assembly foundAssembly = null;
try
{
foundAssembly = Assembly.Load(requestedAssembly);
}
catch(Exception e)
{
Debug.WriteLine($"Could not load assembly: {args.Name} because {e.Message}");
}
searching = false;
if(foundAssembly == null)
{
var home = Environment.GetEnvironmentVariable("HOME") ?? ".";
var possibleFiles = Directory.GetFiles(home, requestedAssembly.Name + ".dll", SearchOption.AllDirectories);
foreach (var file in possibleFiles)
{
var possibleAssembly = AssemblyName.GetAssemblyName(file);
if (possibleAssembly.Version == requestedAssembly.Version)
{
foundAssembly = Assembly.Load(possibleAssembly);
break;
}
}
}
return foundAssembly;
};
}
}

NAsm error with c# and Cosmos

I am trying to build a small operating system using c# and Cosmos
At fisrt this went pretty good only from one moment to the other it stoped working and gave me the following error:
Error occurred while invoking NAsm.
OSBoot.asm Line: 241424 Code: push dword System_Void__System_IO_Stream_Dispose__
OSBoot.asm Line: 242633 Code: push dword System_Void__System_IO_Stream_Dispose__ t
OSBoot.asm Line: 247640 Code: push dword System_Void__System_IO_Stream_Dispose__
OSBoot.asm Line: 248618 Code: push dword System_Void__System_Collections_Generic_List_1_Enumerator__Commands_ICommand_ICommandBase_Dispose_
I even installed a new installation of Microsoft Visual studio with cosmos on a new system and started over from scratch, at first it worked but asgain withoud any chage it stopped working
using System;
using Commands;
using Sys = Cosmos.System;
namespace OS
{
public class Kernel : Sys.Kernel
{
private Env SysEnv { get; set; }
private InputHandeler InputHandler { get; set; }
protected override void BeforeRun()
{
Console.WriteLine("Creating Enviriment...");
SysEnv = new Env();
InputHandler = new InputHandeler(new CommandLister());
Console.WriteLine("Enviriment build");
Console.Clear();
Console.WriteLine("Os booted successfully.");
}
protected override void Run()
{
Console.Write(SysEnv.ConsolCommandPrefix);
var input = Console.ReadLine();
try
{
InputHandler.ExecuteInput(input);
} catch(Exception)
{
//TODO good exeption and the use of it
}
}
}
}
Env class
namespace OS
{
public class Env
{
public string CurrentUser { get; set; }
public string CurrnetDir { get; set; }
private string prefix;
public string ConsolCommandPrefix
{
get { return CurrentUser + "#" + CurrnetDir + prefix; }
set { prefix = value; }
}
public Env()
{
ConsolCommandPrefix = "> ";
CurrentUser = "Admin";
CurrnetDir = "/";
}
}
}
CommandLister class
using System;
using System.Collections.Generic;
using Commands.Command;
using Commands.Command.SystemCommands;
using Commands.ICommand;
namespace Commands
{
public class CommandLister
{
private List<ICommandBase> KnownCommands { get; set; }
private Unkown UnkownCommand { get; set; }
public CommandLister()
{
KnownCommands = new List<ICommandBase>();
addCommand(new Help());
UnkownCommand = new Unkown();
}
public void addCommand(ICommandBase command)
{
KnownCommands.Add(command);
}
public ICommandBase getCommand(String input)
{
foreach (var command in KnownCommands)
{
if(true)
{
return command;
}
}
return UnkownCommand;
}
}
}
The other classes only contain some text and only print something to the Console
I have two questions:
- What is causing this
- How can i solve this problem
Edit: i tried it again ron an other OS (i used vista the first time), this time i used Windows 7. The same thing happend at first it worked (i started from scratch again..) but after a few builds and runs it somehow broke again. even when undoing the changes i made. it wont build anymore.
Kind regards,
Edo Post

How to read signed request using c# 4.0

I am using c# 4.0 and I am integrating facebook registration plugin
Can somebody please tell me what I need to do to read signed request.
I have downloaded Facebook.dll and Newtonsoft.Json.dll
I also have valid
App ID: ***
API Key: **********
App Secret: *************
If possible please give me a sample code how should I pass these keyes and how I can collect the decoded data sent in form of signed request.
Thanks:
There must be more easy ways to read signed request following is what I am using. There are few steps involved to read facebook signed request in c#
Dot net 2010 is required to follow these steps. It is suggested if you make a new web based project named “fb” when you done then you may import this code to your real project.
Download source from http://facebooksdk.codeplex.com/SourceControl/changeset/view/f8109846cba5#Source%2fFacebook%2fFacebookApp.cs
After unzip you will get “facebooksdk-f8109846cba5” inside it you will find a folder facebooksdk-f8109846cba5\Source\Facebook in this folder look for “Facebook.csproj”
Open “Facebook.csproj” in vs 2010 Look for file “FacebookApp.cs” open this file and search “internal protected FacebookSignedRequest ParseSignedRequest(string signedRequestValue)
Change “internal protected” to “public”
Then build the project by right click on project. Now it’s complied file (“Facebook.dll”) is ready to use. Copy it to your project bin directory and add its reference.
Now download Json.Net 3.5 release 8 from http://json.codeplex.com/releases/view/50552 and add to your project bin folder and also add its reference.
Now you are ready to read signed request. It is time to write code to read signed request.
using System;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
using Facebook;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System.Collections.Generic;
namespace fb
{
public partial class test3 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
FacebookApp fap = new FacebookApp();
fap.AppId = "************";
fap.AppSecret = "********************";
string requested_Data = Request.Form["signed_request"];
FacebookSignedRequest fsr = fap.ParseSignedRequest(requested_Data);
// string json = JsonConvert.SerializeObject(fsr.Dictionary, Formatting.Indented);
UserData ud = new UserData(fsr);
Response.Write(ud.name + "");
Response.Write(ud.birthday + "");
Response.Write(ud.country + "");
Response.Write(ud.email + "");
Response.Write(ud.gender + "");
Response.Write(ud.location + "");
Response.Write(ud.userId + "");
}
}
public class UserData
{
public UserData(FacebookSignedRequest fsr)
{
string value = string.Empty;
JObject o;
foreach (string key in fsr.Dictionary.Keys)
{
value = fsr.Dictionary[key];
switch (key)
{
case "user_id":
userId = value;
break;
case "registration":
o = JObject.Parse(value);
name = GetValue(o, "name");
birthday = GetValue(o, "birthday");
email = GetValue(o, "email");
gender = GetValue(o, "gender");
location = GetValue(o, "location.name");
break;
case "user":
o = JObject.Parse(value);
country = GetValue(o, "country");
break;
}
}
}
private string GetValue(JObject o, string token)
{
string ret = string.Empty;
try
{
ret = (string)o.SelectToken(token);
}
catch (Exception ex)
{
throw ex;
}
return ret;
}
public string name { get; set; }
public string birthday { get; set; }
public string gender { get; set; }
public string location { get; set; }
public string country { get; set; }
public string email { get; set; }
public string userId { get; set; }
}
}
This is what I am using and its working fine for me.
Here is using the FB C# SDK v.5.2.1.0
var signed_request = Request.Form["signed_request"];
var fap = Facebook.FacebookApplication.Current;
var signed_request_obj = Facebook.FacebookSignedRequest.Parse(fap, signed_request);
if (signed_request_obj != null)
{
JObject o = JObject.Parse(signed_request_obj.Data.ToString());
}

Categories