Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

[C#/ASP .NET/DotNet Core] Project Aurora Pro [v7/R38]

Joined
Jun 23, 2010
Messages
2,318
Reaction score
2,195
4. How do you mean? You mean replacing all "new ..." instances? I know with the "handler" pipeline it can't be done for some weird reason (trust me, ask DotNetty...) as I tried doing it with DI before.

I'm sure you can do it with DI. Try something like this:

Code:
public class ActionChannelInitializerFactory {
    private readonly IServiceProvider _serviceProvider;

    public ActionChannelInitializerFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public ActionChannelInitializer<IChannel> Create()
    {
        return new ActionChannelInitializer<IChannel>(channel => {
                    channel.Pipeline.AddLast("decoder", _serviceProvider.GetRequiredService<Decoder>();
                    channel.Pipeline.AddLast("encoder", _serviceProvider.GetRequiredService<Encoder>());
                    channel.Pipeline.AddLast("handler", _serviceProvider.GetRequiredService<GameNetworkHandler>());
                });
    }
}

...

public class GameNetworkListener
{
    private readonly ActionChannelInitializerFactory _actionChannelInitializerFactory;

    ...

    public GameNetworkListener(ActionChannelInitializerFactory actionChannelInitializerFactory, ...)
    {
        _actionChannelInitializerFactory = actionChannelInitializerFactory;

        ...
    }

    ...

        bootstrap
            .ChildHandler(_actionChannelInitializerFactory.Create())

    ...
}

6. I always thought LINQ would slow things down (not sure how much it matters nowadays though), of course that can be benchmarked but that's the reason I try to limit my LINQ usage.

LINQ itself is fine. They're just a bunch of extension methods. The .Where(predicate) can be compared to something like this:

Code:
public static IEnumerable<T> Where<T>(this IEnumerable<T> items, Func<T, bool> predicate)
{
    foreach (var item in items)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

You should however becareful on how you use it. An IEnumerable computes it's results when it's needed. For example if you do something like:

Code:
var filteredItems = items.Where(x => x.type == "foe");

And you would have used filteredItems, like the following:

Code:
if (filteredItems.Any())
{
    SomeOtherMethod(filteredItems);
}

The .Any() method, and everything inside the SomeOtherMethod that accesses the IEnumerable would cause a loop and executes the predicate given in the Where. You can avoid this by using .ToList(), .ToArray() or any method that returns a concreet result.
The same applies to IQuerable. You should never return that outside your DAO/Repository/w.e. you use to access your database.

That's just one pitfall you should be aware off. There are a few more, like using First/Single for fetching a single record by an id in a for loop.
 
Joined
Feb 22, 2012
Messages
2,100
Reaction score
1,271
Can you explain a bit more on why I shouldn't?

Decreses the scope for testing whenever necessary. If you need to test, for instance, GameNetworkListener out of a singleton DI scope, you'd need to remove the attribute. But then, you could modularise it and control it however you want from a separate module, giving you more flexibility over your code. You want to be able to modify its behavior, extend, or whatever, and using Meta-Programming isn't the way forward IMO

And about the header IDs I don't quite get the problem to be fair. The decoder decodes incoming data, gets an int (the length of the packet), reads X amount of bytes from the data and uses that X amount of bytes in the ClientMessage. The ClientMessage always should have the correct data and in theory - however like in my old server it isn't needed - I could do a while so no data would be lost if two packets end up getting sent together.

I know how they work, but I don't think messages should be coupled with the message itself. I know you're working on older versions thus the header IDs are (almost) the same, but give it some extensibility for N-release incoming/outgoing, which you can't do if it's tightly coupled with the message itself. If you're working with a two-version-header scenario, you'd need to create a mechanism that can deliver from N-version to N-version.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
I'm sure you can do it with DI. Try something like this:

Code:
public class ActionChannelInitializerFactory {
    private readonly IServiceProvider _serviceProvider;

    public ActionChannelInitializerFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public ActionChannelInitializer<IChannel> Create()
    {
        return new ActionChannelInitializer<IChannel>(channel => {
                    channel.Pipeline.AddLast("decoder", _serviceProvider.GetRequiredService<Decoder>();
                    channel.Pipeline.AddLast("encoder", _serviceProvider.GetRequiredService<Encoder>());
                    channel.Pipeline.AddLast("handler", _serviceProvider.GetRequiredService<GameNetworkHandler>());
                });
    }
}

...

public class GameNetworkListener
{
    private readonly ActionChannelInitializerFactory _actionChannelInitializerFactory;

    ...

    public GameNetworkListener(ActionChannelInitializerFactory actionChannelInitializerFactory, ...)
    {
        _actionChannelInitializerFactory = actionChannelInitializerFactory;

        ...
    }

    ...

        bootstrap
            .ChildHandler(_actionChannelInitializerFactory.Create())

    ...
}



LINQ itself is fine. They're just a bunch of extension methods. The .Where(predicate) can be compared to something like this:

Code:
public static IEnumerable<T> Where<T>(this IEnumerable<T> items, Func<T, bool> predicate)
{
    foreach (var item in items)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

You should however becareful on how you use it. An IEnumerable computes it's results when it's needed. For example if you do something like:

Code:
var filteredItems = items.Where(x => x.type == "foe");

And you would have used filteredItems, like the following:

Code:
if (filteredItems.Any())
{
    SomeOtherMethod(filteredItems);
}

The .Any() method, and everything inside the SomeOtherMethod that accesses the IEnumerable would cause a loop and executes the predicate given in the Where. You can avoid this by using .ToList(), .ToArray() or any method that returns a concreet result.
The same applies to IQuerable. You should never return that outside your DAO/Repository/w.e. you use to access your database.

That's just one pitfall you should be aware off. There are a few more, like using First/Single for fetching a single record by an id in a for loop.

Oh thanks for the reply. I can certainly see what you mean. I'll look into my code and change where it's possible (I should have more today). As soon as it's done I'll make sure to put it in git so you'd see how my changes are. My focus now is to take over all points you've mentioned so far and hopefully make the code even better. A huge thanks for helping me out and giving me tips.

Decreses the scope for testing whenever necessary. If you need to test, for instance, GameNetworkListener out of a singleton DI scope, you'd need to remove the attribute. But then, you could modularise it and control it however you want from a separate module, giving you more flexibility over your code. You want to be able to modify its behavior, extend, or whatever, and using Meta-Programming isn't the way forward IMO



I know how they work, but I don't think messages should be coupled with the message itself. I know you're working on older versions thus the header IDs are (almost) the same, but give it some extensibility for N-release incoming/outgoing, which you can't do if it's tightly coupled with the message itself. If you're working with a two-version-header scenario, you'd need to create a mechanism that can deliver from N-version to N-version.

Hm, I can always change it back to adding it manually. I just thought - and yes I admit that this is a stupid reason maybe - cause I'm used to things like Symfony and Laravel and I don't have to add my own classes in order to inject them. It's not a terrible bad thing to do it manually.
As for the headers, do you have any suggestion how to do it better? I can look into that later then.
 
Joined
Feb 22, 2012
Messages
2,100
Reaction score
1,271
Hm, I can always change it back to adding it manually. I just thought - and yes I admit that this is a stupid reason maybe - cause I'm used to things like Symfony and Laravel and I don't have to add my own classes in order to inject them. It's not a terrible bad thing to do it manually.
As for the headers, do you have any suggestion how to do it better? I can look into that later then.

Code:
using System;
using System.Collections.Generic;

namespace Example
{
    public interface IServerHeaders
    {
        ushort SayHello { get; }
    }

    public interface IServerVersion
    {
        IServerHeaders ServerHeaders { get; }
    }

    public interface IHeader
    {
        ushort HeaderId { get; set; }
    }

    public class R63ServerHeaders : IServerHeaders
    {
        public ushort SayHello => 1;
    }

    public class R63ServerVersion : IServerVersion
    {
        public IServerHeaders ServerHeaders => new R63ServerHeaders();
    }
    
    public interface IServerMessage : IDisposable
    {
        IServerMessage WriteByte(byte b);
        byte[] GetData();
    }

    public class ServerMessage : IServerMessage
    {
        private readonly List<byte> _rawBytes = new List<byte>(); // temp.
        
        // all your servermessage logic
        public IServerMessage WriteByte(byte b)
        {
            _rawBytes.Add(b);
            return this;
        }

        public byte[] GetData() => _rawBytes.ToArray();
        public void Dispose()
        {
        }
    }

    public class User
    {
        public IServerVersion Version { get; set; }

        public void SendMessage(int headerId, IServerMessage message)
        {
            // sub-routine to send message to user TCP socket.
            // calls sub-routine to merge packet received with data, example below
            byte[] header = BitConverter.GetBytes(headerId);
            Array.Reverse(header);
            byte[] body = message.GetData();
            
            byte[] packet = new byte[header.Length + body.Length];
            Array.Copy(header, packet, header.Length);
            Array.Copy(body, 0, packet, header.Length, body.Length);
            Console.WriteLine("Sending message...");
        }
    }
    
    public class Room
    {
        public IEnumerable<User> Guests = new List<User>();
    }
    public class RandomMessage
    {
        public void SomeMessage(Room rooms)
        {
            using (IServerMessage serverMessage = new ServerMessage())
            {
                serverMessage.WriteByte(65)
                    .WriteByte(66)
                    .WriteByte(67);
                
                foreach (var guest in rooms.Guests)
                {
                    guest.SendMessage(guest.Version.ServerHeaders.SayHello, serverMessage);
                }
            }
            
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            User user = new User()
            {
                Version = new R63ServerVersion()
            };
            
            Room room = new Room()
            {
                Guests = new []
                {
                    user
                }
            };
            
            RandomMessage message = new RandomMessage();
            message.SomeMessage(room);
        }
    }
}

Wrote quickly, anyway, you could have a look at that to get some idea of what I meant.

My example wasn't meant to be optimized nor beautiful, rather to just prove a point.

Any user will be able to have any version, and still receive their message. You should also improve the code to make them to be able to handle the message themselves (by the version, and not by the header), this way, R63B (or whatever it's called nowadays) can have its own version handler, same as any other versions.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
Code:
using System;
using System.Collections.Generic;

namespace Example
{
    public interface IServerHeaders
    {
        ushort SayHello { get; }
    }

    public interface IServerVersion
    {
        IServerHeaders ServerHeaders { get; }
    }

    public interface IHeader
    {
        ushort HeaderId { get; set; }
    }

    public class R63ServerHeaders : IServerHeaders
    {
        public ushort SayHello => 1;
    }

    public class R63ServerVersion : IServerVersion
    {
        public IServerHeaders ServerHeaders => new R63ServerHeaders();
    }
    
    public interface IServerMessage : IDisposable
    {
        IServerMessage WriteByte(byte b);
        byte[] GetData();
    }

    public class ServerMessage : IServerMessage
    {
        private readonly List<byte> _rawBytes = new List<byte>(); // temp.
        
        // all your servermessage logic
        public IServerMessage WriteByte(byte b)
        {
            _rawBytes.Add(b);
            return this;
        }

        public byte[] GetData() => _rawBytes.ToArray();
        public void Dispose()
        {
        }
    }

    public class User
    {
        public IServerVersion Version { get; set; }

        public void SendMessage(int headerId, IServerMessage message)
        {
            // sub-routine to send message to user TCP socket.
            // calls sub-routine to merge packet received with data, example below
            byte[] header = BitConverter.GetBytes(headerId);
            Array.Reverse(header);
            byte[] body = message.GetData();
            
            byte[] packet = new byte[header.Length + body.Length];
            Array.Copy(header, packet, header.Length);
            Array.Copy(body, 0, packet, header.Length, body.Length);
            Console.WriteLine("Sending message...");
        }
    }
    
    public class Room
    {
        public IEnumerable<User> Guests = new List<User>();
    }
    public class RandomMessage
    {
        public void SomeMessage(Room rooms)
        {
            using (IServerMessage serverMessage = new ServerMessage())
            {
                serverMessage.WriteByte(65)
                    .WriteByte(66)
                    .WriteByte(67);
                
                foreach (var guest in rooms.Guests)
                {
                    guest.SendMessage(guest.Version.ServerHeaders.SayHello, serverMessage);
                }
            }
            
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            User user = new User()
            {
                Version = new R63ServerVersion()
            };
            
            Room room = new Room()
            {
                Guests = new []
                {
                    user
                }
            };
            
            RandomMessage message = new RandomMessage();
            message.SomeMessage(room);
        }
    }
}

Wrote quickly, anyway, you could have a look at that to get some idea of what I meant.

My example wasn't meant to be optimized nor beautiful, rather to just prove a point.

Any user will be able to have any version, and still receive their message. You should also improve the code to make them to be able to handle the message themselves (by the version, and not by the header), this way, R63B (or whatever it's called nowadays) can have its own version handler, same as any other versions.

This indeed will work. However, if structures changed this won't work, and if features doesn't exist it will - from what I think in my head - cause errors. I know maybe a way to do it but I'm unsure how. I almost think like in this case having like a MessageHandler for every version would be better. If structure/features don't change I don't see a reason to support multiple revisions either. I get your point but I'm not sure whether it's worth it.

This of course also is only the case for post-shuffle. If I want to support R54 and R38 I would already need to do it way different due to those structures being changed a lot.
 
Joined
Feb 22, 2012
Messages
2,100
Reaction score
1,271
This indeed will work. However, if structures changed this won't work, and if features doesn't exist it will - from what I think in my head - cause errors. I know maybe a way to do it but I'm unsure how. I almost think like in this case having like a MessageHandler for every version would be better. If structure/features don't change I don't see a reason to support multiple revisions either. I get your point but I'm not sure whether it's worth it.

This of course also is only the case for post-shuffle. If I want to support R54 and R38 I would already need to do it way different due to those structures being changed a lot.

They usually don't, not for most packets anyway, and when they do that's when people usually make an API deprecated. I'm not saying you should follow that by the rule, but having a gateway to parse your income then translate to an API that makes all the version handling could be done to make it multi version.

And yes, I know which versions you are targetting. I just don't think that hardcoding messages like this is a great idea.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
They usually don't, not for most packets anyway, and when they do that's when people usually make an API deprecated. I'm not saying you should follow that by the rule, but having a gateway to parse your income then translate to an API that makes all the version handling could be done to make it multi version.

And yes, I know which versions you are targetting. I just don't think that hardcoding messages like this is a great idea.

I know usually they don't and for most packet they wont. But if people want to use a completely different version it would be mostly because it has a special feature. If everything is the same but the release I don't see a point for other people (as I'm not going to focus on multiple revisions in one server) to update packets to a newer SWF version without any benefit.

I can look in what you sent me and indeed if you plan on supporting multiple revisions, I can understand, but such plans aren't planned and won't happen, as I think supporting one version is good enough. In both cases:

R63 post shuffle - I can pick the latest. I can always make it so every event and every response is a class in a specific namespace and based on your release string it will use the right packet handler so in case Habbo gets new updates with new features which they wont people can update it as they like or support two versions. Retros only use one client so it's not a big issue.

R38 - Since it would be both Shockwave and Flash (cause most is similar), it works. But adding R54 to it would just mean a bunch of different work cause a lot new features in R54 which aren't in R38.

Again, I understand your point but I don't see it as a priority thing in this server.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
I've put the source on Github. I did do this because I feel like sharing = caring. Regardless, this way when people check out the full code they can give tips or criticism if I do something wrong. I'm 100% well aware of any risks, but it's not a huge problem. Also, keep in mind, it's unusable at the moment. The server is built upon the post-shuffle client, but it isn't final I'll make that.



Also keep in mind there are probably a lot of things I overlooked so feel free to correct me on any major (or minor...) mistakes.
 
Joined
Jun 23, 2010
Messages
2,318
Reaction score
2,195
Few things I noticed when browsing your code on GitHub:
- you don't need to build your own pooling system. That's something the ado.net connector should do for you. That's why you have a pooling option in the connection string;). By the way, I believe it's true by default.
- your dependencyregistar.resolve method depends on another method to be called first. It's not recommend to do such thing. I also feel like you can remove that entire class and use the provided IServiceCollection and IServiceProvider instead. You can also inject the IServiceProvider into implementation classes. It's commen to use interface to separate different layers (domain and storage for example). Fun fact, you can have scoped lifetimes when utilizing IScoperServiceFactory (something like that), make sure to dispose when you're done with it.
- If you don't want to use a have framwork like EF, I suggest Dapper. It still allows you to make your own queries, but handles the ORM part and parameter binding for you.
- another fun fact: you could register all your ICommand implementations in IServiceCollection, like: serviceCollection.AddTransient<ICommand, ClearCommand>();. And that for every command you like to add. In your console command handler thingy you can inject an IEnumerable<ICommand>.
- instead of using any to check if the command exists, use: TryGetValue.
- I would use extension methods on the ISerciveCollection to register your dependencies and services, instead of using a SessionService.Register.
- "protected override async void ChannelRead0(IChannelHandlerContext ctx, ClientMessage msg)" doesn't look like an asynchronous method. Have you checked if that's actually possible? And do you know that exceptions will be eaten and cause unexpected behavior? You can't put async randomly and expecting everything would be fine. I suggest you to wrap it in a Task.Run.

Hope this helps you.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
Few things I noticed when browsing your code on GitHub:
- you don't need to build your own pooling system. That's something the ado.net connector should do for you. That's why you have a pooling option in the connection string;). By the way, I believe it's true by default.
- your dependencyregistar.resolve method depends on another method to be called first. It's not recommend to do such thing. I also feel like you can remove that entire class and use the provided IServiceCollection and IServiceProvider instead. You can also inject the IServiceProvider into implementation classes. It's commen to use interface to separate different layers (domain and storage for example). Fun fact, you can have scoped lifetimes when utilizing IScoperServiceFactory (something like that), make sure to dispose when you're done with it.
- If you don't want to use a have framwork like EF, I suggest Dapper. It still allows you to make your own queries, but handles the ORM part and parameter binding for you.
- another fun fact: you could register all your ICommand implementations in IServiceCollection, like: serviceCollection.AddTransient<ICommand, ClearCommand>();. And that for every command you like to add. In your console command handler thingy you can inject an IEnumerable<ICommand>.
- instead of using any to check if the command exists, use: TryGetValue.
- I would use extension methods on the ISerciveCollection to register your dependencies and services, instead of using a SessionService.Register.
- "protected override async void ChannelRead0(IChannelHandlerContext ctx, ClientMessage msg)" doesn't look like an asynchronous method. Have you checked if that's actually possible? And do you know that exceptions will be eaten and cause unexpected behavior? You can't put async randomly and expecting everything would be fine. I suggest you to wrap it in a Task.Run.

Hope this helps you.

I'll look and do some research on these points. Wouldn't be too hard for most part. Thanks for the suggestions anyways.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
Attempted to do some points:

- Console commands are now registered in the ServiceCollection and injected through DI
- Removed DependencyRegistrar



(Keep in mind I still have to change the .Any I forgot oops)

PHP:
public class ConsoleCommandHandler
    {
        private readonly ILogger<ConsoleCommandHandler> logger;
        private readonly Dictionary<string, ICommand> commands;

        public ConsoleCommandHandler(ILogger<ConsoleCommandHandler> logger, IEnumerable<ICommand> commands)
        {
            this.logger = logger;
            this.commands = commands.ToDictionary(command => command.Command);
        }

        public async Task TryHandleCommandAsync(string input)
        {
            int spacePosition = input.IndexOf(' ');
            string header = spacePosition >= 0 ? input.Substring(0, spacePosition) : input;

            if (commands.Any(command => command.Key == header))
            {
                await commands[header].RunAsync(input.Substring(spacePosition + 1).Split(' '));
            }
            else
            {
                logger.LogWarning("'{0}' is not recognized as an internal command", header);
            }
        }
    }

PHP:
IServiceCollection serviceDescriptors = new ServiceCollection();
            serviceDescriptors.AddLogging(builder => builder.AddConsole());
            serviceDescriptors.AddSingleton<MessageHandler>();
            serviceDescriptors.AddSingleton<GameNetworkListener>();
            serviceDescriptors.AddSingleton<ConsoleCommandHandler>();

            serviceDescriptors.AddService<SessionService>();
            serviceDescriptors.AddService<HabboService>();

            serviceDescriptors.RegisterConsoleCommands();

(view the git repo for all the changed code).

----

Can a MOD change the title of the topic to:

[C#/.NET Core/R63 Post-shuffle] Project Nordlys

(how I wish I could do this myself...)
 
Last edited:
Joined
Jun 23, 2010
Messages
2,318
Reaction score
2,195
Few minor points:
- Console methods are always synchronized between threads. Run the method as normal and return Task.Completed instead.
- GetService returns null when the service can't be found, GetRequiredService on the other hand throws an exception. Makes life easier while debugging to see if you haven't registered it.
- You could've used the Activator class to create an instance of the service, OR by adding the constructor constraint on your generic parameter (where T : new()).
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
Few minor points:
- Console methods are always synchronized between threads. Run the method as normal and return Task.Completed instead.
- GetService returns null when the service can't be found, GetRequiredService on the other hand throws an exception. Makes life easier while debugging to see if you haven't registered it.
- You could've used the Activator class to create an instance of the service, OR by adding the constructor constraint on your generic parameter (where T : new()).

Thanks for the criticism points. About your last point, I assume it'd be like this:

PHP:
public static void AddService<T>(this IServiceCollection serviceDescriptors) where T : IService, new()
        {
            new T().Register(serviceDescriptors);
        }

I assume it wouldn't matter in this case whether to use that or the Activator class? I honestly never thought about these methods (I think I've seen them both before but I never actually used them).

--

Going back on a point you made earlier, I wouldn't mind giving EF a go. My only question / concern is, how many DbContext I should use? I've googled a bit and some say one and some say multiple. When using EF I would use code-first so people don't have to import an SQL for it to work. IIRC I remember reading something about that needing to have only one DbContext but not sure.
 
Last edited:
Joined
Jun 23, 2010
Messages
2,318
Reaction score
2,195
Thanks for the criticism points. About your last point, I assume it'd be like this:

PHP:
public static void AddService<T>(this IServiceCollection serviceDescriptors) where T : IService, new()
        {
            new T().Register(serviceDescriptors);
        }

I assume it wouldn't matter in this case whether to use that or the Activator class? I honestly never thought about these methods (I think I've seen them both before but I never actually used them).

--

Going back on a point you made earlier, I wouldn't mind giving EF a go. My only question / concern is, how many DbContext I should use? I've googled a bit and some say one and some say multiple. When using EF I would use code-first so people don't have to import an SQL for it to work. IIRC I remember reading something about that needing to have only one DbContext but not sure.

A DbContext says something about a group of models that belongs together. I suggest you don't use one DbContext for your entire server, but multiple doing their own thing. You can create as many instances as you like, but don't reuse them. The're designed to be short-lived. When using Asp.NET and are adding a context, it's added as a scope to the service collection. This means one DbContext instance for each request (so you get the idea of the lifetime).
Nice to see you use code-first migrations, makes live easier migrating new database scheme changes. Keep in mind how you handle existing data!

There is no difference between the new() constraint and the Activator class. Under the hood, generics having the new() constraint use the Activator class to create an new instance.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
A DbContext says something about a group of models that belongs together. I suggest you don't use one DbContext for your entire server, but multiple doing their own thing. You can create as many instances as you like, but don't reuse them. The're designed to be short-lived. When using Asp.NET and are adding a context, it's added as a scope to the service collection. This means one DbContext instance for each request (so you get the idea of the lifetime).
Nice to see you use code-first migrations, makes live easier migrating new database scheme changes. Keep in mind how you handle existing data!

There is no difference between the new() constraint and the Activator class. Under the hood, generics having the new() constraint use the Activator class to create an new instance.

I'll give EF a go then as it's surely a lot better (and easier...) than the way I'm doing it now (and I've worked with EF before so it's not completely new to me). I assume the DbContext would be added as scoped to the DI container and resolved in my controller functions (like GetHabboById, GetHabboByName, Authenticate, etc etc.)
 
Joined
Jun 23, 2010
Messages
2,318
Reaction score
2,195
Keep in mind that adding a DbContext as scoped requires you to create the scope yourself. Without creating a scope you basicly and up with a singleton, which you do not want. Also becareful with using singletons with scoped dependencies.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
Keep in mind that adding a DbContext as scoped requires you to create the scope yourself. Without creating a scope you basicly and up with a singleton, which you do not want. Also becareful with using singletons with scoped dependencies.

I assume I have to add it as scoped and then resolve it in a CreateScope using block?
 
Joined
Jun 23, 2010
Messages
2,318
Reaction score
2,195
Yes, that's correct. The scope determines the lifetime of the scoped dependencies. Also do not use the resolved dependencies outside the using statement. The DI will automatically dispose all objects with the IDisposable interface. It's also a nice way to clean up your poop ;). I also wouldn't recommend creating a new scope everytime you need a context. I once did it for the lifetime of a single connection, but it caused issues with the context being reused too many times and such. I think it would be better to create a scope based on the lifetime of a single message handler. However, it's just a thought I had e and you should test out what works best for you etc.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
Started working on integrating entity framework. The code isn't on git yet but basically this means easier communication with the database, as well as no need to import an SQL file and create a database when you start the server.

The following code is used to add a DbContext:

PHP:
serviceDescriptors.AddDbContext<HabboDbContext>();

Then on the startup, this code is used to create the database (if needed) and execute all migrations:

PHP:
await serviceProvider.GetRequiredService<HabboDbContext>().Database.MigrateAsync();

For now, the DbContext will be injected in controllers, like the following:

PHP:
    public class HabboController
    {
        private readonly HabboDbContext dbContext;

        public HabboController(HabboDbContext dbContext)
        {
            this.dbContext = dbContext;
        }

        public Habbo GetHabbo(int id)
        {
            return dbContext.Find<Habbo>(id);
        }

        public Habbo Authenticate(string sso)
        {
            return dbContext.Habbos.Where(habbo => habbo.AuthenticationTicket == sso).FirstOrDefault();
        }
    }

There's still room for improvement I'd guess but it's a start now. Of course these controllers will be injected in any part it's necessary, for example the HabboController will be injected in the message event for SSO ticket so the Authenticate method can be called.
 
Custom Title Activated
Loyal Member
Joined
Oct 26, 2012
Messages
2,357
Reaction score
1,086
It has been a while. I could go on with a lot of excuses, but why bother. This is going to be a long post so bear with me.

This project was started as a little time-filler for when I would be bored, I had something to work on. I had motivation for the project - and I still have - but I can feel developing this feels different. Back in the day (when I was a noob), even if what I did was terrible, it gave me a bit of satisfaction, the satisfaction I lack nowadays. Maybe this is my own fault of trying a million projects (gonna mention it myself before somebody else does), or maybe it's because I've grown out of it, or because the section is dying. Whatever it is, it definitely affects me. Seeing how Cortex is doing, it makes me wonder if there's still a need for a development. If Cortex gets finishes, then what's left to do besides HTML5 development? (yeah, I could work on something HTML5 but that's far outside my league and client development doesn't interest me that much). Obviously, Habbo will launch a new client later this year - which honestly I wonder if we'll ever be able to create a retro from it - but that takes some time. Also, even when it comes out, who knows how long it'll take before real pro's figure out a way to create a retro from it. I definitely have plans to look into it, but again, I'm not a big pro.

I honestly am stuck. Partly I want to develop something, something I can be proud of (sorta...), but I'm not sure if it's the right time for it (if ever). I don't know whether to continue for the sole reason I can leave something I'm proud of, or not continue because it barely makes sense. The development was started as a sole R38 development for both Shockwave and Flash, but it brought v7 when I got the Lingo scripts for it. Shockwave already was EOL, and now Flash will reach EOL this year as well. I wonder, does it even make sense developing something for that? I know Quackster created Kepler even though Shockwave is EOL a few years already, but still. The sole reason I'd continue the development is to leave something I can be sort of proud of and release the first public-usable stable server that works for R38 both Shockwave and Flash and the first full v7 server ever publicly released. But is it worth the time?

I did write my "framework", and I can say I'm proud of how that turned out. I did do some client code for v7 (cause I was bored) and a minor amount for R38. Not much to say I would've wasted a lot of time, but enough to maybe consider going on. I'll definitely try posting updates here, but I can't promise anything 100%.

Now, a few things to mention about my code so far (the code I might or might not put on git depending on whether I'm lazy or not):

- Using .NET Core for multi platform
- Using dependency injection
- Using Entity Framework Core with migrations for easy usage
- Using the powerful PBKDF2 for hashing passwords
- Using DotNetty for easy networking
- Using C# 8.0

A few snippets of new code:

PHP:
public class PasswordHash
    {
        private const int SaltSize = 24; // size in bytes
        private const int HashSize = 24; // size in bytes
        private const int Iterations = 100000; // number of pbkdf2 iterations
        
        public static byte[] GenerateSalt()
        {
            using var provider = new RNGCryptoServiceProvider();
            byte[] salt = new byte[SaltSize];
            provider.GetBytes(salt);
            return salt;
        }

        public static byte[] Hash(string input, byte[] salt)
        {
            using Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(input, salt, Iterations);
            return pbkdf2.GetBytes(HashSize);
        }

        public static bool Verify(string password, Player player)
        {
            // This looks ugly... gotta look at this a bit more later but cba now
            return Convert.ToBase64String(Hash(password, Convert.FromBase64String(player.Salt))).Equals(player.Password);
        }
    }

PHP:
public class HabboDbContext : DbContext
    {
        public HabboDbContext()
            : base ()
        {
            
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseMySql("Server=127.0.0.1;Database=stargazer_db;Uid=root;Pwd=ThisDatabaseIsNowCompromised;");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Player>().Property(p => p.Motto).HasDefaultValue(string.Empty);
            modelBuilder.Entity<Player>().Property(p => p.ConsoleMotto).HasDefaultValue(string.Empty);
            modelBuilder.Entity<Player>().Property(p => p.Coins).HasDefaultValue(100);
            
            modelBuilder.Entity<PlayerRole>().HasKey(pr => new {pr.PlayerId, pr.RoleId});

            modelBuilder.Entity<PlayerRole>()
                .HasOne(pr => pr.Player)
                .WithMany(p => p.Roles)
                .HasForeignKey(pr => pr.PlayerId);

            modelBuilder.Entity<PlayerRole>()
                .HasOne(pr => pr.Role)
                .WithMany(r => r.Players)
                .HasForeignKey(pr => pr.RoleId);

            modelBuilder.Entity<Role>().HasData(
                new Role() { Id = 1, Name = "User"},
                new Role() { Id = 2, Name = "HabboX"},
                new Role() { Id = 3, Name = "Silver Hobba"},
                new Role() { Id = 4, Name = "Gold Hobba"},
                new Role() { Id = 5, Name = "Moderator"},
                new Role() { Id = 6, Name = "Management"});
        }

        public DbSet<Player> Players { get; set; }
        public DbSet<Role> Roles { get; set; }
    }

Also, since I haven't done this in a while I definitely want to thank a few people:

- Quackster: His archive website, shockwave installation stuff, helping with Lingo scripts and stuff (probably more I can't remember)
- Nillus: For some packet stuff from Blunk/Ion
- Everybody else: For being here

I don't ever regret being here or that I ever checked on here, it definitely helped me grow as both a person and a developer and I thank everyone for that.

If you're still reading, let me know what to do, continue with what my plans were, or do something else. I'm happy to get new ideas.
 
Back
Top