SQLiteConnection Query - c#

I'm trying to select data from Database using SQLiteConnection. It's an UWP application.
public class ResumeModel
{
public List<User> Users { get; set; } = new List<User>();
public ResumeModel()
{
using (var connection = new SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), App.path))
{
try
{
object query = connection.Query<User>("Select * From User", null);
if(query != null)
{
Users = (List <User>) query;
}
} catch(Exception ex)
{
Debug.Write(ex.ToString());
}
}
}
}
I'm getting the exception: "Object reference not set to an instance of an object"
Here is my User class:
public class User
{
[SQLite.Net.Attributes.PrimaryKey, SQLite.Net.Attributes.AutoIncrement]
public int userID { get; set; }
public String username { get; set; }
public User()
{ }
public User(int userID, string name)
{
this.userID = userID;
this.username = name;
}
}
Does anyone know what I'm doing wrong?
Thanks

You are not bringing single User. You are selecting "*".
Also you are not passing any parameters. So Instead of Null remove that altogether.
I also see you are checking if query != null you don't have to do that. If there is no data, you will receive count as 0
So your query should be
List<User> query = connection.Query<User>("Select * From User");

Try storing your userID entries and your username entries into seperate, but index related lists. Then loop through and initialize a User object for each of the entries of these two lists as your constructor parameters.
List<int> userIDs = connection.Query<int>("Select userID From User order by userIDs");
List<string> userNames = connection.Query<string>("Select userName From User order by userIDs");
List<User> Users = new List<User>();
for (int i = 0; i < userIDs.Count(); i++)
{
User addition = new User(userIDs[i], userNames[i]);
Users.add(addition);
}
The order by clause is to ensure the indexes of the two queries match.

Related

C# - check if a custom list contains a username and if his password it's correct

Noob question! Need some help!
So I'm using GoogleSheets API to store user credentials.
I created a custom list with all the data of every user in the GoogleSheet, I need to verify if the data inserted by user matches something within the list.
I managed to check if the username matches, but how to check if the password matches that username?
##The class
public class ListadeJogadoresRegistados
{
public int id { get; set; }
public string nome { get; set; }
public string pwd { get; set; }
public int hiscore { get; set; }
}
##Building the list
private static List<ListadeJogadoresRegistados> GetListaJogadores()
{
var request = service.Spreadsheets.Values.Get(SpreadsheetID, range);
var response = request.Execute();
var values = response.Values;
var jogador = new List<ListadeJogadoresRegistados>();
foreach (var row in values)
{
jogador.Add(new ListadeJogadoresRegistados
{
id = Int32.Parse((string)row[0]),
nome = (string)row[1],
pwd = (string)row[2],
hiscore = Int32.Parse((string)row[3])
});
}
return jogador;
}
##Data validation
public static bool ValidarLogin(string username, string pwd)
{
var jogadores = GetListaJogadores();
ListadeJogadoresRegistados item = jogadores.Find(item => item.nome == username && item.pwd == pwd);
if (item != null) // check item isn't null
{
// it is logged in
}
return true;

Return Active Directory for group members with attributes with C# .NET Core 3.1

I need to retrieve certain attributes from members of a given group in Active Directory and I think I'm on the wrong track.
My current LDAP query looks like this:
(&(memberOf:1.2.840.113556.1.4.1941:={DN for group})(objectClass=user)(objectCategory={objectCategory}))
This is a rather heavy query, which in average takes 10 - 20 seconds regardless of the result contains 0, 1 or a 1000 users. The result can be replicated in C# and powershell (Get-ADGroup -LDAPFilter {Your filter})
A colleague of mine proposed implementing something similar to this powershell query
$group = "{samAccountName}"
$attributes = "employeeId","sn","givenName","telephoneNumber","mobile","hPRnr","cn","samAccountName","gender","company","reshId"
Get-ADGroupMember -Identity $group | Get-ADUser -Properties $attributes | select $attributes
Is it possible to use the api available in C# to implement the powershell query somehow or is there a better solution?
To clearify.
I have a C# approach today which has an emphasis on LDAP. The average perfomance is between 10 - 15 seconds whether there are 0 or 1000 members in the AD group.
A complete example of how the code works with the following libraries added to the project:
Microsoft.AspNet.WebApi.Client
Microsoft.Extensions.Logging.Log4Net.AspNetCore
Newtonsoft.Json
System.DirectoryServices
System.DirectoryServices.AccountManagement
System.Runtime.Serialization.Json
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using Microsoft.Extensions.Logging;
namespace ActiveDirectoryLibrary.Standard.Services
{
public class LdapService
{
private ILogger _logger;
private string PersonCategory = "ObjectCategoryForUser";
public LdapService(ILogger logger)
{
_logger = logger;
}
public List<User> GetUserRecordsInNestedGroupDetailed(string nestedGroup, string ou)
{
var groupStopwatch = new Stopwatch();
groupStopwatch.Start();
var group = GetGroup(nestedGroup, ou);
groupStopwatch.Stop();
_logger.LogDebug(
$"Method {nameof(GetUserRecordsInNestedGroupDetailed)}: Getting the group {nestedGroup} took {groupStopwatch.ElapsedMilliseconds} ms");
if (group == null || !string.IsNullOrEmpty(group.DistinguishedName)) return new List<User>();
//PersonCategory is the object category for a user object in Active Directory
var ldapFilter =
$"(&(memberOf:1.2.840.113556.1.4.1941:={group.DistinguishedName})(objectClass=user)(objectCategory={PersonCategory}))";
var groupMembers = new List<User>();
using (var adsEntry = new DirectoryEntry())
{
using (var ds = new DirectorySearcher(adsEntry))
{
var stopwatch = new Stopwatch();
stopwatch.Start();
ds.Filter = ldapFilter;
ds.SearchScope = SearchScope.Subtree;
LoadAdUserProperties(ds);
var members = ds.FindAll();
stopwatch.Stop();
_logger.LogDebug(
$"Method {nameof(GetUserRecordsInNestedGroupDetailed)}: Time consumed {stopwatch.ElapsedMilliseconds} ms for {group.DistinguishedName}");
foreach (SearchResult sr in members)
{
groupMembers.Add(MapSearchResultToUser(sr));
}
}
}
return groupMembers;
}
public Group GetGroup(string samAccountName, string ou)
{
using (var entry = new DirectoryEntry($"LDAP://{ou}"))
{
var ds = new DirectorySearcher(entry)
{
Filter = "(&(objectcategory=group)(SamAccountName=" + samAccountName + "))"
};
var group = ds.FindOne();
return group == null ? null : MapSearchResultToGroup(group);
}
}
public static Group MapSearchResultToGroup(SearchResult #group)
{
var returnGroup = new Group
{
Changed = GetProperty<DateTime>(#group, "whenchanged"),
SamAccountName = GetProperty<string>(group, "SamAccountName"),
Description = GetProperty<string>(group, "Description"),
Created = GetProperty<DateTime>(group, "whencreated"),
DistinguishedName = GetProperty<string>(group, "distinguishedname"),
Name = GetProperty<string>(group, "name")
};
return returnGroup;
}
private static void LoadAdUserProperties(DirectorySearcher ds)
{
ds.PropertiesToLoad.Add("reshid");
ds.PropertiesToLoad.Add("employeeid");
ds.PropertiesToLoad.Add("sn");
ds.PropertiesToLoad.Add("givenname");
ds.PropertiesToLoad.Add("gender");
ds.PropertiesToLoad.Add("telephonenumber");
ds.PropertiesToLoad.Add("mobile");
ds.PropertiesToLoad.Add("cn");
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("samaccountname");
ds.PropertiesToLoad.Add("companyname");
}
public static User MapSearchResultToUser(SearchResult userProperty)
{
var reshId = GetProperty<string>(userProperty, "reshid");
var employeeElement = GetProperty<string>(userProperty, "employeeid");
var surname = GetProperty<string>(userProperty, "sn");
var givenname = GetProperty<string>(userProperty, "givenname");
var gender = GetProperty<string>(userProperty, "gender");
var phone = GetProperty<string>(userProperty, "telephonenumber");
var mobile = GetProperty<string>(userProperty, "mobile");
var hpr = GetProperty<string>(userProperty, "hprnr");
var cn = GetProperty<string>(userProperty, "cn");
var samAccountName = GetProperty<string>(userProperty, "samaccountname");
var company = GetProperty<string>(userProperty, "company");
var account = new User
{
EmployeeId = employeeElement,
Sn = surname,
GivenName = givenname,
Gender = gender,
Telephone = phone,
Mobile = mobile,
Cn = cn,
SamAccountName = samAccountName,
Company = company,
ReshId = reshId
};
return account;
}
private static T GetProperty<T>(SearchResult userProperty, string key)
{
if (userProperty.Properties[key].Count == 1)
{
return (T) userProperty.Properties[key][0];
}
return default(T);
}
public class Group
{
public DateTime Changed { get; set; }
public string SamAccountName { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public string DistinguishedName { get; set; }
public string Name { get; set; }
}
public class User
{
public string EmployeeId { get; set; }
public string Sn { get; set; }
public string GivenName { get; set; }
public string Telephone { get; set; }
public string OfficePhone { get; set; }
public string Mobile { get; set; }
public string Mail { get; set; }
public string Cn { get; set; }
public string SamAccountName { get; set; }
public string Gender { get; set; }
public string Company { get; set; }
public string ReshId { get; set; }
}
}
}
I've written about this in an article I wrote about finding members of a group, since group membership can be an oddly complicated thing sometimes. But here is a method that I put there that will likely be good enough for your case.
I've modified it to return a User object like you are in your code. If you pass true for the recursive parameter, it will traverse nested groups. You should be able to modify it to suit your needs.
public static IEnumerable<User> GetGroupMemberList(DirectoryEntry group, bool recursive = false) {
var members = new List<User>();
group.RefreshCache(new[] { "member" });
while (true) {
var memberDns = group.Properties["member"];
foreach (string member in memberDns) {
using (var memberDe = new DirectoryEntry($"LDAP://{member.Replace("/", "\\/")}")) {
memberDe.RefreshCache(new[] { "objectClass", "samAccountName", "mail", "mobile" });
if (recursive && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true));
} else {
members.Add(new User {
SamAccountName = (string) memberDe.Properties["samAccountName"].Value,
Mail = (string) memberDe.Properties["mail"].Value,
Mobile = (string) memberDe.Properties["mobile"].Value,
});
}
}
}
if (memberDns.Count == 0) break;
try {
group.RefreshCache(new[] {$"member;range={members.Count}-*"});
} catch (COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
break;
}
throw;
}
}
return members;
}
You do have to pass it a DirectoryEntry object for the group. If you already have the DN of the group, you can create it like this:
new DirectoryEntry($"LDAP://{dn.Replace("/", "\\/")}")
If you don't, you can find the group by its sAMAccountName like this:
var groupSamAccountName = "MyGroup";
var ds = new DirectorySearcher($"(sAMAccountName={groupSamAccountName})") {
PropertiesToLoad = { "cn" } //just to stop it from returning every attribute
};
var groupDirectoryEntry = ds.FindOne()?.GetDirectoryEntry();
var members = GetGroupMemberList(groupDirectoryEntry, false); //pass true if you want recursive members
There is other code in my article for finding members from external trusted domains (if you have any trusted domains) and for finding users who have the group as their primary group, since the primary group relationship isn't visible in the group's member attribute.
To use this code in .NET Core, you need to install the Microsoft.Windows.Compatibility NuGet package to be able to use the System.DirectoryServices namespace. This will limit you to only being able to run your application on Windows. If you need to run your app on non-Windows operating systems, you can look into the Novell.Directory.Ldap.NETStandard, but I can't help there.
Since my current answer diverges heavily from Gabriel Lucis, I figured it be better to propose what I came up with:
public IEnumerable<User> GetContactDetailsForGroupMembersWithPrincipalContext(string samAccountName, string ou, bool recursive)
{
var ctx = new PrincipalContext(ContextType.Domain);
var grp = GroupPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, samAccountName);
var users = new List<User>();
if (grp != null)
{
foreach (var principal in grp.GetMembers(true))
{
var member = (UserPrincipal) principal;
var user = GetUser(member);
if (user != null)
{
users.Add(user);
}
}
}
return users;
}
private User GetUser(UserPrincipal member)
{
var entry = (DirectoryEntry) member.GetUnderlyingObject();
var search = new DirectorySearcher(entry);
search.PropertiesToLoad.Add("samAccountName");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("mobile");
var result = search.FindOne();
var user = MapSearchResultToUser(result);
return user;
}
public static User MapSearchResultToUser(SearchResult userProperty)
{
var mobile = GetProperty<string>(userProperty, "mobile");
var mail = GetProperty<string>(userProperty, "mail");
var samAccountName = GetProperty<string>(userProperty, "samaccountname");
var account = new User
{
Mobile = mobile,
Mail = mail,
SamAccountName = samAccountName,
};
return account;
}
private static T GetProperty<T>(SearchResult userProperty, string key)
{
if (userProperty.Properties[key].Count == 1)
{
return (T)userProperty.Properties[key][0];
}
return default(T);
}
The average performance up to 50 members is about 500 to 1000 ms. The Code does not scale very well, a nested group with 1079 members had in average 13 000 ms.
The reason why I have to query AD two times:
Find the AD Group to get the group members
Get custom properties from the User which are not available in the UserPrincipal object.

how to convert object's properties as list elements

I am trying to get all data from DB and display it in a table using ajax and stored procedure.
public List<string> ShowDetailsFromDB()
{
using (adoHelper = new AdoHelper(connectionString))
{
List<string> users = new List<string>();
string procedureName = "GetDetails";
SqlDataReader dataReader = adoHelper.ExecuteDataReaderByProcedure(procedureName);
while (dataReader.Read())
{
User user = new User();
user.userId = dataReader[1] as string;
user.password = dataReader[2] as string;
user.userName = dataReader[3] as string;
user.address = dataReader[4] as string;
user.email = dataReader[5] as string;
user.phone = dataReader[6] as string;
//here I want to assign each object property as list element
}
return users;
}
}
Below are two ways to generate a list of strings from the properties of a User instance.
internal class User
{
public string userId { get; set; }
public string password { get; set; }
public string userName { get; set; }
public string address { get; set; }
public string email { get; set; }
public string phone { get; set; }
public string[] GetProperties()
{
return new string[]
{
userId,
password,
userName,
address,
email,
phone
};
}
static PropertyInfo[] properties = typeof(User).GetProperties();
public string[] GetPropertiesAuto()
{
return properties.Select((prop) => prop.GetValue(this) as string).ToArray();
}
}
The above can be used in your code quite simply, although you have to return a list of string array to get all the properties for all the users.
static public List<string[]> ShowDetailsFromDB()
{
using (var adoHelper = new AdoHelper(connectionString))
{
List<string[]> users = new List<string[]>();
string procedureName = "GetDetails";
SqlDataReader dataReader = adoHelper.ExecuteDataReaderByProcedure(procedureName);
while (dataReader.Read())
{
var user = new User
{
userId = dataReader[1] as string,
password = dataReader[2] as string,
userName = dataReader[3] as string,
address = dataReader[4] as string,
email = dataReader[5] as string,
phone = dataReader[6] as string
};
//here I want to assign each object property as list element
users.Add(user.GetPropertiesAuto());
}
return users;
}
}
You can do it easy using a List of Users.
public class User
{
public string userId { get; set; }
}
public List<User> ShowDetailsFromDB()
{
using (adoHelper = new AdoHelper(connectionString))
{
List<User> users = new List<User>();
string procedureName = "GetDetails";
SqlDataReader dataReader = adoHelper.ExecuteDataReaderByProcedure(procedureName);
while (dataReader.Read())
{
User user = new User
{
userId = dataReader[1] as string
};
users.Add(user);
//here I want to assign each object property as list element
}
return users;
}
}
Please tell me if it works

Return Row to Array

I'm work with C# and linq, and am trying to return some results from a table in a string, or array.
I am able to create a query, look up the row, but when I try to return column values, I get empty array of my table column names as my string.
Here is my result:
SELECT [t0].[UserID], [t0].[First], [t0].[Last], [t0].[Username], [t0].[Password], [t0].[Employee], [t0].[City], [t0].[Branch], [t0].[UserPoints], [t0].[CurrentPoints], [t0].[LastOnline], [t0].[Status] FROM [dbo].[mrobUsers] AS [t0] WHERE [t0].[Username] = #p0
Here is my Linq query:
public string GetUserInfo(string username)
{
try
{
using (UserDataDataContext db = new UserDataDataContext())
{
var getInfo = (from row
in db.mrobUsers
where row.Username == username
select row).ToString();
return getInfo;
// I've debugged up to here, and my user name is passed into this
}
}
catch (Exception e)
{
return MySerializer.Serialize(false);
}
}
My ideal result would be:
1,Mark,Rob,mrob88, password....etc
You could try this one:
// You should change the return type of your method, if you want to return an array of
// strings.
public string[] GetUserInfo(string username)
{
try
{
using (UserDataDataContext db = new UserDataDataContext())
{
// Get the user's info.
var userInfo = (from row in db.mrobUsers
where row.Username == username
select row).SingleOrDefault();
// If the user's info found return the corresponding values.
if(userInfo!=null)
{
var t = typeof(userInfo);
List<string> values = new List<string>();
foreach(var prop in t.GetProperties())
{
values.Add(prop.GetValue(userInfo, null);
}
return values.ToArray();
}
else // the user's info not found and return an empty array.
{
return new string[] { };
}
// I've debugged up to here, and my user name is passed into this
}
}
catch (Exception e)
{
return MySerializer.Serialize(false);
}
}
}
However, I suggest you not follow this approach. I think it would be better, if you declare a class with properties the values that you want to retrieve, like below:
public class UserInfo
{
public int UserId { get; set; }
public string First { get; set; }
public string Last { get; set; }
// the same way you will declare the rest of your properties.
}
Then you could change your method to the following one:
public UserInfo GetUserInfo(string username)
{
try
{
using (UserDataDataContext db = new UserDataDataContext())
{
// Get the user's info.
var userInfo = (from row in db.mrobUsers
where row.Username == username
select new UserInfo
{
UserId = row.UserId,
First = row.First
Last = row.Last
}).SingleOrDefault();
return userInfo;
}
catch (Exception e)
{
return MySerializer.Serialize(false);
}
}
}

Convert FQL Results to custom class?

I am a fairly new C# programmer and am getting stuck on trying to convert FQL results into a custom class...for example, I am doing the following, but it seems like a lot of steps...I was just returning a datatable, but wanted the result to be strongly typed class collection. I'd appreciate any insights. I'm open to other ways of achieving similar results as well.
Thanks,
Chad
public class FacebookFriends
{
public string FriendID { get; set; }
public string FriendName { get; set; }
public string PicURLSquare { get; set; }
public string ProfileLink { get; set; }
//Gets your FB friends that are NOT currently using this application so you can invite them
public IEnumerable<FacebookFriends> GetFriendsNotUsingApp()
{
string strQuery = "SELECT uid, name, pic_square, link FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=me()) AND NOT is_app_user";
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
//Construct the new, formated, merged datatable to store the results the way we want them
DataTable dtFriendsNotUsingApp = new DataTable();
dtFriendsNotUsingApp.Columns.Add("FriendID");
dtFriendsNotUsingApp.Columns.Add("FriendName");
dtFriendsNotUsingApp.Columns.Add("PicURLSquare");
dtFriendsNotUsingApp.Columns.Add("Link");
if (objFQL != null)
{
foreach (dynamic row in objFNU.data)
{
//Add New DataRow to new DataTable
DataRow drRow = dtFriendsNotUsingApp.NewRow();
//Get various values from original JSON Friend List returned
drRow["FriendID"] = row.uid;
drRow["FriendName"] = row.name;
drRow["PicURLSquare"] = row.pic_square;
drRow["Link"] = row.link;
//Add New Row to New Resulting Data Table
dtFriendsNotUsingApp.Rows.Add(drRow);
}
dtFriendsNotUsingApp.DefaultView.Sort = "FriendName";
}
IEnumerable<FacebookFriends> objFriendsListCollection = null;
var toLinq = from list in dtFriendsNotUsingApp.AsEnumerable()
select new FacebookFriends
{
FriendID = list["FriendID"].ToString(),
FriendName = list["FriendName"].ToString(),
PicURLSquare = list["PicURLSquare"].ToString(),
ProfileLink = list["ProfileLink"].ToString()
};
objFriendsListCollection = toLinq.OrderByDescending(p => p.FriendName);
return objFriendsListCollection;
} //Get FB Friends not already using this app
I belive this may help.
1st: I've never used the Facebook API, so I'm just using your code as an example.
2nd: As the method is inside the class, I've changed it to static. This way, you can use it by simply calling FacebookFriends.GetFriendsNotUsingApp(), instead of new FacebookFriends().GetFriendsNotUsingApp().
3rd The code:
public class FacebookFriends
{
public string FriendID { get; set; }
public string FriendName { get; set; }
public string PicURLSquare { get; set; }
public string ProfileLink { get; set; }
//Gets your FB friends that are NOT currently using this application so you can invite them
public static IEnumerable<FacebookFriends> GetFriendsNotUsingApp()
{
string strQuery = "SELECT uid, name, pic_square, link FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=me()) AND NOT is_app_user";
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
List<FacebookFriends> friendsToReturn = new List<FacebookFriends>();
if (objFQL != null)
{
foreach (dynamic row in objFNU.data)
{
friendsToReturn.Add(new FacebookFriends()
{
FriendID = row.uid,
FriendName = row.name,
PicURLSquare = row.pic_square,
ProfileLink = row.link
}
);
}
}
return friendsToReturn;
} //Get FB Friends not already using this app
}
Hope this helps.
Regards
I have no experience with Facebook API or FQL as well, but by looking at your code objFNU.data appears to implement IEnumerable, hence you can use LINQ extension methods directly with it:
public class FacebookFriends
{
public string FriendID { get; set; }
public string FriendName { get; set; }
public string PicURLSquare { get; set; }
public string ProfileLink { get; set; }
//Gets your FB friends that are NOT currently using this application so you can invite them
public static IEnumerable<FacebookFriends> GetFriendsNotUsingApp()
{
string strQuery = "SELECT uid, name, pic_square, link FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=me()) AND NOT is_app_user";
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
if (objFQL != null) // shouldn't you check objFNU for being null here instead?
{
IEnumerable<dynamic> objFNUdata = (IEnumerable<dynamic>)objFNU.data; // explicit cast might not be necessary
return objFNUdata.Select(row => new FacebookFriends()
{
FriendID = row.uid,
FriendName = row.name,
PicURLSquare = row.pic_square,
ProfileLink = row.link
}).OrderByDescending(p => p.FriendName);
}
else
{
return new List<FacebookFriends>();
}
} //Get FB Friends not already using this app
}
In the end, this worked best for me. Thanks to both, and especially #DarmirArh for all his help in getting this to work.
try
{
FacebookSDKInterface objFQL = new FacebookSDKInterface();
dynamic objFNU = objFQL.FBFQL(strQuery);
if (objFNU != null) // shouldn't you check objFNU for being null here instead?
{
IEnumerable<dynamic> objFNUdata = (IEnumerable<dynamic>)objFNU.data; // explicit cast might not be necessary
IEnumerable<FacebookFriends> objMyFriends =
from row in objFNUdata
select new FacebookFriends()
{
FriendID = row.uid,
FriendName = row.name,
PicURLSquare = row.pic_square,
ProfileLink = row.profile_url
};
objMyFriends = objMyFriends.OrderBy(p => p.FriendName);
return objMyFriends;
}
else
{
return new List<FacebookFriends>();
}
}
catch (Exception ex)
{
return new List<FacebookFriends>();
}

Categories