I have a Neo4j Graphdatabase with access via the Neo4jClient. (It is a .NET client for the REST api of Neo4j)
There is the beginning of a documentation.
What I have done
The connection to the database works.
Client = new GraphClient(new Uri("http://localhost:7474/db/data"));
Client.Connect();
This way I can insert Nodes...
Client.Create(new myNodeClass { name = "Nobody" });
... and query them.
Node<myNodeClass> Node = Client.Get<WordNode>(138);
return Node.Data.name;
What I want to do
I simply want to add and update relationships between Nodes. (The type of relationship have to be numeric.)
Unfortunately there is no documentation about relationships yet.
There is a command named CreateRelationship. But I can't get it work.
Client.CreateRelationship(Neo4jClient.NodeReference<TSourceNode>, TRelationship);
Can you give me an example of adding and updating (numeric) relationships?
There's a lot to be found in the test cases... Such as this:
http://hg.readify.net/neo4jclient/src/4693da483a90/Test/ApiUsageIdeas.cs
I was stuck too then I realized I needed to specify the type parameter of the source node reference parameter in the CreateRelationship method.
In this example, I have created the relationship. I have not yet updated the relationship.
Disclosure(It works on my machine as a console application running visual studio 2012, YMMV)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Neo4jClient;
namespace Neo4jClientExample
{
class MyConsoleProgram
{
private GraphClient Client {get;set; }
static void Main(string[] args)
{
try{
GraphClient client = new GraphClient(new Uri("http://localhost:7474/db/data"));
client.Connect();
Us us = new Us { Name = "We are Us" };
NodeReference<Us> usRef = client.Create(us);
Console.WriteLine("us node.id: {0}", usRef.Id);
var queryUs = client.Cypher.Start("n", "node(" + usRef.Id + ")").Return<Node<Us>>("n");
Console.WriteLine("Us node name: {0}\n", queryUs.Results.AsEnumerable<Node<Us>>().First().Data);
AllYourBase allYourBase = new AllYourBase { Name = "We are all your base" };
NodeReference<AllYourBase> allYourBaseRef = client.Create(allYourBase);
Console.WriteLine("AllYourBase node.id: {0}",allYourBaseRef.Id);
var queryAllYourBase = client.Cypher.Start("n", "node(" + allYourBaseRef.Id + ")").Return<Node<AllYourBase>>("n");
Console.WriteLine("AllYourBase node name: {0}\n", queryAllYourBase.Results.AsEnumerable<Node<AllYourBase>>().First().Data);
RelationshipReference areBelongToRef = client.CreateRelationship(allYourBaseRef, new AreBelongTo(usRef));
var query = client.Cypher.Start("allyourbase", "node(" + allYourBaseRef.Id + ")").Match("allyourbase-[:ARE_BELONG_TO]->us").Return<Node<AllYourBase>>("allyourbase");
query.ExecuteWithoutResults();
Console.WriteLine("Result of querying for all your base that belongs to us: {0}", query.Results.AsEnumerable<Node<AllYourBase>>().First().Data.Name);
}
catch(Exception ex)
{
Console.WriteLine("{0}", ex.Message);
Console.WriteLine("{0}", ex.InnerException);
}
Console.ReadKey();
}
}
public class Us
{
public string Name {get; set;}
public Us()
{
}
}
public class AllYourBase
{
public string Name { get; set; }
public AllYourBase()
{
}
}
public class AreBelongTo : Relationship, IRelationshipAllowingSourceNode<AllYourBase>,
IRelationshipAllowingTargetNode<Us>
{
public AreBelongTo(NodeReference targetNode)
: base(targetNode)
{}
public const string TypeKey = "ARE_BELONG_TO";
public override string RelationshipTypeKey
{
get { return TypeKey; }
}
}
you could have a look at the tests, http://hg.readify.net/neo4jclient/src/4693da483a90/Test/RelationshipTests.cs or contact the author on the Neo4j mailing list, groups.google.com/group/neo4j ?
Related
I created the post action in WEB-API project and this is the steps :
1- connect to database and updated Model from the database and using Data.Edmx
2- Created the Model homeVisit
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using aspnetWEBAPI;
public class homeVisit
{
public bool Success { get; set; }
public List<Home_Visits_Request> Data { get; set; }
public string Message { get; set; }
}
3- Created the controller and post action
[HttpPost]
[ActionName("posthomevisitrequest")]
public HttpResponseMessage posthomevisitrequest([FromBody] Home_Visits_Request Labrequest)
{
using (DBEntities1 entities = new DBEntities1())
{
entities.Home_Visits_Request.Add(Labrequest);
entities.SaveChanges();
homeVisit homeRequest = new homeVisit
{
Success = true,
Data = entities,
Message = "Data Inserted "
};
FalseStatus falsestat = new FalseStatus
{
Success = false,
Message = "Cannot Insert Data "
};
if (!entities.Home_Visits_Request.Any())
{
return Request.CreateResponse(HttpStatusCode.NotFound, falsestat);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, homeRequest);
}
}
the error shows in step 3 in this line Data = entities,
"cannot implicitly convert type db.entities1 to system.collection.generic.list<aspnetWEBAPI.Home_Visits_Request>
and another error in this line if (!entities.Any())
"DBEntities1 does not contain a definition for 'Any' and no extension method Any accepting the first argument for DBEntities1 could be found are you missing a using directive or assembly reference ? "
I used same code and structure with Get Method but I think there is some changes I need to change with post method , How to solve this error your help please .
Change your code:
using (DBEntities1 entities = new DBEntities1())
{
entities.Set<Home_Visits_Request>().Add(Labrequest);
var result= entities.SaveChanges();
if (result == 0)
{
FalseStatus falsestat = new FalseStatus
{
Success = false,
Message = "Cannot Insert Data "
};
return Request.CreateResponse(HttpStatusCode.NotFound,
falsestat);
}
homeVisit homeRequest = new homeVisit
{
Success = true,
Data = entities.Set<Home_Visits_Request>().ToList(),
Message = "Data Inserted "
};
return Request.CreateResponse(HttpStatusCode.OK,
homeRequest);
and check that you have using System.Linq; in your controller class.
Update:
If you want to return only the added record, change your model class
public class homeVisit
{
public bool Success { get; set; }
public Home_Visits_Request Data { get; set; }
public string Message { get; set; }
}
and use this code:
homeVisit homeRequest = new homeVisit
{
Success = true,
Data = Labrequest,
Message = "Data Inserted "
};
return Request.CreateResponse(HttpStatusCode.OK,
homeRequest);
I think it is a good idea to wrap save in a try...catch block too
I have an asp.net core project which needs to be able to support plugins at runtime, and as a consequence, I need to generate database tables based on what has been plugged in. The plugins are each divided in separate projects and they have have their own DbContext class. The plugins to be used are not known during compile-time, only at runtime.
Now in EF Core I thought that there would be a method like "UpdateDatabase" where you can just add tables to the existing database, but I was wrong. Is there a way to accomplish this? I was able to generate a separate database for each of the plugins, but that wasn't quite what I had in mind..I needed all tables in one database.
Here's the code for the "HRContext" plugin:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Plugins.HR.Models.Entities;
namespace Plugins.HR.Contexts
{
public class HrContext : DbContext
{
public HrContext()
{
}
public HrContext(DbContextOptions<HrContext> contextOptions) : base(contextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("HR");
base.OnModelCreating(modelBuilder);
}
public DbSet<Address> Address { get; set; }
public DbSet<Attendance> Attendance { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<JobTitle> JobTitles { get; set; }
}
}
Here's another piece of code for the "CoreContext" plugin:
using System;
using System.Collections.Generic;
using System.Text;
using Core.Data.Models;
using Microsoft.EntityFrameworkCore;
namespace Core.Data.Contexts
{
public class CoreContext : DbContext
{
public CoreContext()
{
}
public CoreContext(DbContextOptions<CoreContext> contextOptions) : base(contextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Core");
base.OnModelCreating(modelBuilder);
}
public DbSet<Test> Tests { get; set; }
}
}
My ConfigureServices method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CoreContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.Core; integrated security = true;"))
.AddDbContext<HrContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.HR; integrated security = true;"));
// Add framework services.
services.AddMvc();
}
If I try to change the connection string to be the same, sooner or later I will get an error that says that the table for one plugin does not exist. I tried "EnsureCreated" but that didn't work too.
I had the same issue. See my solution on GitHub a few days ago, here: EF Core Issue #9238
What you need is something like the following:
// Using an interface, so that we can swap out the implementation to support PG or MySQL, etc if we wish...
public interface IEntityFrameworkHelper
{
void EnsureTables<TContext>(TContext context)
where TContext : DbContext;
}
// Default implementation (SQL Server)
public class SqlEntityFrameworkHelper : IEntityFrameworkHelper
{
public void EnsureTables<TContext>(TContext context)
where TContext : DbContext
{
string script = context.Database.GenerateCreateScript(); // See issue #2943 for this extension method
if (!string.IsNullOrEmpty(script))
{
try
{
var connection = context.Database.GetDbConnection();
bool isConnectionClosed = connection.State == ConnectionState.Closed;
if (isConnectionClosed)
{
connection.Open();
}
var existingTableNames = new List<string>();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT table_name from INFORMATION_SCHEMA.TABLES WHERE table_type = 'base table'";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
existingTableNames.Add(reader.GetString(0).ToLowerInvariant());
}
}
}
var split = script.Split(new[] { "CREATE TABLE " }, StringSplitOptions.RemoveEmptyEntries);
foreach (string sql in split)
{
var tableName = sql.Substring(0, sql.IndexOf("(", StringComparison.OrdinalIgnoreCase));
tableName = tableName.Split('.').Last();
tableName = tableName.Trim().TrimStart('[').TrimEnd(']').ToLowerInvariant();
if (existingTableNames.Contains(tableName))
{
continue;
}
try
{
using (var createCommand = connection.CreateCommand())
{
createCommand.CommandText = "CREATE TABLE " + sql.Substring(0, sql.LastIndexOf(";"));
createCommand.ExecuteNonQuery();
}
}
catch (Exception)
{
// Ignore
}
}
if (isConnectionClosed)
{
connection.Close();
}
}
catch (Exception)
{
// Ignore
}
}
}
}
Then at the end of Startup.Configure(), I resolve an IEntityFrameworkHelper instance and use that with an instance of DbContext to call EnsureTables().
One issue is I need to still account for the parts of the script which are not CREATE TABLE statements. For example, the CREATE INDEX statements.
I requested they give us a clean solution, for example: add a CreateTable<TEntity>() method to IRelationalDatabaseCreator. Not holding my breath for that though...
EDIT
I forgot to post the code for GenerateCreateScript(). See below:
using System.Text;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
public static class DatabaseFacadeExtensions
{
public static string GenerateCreateScript(this DatabaseFacade database)
{
var model = database.GetService<IModel>();
var migrationsModelDiffer = database.GetService<IMigrationsModelDiffer>();
var migrationsSqlGenerator = database.GetService<IMigrationsSqlGenerator>();
var sqlGenerationHelper = database.GetService<ISqlGenerationHelper>();
var operations = migrationsModelDiffer.GetDifferences(null, model);
var commands = migrationsSqlGenerator.Generate(operations, model);
var stringBuilder = new StringBuilder();
foreach (var command in commands)
{
stringBuilder
.Append(command.CommandText)
.AppendLine(sqlGenerationHelper.BatchTerminator);
}
return stringBuilder.ToString();
}
}
It's based on the code found here: EF Core Issue #2943
Forgive the lengthy code here, and I also realise this may be a very basic fundamental question for any object-oriented developer, but I'm a front-end developer in at the deep end with .NET and trying to learn about classes and methods with an actual example. I've read resources to explain this stuff but immediately get stuck with the complexities of real-world code.
Basically I have a bunch of methods for adding comments to a web page and manipulating the status (marking as spam, deleting etc). Many of these methods call an 'EmailNotification' method, which sends an email to an administrator at each stage. It works great.
However, I'd like to use the 'EmailNotification' method elsewhere in the project, calling it from a different .cs file. When I try to do this it doesn't recognise the method because (I think!?) it's not a public static method.
Can anyone explain to me how to extract the EmailNotification method so that I can use it in different places around the code? I have tried creating a new class with this method inside it, but I just can't get it to work.
using System;
using System.Net.Mail;
namespace UComment.Domain
{
public class Comment
{
public delegate void CommentCreatedEventHandler(Comment sender, EventArgs e);
public delegate void CommentDeletedEventHandler(Comment sender, EventArgs e);
public delegate void CommentSpamEventHandler(Comment sender, EventArgs e);
public delegate void CommentApprovedEventHandler(Comment sender, EventArgs e);
public static event CommentCreatedEventHandler CommentCreated;
public static event CommentDeletedEventHandler CommentDeleted;
public static event CommentSpamEventHandler CommentSpam;
public static event CommentApprovedEventHandler CommentApproved;
protected virtual void OnCommentCreated(EventArgs e)
{
if (CommentCreated != null) CommentCreated(this, e);
}
protected virtual void OnCommentSpam(EventArgs e)
{
if (CommentSpam != null) CommentSpam(this, e);
}
protected virtual void OnCommentApproved(EventArgs e)
{
if (CommentApproved != null) CommentApproved(this, e);
}
protected virtual void OnCommentDelete(EventArgs e)
{
if (CommentDeleted != null) CommentDeleted(this, e);
}
public int Id { get; set; }
public int ParentNodeId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public bool Spam { get; set; }
public bool Approved { get; set; }
public DateTime Created { get; set; }
public string CommenText { get; set; }
public int StatusId { get; set; }
public Comment(int id)
{
Id = id;
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
var reader = sqlHelper.ExecuteReader("select * from Comment where id = #id",
sqlHelper.CreateParameter("#id", id));
if(!reader.HasRecords) throw new Exception(string.Format("Comment with id {0} was not found", id));
reader.Read();
Name = reader.GetString("name");
ParentNodeId = reader.GetInt("nodeid");
Email = reader.GetString("email");
Website = reader.GetString("website");
Approved = reader.GetBoolean("approved");
Spam = reader.GetBoolean("Spam");
Created = reader.GetDateTime("created");
CommenText = reader.GetString("comment");
StatusId = reader.GetInt("statusid");
}
private Comment()
{
}
/// <summary>
/// Set as approved, mark as Not Spam - ignore HAM status
/// </summary>
public void MarkAsApproved()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set approved = 1, spam = 0, statusid = 2 where id = #id",
sqlHelper.CreateParameter("#id", Id));
OnCommentApproved(EventArgs.Empty);
// Send approval email
EmailNotification(1);
}
/// <summary>
/// Remove approval status. Ignore Spam and Ham states
/// </summary>
public void MarkAsNotApproved()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set approved = 0, statusid = 3 where id = #id",
sqlHelper.CreateParameter("#id", Id));
OnCommentApproved(EventArgs.Empty);
// Send rejection email
EmailNotification(2);
}
/// <summary>
/// Spam cannot be ham or approved
/// </summary>
public void MarkAsSpam()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set spam = 1, ham = 0, approved = 0, statusid = 3 where id = #id",
sqlHelper.CreateParameter("#id", Id));
OnCommentSpam(EventArgs.Empty);
// No email notification required - spammer not worthy of a reason for rejection
}
/// <summary>
/// Ham is "not spam" - approved comments from Akismet.
/// </summary>
public void MarkAsHam()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set spam = 0, ham = 1 where id = #id",
sqlHelper.CreateParameter("#id", Id));
// No email notification required, simply marking spam as ham
}
public void Delete()
{
if (Id < 1) return;
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery("delete from comment where id = #id", sqlHelper.CreateParameter("#id", Id));
Id = -1;
OnCommentDelete(EventArgs.Empty);
// Permanent deletion
}
public void Reject()
{
if (Id < 1) return;
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery("update comment set statusid = 3 where id = #id", sqlHelper.CreateParameter("#id", Id));
//Id = -1;
//OnCommentDelete(EventArgs.Empty);
// Send rejection email
EmailNotification(2);
}
public static Comment MakeNew(int parentNodeId, string name, string email, string website, bool approved, bool spam, DateTime created, string commentText, int statusId)
{
var c = new Comment
{
ParentNodeId = parentNodeId,
Name = name,
Email = email,
Website = website,
Approved = approved,
Spam = spam,
Created = created,
CommenText = commentText,
StatusId = statusId
};
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
c.Id = sqlHelper.ExecuteScalar<int>(
#"insert into Comment(mainid,nodeid,name,email,website,comment,approved,spam,created,statusid)
values(#mainid,#nodeid,#name,#email,#website,#comment,#approved,#spam,#created,#statusid)",
sqlHelper.CreateParameter("#mainid", -1),
sqlHelper.CreateParameter("#nodeid", c.ParentNodeId),
sqlHelper.CreateParameter("#name", c.Name),
sqlHelper.CreateParameter("#email", c.Email),
sqlHelper.CreateParameter("#website", c.Website),
sqlHelper.CreateParameter("#comment", c.CommenText),
sqlHelper.CreateParameter("#approved", c.Approved),
sqlHelper.CreateParameter("#spam", c.Spam),
sqlHelper.CreateParameter("#created", c.Created),
sqlHelper.CreateParameter("#statusid", c.StatusId));
c.OnCommentCreated(EventArgs.Empty);
if (c.Spam)
{
c.OnCommentSpam(EventArgs.Empty);
}
if (c.Approved)
{
c.OnCommentApproved(EventArgs.Empty);
}
return c;
}
public override string ToString()
{
return #"ParentNodeId " + ParentNodeId + #"
Name " + Name + #"
Email " + Email + #"
Website " + Website + #"
Approved " + Approved + #"
Spam " + Spam + #"
Created "+ Created + #"
CommenText " + CommenText + Environment.NewLine;
}
/// <summary>
/// Send email notification
/// </summary>
public void EmailNotification(int notificationType)
{
var uCommentAdminEmail = Config.GetUCommentSetting("uCommentAdminEmail");
MailAddress to = null;
MailAddress from = new MailAddress(uCommentAdminEmail);
string subject = null;
string body = null;
switch (notificationType)
{
case 1:
// Comment approved
to = new MailAddress("me#mydomain.com");
subject = "Comment approved";
body = #"The comment you posted has been approved";
break;
case 2:
// Comment rejected
to = new MailAddress("me#mydomain.com");
subject = "Comment rejected";
body = #"The comment you posted has been rejected";
break;
}
MailMessage message = new MailMessage(from, to);
message.Subject = subject;
message.Body = body;
SmtpClient client = new SmtpClient();
try
{
client.Send(message);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught in EmailNotification: {0}", ex.ToString());
}
finally
{
//
}
}
}
}
Thanks for any pointers folks!
You can:
Extract it to a static method on static class
Create a singleton class that has an instance method
Create a class and interface for MessageSender and use DI to inject it where it's needed.
It depends on the size of the project: for small ones 1. may be enough, for big and complex (and if you have DI in place) 3 would be required.
What you have here is a public method, but because it's not declared as static (public static void EmailNotification...), it cannot be used without creating an instance of the class that it lives in.
using System;
namespace UComment.Domain
{
public class MyOtherClass
{
public void MyMethod()
{
Comment c = new Comment();
c.EmailNotification(1);
}
}
}
You could declare the method static which would let you call it like this:
using System;
namespace UComment.Domain
{
public class MyOtherClass
{
public void MyMethod()
{
Comment.EmailNotification(1);
}
}
}
If you're trying to use it from a different namespace then you would need to include the namespace either by a using statement or by specifying the full namespace inline.
using System;
using UComment.Domain;
namespace UComment.OtherNamespace
{
public class MyOtherClass
{
public void MyMethod()
{
Comment c = new Comment();
c.EmailNotification(1);
}
}
}
Or
using System;
namespace UComment.OtherNamespace
{
public class MyOtherClass
{
public void MyMethod()
{
UComment.Domain.Comment c = new UComment.Domain.Comment();
c.EmailNotification(1);
}
}
}
You are correct in thinking that if you wish to make this a common method, it should independent of the Comment class. The same limitations that I've just described apply to doing that. In addition, you'll have to make sure that any appropriate using statements are on the new class and that the dependencies within the EmailNotification are accounted for as well.
Your class makes too many things!
Split it in different types, each one has to solve only one type of problem according to Separation of concerns.
Same thing for the email sending, create an EmailSender class (or another name) and centralize the Send method there.
You can also create an interface (e.g. ISmtpClientFactory) to pass to the EmailSender class to abstract the concrete system to send emails and improve the testing experience.
Only on the production environment you really send emails, in the test environment you can use a fake factory to simulate the sending.
public class EmailSender
{
private readonly ISmtpClientFactory factory;
public EmailSender(ISmtpClientFactory factory)
{
this.factory = factory;
}
public void Send(MailMessage message)
{
using (var client = factory.Create())
{
using (message)
{
client.Send(message);
}
}
}
}
new EmailSender(new SmtpClientFactory()).Send(AdviceMessageFactory.Create(...));
You could put it in it's own class (like you already tried) and make the method static.
If this new class was EmailHelper, you would call the method like so:
EmailHelper.EmailNotification(1);
Depending on the namespace of the new class, you may also need a using statement at the top of every file you use it in.
It doesn't look like It should cause any issue If you create a (public) class and have that method in it. That method should accept all the properties it needs to send an email. You can create an instance of that class and call that method.
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());
}
I have an application that stores a collection of objects in the user settings, and is deployed via ClickOnce. The next version of the applications has a modified type for the objects stored. For example, the previous version's type was:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
And the new version's type is:
public class Person
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
Obviously, ApplicationSettingsBase.Upgrade wouldn't know how to perform an upgrade, since Age needs to be converted using (age) => DateTime.Now.AddYears(-age), so only the Name property would be upgraded, and DateOfBirth would just have the value of Default(DateTime).
So I'd like to provide an upgrade routine, by overriding ApplicationSettingsBase.Upgrade, that would convert the values as needed. But I've ran into three problems:
When trying to access the previous version's value using ApplicationSettingsBase.GetPreviousVersion, the returned value would be an object of the current version, which doesn't have the Age property and has an empty DateOfBirth property (since it can't deserialize Age into DateOfBirth).
I couldn't find a way to find out from which version of the application I'm upgrading. If there is an upgrade procedure from v1 to v2 and a procedure from v2 to v3, if a user is upgrading from v1 to v3, I need to run both upgrade procedures in order, but if the user is upgrading from v2, I only need to run the second upgrade procedure.
Even if I knew what the previous version of the application is, and I could access the user settings in their former structure (say by just getting a raw XML node), if I wanted to chain upgrade procedures (as described in issue 2), where would I store the intermediate values? If upgrading from v2 to v3, the upgrade procedure would read the old values from v2 and write them directly to the strongly-typed settings wrapper class in v3. But if upgrading from v1, where would I put the results of the v1 to v2 upgrade procedure, since the application only has a wrapper class for v3?
I thought I could avoid all these issues if the upgrade code would perform the conversion directly on the user.config file, but I found no easy way to get the location of the user.config of the previous version, since LocalFileSettingsProvider.GetPreviousConfigFileName(bool) is a private method.
Does anyone have a ClickOnce-compatible solution for upgrading user settings that change type between application versions, preferably a solution that can support skipping versions (e.g. upgrading from v1 to v3 without requiring the user to in install v2)?
I ended up using a more complex way to do upgrades, by reading the raw XML from the user settings file, then run a series of upgrade routines that refactor the data to the way it's supposed to be in the new next version. Also, due to a bug I found in ClickOnce's ApplicationDeployment.CurrentDeployment.IsFirstRun property (you can see the Microsoft Connect feedback here), I had to use my own IsFirstRun setting to know when to perform the upgrade. The whole system works very well for me (but it was made with blood and sweat due to a few very stubborn snags). Ignore comments mark what is specific to my application and is not part of the upgrade system.
using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Xml;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Reflection;
using System.Text;
using MyApp.Forms;
using MyApp.Entities;
namespace MyApp.Properties
{
public sealed partial class Settings
{
private static readonly Version CurrentVersion = Assembly.GetExecutingAssembly().GetName().Version;
private Settings()
{
InitCollections(); // ignore
}
public override void Upgrade()
{
UpgradeFromPreviousVersion();
BadDataFiles = new StringCollection(); // ignore
UpgradePerformed = true; // this is a boolean value in the settings file that is initialized to false to indicate that settings file is brand new and requires upgrading
InitCollections(); // ignore
Save();
}
// ignore
private void InitCollections()
{
if (BadDataFiles == null)
BadDataFiles = new StringCollection();
if (UploadedGames == null)
UploadedGames = new StringDictionary();
if (SavedSearches == null)
SavedSearches = SavedSearchesCollection.Default;
}
private void UpgradeFromPreviousVersion()
{
try
{
// This works for both ClickOnce and non-ClickOnce applications, whereas
// ApplicationDeployment.CurrentDeployment.DataDirectory only works for ClickOnce applications
DirectoryInfo currentSettingsDir = new FileInfo(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath).Directory;
if (currentSettingsDir == null)
throw new Exception("Failed to determine the location of the settings file.");
if (!currentSettingsDir.Exists)
currentSettingsDir.Create();
// LINQ to Objects for .NET 2.0 courtesy of LINQBridge (linqbridge.googlecode.com)
var previousSettings = (from dir in currentSettingsDir.Parent.GetDirectories()
let dirVer = new { Dir = dir, Ver = new Version(dir.Name) }
where dirVer.Ver < CurrentVersion
orderby dirVer.Ver descending
select dirVer).FirstOrDefault();
if (previousSettings == null)
return;
XmlElement userSettings = ReadUserSettings(previousSettings.Dir.GetFiles("user.config").Single().FullName);
userSettings = SettingsUpgrader.Upgrade(userSettings, previousSettings.Ver);
WriteUserSettings(userSettings, currentSettingsDir.FullName + #"\user.config", true);
Reload();
}
catch (Exception ex)
{
MessageBoxes.Alert(MessageBoxIcon.Error, "There was an error upgrading the the user settings from the previous version. The user settings will be reset.\n\n" + ex.Message);
Default.Reset();
}
}
private static XmlElement ReadUserSettings(string configFile)
{
// PreserveWhitespace required for unencrypted files due to https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=352591
var doc = new XmlDocument { PreserveWhitespace = true };
doc.Load(configFile);
XmlNode settingsNode = doc.SelectSingleNode("configuration/userSettings/MyApp.Properties.Settings");
XmlNode encryptedDataNode = settingsNode["EncryptedData"];
if (encryptedDataNode != null)
{
var provider = new RsaProtectedConfigurationProvider();
provider.Initialize("userSettings", new NameValueCollection());
return (XmlElement)provider.Decrypt(encryptedDataNode);
}
else
{
return (XmlElement)settingsNode;
}
}
private static void WriteUserSettings(XmlElement settingsNode, string configFile, bool encrypt)
{
XmlDocument doc;
XmlNode MyAppSettings;
if (encrypt)
{
var provider = new RsaProtectedConfigurationProvider();
provider.Initialize("userSettings", new NameValueCollection());
XmlNode encryptedSettings = provider.Encrypt(settingsNode);
doc = encryptedSettings.OwnerDocument;
MyAppSettings = doc.CreateElement("MyApp.Properties.Settings").AppendNewAttribute("configProtectionProvider", provider.GetType().Name);
MyAppSettings.AppendChild(encryptedSettings);
}
else
{
doc = settingsNode.OwnerDocument;
MyAppSettings = settingsNode;
}
doc.RemoveAll();
doc.AppendNewElement("configuration")
.AppendNewElement("userSettings")
.AppendChild(MyAppSettings);
using (var writer = new XmlTextWriter(configFile, Encoding.UTF8) { Formatting = Formatting.Indented, Indentation = 4 })
doc.Save(writer);
}
private static class SettingsUpgrader
{
private static readonly Version MinimumVersion = new Version(0, 2, 1, 0);
public static XmlElement Upgrade(XmlElement userSettings, Version oldSettingsVersion)
{
if (oldSettingsVersion < MinimumVersion)
throw new Exception("The minimum required version for upgrade is " + MinimumVersion);
var upgradeMethods = from method in typeof(SettingsUpgrader).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
where method.Name.StartsWith("UpgradeFrom_")
let methodVer = new { Version = new Version(method.Name.Substring(12).Replace('_', '.')), Method = method }
where methodVer.Version >= oldSettingsVersion && methodVer.Version < CurrentVersion
orderby methodVer.Version ascending
select methodVer;
foreach (var methodVer in upgradeMethods)
{
try
{
methodVer.Method.Invoke(null, new object[] { userSettings });
}
catch (TargetInvocationException ex)
{
throw new Exception(string.Format("Failed to upgrade user setting from version {0}: {1}",
methodVer.Version, ex.InnerException.Message), ex.InnerException);
}
}
return userSettings;
}
private static void UpgradeFrom_0_2_1_0(XmlElement userSettings)
{
// ignore method body - put your own upgrade code here
var savedSearches = userSettings.SelectNodes("//SavedSearch");
foreach (XmlElement savedSearch in savedSearches)
{
string xml = savedSearch.InnerXml;
xml = xml.Replace("IRuleOfGame", "RuleOfGame");
xml = xml.Replace("Field>", "FieldName>");
xml = xml.Replace("Type>", "Comparison>");
savedSearch.InnerXml = xml;
if (savedSearch["Name"].GetTextValue() == "Tournament")
savedSearch.AppendNewElement("ShowTournamentColumn", "true");
else
savedSearch.AppendNewElement("ShowTournamentColumn", "false");
}
}
}
}
}
The following custom extention methods and helper classes were used:
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Xml;
namespace MyApp
{
public static class ExtensionMethods
{
public static XmlNode AppendNewElement(this XmlNode element, string name)
{
return AppendNewElement(element, name, null);
}
public static XmlNode AppendNewElement(this XmlNode element, string name, string value)
{
return AppendNewElement(element, name, value, null);
}
public static XmlNode AppendNewElement(this XmlNode element, string name, string value, params KeyValuePair<string, string>[] attributes)
{
XmlDocument doc = element.OwnerDocument ?? (XmlDocument)element;
XmlElement addedElement = doc.CreateElement(name);
if (value != null)
addedElement.SetTextValue(value);
if (attributes != null)
foreach (var attribute in attributes)
addedElement.AppendNewAttribute(attribute.Key, attribute.Value);
element.AppendChild(addedElement);
return addedElement;
}
public static XmlNode AppendNewAttribute(this XmlNode element, string name, string value)
{
XmlAttribute attr = element.OwnerDocument.CreateAttribute(name);
attr.Value = value;
element.Attributes.Append(attr);
return element;
}
}
}
namespace MyApp.Forms
{
public static class MessageBoxes
{
private static readonly string Caption = "MyApp v" + Application.ProductVersion;
public static void Alert(MessageBoxIcon icon, params object[] args)
{
MessageBox.Show(GetMessage(args), Caption, MessageBoxButtons.OK, icon);
}
public static bool YesNo(MessageBoxIcon icon, params object[] args)
{
return MessageBox.Show(GetMessage(args), Caption, MessageBoxButtons.YesNo, icon) == DialogResult.Yes;
}
private static string GetMessage(object[] args)
{
if (args.Length == 1)
{
return args[0].ToString();
}
else
{
var messegeArgs = new object[args.Length - 1];
Array.Copy(args, 1, messegeArgs, 0, messegeArgs.Length);
return string.Format(args[0] as string, messegeArgs);
}
}
}
}
The following Main method was used to allow the system to work:
[STAThread]
static void Main()
{
// Ensures that the user setting's configuration system starts in an encrypted mode, otherwise an application restart is required to change modes.
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
SectionInformation sectionInfo = config.SectionGroups["userSettings"].Sections["MyApp.Properties.Settings"].SectionInformation;
if (!sectionInfo.IsProtected)
{
sectionInfo.ProtectSection(null);
config.Save();
}
if (Settings.Default.UpgradePerformed == false)
Settings.Default.Upgrade();
Application.Run(new frmMain());
}
I welcome any input, critique, suggestions or improvements. I hope this helps someone somewhere.
This may not really be the answer you are looking for but it sounds like you are overcomplicating the problem by trying to manage this as an upgrade where you aren't going to continue to support the old version.
The problem isn't simply that the data type of a field is changing, the problem is that you are totally changing the business logic behind the object and need to support objects that have data relating to both old and new business logic.
Why not just continue to have a person class which has all 3 properties on it.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime DateOfBirth { get; set; }
}
When the user upgrades to the new version, the age is still stored, so when you access the DateOfBirth field you just check if a DateOfBirth exists, and if it doesn't you calculate it from the age and save it so when you next access it, it already has a date of birth and the age field can be ignored.
You could mark the age field as obsolete so you remember not to use it in future.
If necessary you could add some kind of private version field to the person class so internally it knows how to handle itself depending on what version it considers itself to be.
Sometimes you do have to have objects that aren't perfect in design because you still have to support data from old versions.
I know this has already been answered but I have been toying with this and wanted to add a way I handled a similar (not the same) situation with Custom Types:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
private DateTime _dob;
public DateTime DateOfBirth
{
get
{
if (_dob is null)
{ _dob = DateTime.Today.AddYears(Age * -1); }
else { return _dob; }
}
set { _dob = value; }
}
}
If both the private _dob and public Age is null or 0, you have another issue all together. You could always set DateofBirth to DateTime.Today by default in that case. Also, if all you have is an individual's age, how will you tell their DateOfBirth down to the day?