This open source library allows you to integrate applications built using Microsoft .NET or Mono with the Appacitive platform. To learn more about the Appacitive platform, please visit www.appacitive.com.
LICENSE
Except as otherwise noted, the .NET SDK for Appacitive is licensed under the Apache License, Version 2.0.
The appacitive .NET SDK can be added to your project via nuget. Simply run the following command
on the nuget package manager console to include the appacitive .NET SDK in your project.
Before we dive into using the SDK, we need to understand a couple of things about using the Appacitive api.
All calls to the appacitive platform are secured using an app specific api key. To access the api key for your app, go to app listing via the portal and click on the key icon on right side.
####Initialize your SDK,
AppContext.Initialize(
platform, // Use the Platforms helper class for options.
appid, // Set your application id here.
apiKey, // Set your api key here.
env, // Set your environment here. Use Environment enum.
settings); // App settings (optional).
You can use the .NET sdk as part of your mobile app as well as any server side application like ASP.net or WCF. These enviroments vary quite significantly in terms of how certain operations are performed e.g., network calls.
The platform
input parameter is provided to hint the SDK into the runtime environment in which it is hosted. For example, using Platforms.Aspnet
essentially tells the sdk that it is being hosted inside a web application. Based on this, the SDK can pick the right mechanism for features like user auth and session management.
Depending upon the type of project that you are using, the Platforms helper class may present one or more of the following options - Platforms.WindowsPhone7
, Platforms.WindowsPhone8
, Platforms.Aspnet
, Platforms.Wcf
or Platforms.Nonweb
Now you are ready to use the SDK
-
The .NET SDK makes extensive use of .NET Tasks along with async \ await feature recently introduced with c# 5.0. As a result, most methods exposed via the SDK are async methods.
-
The SDK follows a certain naming convention for the various appacitive data types.
- The SDK defines the following primary data types -
APObject
,APConnection
,APUser
andAPDevice
. - For each primary data type, there is a corresponding helper type with a plural name -
APObjects
,APConnections
,APUsers
andAPDevices
. - Write operations are performed on instances of a specific type.
- Read, Delete and Find operations are performed via static operations on the corresponding helper type.
// To read an object // Notice the plural APObject(s) below. var obj = await APObjects.GetAsync("object", "123"); // To create or update await obj.SaveAsync();
- Incase an api call fails, an exception extending from
BaseAppacitiveException
would be raised.
Debugging in the sdk uses standard .NET trace infrastructure via the AppContext.Debug class. To setup debugging for your app
Add the following configuration to your app.config or web.config. Add a trace listener of your choice. The given configuration will log everything to the console.
<system.diagnostics>
<trace autoflush="true" indentsize="4">
<listeners>
<clear />
<add name="configConsoleListener" type="System.Diagnostics.ConsoleTraceListener" />
</listeners>
</trace>
</system.diagnostics>
The code below shows how to enable debugging for standard scenarios.
// To enable logging of all transactions.
AppContext.Debug.ApiLogging.LogEverything();
// To log only failed calls.
AppContext.Debug.ApiLogging.LogFailures();
// To log calls taking more than 600ms.
AppContext.Debug.ApiLogging.LogSlowCalls(600);
// To log calls based on runtime condition.
AppContext.Debug.ApiLogging.LogIf((rq, rs) => rs.Status.Code == "400");
// To log failed and slow calls.
AppContext.Debug.ApiLogging
.LogFailures()
.LogSlowCalls(600);
All data is represented as objects. This will become clearer as you read on. Let's assume that we are building a game and we need to store player data on the server.
To create a player via the sdk, do the following
var player = new APObject("player");
Huh?
An APObject
comprises of an entity (referred to as object
in Appacitive jargon). To initialize an object, we need to provide it some options. The mandatory argument is the __type
argument.
What is a type? In short, think of types as tables in a contemporary relational database. A type has properties which hold values just like columns in a table. A property has a data type and additional constraints are configured according to your application's need. Thus we are specifying that the player is supposed to contain an entity of the type 'player' (which should already be defined in your application).
The player object is an instance of APObject
. An APObject
is a class which encapsulates the data (the actual entity or the object) and methods that provide ways to update it, delete it etc. To see the raw entity that is stored within the APObject
, fire player.ToJSON()
.
Now we need to name our player 'John Doe'. This can be done as follows
// Using setters
var player = new APObject("player");
player.Set("name", "John Doe");
// dynamically assigning properties (via the use of the dynamic keyword)
dynamic player = new APObject("player");
player.name = "John Doe";
Lets verify that our player is indeed called 'John Doe'
// using the getters
var name = player.Get<String>("name");
// Using the getter with a default value
// This will return N.A. incase the name property is not available.
var name = playet.Get<string>("name", "N.A.");
// dynamic usage
// Assuming that player is declared via the dynamic keyword.
// player.Name, player.NAme etc would also work here.
var name = player.name;
Saving a player to the server is easy.
var player = new APObject("player");
player.Set("name", "John Doe");
player.Set("age", 22);
await player.SaveAsync();
Console.WriteLine("New player created with id {0}.", player.Id);
When you call save, the object is taken and stored on Appacitive's servers. A unique object id is generated and is stored along with the player object. This identifier is also returned to the object on the client-side. You can access it directly using Id
property. This is available in the player
object after a successful save.
You can retrieving an existing object instance from the backend via its type and unique id.
// retrieve the player
var id = "12345";
var player = await APObjects.GetAsync("player", id);
If you want to update an object and need a reference to it without making a call to the server, you can create a new instance by passing the type and id in the constructor. This instance is a loose reference to the object on the server side without actually making a call to the backend.
// Retrieve the player
// The existingPlayer object represents the player object with id 12345.
var existingPlayerId = "12345";
var existingPlayer = new APObject("player", existingPlayerId);
Note: You can mention exactly which all fields you want returned so as to reduce the overall payload size. By default all fields are returned. Fields Id
and Type
are the fields which will always be returned.
// You can choose to get specific fields only by passing the fields needed.
// The following sample will get only the name and age fields.
var player = await APObjects.GetAsync("player", "12345", new[] { "name", "age" });
You can also retrieve multiple objects at a time, which will return an array of APObject
instances. Here's an example
var ids = new [] {"14696753262625025", "14696753262625026", "14696753262625027"};
var fields = new[] { "name", "age" }; // optional fields to get
var players = await APObjects.MultiGetAsync("player", ids, fields);
You can update an existing object via the SaveAsync() instance method on APObject
.
Saving an object will update the instance with the latest values from the server.
// Get existing player
var player = await APObjects.GetAsync("player", "12345");
// Update name and age
player.Set("name", "Jack");
player.Set("age", 24);
// Save
await player.SaveAsync();
As you might notice, updates and creates are both done via the SaveAsync
method. The SDK combines the create operation and the update operation under the hood and provides a unified interface. This is done be detecting the presence of a non-zero Id
property.
This also means that the Id
property on all APObjects or derived types are immutable.
It is possible that multiple clients may be updating the same object instance on the backend. To prevent any accidental bad writes, Appacitive provides a revision number based Multi Version Concurrency Control (MVCC) mechanism.
Each object instance has a integer based Revision property which is internally incremented on each update. If you want to ensure that you are always updating the correct object, you can pass the current revision number of the object during the update. If this revision number matches the revision on the server, the update will be allowed. In case the revision number does not match, it implies that the object has changed since you last read it. Accordingly the update operation will be cancelled.
// Get existing player (say at revision # 3 on the server)
var player = await APObjects.GetAsync("player", "12345");
// Update name and age
player.Set("name", "Jack");
player.Set("age", 24);
// Save (this call will succeed)
var revision = player.Revision; // revision = 3
await player.SaveAsync(revision);
// Save again with old revision (this call will fail as revision number is now 4)
await player.SaveAsync(revision);
You can delete an APObject via the DeleteAsync
method on the APObjects
helper class. Let's say we've had enough of John Doe and delete his player account from the server. Here's how you can do that -
// This will delete player with id 12345.
await APObjects.DeleteAsync("player", "12345");
You can also delete object along with all its first degree connections. Note that this will only delete the connections and not the connected objects.
var deleteConnections = true;
await APObjects.DeleteAsync("player", "12345", deleteConnections);
Multiple objects can also be deleted at a time. The delete connections options is not available for multi deletes. Here's an example
var ids = var ids = new [] {"14696753262625025", "14696753262625026"};
await APObjects.MultiDeleteAsync("player", ids);
All data that resides in the Appacitive platform is relational, like in the real world. This means you can do operations like fetching all games that any particular player has played, adding a new player to a team or disbanding a team whilst still keeping the other teams and their players
data perfectly intact.
Two entities can be connected via a relation, for example two entites of type person
might be connected via a relation friend
or enemy
and so on. An entity of type person
might be connected to an entity of type house
via a relation owns
.
One more thing to understand is the concept of labels. Consider an entity of type person
. This entity is connected to another person
via relation marriage
. Within the context of the relation marriage
, one person is the husband
and the other is the wife
. Similarly the same entity can be connected to an entity of type house
via the relation house_owner
. In context of this relation, the entity of type person
can be referred to as the owner
.
Wife
, husband
and owner
from the previous example are labels
. Labels are used within the scope of a relation to give contextual meaning to the entities involved in that relation. They have no meaning or impact outside of the relation.
Let's jump in!
The SDK provides a very fluent interface for creating connections. You can mix and match various options, to create a connection for your specific application scenario.
Before we go about creating connections, we need two entities. Consider the following
var idForJohn = "12345";
var idForJane = "39209";
// initialize and set up a connection
// This will setup a connection of type sibling between the objects
// with the given ids.
var conn = await APConnection
.New("marriage")
.FromExistingObject("husband", idForJohn)
.ToExistingObject("wife", idForJane)
.SaveAsync();
If you've read the previous guide, most of this should be familiar. What happens in the APConnection
class is that the relation is configured to actually connect the two entities. We provide the ids of the two entities to be connected and specify which is which. For example here, John is the husband and Jane is the wife.
In case you are wondering why this is necessary then here is the answer, it allows you to structure queries like 'who is John's wife?' and much more. Queries are covered in later guides.
conn
is an instance of APConnection
of type marriage
. Similar to an entity, you may call ToJSON
on a connection to get a json representation of the connection.
Apart from connecting existing objects, you can also create and connect new objects in one go. For example, say I want to create an order and its invoice in one go.
// Create new objects for order and invoice
var order = new APObject("order");
order.Set("order_number", 747383);
var invoice = new APObject("invoice");
invoice.Set("invoice_date", DateTime.Now);
// Create both objects and connect them with a connection of type invoices
await APConnection.New("invoices")
.FromNewObject("order", order)
.ToNewObject("invoice", invoice)
.SaveAsync();
Console.WriteLine("Order id: {0}", order.Id);
Console.WriteLine("Invoice id: {0}", invoice.Id);
This is the recommended way to do it. In this case, the invoices relation will create the objects order and invoice first and then connect them using the relation invoices
.
As you can probably guess from the examples above, you can change FromNewObject
to FromExistingObject
and ToNewObject
to ToExistingObject
, depending on whether you are connecting an new object or an existing one.
Like in the case of objects, connections can have user defined properties and attributes as well. For example, a relation of type employed
between a user
and a company
can contain a property called joining_date
. This makes sense since the joining date is not a property of the user or the company in isolation.
Properties in connections work exactly in the same way as they work in objects.
// This works exactly the same as in case of your standard objects.
// Setting property values
APConnection employed;
employed.Set("joining_date", DateTime.Now);
// Reading property values
var joiningDate = employed.Get<DateTime>("joining_date");
var conn = await APConnections.GetAsync("employed", "123456");
The APConnection
object is similar to the APObject
, except you get one new field viz. Endpoints
. Endpoints represent the two objects that the connection links.
var idForJohn = "12345";
var idForJane = "39209";
var conn = await APConnection
.New("marriage")
.FromExistingObject("husband", idForJohn)
.ToExistingObject("wife", idForJane)
.SaveAsync();
var john = await conn.Endpoints["husband"].GetObjectAsync();
var jane = await conn.Endpoints["wife"].GetObjectAsync();
Consider you want to get a list of all people who are friends of John. Essentially what you want is all objects of type person
who are connected to John via the relation 'friend'.
var john = new APObject("person", "12345");
// Returns a paged list of friends.
// We are assuming that the labels for the friend relationship is the same for both endpoints as a friend relationship is not directed.
var friends = await john.GetConnectedObjectsAsync("friend", pageSize:200);
// Write all names to console.
friends.ForEach( f => Console.WriteLine( f.Get<string>("name"));
// Get next page.
var nextPage = await friends.NextPageAsync();
NOTE: Incase the relation you are querying is between the same type with different labels, then you must provide the label of the object that you are querying. Example, consider the relationship father
between person
objects. In this relationship, the same object can participate as the father
as well as the child
with different objects. When querying the father
connection for John, you will need to specify the label as father
to get a list of all John's children.
In all other scenarios, passing a label is optional.
Scenarios where you may need to just get all connections of a particular relation for an objectId, this query comes to rescue.
Consider Jane
is connected to some objects of type person
via invite
relationship, that also contains a bool
property viz. attending
, which is false by default and will be set to true if that person is attending marriage.
Now she wants to know who all are attending her marriage without actually fetching their connected person
object, this can be done as
//set an instance of person Object for Jane
var jane = new Appacitive.Object({ __id : '123345456', __type : 'person');
//call fetchConnectedObjects with all options that're supported by queries syntax
// we'll cover queries in dept in next section
var query = jane.getConnections({
relation: 'invite', //mandatory
label: 'invitee', //mandatory
filter: Appacitive.Filter.Property('attending').equalTo(true)
});
query.fetch().then(function(invites) {
//invites is an array of connections
console.log(invites);
});
In this query, you provide a relation type (name) and a label of opposite side whose conenction you want to fetch and what is returned is a list of all the connections for above object.
Appacitive also provides a reverse way to fetch a connection between two objects. If you provide two object ids of same or different type types, all connections between those two objects are returned.
Consider you want to check whether Tarzan
and Jane
are married, you can do it as
//'marriage' is the relation between person type
//and 'husband' and 'wife' are the endpoint labels
var query = Appacitive.Connection.getBetweenObjectsForRelation({
relation: "marriage", //mandatory
objectAId : "22322", //mandatory
objectBId : "33422", //mandatory
label : "wife" //madatory for a relation between same type and differenct labels
});
query.fetch().then(function(marriage){
if(marriage != null) {
// connection obj is returned as argument to onsuccess
alert('Tarzan and jane are married at location ', marriage.get('location'));
} else {
alert('Tarzan and jane are not married');
}
});
//For a relation between same type type and differenct endpoint labels
//'label' parameter becomes mandatory for the get call
Conside you want to check that a particular house
is owned by Jane
, you can do it by fetching connection for relation owns_house
between person
and house
.
var query = Appacitive.Connection.getBetweenObjectsForRelation({
relation: "owns_house",
objectAId : "22322", // person type entity id
objectBId : "33422" //house type entity id
});
query.fetch().then(function(obj) {
if(obj != null) {
alert('Jane owns this house');
} else {
alert("Jane doesn't owns this house");
}
});
Consider jane
is connected to tarzan
via a marriage
and a freind
relationship. If we want to fetch al connections between them we could do this as
var query = Appacitive.Connection.getBetweenObjects({
objectAId : "22322", // id of jane
objectBId : "33422" // id of tarzan
});
query.fetch().then(function(connections) {
console.log(connections);
});
On success, we get a list of all connections that connects jane
and tarzan
.
Consider, jane
wants to what type of connections exists between her and a group of persons and houses , she could do this as
var query = Appacitive.Connection.getInterconnects({
objectAId: '13432',
objectBIds: ['32423423', '2342342', '63453425', '345345342']
});
query.fetch().then(function(connections) {
console.log(connections);
}, function(err) {
alert("code:" + err.code + "\nmessage:" + err.message);
});
This would return all connections with object id 13432 on one side and '32423423', '2342342', '63453425' or '345345342' on the other side, if they exist.
Updating is done exactly in the same way as entities, i.e. via the save()
method.
Important: Updating the endpoints (the __endpointa
and the __endpointb
property) will not have any effect and will fail the call. In case you need to change the connected entities, you need to delete the connection and create a new one.
marriage.set('location', 'Las Vegas');
marriage.save().then(function(obj) {
alert('saved successfully!');
});
As before, do not modify the __id
property.
Deleting is provided via the del
method.
marriage.destroy().then(function() {
alert('Tarzan and Jane are no longer married.');
});
// Multiple coonection can also be deleted at a time. Here's an example
Appacitive.Object.multiDelete({
relation: 'freinds', //name of relation
ids: ["14696753262625025", "14696753262625026", "14696753262625027"], //array of connection ids to delete
}).then(function() {
//successfully deleted all connections
});
Queries provide a mechanism to search your application data.
All searching in SDK is done via Appacitive.Sdk.Query
object. You can retrieve many objects at once, put conditions on the objects you wish to retrieve, and much more.
The following basic uqery apis are available inside the SDK. Queries apart from these can be made using the Graph api feature discussed later.
/// Find all objects of a given type with the given filters.
Task<PagedList<APObject>> FindAllAsync(
string type, // type to query
IQuery query = null, // query filter
IEnumerable<string> fields = null, // fields to retrieve
int page = 1, // page number
int pageSize = 20, // page size
string orderBy = null, // sort field
SortOrder sortOrder = SortOrder.Descending // sort order
)
/// Find all objects connected to the given object with the given filters.
Task<PagedList<APObject>> GetConnectedObjectsAsync(
string relation, // relation to query
string query = null, // query filter
string label = null, // label of the object
IEnumerable<string> fields = null, // fields to retrieve
int pageNumber = 1, // page number
int pageSize = 20, // page size
string orderBy = null, // sort field
SortOrder sortOrder = SortOrder.Descending) // sort order
As an example, to find all people with first name as John, we would do the following.
List<APObject> allMatches = new List<APObject>();
var query = Query.Property("firstname").IsEqualTo("john");
// Run query. Returns a paged response with pagesize 200.
var matches = await APObjects.FindAllAsync("person", query, pageSize: 200);
allMatches.AddRange(matches);
// Incase more records are present, paginate
while( !matches.IsLastPage )
{
matches = await matches.GetNextPageAsync();
allMatches.AddRange(matches);
}
Notice the page
, pageNumber
, orderBy
, sortOrder
, query
, and fields
? These're the optional parameters that you can specify in a query. Lets get to them one by one.
All queries on the Appacitive platform support pagination and sorting. To specify pagination and sorting on your queries, simply pass the page
, pagesize
. The resulting object also contains custom properties like IsLastPage
as well as methods like GetNextPageAsync()
to help you get to the next page without having to pass all your parameters again.
Query results can be sorted using the orderBy
and sortOrder
parameters.
The orderBy
parameter takes the name of the property on which to sort and the sortOrder
parameter lets you select the order of sorting (ascending or descending).
As in the case of single and multi get apis, you can choose the exact set of fields that you want the api to return. Pass in an array of the properties to be returned in the fields
parameter to apply this modifier to the query results.
Filters are useful for fine tuning the results of your search. Objects and connections inside Appacitive have 4 different types of data, namely - properties, attributes, aggregates and tags. Filters can be applied on each and every one of these. Combinations of these filters is also possible.
The Query
object provides a factory for creating filters for appacitive without having to learn the specialized query format used by the underlying REST api.
The typical format for the Query helper class is
Query.{Property|Attribute|Aggregate}("name").<Condition>(condition args);
Some sample examples of how it can be used are
// To query on a property called firstname
var query = Query.Property("firstname").IsEqualTo("John");
// To query on an attribute called nickname
var query = Query.Attribute("nickname").IsEqualTo("John");
// To query on an aggregate called avg_rating
var query = Query.Aggregate("avg_rating").IsGreaterThan(4.5);
In response it returns you an IQuery
object, which encapsulates the specified filter in object form.
Condition | Sample usage |
---|---|
Geography properties | |
WithinPolygon() | Query.Property("location").WithinPolygon(geocodes); |
WithinCircle() | Query.Property("location").WithinCircle(geocode, radius); |
String properties | |
StartsWith() | Query.Property("name").StartsWith("Ja"); |
Like() | Query.Property("name").Like("an"); |
FreeTextMatches()** | Query.Property("description").FreeTextMatches("roam~0.8"); |
EndsWith() | Query.Property("name").EndsWith("ne"); |
IsEqualTo() | Query.Property("name").IsEqualTo("Jane"); |
Text properties | |
FreeTextMatches()** | Query.Property("description").FreeTextMatches("roam~0.8"); |
**Time properties ** | |
BetweenTime() | Query.Property("start_time").BetweenTime(startDate, endDate); |
IsEqualToTime() | Query.Property("start_time").IsEqualToTime(DateTime.Now); |
IsLessThanTime() | Query.Property("start_time").IsLessThanTime(DateTime.Now); |
IsLessThanEqualToTime() | Query.Property("start_time").IsLessThanEqualToTime(DateTime.Now); |
IsGreaterThanTime() | Query.Property("start_time").IsGreaterThanTime(DateTime.Now); |
IsGreaterThanEqualToTime() | Query.Property("start_time").IsGreaterThanEqualToTime(DateTime.Now); |
**Date properties ** | |
BetweenDate() | Query.Property("start_at").BetweenDate(startDateTime, endDateTime); |
IsEqualToDate() | Query.Property("start_at").IsEqualToDate(DateTime.Now); |
IsLessThanDate() | Query.Property("start_at").IsLessThanDate(DateTime.Now); |
IsLessThanEqualToDate() | Query.Property("start_at").IsLessThanEqualToDate(DateTime.Now); |
IsGreaterThanDate() | Query.Property("start_at").IsGreaterThanDate(DateTime.Now); |
IsGreaterThanEqualToDate() | Query.Property("start_at").IsGreaterThanEqualToDate(DateTime.Now); |
Datetime, int and decimal properties | |
IsLessThan() | Query.Property("field").IsLessThan(value); |
IsLessThanEqualTo() | Query.Property("field").IsLessThanEqualTo(value); |
Between() | Query.Property("field").Between(start, end); |
IsGreaterThanEqualTo() | Query.Property("field").IsGreaterThanEqualTo(value); |
IsGreaterThan() | Query.Property("field").IsGreaterThan(value); |
IsEqualTo() | Query.Property("field").IsEqualTo(value); |
** Supports Lucene query parser syntax |
You can specify a property type as a geography type for a given type or relation. These properties are essential latitude-longitude pairs. Such properties support geo queries based on a user defined radial or polygonal region on the map. These are extremely useful for making map based or location based searches. E.g., searching for a list of all restaurants within 20 miles of a given users locations.
A radial search allows you to search for all records of a specific type which contain a geocode which lies within a predefined distance from a point on the map. A radial search requires the following parameters.
//create a new Geocode object
var center = new Geocode(36.1749687195m, -115.1372222900m);
//create filter
var query = Query.Property("location").WithinCircle(center, 20.0m, DistanceUnit.Miles);
//create query object
var restaurants = await APObjects.FindAllAsync("restaurant", query);
A polygon search is a more generic form of geographcal search. It allows you to specify a polygonal region on the map via a set of geocodes indicating the vertices of the polygon. The search will allow you to query for all data of a specific type that lies within the given polygon. This is typically useful when you want finer grained control on the shape of the region to search.
//create geocode objects
var pt1 = new Geocode(36.1749687195m, -115.1372222900m);
var pt2 = new Geocode(34.1749687195m, -116.1372222900m);
var pt3 = new Geocode(35.1749687195m, -114.1372222900m);
var pt4 = new Geocode(36.1749687195m, -114.1372222900m);
//create polygon filter
var query = Query.Property("location").WithinPolygon(pt1, pt2, pt3, pt4);
//create query object
var restaurants = await APObjects.FindAllAsync("restaurant", query);
The Appacitive platform provides inbuilt support for tagging on all data (objects, connections, users and devices). You can use this tag information to query for a specific data set. The different options available for searching based on tags is detailed in the sections below.
For data of a given type, you can query for all records that are tagged with one or more tags from a given list. For example - querying for all objects of type message that are tagged as personal or private.
// Create the query
var query = Query.Tags.MatchOneOrMore("personal", "private");
// Will return messages tagged with either personal or private or both.
var messages = await APObjects.FindAllAsync("message", query);
An alternative variation of the above tag based search allows you to query for all records that are tagged with all the tags from a given list. For example, querying for all objects of type message that are tagged as personal AND private.
// Create the query
var query = Query.Tags.MatchAll("personal", "private");
// Will return messages tagged with both personal and private.
var messages = await APObjects.FindAllAsync("message", query);
Compound queries allow you to combine multiple queries into one single query. The multiple queries can be combined using Query.And
and Query.Or
operators.
NOTE
: All types of queries with the exception of free text queries can be combined into a compound query.
/*
Find all users
- whose first name is John or Jane and
- who stay within 20 mile radius of the empire state building.
*/
var locationOfEmpireState = new Geocode(40.7484m, 73.9857m);
var query = Query.And(
Query.Or(
Query.Property("firstname").IsEqualTo("john"),
Query.Property("firstname").IsEqualTo("jane")
),
Query.Property("location").WithinCircle(locationOfEmpireState, 20.0m, DistanceUnit.Miles)
);
var matches = await APObjects.FindAllAsync("person", query);
There are situations when you would want the ability to search across all text content inside your data. Free text queries are ideal for implementing this kind of functionality. As an example, consider a free text lookup for users which searches across the username, firstname, lastname, profile description etc.
NOTE
: Free text queries support the Lucene query parser syntax for free text search.
var places = await APObjects.FreeTextSearchAsync("places", "+champs +palais", pageSize:200);
Appacitive graph queries offer immense potential when it comes to filtering and retreiving connected data. There are two kinds of graph operations, graph queries and graph apis.
You can create graph queries and graph apis from the management portal. When you create such queries from the portal, you are required to assign a unique name with every saved search query. You can then use this name to execute the query from your app by making the appropriate api call to Appacitive.
You can execute a saved graph api (query or api) by using it�s name that you assigned to it while creating it from the management portal. You will need to send any placeholders you might have set up while creating the query as a list of key-value pairs in the body of the request.
// Name of graph query
var graphQueryName = "find_connected_users";
// any placeholders if provided : optional
var placeHolders = new Dictionary<string, string>
{
{"key1", "value1"},
{"key2", "value2"}
};
var matchingIDs = await Graph.Query(graphQueryName, placeHolders);
Executing saved graph apis works the same way as executing saved graph queries. The only difference is that you also need to pass the initial ids as an array of strings to feed the api. The response to a graph api will depend on how you design your graph api. Do test them out using the query builder from the query tab on the management portal and from the test harness.
// Name of graph projection query
var graphApiName = "get_my_profile";
// List of ids for which to run the graph api
var userIds = ["34912447775245454", "34322447235528474", "34943243891025029"];
// any placeholders if provided : optional
var placeHolders = new Dictionary<string, string>
{
{"key1", "value1"},
{"key2", "value2"}
};
GraphNode[] results = await Graph.Select(graphApiName, userIds, placeHolders);
Users represent your app's users. There is a host of different functions/features available in the SDK to make managing users easier. The APUser
and APUsers
types deals with user management.
There are multiple ways to create users.
You create users the same way you create any other data.
// set the fields
var user = new APUser();
user.Username = "john.doe";
user.Password = "p@ssw0rd";
user.Email = "[email protected]";
user.FirstName = "John";
user.LastName = "Doe";
// Save the user.
await user.SaveAsync();
There are three ways you could retreive the user
Fetching users by Id
is exactly like fetching objects and connections. Let's say you want to fetch user with Id
12345.
var user = await APUsers.GetByIdAsync("12345");
Note: The APUser class is a subclass of APObject. As a result, all operations supported by APObject
, can be performed on APUser
object. So, above data documenation is valid for users too.
//fetch user by username
var user = await APUsers.GetByUsernameAsync("john.doe");
There's no difference between updating a user and updating an APObject. Updates can be applied to the backend via the SaveAsync
method.
var user = await APUsers.GetByIdAsync("12345");
user.Set("email", "[email protected]");
await user.SaveAsync();
You can delete an existing user via the DeleteUserAsync
method on the APUsers helper class.
var userId = "12345";
await APUsers.DeleteUserAsync(userId);
Authentication is the core of user management. You can authenticate (log in) users in multiple ways. Once the user has authenticated successfully, you will be provided the user's details and an access token. This access token identifies the currently logged in user session. You can also initialize the app with this token. This will send this token with every api call. This way the api can infer that the given call is made in the context of the logged in user. Access control rules may also dictate the need to send this token.
// To initialize the app with an existing token.
var token = "1lqsljkasldjalsu1....";
await AppContext.LoginAsync(new UserTokenCredentials(token));
// To authenticate and initialize the app with the logged in user.
var usernamePassword = new UsernamePasswordCredentials(username, password);
await AppContext.LoginAsync(usernamePassword);
You can ask your users to authenticate via their username and password.
// To simply authenticate the username and password
var credentials = new UsernamePasswordCredentials(username, password);
var userSession = await credentials.AuthenticateAsync();
Console.WriteLine("Logged in user: {0}", userSession.LoggedInUser.Username);
Console.WriteLine("User token: {0}", userSession.UserToken);
You can also authenticate the user and set the user as the logged in user for the app in one go.
var usernamePassword = new UsernamePasswordCredentials(username, password);
await AppContext.LoginAsync(usernamePassword);
You can ask your users to log in via facebook. To do this, you will need to implement the facebook oauth handshake in your app to get the user access token.
You can use the facebook sdk specific to your platform for this purpose.
Once you have the access token, simply create a new instance of an OAuth2Credentials
object to authenticate.
var authType = "facebook";
var facebookAccessToken = "laksjalsjd... "; // Facebook access token from oauth handshake
var facebookCredentials = new OAuth2Credentials(
facebookAccessToken,
authType);
facebookCredentials.CreateUserIfNotExists = true; // This will also create a user if it does not existing in the system.
var session = await facebookCredentials.AuthenticateAsync();
// Alternatively, you could have used the following as well.
await AppContext.LoginAsync(facebookCredentials);
You can ask your users to log in via Twitter. This'll require you to implement twitter login and provide the SDK with consumerkey, consumersecret, oauthtoken and oauthtokensecret
//For login with twitter, pass twitter credentials to SDK
var twitterCredentials = new OAuth1Credentials(
consumerKey,
consumerSecret,
accessToken,
accessSecret,
"twitter");
twitterCredentials.CreateUserIfNotExists = true;
var session = await twitterCredentials.AuthenticateAsync();
Whenever you authenticate a user using the AppContext.LoginAsync()
method, the user is stored in the platform specific local environment and can be retrieved using AppContext.Current.GetCurrentUser()
.
var userInfo = AppContext.Current.GetCurrentUser();
// If user is not logged in, userInfo would be null.
if( userInfo != null )
{
var userToken = userInfo.UserToken;
var loggedInUser = userInfo.User;
}
You can clear the current logged in user by calling AppContext.LogoutAsync()
method.
await AppContext.LogoutAsync();
Users often forget their passwords for your app. So you are provided with an API to reset their passwords. This api emails a single use url for resetting the password to the user's email address.
await APUsers.InitiateResetPasswordAsync(
username, // username of the user
emailSubject // subject of the password reset email (optional)
);
Sending emails from the sdk is quite easy. There are primarily two types of emails that can be sent
- Raw Emails
- Templated Emails
Before you get to sending emails, you need to configure smtp settings. You can either configure it from the portal or in the api call with your mail provider's settings. To send emails via the SDK use the Email class. Alternatively, you can use the NewEmail fluent interface for sending emails as well, as shown below.
A raw email is one where you can specify the entire body of the email. An email has the structure
var to = new [] {"email1", "email2"..}
var cc = new [] {"email1", "email2"..}
var bcc = new [] {"email1", "email2"..}
await NewEmail
.Create("This is a raw email test from the .NET SDK.")
.To(to, cc, bcc)
.From("[email protected]", "[email protected])
.WithBody("This is a raw body email.")
.SendAsync();
You can also save email templates in Appacitive and use these templates for sending mails. The template can contain placeholders that can be substituted before sending the mail.
For example, if you want to send an email to every new registration, it is useful to have an email template with placeholders for username and confirmation link.
Consider we have created an email template called welcome_email
with the following content.
"Welcome [#username] ! Thank you for downloading [#appname]."
Here, [#username] and [#appname] denote the placeholders that we would want to substitute while sending an email. To send an email using this tempate, you would use something like this.
var to = new [] {"email1", "email2"..}
var cc = new [] {"email1", "email2"..}
var bcc = new [] {"email1", "email2"..}
// Sending out a templated email
await NewEmail
.Create("Thank you for downloading our app")
.To(to, cc, bcc)
.From("[email protected]", "[email protected]")
.WithTemplateBody( "welcome_email",
new Dictionary<string, string>
{
{"username", "john.doe"},
{"appname", "appacitive"}
})
.SendAsync();
Note
: Emails are not transactional. This implies that a successful send operation would mean that your email provider was able to dispatch the email. It DOES NOT mean that the intended recipient(s) actually received that email.
To receive push notifications via the Appacitive SDK you need to do a one time setup to register the app with the platform for receiving push notifications.
You can do this using the AppContext.DeviceContext
helper class.
/// Registering the device for push
// This can be called multiple times safely.
await AppContext.DeviceContext.RegisterCurrentDeviceAsync();
After registering the device with the platform, you can subscribe to notifications by simply providing event handlers to be triggered whenever a push notification is received. The code sample below shows how this can be done.
/// For http notifications.
AppContext.DeviceContext.Notifications.HttpNotificationReceived += OnHttpNotificationReceived;
// event handler.
void OnHttpNotificationReceived(object sender, HttpNotificationEventArgs e)
{
...
}
/// For shell toast notifications.
AppContext.DeviceContext.Notifications.ShellToastNotificationReceived += OnShellToastNotificationReceived;
// event handler
void OnShellToastNotificationReceived(object sender, NotificationEventArgs e)
{
...
}
Using Appacitive platform you can send push notification to iOS devices, Android base devices and Windows phone.
You will need to provide some basic one time configurations like certificates, using which we will setup push notification channels for different platforms for you. Also we provide a Push Console using which you can send push notification to the users.
In the .NET SDK, the static object PushNotification
provides methods to send push notification.
Appacitive provides five ways to select the recipients
- Broadcast
- Platform specific Devices
- Specific List of Devices
- To List of Channels
- Query
First we'll see how to send a push notification and then we will discuss the above methods with their options one by one.
If you want to send a push notification to all active devices, you can use the following options
await PushNotification
// Send broadcast
.Broadcast("Push from .NET SDK")
// Increment existing badge by 1
.WithBadge("+1")
// Custom data field1 and field2
.WithData(new { field1 = "value1", field2 = "value2" })
// Expiry in seconds
.WithExpiry(1000)
// Device platform specific options
.WithPlatformOptions(
new IOsOptions
{
SoundFile = soundFile
})
.WithPlatformOptions(
new AndroidOptions
{
NotificationTitle = title
})
.WithPlatformOptions(
new WindowsPhoneOptions
{
Notification = new ToastNotification
{
Text1 = wp_text1,
Text2 = wp_text2,
Path = wp_path
}
})
.SendAsync();
If you want to send push notifications to specific platforms, you can use this option. To do so you will need to provide the devicetype in the query.
await PushNotification
// Send to specific device types
.ToQueryResult( Query.Property("devicetype").IsEqualTo("ios"))
// Increment existing badge by 1
.WithBadge("+1")
// Custom data field1 and field2
.WithData(new { field1 = "value1", field2 = "value2" })
// Expiry in seconds
.WithExpiry(1000)
.SendAsync();
If you want to send push notifications to specific devices, you can use this option. To do so you will need to provide the device ids.
var deviceIDs = new [] {"id1", "id2",..};
await PushNotification
// Send specific device ids
.ToDeviceIds("Push from .NET SDK", deviceIDs);
// Increment existing badge by 1
.WithBadge("+1")
// Custom data field1 and field2
.WithData(new { field1 = "value1", field2 = "value2" })
// Expiry in seconds
.WithExpiry(1000)
.SendAsync();
Device object has a Channel property, using which you can club multiple devices. This is helpful if you want to send push notification using channel.
var channels = new [] {"channel1", "channel2",..};
await PushNotification
// Send specific channels
.ToChannels("Push from .NET SDK", channels);
// Increment existing badge by 1
.WithBadge("+1")
// Custom data field1 and field2
.WithData(new { field1 = "value1", field2 = "value2" })
// Expiry in seconds
.WithExpiry(1000)
.SendAsync();
You can send push notifications to devices using a Query. All the devices which comes out as result of the query will receive the push notification.
IQuery query = Query.Property(..; // create query
await PushNotification
// Send to results from a query
.ToQueryResult(query)
// Increment existing badge by 1
.WithBadge("+1")
// Custom data field1 and field2
.WithData(new { field1 = "value1", field2 = "value2" })
// Expiry in seconds
.WithExpiry(1000)
.SendAsync();
The PushNotification
fluent interface provides a WithPlatformOptions
method to pass different phone platform specific options as shown in the example below.
await PushNotification
// Send specific device ids
.Broadcast("Hello from the .NET SDK")
// Device platform specific options
.WithPlatformOptions(
new IOsOptions
{
SoundFile = soundFile
})
.WithPlatformOptions(
new AndroidOptions
{
NotificationTitle = title
})
.WithPlatformOptions(
new WindowsPhoneOptions
{
Notification = new ToastNotification
{
Text1 = wp_text1,
Text2 = wp_text2,
Path = wp_path
}
})
.SendAsync();
For windows phone 3 types of notifications are supported.
- Toast notifications.
- Tile notifications (flip, cyclic and iconic)
- Raw notifications (string based raw data)
The windows phone platform options allows you to choose the specific kind of notification to be send to each windows phone device type (WP7, WP75 and WP8). The sample below shows how this can be done.
// Toast
await PushNotification
.Broadcast("message")
.WithPlatformOptions(
new WindowsPhoneOptions
{
Notification = new ToastNotification
{
Text1 = wp_text1,
Text2 = wp_text2,
Path = wp_path
}
})
.SendAsync();
// Raw notification
await PushNotification
.Broadcast("message")
.WithPlatformOptions(
new WindowsPhoneOptions
{
Notification = new RawNotification() { RawData = "string data.." }
})
.SendAsync();
// Tile notification (Flip tile for all)
await PushNotification
.Broadcast("message")
.WithPlatformOptions(
new WindowsPhoneOptions
{
Notification = TileNotification.CreateNewFlipTile(
new FlipTile() { FrontTitle = title, .. } )
})
.SendAsync();
// Tile notification (cyclic tile for wp8, flip tile for others)
await PushNotification
.Broadcast("message")
.WithPlatformOptions(
new WindowsPhoneOptions
{
Notification = TileNotification.CreateNewCyclicTile(
new CyclicTile(), new FlipTile() )
})
.SendAsync();
// Tile notification (iconic tile for wp8, flip tile for others)
await PushNotification
.Broadcast("message")
.WithPlatformOptions(
new WindowsPhoneOptions
{
Notification = TileNotification.CreateNewIconicTile(
new IconicTile(), new FlipTile() )
})
.SendAsync();
Appacitive supports file storage and provides api's for you to easily upload and download file. In the background we use amazon's S3 services for persistance.
All file upload and download operations in the SDK are handled by the FileUpload
and FileDownload
classes respectively.
NOTE
: All file operations need a file name. This name can be supplied on upload by the user or can be auto generated to be unique by the api. Some important things to note about the file name are -
- It is not mandatory.
- It does not have to be the same is the name of the file being uploaded.
- Incase it is not supplied, it will be auto generated and returned in the response.
- It is the only handle to download the file, so do not lose it.
To upload a new file, create a new instance of the FileUpload
class with the mime type and optional filename. If you do not supply a file name, an auto generated name will be returned in the response.
// To upload an png image with filename testimage.png
var mimeType = "image/png";
var filePath = ... // Path to the file
var userSpecifiedFileName = "testimage.png";
// filenameOnServer will be testimage.png.
var fileNameOnServer = await new FileUpload(mimeType, userSpecifiedFileName).UploadFileAsync(filePath);
// To upload an png image without specifying a filename
var mimeType = "image/png";
var filePath = ... // Path to the file
// filenameOnServer will be auto generated
var fileNameOnServer = await new FileUpload(mimeType).UploadFileAsync(filePath);
// To upload an png image as a byte array
var mimeType = "image/png";
byte[] data = ... // PNG file data
var filePath = ... // Path to the file
// filenameOnServer will be auto generated
var fileNameOnServer = await new FileUpload(mimeType).UploadAsync(data);
Incase you want to handle the file upload yourself via some custom control or code, you can generate an upload url which will be available for a limited time period to which you can upload the file. The life time of the upload url can be specified in the api call.
The following code snippet shows how this can be done.
// Say you want the upload url to upload a PNG image.
var mimeType = "image/png";
int expiryInMinutes = 10; // Upload url will remain active for next 10 mins
var fileUrl = await new FileUpload(mimeType).GetUploadUrlAsync(expiryInMinutes);
var nameOnServer = fileUrl.FileName; // This will be the filename on the server (auto generated in this case)
var urlToUpload = fileUrl.Url; // This will be the url to upload to.
To download an existing file from the platform, you need the filename of the file returned from the Upload api. With this filename you can download the file using the FileDownload
class.
// To download a file and save to disk
var filename = ...; // File name of the file to download.
var saveAsFilePath = ...; // Path to save the downloaded file.
// The file will be downloaded and saved to the saveAs path specified.
await new FileDownload(filename).DownloadFileAsync(saveAsFilePath);
// To download file contents as byte array
var filename = ...; // File name of the file to download.
var saveAsFilePath = ...; // Path to save the downloaded file.
// The file will be downloaded and saved to the saveAs path specified.
byte[] contents = await new FileDownload(filename).DownloadAsync();
All files uploaded to the Appacitive platform are private by default and cannot be accessed via a http GET to a url alone. You can generate a permanent public url for your file or a limited time public url for your file.
To generate a permanently public url for a file, use the FileDownload.GetPublicUrlAsync()
api. To generate a limited time public url for your file, use the FileDownload.GetDownloadUrlAsync
method.
NOTE
: One important thing to note is that generating a permanently public url for a file marks the file as public. Such files cannot be used to generate limited time public urls any more.
// To generate a limited time (10 mins) public url for a file
var filename = ...; // File name of the file to download.
var expiryInMinutes = 10; // Public url that will be active for next 10 mins
// Get limited time public url
string limitedTimePublicUrl = await new FileDownload(filename).GetDownloadUrlAsync(expiryInMinutes);
// Get permanently public url
string permanentPublicUrl = await new FileDownload(filename).GetPublicUrlAsync();
When using the SDK on the server side inside a web application or web service, we need to make sure that the ambient user context and sdk state is available on a per request basis instead of being statically stored for the entire application. The SDK uses the WCF OperationContext to store and manage this information on a per request basis.
However given the fact that the SDK methods are async, special provisions need to be made to ensure that the OperationContext is available across threads. To do this, service implementations using the SDK must apply the AllowAsyncService
service
behavior to their service implementations. This can be done in two ways.
/*
Add the AllowAsyncService to ensure that OperationContext
is propogated across async calls.
*/
[AllowAsyncService]
public class MyWebService : IMyWebService
{
...
}
<!-- Define a behavior extension inside the wcf service model configuration -->
<behaviorExtensions>
<!-- incase of using .NET 4.0 version of sdk -->
<add name="allowAsyncCalls" type="Appacitive.Sdk.Wcf.AllowAsyncServiceBehaviorExtension, Appacitive.Sdk.Net40" />
<!-- incase of using .NET 4.5 version of sdk -->
<add name="allowAsyncCalls" type="Appacitive.Sdk.Wcf.AllowAsyncServiceBehaviorExtension, Appacitive.Sdk.Net45" />
</behaviorExtensions>
<!-- Use the extension inside your service / endpoint / operation behavior configuration. -->