I made a database in Unity with C#, and for some reason it is not working. I made a static class with all the variables static and made save, load, check, create and reset functions. I also made a script that sits on a ScriptHolder GameObject and all the Buttons and InputFields reference. Here is my code:
Database Class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class Database
{
public enum Roles{
Teacher,
Student
}
public static Dictionary<string, string> logins = new Dictionary<string, string>();
public static List<Roles> roles = new List<Roles>();
public static void AddUser(string username, string password, Roles role)
{
logins.Add(username, password);
roles.Add(role);
}
public static void SaveUsers()
{
LoadUsers();
PlayerPrefs.DeleteAll();
if(logins.Count != roles.Count)
{
throw new System.Exception("Roles count and login count do not match!");
}
int counter = 0;
foreach (KeyValuePair<string, string> login in logins)
{
PlayerPrefs.SetString(login.Key, login.Value);
PlayerPrefs.SetString("usernames", "");
PlayerPrefs.SetString("usernames", PlayerPrefs.GetString("usernames") + "/" + login.Key);
PlayerPrefs.SetInt(login.Key, (int)roles[counter]);
counter += 1;
Debug.Log(PlayerPrefs.GetString("usernames") + "/" + login.Key);
}
}
public static void LoadUsers()
{
logins = new Dictionary<string, string>();
roles = new List<Roles>();
foreach (string key in PlayerPrefs.GetString("usernames").Split('/'))
{
logins.Add(key, PlayerPrefs.GetString(key));
roles.Add((Roles)PlayerPrefs.GetInt(key));
}
}
public static void FactorySettings()
{
PlayerPrefs.DeleteAll();
LoadUsers();
logins = new Dictionary<string, string>();
}
public static bool CheckUser(string username, string password)
{
if (logins.ContainsKey(username))
{
if (password == logins[username])
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
}
ScriptHolder:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;
public class LoginProcess : MonoBehaviour
{
public TextMeshProUGUI username;
public TextMeshProUGUI password;
public Slider loadingSlider;
public GameObject error;
public GameObject loading;
public float progress = 0f;
public Dictionary<string, string> show = Database.logins;
public void Login()
{
if(Database.CheckUser(username.text, password.text))
{
StartCoroutine(LoadAsync(1));
loading.SetActive(true);
}
else
{
error.SetActive(false);
}
}
public void Create()
{
Database.AddUser(username.text, password.text, Database.Roles.Teacher);
Database.SaveUsers();
Database.LoadUsers();
}
public void _Reset()
{
Database.FactorySettings();
}
public void Start()
{
Database.LoadUsers();
}
IEnumerator LoadAsync (int index)
{
AsyncOperation operation = SceneManager.LoadSceneAsync(index);
while (!operation.isDone)
{
float _progress = Mathf.Clamp01(operation.progress / .9f);
progress = _progress;
yield return null;
}
}
public void Update()
{
if (progress != 0)
{
loadingSlider.value = progress;
}
if (Input.GetMouseButtonDown(0) && error.activeSelf)
{
error.SetActive(true);
}
}
}
My Error:
ArgumentException: An item with the same key has already been added. Key:
System.Collections.Generic.Dictionary'2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) (at <599589bf4ce248909b8a14cbe4a2034e>:0)
System.Collections.Generic.Dictionary'2[TKey,TValue].Add (TKey key, TValue value) (at <599589bf4ce248909b8a14cbe4a2034e>:0)
In general: it is a bad idea to use PlayerPrefs for sensitive data .. they are stored on the device as raw text so you will be able to read out all passwords or modify it in order to e.g. change a users Role!
What don't you understand in the error?
Apparently you are trying to .Add a key-value-pair to the Dictionary when the given key already exists in that Dictionary.
There is no check in AddUser to catch such a case so maybe you should rather do something like
public static void AddUser(string username, string password, Roles role)
{
// in general check if a string is empty!
if(username.IsNullOrWhitespace())
{
// TODO: SHOW AN ERROR IN THE GUI THAT USERNAME MAY NOT BE EMPTY
Debug.LogWarning("username may not be empty!", this);
return;
}
if(logins.ContainsKey(username))
{
// TODO: SHOW AN ERROR IN THE GUI THAT THIS USER ALREADY EXISTS
Debug.LogWarning($"A User with name \"{username}\" already exists! Please chose another username.", this);
return;
}
logins.Add(username, password);
roles.Add(role);
}
In general there are some really strange orders in your code for example
Database.AddUser(username.text, password.text, Database.Roles.Teacher);
Database.SaveUsers();
Database.LoadUsers();
You add the user username.text but in SaveUsers the first thing you do is calling LoadUsers which resets both dictionaries and then loads already existing users ... so the one you just created is lost.
Or if you do FactorySettings
PlayerPrefs.DeleteAll();
LoadUsers();
logins = new Dictionary<string, string>();
You first delete all PlayerPRefs, then LoadUsers .. knowing there should be no outcome anyway except resetting logins and roles and then you reset logins again. This is quite redundant. You could just say
PlayerPrefs.DeleteAll();
logins.Clear();
roles.Clear();
Or have a look at the SaveUsers method again: In your foreach loop you do
PlayerPrefs.SetString(login.Key, login.Value);
PlayerPrefs.SetString("usernames", "");
PlayerPrefs.SetString("usernames", PlayerPrefs.GetString("usernames") + "/" + login.Key);
so apparently you anyway store each username, password pair individually .. so why even bother using the other thing?
Now to the other thing: You reset PlayerPrefs.SetString("usernames", ""); in every iteration of the loop ... so there will always only be exactly one username/password stored!
Also it seems a bit odd/unsecure for me that for matching a username with a password you are using a Dictionary but when it comes to this users role (which is almost equally important) you just use a List and access it by index.
So Overall
I would rather use a proper class like e.g.
[Serializable]
public class User
{
private const SECRET_SALT = "1234My_Example";
public string Name;
// Never store a naked Password!
// Rather store the hash and everytime you want to check the password
// compare the hashes!
public string PasswordHash;
// As said actually you should also not store this as a simple modifiable value
// Since it almost has the same security impact as a password!
public Roles Role;
public User(string name, string password, Roles role)
{
Name = name;
// Instead of the raw password store a hash
PasswordHash = GetHash(password);
Role = role;
}
// Compare the hash of the given password attempt to the stored one
public bool CheckPassword(string attemptedPassword, string hash)
{
var base64AttemptedHash = GetHash(attemptedPassword);
return base64AttemptedHash.Equals(PasswordHash);
}
private static string GetHash(string password)
{
// Use the secret salt so users can not simply edit the stored file
// and add a users password brute-forcing the known blank hashing methods
var unhashedBytes = Encoding.Unicode.GetBytes(SECRET_SALT + password);
var sha256 = new SHA256Managed();
var hashedBytes = sha256.ComputeHash(unhashedBytes);
return Convert.ToBase64String(hashedBytes);
}
}
And then have e.g. this as the DataBase.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using UnityEditor;
using UnityEngine;
public static class DataBase
{
// Do not make the Dictionary public otherwise it can be modified by any class!
// Rather only provide specific setters and getters
private static Dictionary<string, User> _users = new Dictionary<string, User>();
// You might even want to give this bit a less obvious name ;)
private const string fileName = "DataBaseCredentials.cfg";
private static string filePath;
// This method will be automatically called on app start
[InitializeOnLoadMethod]
private static void Initialize()
{
filePath = Path.Combine(Application.persistentDataPath, fileName);
if (!Directory.Exists(Application.persistentDataPath))
{
Directory.CreateDirectory(Application.persistentDataPath);
}
if (!File.Exists(filePath))
{
File.Create(filePath);
}
LoadUsers();
}
private static void LoadUsers()
{
Debug.Log("DataBase: Loading Users from " + filePath);
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
if (stream.Length == 0) return;
var bf = new BinaryFormatter();
_users = (Dictionary<string, User>)bf.Deserialize(stream);
}
}
private static void SaveUsers()
{
Debug.Log("DataBase: Storing Users to " + filePath);
using (var stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
{
var bf = new BinaryFormatter();
bf.Serialize(stream, _users);
}
LoadUsers();
}
public static User GetUserByName(string username)
{
if (string.IsNullOrWhiteSpace(username))
{
Debug.LogWarning("username may not be empty!");
return null;
}
if (!_users.ContainsKey(username))
{
Debug.LogWarning($"A user with name \"{username}\" does not exist!");
return null;
}
return _users[username];
}
public static bool LogIn(string username, string password)
{
var user = GetUserByName(username);
return user == null ? false : user.CheckPassword(password);
}
public static void AddUser(string username, string password, Roles role)
{
// Check the name
if (string.IsNullOrWhiteSpace(username))
{
Debug.LogWarning("username may not be empty!");
return;
}
if (_users.ContainsKey(username))
{
Debug.LogWarning($"A user with name \"{username}\" already exists! Chose another username!");
return;
}
_users.Add(username, new User(username, password, role));
SaveUsers();
}
public static void FactorySettings()
{
Debug.Log("FactorySettings!");
_users.Clear();
SaveUsers();
}
}
[Serializable]
public class User
{
public string Name;
public string PasswordHash;
public Roles Role;
public User(string name, string password, Roles role)
{
Name = name;
// Never store a naked Password!
// Rather store the hash and everytime you want to check the password
// compare the hashes!
PasswordHash = GetHash(password);
// As said actually you should also not store this as a simple modifiable value
// Since it almost has the same security impact as a password!
Role = role;
}
private static string GetHash(string password)
{
var unhashedBytes = Encoding.Unicode.GetBytes(password);
var sha256 = new SHA256Managed();
var hashedBytes = sha256.ComputeHash(unhashedBytes);
return Convert.ToBase64String(hashedBytes);
}
public bool CheckPassword(string attemptedPassword)
{
var base64AttemptedHash = GetHash(attemptedPassword);
return base64AttemptedHash.Equals(PasswordHash);
}
}
public enum Roles
{
Teacher,
Student
}
I know BinaryFormatter creates quite huge files and you could also do this e.g. as JSON but this was the easiest way for showing how to (de)serialize a Dictionary into a system file.
And just for a little Demo Class
using UnityEditor;
using UnityEngine;
public class UsersManager : MonoBehaviour
{
public string username;
public string password;
public Roles role;
public User User;
}
[CustomEditor(typeof(UsersManager))]
public class UsersManagerEditor : Editor
{
private UsersManager manager;
private void OnEnable()
{
manager = (UsersManager)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("GetUserByName"))
{
manager.User = DataBase.GetUserByName(manager.username);
}
if (GUILayout.Button("AddUser"))
{
DataBase.AddUser(manager.username, manager.password, manager.role);
}
if (GUILayout.Button("CheckPassword"))
{
manager.User = DataBase.GetUserByName(manager.username);
if (manager.User != null)
{
if (manager.User.CheckPassword(manager.password))
{
Debug.Log("Password CORRECT!");
}
else
{
Debug.LogWarning("PASSWORD WRONG!");
}
}
}
if (GUILayout.Button("FactorySettings"))
{
DataBase.FactorySettings();
}
}
}
I've created a class that confirms if it's able to connect to a db on mysql using login info provided via string CadenaConexion. How can I process an additional db and have the code connect to both when executing?
First I create a connection and assign the login info (here is where I'd add the second db):
using MySql.Data.MySqlClient;
namespace DBManager
{
public class DBConexion
{
protected MySqlConnection oConexion;
String CadenaConexion = "Server=localhost; Port=3306;Database=blabla;Uid=root;Pwd=blabla; AllowZeroDateTime=True;";
string _estado;
public string Estado
{
get => _estado;
}
Then I determine if connected or not through a boolean variable:
public Boolean Conectar()
{
Boolean Conectado = false;
oConexion = new MySqlConnection(CadenaConexion);
try
{
oConexion.Open();
Conectado = true;
}
catch
{
Conectado = false;
}
_estado = oConexion.State.ToString();
return Conectado;
}
Optional method to confirm if successful, as it will not perform the disconnect unless it was previously connected:
public void Desconectar()
{
try
{
if (oConexion.State == System.Data.ConnectionState.Open)
{
try
{
oConexion.Close();
}
catch
{
}
}
_estado = oConexion.State.ToString();
}
catch
{
_estado = "Indefinido";
}
}
First Look at my code
namespace HealthClub
{
public partial class frmTrainerMaster : Form
{
DataTable dt = new DataTable();
frmHome Home = new frmHome();
public frmTrainerMaster()
{
InitializeComponent();
}
}
private void frmTrainerMaster_Load(object sender, EventArgs e)
{
FillValues("UserNameText");
}
public void FillValues(string UserName)
{
DataTable DT;
SqlCommand cmd = new SqlCommand();
try
{
cmd.Connection = Connections.Connection[UserName];
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "TrainerMaster_pro";
cmd.Parameters.AddWithValue("Option", "FillValues".Trim());
if (Connections.Connection[UserName].State == ConnectionState.Closed)
Connections.Connection[UserName].Open();
SqlDataAdapter adp = new SqlDataAdapter(cmd);
DT = new DataTable();
adp.Fill(DT);
lblId___.Text = DT.Rows[0][0].ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
cmd.Parameters.Clear();
cmd.Dispose();
Connections.Connection[UserName].Close();
}
}
}
Now I am calling FillValues() from another class like this
class AnotherClass
{
public void TestMethod(string FormName)
{
Type tp = Type.GetType("HealthClub." + FormName);
object myobj = Activator.CreateInstance(tp);
MethodInfo method = myobj.GetType().GetMethod("FillValues");
object[] parametersArray = new object[] { UserName };
method.Invoke(myobj, parametersArray);
}
}
If you look at the FillValues(), I am assigning the database value to a label. When I am calling it in my first class in page load it's working fine.
But when I am Invoking the medthod from second class, Method invokes but database value does not assign to the label.
What extra effort I need to do ?
There is a class and there is an instance. This is very basic concept you need to understand (not just in C#, but in any objective-oriented language).
// class example
class FrmTrainerMaster { public int SomeProperty { get; set;} }
When you create new instance:
// creates one window
var frmTrainerMasterInstanceOne = new FrmTrainerMaster();
frmTrainerMasterInstanceOne.SomeProperty = 1;
// creates second window
var frmTrainerMasterInstanceTwo = new FrmTrainerMaster();
frmTrainerMasterInstanceTwo.SomeProperty = 2;
Instances are SEPARATE - so at this point querying
// will return 1
Console.Out.WriteLine(frmTrainerMasterInstanceOne.SomeProperty);
// will return 2
Console.Out.WriteLine(frmTrainerMasterInstanceTwo.SomeProperty);
With reflection var myobj = Type.GetType("HealthClub.FrmTrainerMaster"); is equal to var myobj = new FrmTrainerMaster(); so by doing anything with myobj, you still can't affect frmTrainerMasterInstanceOne or frmTrainerMasterInstanceTwo.
What do you need is actually method how to pass reference to instance (of FrmTrainerMaster class) to place where you need it (lets call it AnotherForm), there is no magic list of all instances for given class unless you explicitly build it.
public partial class FrmTrainerMaster : Form
{
public void FillValues(string userName) { ... }
}
One way is via constructor injection (generally proffered - since at time when object (AnotherForm) is constructed you have it in valid state (i.e. with all dependencies initialized).
public class AnotherForm : Form {
private readonly FrmTrainMaster _frmTrainMaster;
public AnotherForm(FrmTrainMaster frmTrainMaster) {
if (frmTrainMaster == null) {
throw new ArgumentNullException(nameof(frmTrainMaster));
}
_frmTrainMaster = frmTrainMaster;
}
}
Or via setter injection:
public class AnotherForm : Form {
private FrmTrainMaster _frmTrainMaster;
public FrmTrainMaster MasterForm { set { _frmTrainMaster = value; } }
}
Either way the reflection is not necessary at all. At any place in your AnotherForm you can just call
class AnotherForm : Form {
...
public void FooMethodThatCallFillValues() {
_frmTrainMaster.FillValues("...");
}
}
I am trying to use AppFabric for fasten my image retrieval from SQL Database. I created my provider file and load it to my cache. However, I am struggling right now.
How can I call get function from cache using my provider file or do I need to use my provider file while retrieving data?
When I call .Get(key.Key), do I need to see the data coming from my database?
My Read method from provider is as follows, is this correct?
public override DataCacheItem Read(DataCacheItemKey key)
{
try
{
Object retrievedValue = null;
DataCacheItem cacheItem;
retrievedValue = *Running SQL Query Retrieving Image from DB*;//Is that a correct approach?
if (retrievedValue == null)
cacheItem = null;
else
cacheItem = DataCacheItemFactory.GetCacheItem(key, cacheName, retrievedValue, null);
return cacheItem;
}
catch
{
return null;
}
}
Example:>
using Microsoft.ApplicationServer.Caching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
namespace SampleProvider
{
public class Provider : DataCacheStoreProvider
{
private readonly String dataCacheName;
private readonly Dictionary<string, string> config;
public Provider(string cacheName, Dictionary<string, string> config)
{
this.dataCacheName = cacheName; //Store the cache name for future use
this.config = config;
}
public override DataCacheItem Read(DataCacheItemKey key)
{
Object retrievedValue = null;
DataCacheItem cacheItem;
retrievedValue = ReadFromDatabase(key.Key); //Your implemented method that searches in the backend store based
if (retrievedValue == null)
cacheItem = null;
else
cacheItem = DataCacheItemFactory.GetCacheItem(key, dataCacheName, retrievedValue, null);
return cacheItem;
}
public override void Read(System.Collections.ObjectModel.ReadOnlyCollection<DataCacheItemKey> keys, IDictionary<DataCacheItemKey, DataCacheItem> items)
{
foreach (var key in keys)
{
items[key] = Read(key);
}
}
public override void Delete(System.Collections.ObjectModel.Collection<DataCacheItemKey> keys) { }
public override void Delete(DataCacheItemKey key) { }
protected override void Dispose(bool disposing) { }
public override void Write(IDictionary<DataCacheItemKey, DataCacheItem> items) { }
public override void Write(DataCacheItem item) { }
private string ReadFromDatabase(string key)
{
string value = string.Empty;
object retrievedValue = null;
using (SqlConnection connection = new SqlConnection(config["DBConnection"]))
{
SqlCommand cmd = new SqlCommand();
cmd.CommandText = string.Format("select Value from KeyValueStore where [Key] = '{0}'", key);
cmd.Connection = connection;
connection.Open();
retrievedValue = cmd.ExecuteScalar();
if (retrievedValue != null)
{
value = retrievedValue.ToString();
}
}
return value;
}
}
}
I have a method that is a search for Branches. the parameter is Branch Code and it should return the details of the branch
public bool SearchBranch()
{
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["DBReader"].ConnectionString))
{
using (SqlCommand com = new SqlCommand("Reader.usp_SearchBranch", con))
{
com.CommandType = CommandType.StoredProcedure;
com.Parameters.Add("#BranchCode", SqlDbType.Int).Value = this.BranchCode;
con.Open();
SqlDataReader dr = com.ExecuteReader();
if (dr.Read())
{
this.BranchName = dr.GetValue(0).ToString();
this.AreaCode = dr.GetValue(1).ToString();
this.RegionCode = dr.GetValue(2).ToString();
this.CompanyCode = dr.GetValue(3).ToString();
this.CompanyName = dr.GetValue(4).ToString();
return true;
}
else
{
return false;
}
}
}
}
Here is my code in my Web Method in my Web Service (I dont know if this is correct)
[WebMethod(Description = "Search Affected User from Database in Access Request")]
public bool SearchBranchAccessRequest(AccessRequest accessrequest)
{
return accessrequest.SearchBranch();
}
And this is how I access/call the web method in my web page
protected void SearchBranchButton_Click(object sender, EventArgs e)
{
try
{
accessrequest.BranchCode = Convert.ToInt32(BranchCodeTextBox.Text);
iTicketWebService.SearchBranchAccessRequest(accessrequest);
if (iTicketWebService.SearchBranchAccessRequest(accessrequest) == true)
{
BranchNameLabel.Text = accessrequest.BranchName;
AreaLabel.Text = accessrequest.AreaCode;
RegionLabel.Text = accessrequest.RegionCode;
CompanyCodeLabel.Text = accessrequest.CompanyCode;
CompanyLabel.Text = accessrequest.CompanyName;
BranchEmailLabel.Text = accessrequest.BranchCode + "#pjlhuillier.com";
}
else
{
this.ClientScript.RegisterClientScriptBlock(this.GetType(), "clientScript", "<script type=\"text/javascript\">alert('Record not found. Please try again');</script>");
}
}
catch (Exception)
{
this.ClientScript.RegisterClientScriptBlock(this.GetType(), "clientScript", "<script type=\"text/javascript\">alert('Wrong Input. Please try again');</script>");
}
}
Help! it doesnt return Branch Name,Area Code,Region Code,Company Code and Company name?
change your web method as below
public AccessRequest SearchBranchAccessRequest(AccessRequest accessrequest)
{
return accessrequest.SearchBranch(accessrequest);
}
and you need to change SearchBranch() method as well
public accessrequest SearchBranch(AccessRequest accessrequest)
{
if(you found record in database)
{
// update accessrequest here
}else
{
accessrequest =null;
}
// finally return the object
return accessrequest;
}
when you call this web service
AccessRequest request = iTicketWebService.SearchBranchAccessRequest(accessrequest);
if(request!=null)
{
BranchNameLabel.Text = request.BranchName;
}
since your method signature change with above implementation, in case of null object return you can consider it as false case as your current implementation and if object return from the service you can consider it as true case.
If you need return true false from the service method and also need to have the updated object then you can have custom class to return both, like below
public class SearchBrancResponse
{
public bool SearchStatus { get; set; }
public AccessRequest AccessReq { get; set; }
}
you can then return above from the service method. from client side you have both Boolean value and the AccessRequest