diff --git a/Examples/DependencyInjection/Database/BotDbContext.cs b/Examples/DependencyInjection/Database/BotDbContext.cs new file mode 100644 index 0000000..b7e20f5 --- /dev/null +++ b/Examples/DependencyInjection/Database/BotDbContext.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; + +namespace DependencyInjection.Database; + +public class BotDbContext : DbContext +{ + public BotDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Users { get; set; } +} \ No newline at end of file diff --git a/Examples/DependencyInjection/Database/User.cs b/Examples/DependencyInjection/Database/User.cs new file mode 100644 index 0000000..3c01b63 --- /dev/null +++ b/Examples/DependencyInjection/Database/User.cs @@ -0,0 +1,7 @@ +namespace DependencyInjection.Database; + +public class User +{ + public long Id { get; set; } + public string LastMessage { get; set; } +} \ No newline at end of file diff --git a/Examples/DependencyInjection/Forms/ConfirmationForm.cs b/Examples/DependencyInjection/Forms/ConfirmationForm.cs new file mode 100644 index 0000000..9152242 --- /dev/null +++ b/Examples/DependencyInjection/Forms/ConfirmationForm.cs @@ -0,0 +1,67 @@ +using DependencyInjection.Database; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TelegramBotBase.Base; +using TelegramBotBase.Form; +using TelegramBotBase.DependencyInjection; + +namespace DependencyInjection.Forms +{ + public class ConfirmationForm : FormBase + { + private readonly BotDbContext _dbContext; + + public ConfirmationForm(BotDbContext dbContext) + { + _dbContext = dbContext; + } + + public override async Task Load(MessageResult message) + { + var user = await _dbContext.Users.FindAsync(Device.DeviceId); + + if (user == null) + { + await this.NavigateTo(); + return; + } + } + + public override async Task Action(MessageResult message) + { + await message.ConfirmAction("Go back"); + + switch (message.RawData) + { + + case "back": + + await this.NavigateTo(); + + break; + + } + + + } + + public override async Task Render(MessageResult message) + { + var user = await _dbContext.Users.FindAsync(Device.DeviceId); + if (user == null) + return; + + var bf = new ButtonForm(); + bf.AddButtonRow("Back", "back"); + + await Device.Send($"ConfirmationForm: Your last message was: {user.LastMessage}. Click \"Back\" to get back.", bf); + } + + + + + } +} diff --git a/Examples/DependencyInjection/StartForm.cs b/Examples/DependencyInjection/StartForm.cs new file mode 100644 index 0000000..92c6ec0 --- /dev/null +++ b/Examples/DependencyInjection/StartForm.cs @@ -0,0 +1,79 @@ +using DependencyInjection.Database; +using DependencyInjection.Forms; +using TelegramBotBase.Base; +using TelegramBotBase.Form; +using TelegramBotBase.DependencyInjection; + +namespace DependencyInjection; + +public class StartForm : FormBase +{ + private readonly BotDbContext _dbContext; + + public StartForm(BotDbContext dbContext) + { + _dbContext = dbContext; + } + + public override async Task Load(MessageResult message) + { + var user = await _dbContext.Users.FindAsync(Device.DeviceId); + if (user is null) + { + user = new User + { + Id = Device.DeviceId, + LastMessage = "" + }; + + _dbContext.Users.Add(user); + await _dbContext.SaveChangesAsync(); + } + + if (message.IsAction) + return; + + + user.LastMessage = string.IsNullOrWhiteSpace(message.MessageText) ? "" : message.MessageText; + await _dbContext.SaveChangesAsync(); + } + + public override async Task Action(MessageResult message) + { + await message.ConfirmAction("Ok"); + + switch(message.RawData) + { + + case "open": + + await this.NavigateTo(typeof(ConfirmationForm)); + + var new_form = await this.NavigateTo(); + + if (new_form == null) + { + await Device.Send("Cant open ConfirmationForm"); + } + + break; + + } + + + } + + public override async Task Render(MessageResult message) + { + var user = await _dbContext.Users.FindAsync(Device.DeviceId); + if (user == null) + return; + + var bf = new ButtonForm(); + + bf.AddButtonRow("Open confirmation", "open"); + + await Device.Send($"Your last message's text was: `{user.LastMessage}`", bf); + } + +} diff --git a/README.md b/README.md index 644e992..ca629cb 100644 --- a/README.md +++ b/README.md @@ -1072,3 +1072,8 @@ Having already a web application and want to add a TelegramBot side-by-side with Want to use Inline- and ReplyMarkup at the same time ? Here is an example how you can do that: - [Examples/InlineAndReplyCombination](Examples/InlineAndReplyCombination) + + +Alpha: Full Dependency Injection example within this framework. + +- [Examples/DependencyInjection](Examples/DependencyInjection) \ No newline at end of file diff --git a/TelegramBotBase/Base/FormBase.cs b/TelegramBotBase/Base/FormBase.cs index 479e5ee..160952f 100644 --- a/TelegramBotBase/Base/FormBase.cs +++ b/TelegramBotBase/Base/FormBase.cs @@ -32,6 +32,8 @@ public class FormBase : IDisposable public MessageClient Client { get; set; } + IServiceProvider _serviceProvider = null; + /// /// has this formular already been disposed ? /// diff --git a/TelegramBotBase/DependencyInjection/Extensions.cs b/TelegramBotBase/DependencyInjection/Extensions.cs new file mode 100644 index 0000000..f9a0bfc --- /dev/null +++ b/TelegramBotBase/DependencyInjection/Extensions.cs @@ -0,0 +1,86 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using TelegramBotBase.Form; + +namespace TelegramBotBase.DependencyInjection +{ + public static class Extensions + { + internal static FieldInfo _ServiceProviderField = typeof(FormBase).GetField("_serviceProvider", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + + /// + /// Use Dependency Injection to create new form and inject parameters. (Main variant) + /// + /// + /// + /// + public static async Task NavigateTo(this FormBase current_form, params object[] args) + where NewForm : FormBase + { + var _serviceProvider = current_form.GetServiceProvider(); + + var instance = ActivatorUtilities.CreateInstance(_serviceProvider, typeof(NewForm)) as NewForm; + + if (instance == null) + return null; //throw new Exception("Could not instantiate new form via DI."); + + instance.SetServiceProvider(_serviceProvider); + + await current_form.NavigateTo(instance, args); + + return instance; + } + + /// + /// Use Dependency Injection to create new form and inject parameters. (Alternative variant) + /// + /// + /// + /// + /// + /// + public static async Task NavigateTo(this FormBase current_form, Type formBaseType, params object[] args) + { + if (!typeof(FormBase).IsAssignableFrom(formBaseType)) + throw new ArgumentException($"{nameof(formBaseType)} argument must be a {nameof(FormBase)} type"); + + var _serviceProvider = current_form.GetServiceProvider(); + + var instance = ActivatorUtilities.CreateInstance(_serviceProvider, formBaseType) as FormBase; + + if (instance == null) + return null; //throw new Exception("Could not instantiate new form via DI."); + + instance.SetServiceProvider(_serviceProvider); + + await current_form.NavigateTo(instance, args); + + return instance; + } + + /// + /// Sets the internal service provider field. + /// + /// + /// + public static void SetServiceProvider(this FormBase form, IServiceProvider serviceProvider) + { + _ServiceProviderField?.SetValue(form, serviceProvider); + } + + /// + /// Gets the internal service provider field value. + /// + /// + /// + public static IServiceProvider GetServiceProvider(this FormBase form) + { + var sp = _ServiceProviderField?.GetValue(form) as IServiceProvider; + return sp; + } + } +} diff --git a/TelegramBotBase/Factories/ServiceProviderStartFormFactory.cs b/TelegramBotBase/Factories/ServiceProviderStartFormFactory.cs index fbe4394..8bac651 100644 --- a/TelegramBotBase/Factories/ServiceProviderStartFormFactory.cs +++ b/TelegramBotBase/Factories/ServiceProviderStartFormFactory.cs @@ -1,5 +1,6 @@ using System; using Microsoft.Extensions.DependencyInjection; +using TelegramBotBase.DependencyInjection; using TelegramBotBase.Form; using TelegramBotBase.Interfaces; @@ -16,14 +17,19 @@ public class ServiceProviderStartFormFactory : IStartFormFactory { throw new ArgumentException($"{nameof(startFormClass)} argument must be a {nameof(FormBase)} type"); } - + _startFormClass = startFormClass; _serviceProvider = serviceProvider; } public FormBase CreateForm() { - return (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass); + var fb = (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass); + + //Sets an internal field for future ServiceProvider navigation + fb.SetServiceProvider(_serviceProvider); + + return fb; } }