I've read a bunch of guides on how to do this, but being new to programming I'm not really getting it.
I have a script called DatabaseHandler where I wrote a new class and function to write some basic user information to a Firebase database when someone creates a new account.
DatabaseReference userRef = FirebaseDatabase.DefaultInstance.RootReference;
public class User
{
public string email;
public int score;
public int round;
public int lives;
public User()
{
}
public User(string email)
{
this.email = email;
this.score = 0;
this.round = 1;
this.lives = 3;
}
}
public void writeNewUser(string email, int score, int round, int lives)
{
User user = new User(email);
string json = JsonUtility.ToJson(user);
userRef.Child("users").Child(email).SetRawJsonValueAsync(json);
}
Also, I have another script called LoginHandler that calls CreateUserAsync() when someone click the button to create an account. I'm using a code snippet I found on a guide online for this part, and am trying to figure out where and how I can call the writeNewUser function that's written in DatabaseHandler from within LoginHandler? Should I call it from Task HandleCreateUserAsync after the auth.CurrentUser != null to make sure the data is only written if the username has not already been created?
public void CreateUserAsync() {
DebugLog(String.Format("Attempting to create user {0}...", email));
// This passes the current displayName through to HandleCreateUserAsync
// so that it can be passed to UpdateUserProfile(). displayName will be
// reset by AuthStateChanged() when the new user is created and signed in.
string newDisplayName = displayName;
auth.CreateUserWithEmailAndPasswordAsync(email, password)
.ContinueWith((task) => {
return HandleCreateUserAsync(task, newDisplayName: newDisplayName);
}).Unwrap();
}
Task HandleCreateUserAsync(Task<Firebase.Auth.FirebaseUser> authTask,
string newDisplayName = null) {
if (LogTaskCompletion(authTask, "User Creation")) {
if (auth.CurrentUser != null) {
DebugLog(String.Format("User Info: {0} {1}", auth.CurrentUser.Email,
auth.CurrentUser.ProviderId));
return UpdateUserProfileAsync(newDisplayName: newDisplayName);
}
}
// Nothing to update, so just return a completed Task.
return Task.FromResult(0);
}
Related
I want to know what is the correct way of doing this: lets say I have a login method that receives username and password, and log ins the user or return invalid username/password or not enough permissions. What is the correct way of doing this?
Way #1: throwing exception and handling in the user interface to display the error
public void Login(string username, string password)
{
if (SessionService.GetSession.IsLoggedIn)
{
throw new Exception("User is already logged in");
}
var user = GetByUsername(username);
if (user == null)
{
throw new LoginException(LoginResultEnum.InvalidUsername);
}
var hashPass = EncryptionService.Hash(password);
if (hashPass != user.password)
{
throw new LoginException(LoginResultEnum.InvalidPassword);
}
if (!user.HasPermission(PermissionTypeEnum.CanLogIn))
{
throw new MissingPermissionException(TipoPermisoEnum.CanLogIn);
}
SessionService.GetSession.Login(user);
}
Way #2: returning boolean true/false and handle the error in the UI (success or fail)
public bool Login(string username, string password)
{
if (SessionService.GetSession.IsLoggedIn)
{
return false;
}
var user = GetByUsername(username);
if (user == null)
{
return false;
}
var hashPass = EncryptionService.Hash(password);
if (hashPass != user.password)
{
return false;
}
if (!user.HasPermission(PermissionTypeEnum.CanLogIn))
{
return false;
}
SessionService.GetSession.Login(user);
return true;
}
Way #3: returning a LoginResult enum and handle in the UI
public LoginResult Login(string username, string password)
{
if (SessionService.GetSession.IsLoggedIn)
{
return LoginResult.AlreadyLoggedIn;
}
var user = GetByUsername(username);
if (user == null)
{
return LoginResult.InvalidUsername;
}
var hashPass = EncryptionService.Hash(password);
if (hashPass != user.password)
{
return LoginResult.InvalidPassword;
}
if (!user.HasPermission(PermissionTypeEnum.CanLogIn))
{
return LoginResult.Forbidden;
}
SessionService.GetSession.Login(user);
return LoginResult.OK;
}
In my view it better to create some dto if it is eligible for your case. So this dto will have the following properties:
public class LoginResponseDto
{
public bool Success { get; set; }
public string Error { get; set; }
}
And then you will return your response something like this:
public LoginResponseDto Login(string username, string password)
{
if (SessionService.GetSession.IsLoggedIn)
{
return new LoginResponseDto { Error = "User is already logged in" };
}
var user = GetByUsername(username);
if (user == null)
{
return new LoginResponseDto { Error = "There is no such user" };
}
var hashPass = EncryptionService.Hash(password);
if (hashPass != user.password)
{
return new LoginResponseDto { Error = "Incorrect password or username" };
}
if (!user.HasPermission(PermissionTypeEnum.CanLogIn))
{
return new LoginResponseDto { Error = "There is no permission to log in" };
}
SessionService.GetSession.Login(user);
return new LoginResponseDto { Success = true };
}
It is possible to see this tutorial "Create a secure ASP.NET MVC 5 web app with log in, email confirmation and password reset". Author of article use ViewBag in this article to send errors from controller and Succeeded to check whether login is okay.
In addition, try to avoid to show message about what is exactly wrong username or password.
I would say #3 is the best way.
#1 you are using Exception for non-exceptional circumstances. The control path is expected, so don't use Exceptions.
#2 By using a bool you are discarding information, is it InvalidPassword or Forbidden?
#3 Returns all information, allowing the UI to surface that information to the User.
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 have a base class called ServicePluginBase that implements logging.
public class PluginLog
{
public int Id { get; set; }
public int? ServiceId { get; set; }
public string Event { get; set; }
public string Details { get; set; }
public DateTime DateTime { get; set; }
public string User { get; set; }
}
public class SQLPluginLogger : IPluginLogger
{
//EFLogginContext maps PluginLog like so:
// modelBuilder.Entity<PluginLog>().ToTable("log").HasKey(l => l.Id)
private EFLoggingContext _logger = new EFLoggingContext();
public IQueryable<PluginLog> LogItems
{
get { return _logger.LogItems; }
}
public void LogEvent(PluginLog item)
{
_logger.LogItems.Add(item);
_logger.SaveChanges();
}
}
public abstract class ServicePluginBase : IPlugin
{
private IPluginLogger _logger;
public ServicePluginBase(IPluginLogger logger)
{
_logger = logger;
}
protected LogEvent(string eventName, string details)
{
PluginLog _event = new PluginLog()
{
ServiceId = this.Id,
Event = eventName,
Details = details,
User = Thread.CurrentPrincipal.Identity.Name,
DateTime = DateTime.Now
};
_logger.LogEvent(_event);
}
}
Now, within my concrete class, I log events as they happen. In one class, I have some asynchronous methods running -- and logging. Sometimes this works great. Other times, I get the error stating that "Property 'Id' is part of the object's key and cannot be updated." Interestingly enough, I have absolutely no code that updates the value of Id and I never do Updates of log entries -- I only Add new ones.
Here is the async code from one of my plugins.
public class CPTManager : ServicePluginBase
{
public override async Task HandlePluginProcessAsync()
{
...
await ProcessUsersAsync(requiredUsersList, currentUsersList);
}
private async Task ProcessUsersAsync(List<ExtendedUser> required, List<ExtendedUser> current)
{
using (var http = new HttpClient())
{
var removals = currentUsers.Where(cu => !required.Select(r => r.id).Contains(cu.id)).ToList();
await DisableUsersAsync(removals http);
await AddRequiredUsersAsync(requiredUsers.Where(ru => ru.MustAdd).ToList());
}
}
private async Task DisableUsersAsync(List<ExtendedUser> users, HttpClient client)
{
LogEvent("Disable Users","Total to disable: " + users.Count());
await Task.WhenAll(users.Select(async user =>
{
... //Http call to disable user via Web API
string status = "Disable User";
if(response.status == "ERROR")
{
EmailFailDisableNotification(user);
status += " - Fail";
}
LogEvent(statusText, ...);
if(response.status != "ERROR")
{
//Update FoxPro database via OleDbConnection (not EF)
LogEvent("ClearUDF", ...);
}
}));
}
private async Task AddRequiredUsersAsync(List<ExtendedUser> users, HttpClient client)
{
LogEvent("Add Required Users", "Total users to add: " + users.Count());
await Task.WhenAll(users.Select(async user =>
{
... //Http call to add user via web API
LogEvent("Add User", ...);
if(response.status != "ERROR")
{
//Update FoxPro DB via OleDBConnection (not EF)
LogEvent("UDF UPdate",...);
}
}));
}
}
First, I'm confused why I'm getting the error mentioned above -- "Id can't be updated" ... I'm not populating the Id field nor am I doing updates to the Log file. There are no related tables -- just the single log table.
My guess is that I'm doing something improperly in regards to asynchronous processing, but I'm having trouble seeing it if I am.
Any ideas as to what may be going on?
I have a model Administrator that has its properties, but it also consists of numerous static methods that do not really tied any way to the current object itself like for example GetByCredentials(string username, string password);. Is it somehow possible to divide static methods someplace else and lave object as pure as possible?
Example
public class Administrator : Entity
{
// OBJECT START
public int Id { get; set; }
public DateTime CreatedDateTime { get; set; }
public DateTime UpdatedDateTime { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string PasswordSalt { get; set; }
public void SetNewPassword(string password)
{
var cryptoService = new PBKDF2();
this.Password = cryptoService.Compute(password);
this.PasswordSalt = cryptoService.Salt;
}
public override void OnBeforeInsert()
{
this.CreatedDateTime = DateTime.Now;
this.UpdatedDateTime = DateTime.Now;
this.SetNewPassword(this.Password);
}
public override void OnBeforeUpdate()
{
this.UpdatedDateTime = DateTime.Now;
}
// OBJECT END
// Now I have multiple static methods that do not really
// have anything to do with current object
public static Administrator GetByCredentials(string username, string password)
{
var db = new MainDataContext();
var admin = db.Administrators.SingleOrDefault(x => x.Username == username);
if (admin == null) return null;
ICryptoService cryptoService = new PBKDF2();
var hash = cryptoService.Compute(password, admin.PasswordSalt);
if (hash == admin.Password) return admin;
return null;
}
public static bool IsCurrentIpBanned
{
get
{
const int minutesBlocked = 5;
const int maxLoginCount = 5;
var db = new MainDataContext();
var loginCount = db.AdministratorAuthorizationLogs.AsEnumerable().Count(x => x.Ip == HttpContext.Current.Request.UserHostAddress && x.CreatedDateTime.AddMinutes(minutesBlocked) > DateTime.Now && x.IsSuccess == false);
return loginCount > maxLoginCount;
}
}
public static void LogSuccess(Administrator admin)
{
Administrator.Log(admin, true);
}
public static void LogFailure(Administrator admin)
{
Administrator.Log(admin, false);
}
private static void Log(Administrator admin, bool success)
{
var db = new MainDataContext();
db.AdministratorAuthorizationLogs.Add(new AdministratorAuthorizationLog
{
Username = admin.Username,
Password = admin.Password,
Ip = HttpContext.Current.Request.UserHostAddress,
IsSuccess = success
});
db.SaveChanges();
}
}
There are several options here, but the main thing is that C# classes are the tool for separating concerns.
The most obvious is to capture those things in their own abstraction(s). For example, the GetByCredentials might be better as a (non-static) member of a different class Authority or similar. That class only needs to be able to create an Administrator type.
You can also use extension methods. A possible candidate for that is Log, which takes an Administrator as an argument and uses only public facilities on it. Extension methods are defined in a separate class, but allow you to use them "as if" they were members of the extended class, e.g.:
public static class AdministratorExtensions
{
public static void log( this Administrator admin, bool success ) { ... }
}
var admin = new Administrator();
admin.Log( true );
The key thing is to identify real abstractions and build your system up from them by combining them in sensible ways. Separating out concerns is part of that process.
This is a hint that your class "knows too much". The Administrator class should only know what concerns an administrator. He shouldn't be able to query the database and retrieve entities.
You should look into the repository pattern. Try to decompose your application into multiple layers. For example, you could have a DataRepository class whose main concern is to query and update the database entities.
I am making an IRC Chat bot for my stream. I found a few basic connectivity examples using C# so I decided to give it a try.
So far I love it
But i am stuck on this one part.
I want to store the bot commands inside an array of a structure type.
public delegate void cmdHandler(string[]);
struct botCommand
{
string name;
cmdHandler chandler;
bool isAdmin = false;
string help = "Nothing here.";
}
Is currently what I have, and then I want to beable to do this:
botCommand[]commands =
{
{ "TestCommand", testCommand(), 0, "Help for this" },
{ "TestCommand2", testCommand2(), 0 "..." },
......
};
So how do I link a generic function in that array?
or am I going about this all the wrong way?
Basically instead of having a giant Switch() statement to check for which command was used I want to loop through an array and see if the command is in there. If it is then call the function associated with that command.
EDIT:
This is exactly what I have now so you can see what I am trying to do
public delegate void cmdHandler(string[] ex);
struct botCommand
{
string name;
cmdHandler chandler;
bool isAdmin = false;
string help = "Nothing here.";
}
botCommand[] commands =
{
{"test", new cmdHandler(testf), 0, "" }
};
public void testf(string[] ex) {
return;
}
Steps of logic:
user enters the test command
Loop through all botCommands to see if we find the test command
Test command is found
Call the function associated with the test command and pass on an argument (the rest of the command)
To me it seems like you're mixing C/C++ concepts with C# (using struct instead of class, 0 for false, object initializers, etc...).
To solve your individual problem, you must instantiate your struct differently.
botCommand[] commands = new []
{
new botCommand {
name = "Test",
chandler = new cmdHandler(MyMethod),
isAdmin = false,
help = "No help for you..."
}
};
where MyMethod is defined as.
public static void MyMethod(string[] myArgs)
{
//... do something ...
}
However, I think a better approach would be to have an abstract class / interface for an individual command, list or dictionary of your commands.
public interface IBotCommand
{
string Name { get; }
bool IsAdmin { get; }
void Process(string[] args);
string HelpText { get; }
}
public class MyCommand : IBotCommand
{
string Name
{
get
{
return "NameOfTheCommand";
}
}
bool IsAdmin
{
get
{
return false;
}
}
void Process(string[] args)
{
// bla bla, im processing stuff
}
string HelpText
{
get
{
return "This is the help text";
}
}
}
And then using it.
List<IBotCommand> commands = new List<IBotCommand>();
commands.Add(new MyCommand());
// to find a command and execute it
IBotCommand cmdToExecute = commands.SingleOrDefault(c => c.Name == "NameOfTheCommand");
if (cmdToExecute != null)
{
cmdToExecute.Process(args); // where-ever args comes from
}
else
{
// unknown command "NameOfTheCommand"
}