How to decoupling a message from its information and implementation? - c#

Scenario written by C# language, three principal projects in my solution: Remote, Event and Model.
Remote: manages a socket from a remote system. Remote has two handlers: to notify the connection status, send the message from the remote system.
Event: publish messages around all the solution
Model: businnes logic.
I want Remote would be isolated from the rest of the system, I mean to build a manager in Model which intercepts the notifications from Remote and uses Event to spread the message.
I want every other managers in model know only Model, no the Remote implementation of the message.
I have already made custom messages to publish by Event the connection status from Remote, my problem is the follow: How can I send message without who intercept the
message know the implementation? Every messages have different properties.
I tried to made a Message in model which has the same interface of the message in remote.
But in this case everyone can register mode to get the message have to know the implementation of the message to get properties.
The message which send around the Model by Event
MessageEvent: IEvent
public const string Name="MessageEvent"
// The message implemented in Remote
public IRemoteMessage RemoteMessage {get; private set;}
public void MessageEvent(IRemoteMessage rm)
{
// I want to avoid make a copy of the original message, too much classes to have same information
RemoteMessage = rm;
}
Handlers from Remote in CommunicationManager in Model
RemoteService.ReceivedData += OnReceiveData;
OnReceiveData(object sender, DataArgs e)
{
var remoteMessage = e as IRemoteMessage;
EventService.Publish(new MessageEvent(remoteMessage))
}
Everyone can register the Event (Observer) in the Model as:
EventService.Register(OnManageData, MessageEvent.Name)
\\..
private void OnManageData(EvtData arg)
{
if (arg is MessageEvent)
{
var me = arg as MessageEvent;
// I have the problem here, I can cast remoteMessage by its impementation in Remote to get the properties but I don't want it!!!
var remoteMessage = me.RemoteMessage;
}
}
Everything works in my real scenario, but I repeat my self:
Remote hasn't to refer to Model or Event
Model have to spread by a CommunicationManager the message from Remote to every other managers in Model
Nobody have to know of the implementation of the Remote message
Every suggestions will be appreciate

I recommend using the "Bridge Design Pattern". The Bridge is a structural design pattern that divides business logic or huge class into separate class hierarchies that can be developed independently.
here a sample to send multiple types of messages with multiple input parameter:
/// <summary>
/// The 'Abstraction' class
/// </summary>
public abstract class Message
{
public IMessageSender MessageSender { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public abstract void Send();
}
/// <summary>
/// The 'RefinedAbstraction' class
/// </summary>
public class SystemMessage : Message
{
public override void Send()
{
MessageSender.SendMessage(Subject, Body);
}
}
/// <summary>
/// The 'RefinedAbstraction' class
/// </summary>
public class UserMessage : Message
{
public string UserComments { get; set; }
public override void Send()
{
string fullBody = string.Format("{0}\nUser Comments: {1}", Body, UserComments);
MessageSender.SendMessage(Subject, fullBody);
}
}
/// <summary>
/// The 'Bridge/Implementor' interface
/// </summary>
public interface IMessageSender
{
void SendMessage(string subject, string body);
}
/// <summary>
/// The 'ConcreteImplementor' class
/// </summary>
public class EmailSender : IMessageSender
{
public void SendMessage(string subject, string body)
{
Console.WriteLine("Email\n{0}\n{1}\n", subject, body);
}
}
/// <summary>
/// The 'ConcreteImplementor' class
/// </summary>
public class MSMQSender : IMessageSender
{
public void SendMessage(string subject, string body)
{
Console.WriteLine("MSMQ\n{0}\n{1}\n", subject, body);
}
}
/// <summary>
/// The 'ConcreteImplementor' class
/// </summary>
public class WebServiceSender : IMessageSender
{
public void SendMessage(string subject, string body)
{
Console.WriteLine("Web Service\n{0}\n{1}\n", subject, body);
}
}
/// <summary>
/// Bridge Design Pattern Demo
/// </summary>
class Program
{
static void Main(string[] args)
{
IMessageSender email = new EmailSender();
IMessageSender queue = new MSMQSender();
IMessageSender web = new WebServiceSender();
Message message = new SystemMessage();
message.Subject = "Test Message";
message.Body = "Hi, This is a Test Message";
message.MessageSender = email;
message.Send();
message.MessageSender = queue;
message.Send();
message.MessageSender = web;
message.Send();
UserMessage usermsg = new UserMessage();
usermsg.Subject = "Test Message";
usermsg.Body = "Hi, This is a Test Message";
usermsg.UserComments = "I hope you are well";
usermsg.MessageSender = email;
usermsg.Send();
Console.ReadKey();
}
}
For more information see the following link:
Bridge Design Pattern

Related

Update React front-end from .Net back-end

I am recently approaching React and I was wondering how can I send from my ASP.NET back-end some updates to all the front-ends, but first let me explain myself.
I am sending some inputs from one front-end to the back-end. Just for reference here's how I do it:
Front-end
async SendInput(data) {
await fetch('Status/ChangeState', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
Back-end
[ApiController]
[Route("[controller]")]
public class StatusController : Controller
{
[HttpPost("ChangeState")]
public void ChangeState([FromBody] short i)
{
//Do stuff here
}
}
Now what I need is keep all the connected users variables updated when this "state" is changed by one of them.
My first thought was to send repeatedly a POST or GET request to the back-end with setInterval(), but even if there are not so many users I still would prefer a method like Long polling or Server Sent Events.
What would a good recommended approach be? If possible include an example.
The simplest possible state manager in my opinion for long polling can be created with a TaskCompletionSource. The controller waits for a state change with await and sends the result as ActionResult. The client opens the connection to this controller immediately after receiving the data.
However, I would only use this implementation for very small projects and is intended more as an illustrative example. For larger projects SignalR can be used.
/// <summary>
/// Class for your state properties.
/// </summary>
public class State
{
public string MyStateField1 { get; set; } = string.Empty;
public string MyStateField2 { get; set; } = string.Empty;
}
/// <summary>
/// StateManager
/// Register with
/// services.AddSingleton<StateManager<State>>();
/// in ConfigureServices().
/// </summary>
/// <typeparam name="T">Type of the managed state class.</typeparam>
public class StateManager<T> where T : new()
{
private readonly T _state;
private TaskCompletionSource<T> _taskCompletition = new TaskCompletionSource<T>();
private readonly object _taskCompletitionLock = new object();
public StateManager()
{
_state = new T();
}
public void ChangeState(Action<T> updateFunction)
{
lock (_taskCompletitionLock)
{
updateFunction(_state);
_taskCompletition.SetResult(_state);
_taskCompletition = new TaskCompletionSource<T>();
}
}
public Task<T> GetChangedState() => _taskCompletition.Task;
}
Register in Startup.ConfigureServices:
services.AddSingleton<StateManager<State>>();
Usage (in your controller):
public async Task<IActionResult> StateMonitor()
{
var newState = await _stateManager.GetChangedState();
return Ok(newState);
}
public async Task<IActionResult> SetNewState()
{
_stateManager.ChangeState(state => state.MyStateField1 = "My changed State");
return NoContentResult();
}

SignalR .NET Client: Handle Broadcast Across Connections When Excluding a Connection

I am writing unit tests for my SignalR hubs and using the SignalR .NET client (version 2.3.0) to carry out the tests, but I am not able to get other connections to receive a broadcast when the current connection is excluded from the broadcast.
The hub method is making a broadcast as such:
Clients.Group(groupName, Context.ConnectionId).sendMessage("A message");
My test is configured to have 2 connections to the hub and each connection will call a hub method that will put the connection into groupName. I then have a HubConnection.On event to handle the broadcast the hub method makes, the result of which is then used to assert the test. This might be clearer explained in code (I have removed some AddBroadcast overloads for brevity):
/// <summary>
/// Class to handle the connection, calling of methods and broadcast for a SignalR hub
/// </summary>
public class HubManager
{
public enum Froms
{
Other1,
Other2
}
private HubConnection hubConnection = null;
private IHubProxy hub = null;
/// <summary>
/// The outcome of the broadcast that is made
/// </summary>
public object Result { get; private set; } = null;
/// <summary>
/// The connection ID of this hub connection
/// </summary>
public string ConnectionID { get { return this.hubConnection.ConnectionId; } }
public HubManager(string h)
{
//Create the SignalR connection
this.hubConnection = new HubConnection("http://mywebsite.com");
//Gain access to the hub
this.hub = hubConnection.CreateHubProxy(h);
//Start the connection up and wait for it to complete
this.hubConnection.Start()
.ContinueWith(task =>
{
if (task.IsFaulted)
{
throw new Exception($"Error opening the hub connection ({h}): {task.Exception.GetBaseException()}");
}
})
.Wait();
}
/// <summary>
/// Broadcast a message to clients
/// </summary>
/// <param name="methodName">The name of broadcast message</param>
public void AddBroadcast(string methodName)
{
this.hub.On(methodName, () => {
this.Result = methodName;
});
}
/// <summary>
/// Broadcast a message to clients
/// </summary>
/// <param name="methodName">The name of broadcast message</param>
public void AddBroadcast<T>(string methodName)
{
this.hub.On<T>(methodName, _1 => {
this.Result = _1;
});
}
/// <summary>
/// Invokes a specific hub method
/// </summary>
/// <param name="methodName">The name of the hub method to invoke</param>
/// <param name="args">The parameters for the method</param>
public void CallMethod(string methodName, params object[] args)
{
this.hub.Invoke(methodName, args)
.ContinueWith(task =>
{
if (task.IsFaulted)
{
throw new Exception($"Error calling hub method {methodName}: {task.Exception.GetBaseException()}");
}
})
.Wait();
}
}
Usage case:
//Create a dictionary to hold the connections
var hubManagers = new Dictionary<HubManager.Froms, HubManager>();
hubManagers.Add(HubManager.Froms.Other1, new HubManager(hubName));
hubManagers.Add(HubManager.Froms.Other2, new HubManager(hubName));
//Call HubMethod1 which will add the connection to the same group
hubManagers[HubManager.Froms.Other1].CallMethod("HubMethod1", user1ID);
hubManagers[HubManager.Froms.Other2].CallMethod("HubMethod1", user2ID);
//Set a broadcast handle for the second connection (Other2)
hubManagers[HubManager.Froms.Other2].AddBroadcast<string, string>("callbackMethod");
//Make a hub method call (from Other1) to cause the callbackMethod to run
//As from above, the Other1 connection should not receive it but Other2 should
hubManagers[HubManager.Froms.Other1].CallMethod("HubMethod2", user1ID);
//Get the broadcast result for the second connection (Other2)
var result = hubManagers[HubManager.Froms.Other2].Result;
//result == null
I have tried using the AdddBroadcast on the following combinations and each case results in result being null: Other1, Other2 and Other1 & Other2.
If I change Clients.Group(groupName, Context.ConnectionId).sendMessage("A message"); to not exclude the current connection (Clients.Group(groupName).sendMessage("A message");) and I use AddBroadcast on Other2, result contains the expected value.
The process works as expected on the live system (using ASP.NET and JavaScript) where the calling connection does not get sent the sendMessage but the other members of the group do.
Any ideas on how I make the second SignalR .NET client connection receive the broadcast when the first connection is excluded from the broadcast are very much welcomed!
After some time, I realised that the this.hub.On event was firing, but after the call to get the result (var result = hubManagers[HubManager.Froms.Other2].Result;). I therefore added a crude wait method (which I am not massively proud of!) and that has done the trick.
New method:
/// <summary>
/// Wait for up to 5 seconds for the broadcast to complete
/// </summary>
public void WaitForBroadcastResult()
{
bool stop = false;
const double WAIT_FOR = 5D;
DateTime started = DateTime.Now;
while (!stop)
{
if (this.Result != null || (DateTime.Now - started).TotalSeconds >= WAIT_FOR)
{
stop = true;
}
else
{
System.Threading.Thread.Sleep(100);
}
}
}
Use:
...
//Set a broadcast handle for the second connection (Other2)
hubManagers[HubManager.Froms.Other2].AddBroadcast<string, string>("callbackMethod");
//Make a hub method call (from Other1) to cause the callbackMethod to run
//As from above, the Other1 connection should not receive it but Other2 should
hubManagers[HubManager.Froms.Other1].CallMethod("HubMethod2", user1ID);
//Wait for the broadcast to complete
this.HubManagers[_HubManager.Froms.Other2].WaitForBroadcastResult();
//Get the broadcast result for the second connection (Other2)
var result = hubManagers[HubManager.Froms.Other2].Result;
If anybody has a better idea, then I'm all ears!

Why might similar .NET Core services behave differently?

I have two .net core 2.2 services, InitialisationHandler and WorkingTicketHandler, one holds initial information and the other is intended to hold a working ticket.
The InitialisationHandler always has its information ready when asked, but the WorkingTicketHandler always returns a null record.
I need to figure out how to have WorkingTicketHandler hold on to its data just like InitialisationHandler does.
Both are added to as transient in ConfigureServices, and created as identically as possible.
There is one key difference, the InitialisationHandler has its data created during startup by its constructor method and is only ever read from thereafter, whereas the WorkingTicketHandler is written to by a controller and re read in the same controller (Get vs Post).
using VTModels.Other;
namespace VT_Online22.Infrastructure.Services
{
/// <summary>
/// Interface for the WorkingTicket Service
/// </summary>
public interface IWorkingTicketHandler
{
WorkingTicket GetWorkingTicket();
void SaveWorkingTicket(WorkingTicket argWorkingTicket);
void ClearWorkingTicket();
}
/// <summary>
/// WorkingTicket service class definition
/// </summary>
public class WorkingTicketHandler : IWorkingTicketHandler
{
public WorkingTicketHandler()
{
this.WorkingTicket = new WorkingTicket();
}
public WorkingTicket GetWorkingTicket()
{
return this.WorkingTicket;
}
public void SaveWorkingTicket(WorkingTicket arg_WorkingTicket)
{
this.WorkingTicket = arg_WorkingTicket;
}
public void ClearWorkingTicket()
{
this.WorkingTicket = new WorkingTicket();
}
private WorkingTicket WorkingTicket;
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(typeof(WorkingTicketHandler));
}
}
using Microsoft.Extensions.Configuration;
using SharedModels.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
using VT_OnlineCore.Infrastructure.Other;
using VTModels.Models;
namespace VT_Online22.Infrastructure.Services
{
/// <summary>
/// Interface for the Initialization Service
/// </summary>
public interface IInitializationHandler
{
void Initialize();
CConfigMasterBase GetConfig();
IEnumerable<CScopeMasterBase> GetScopes(string clientId);
}
/// <summary>
/// Initialization service class definition
/// </summary>
public class InitializationHandler : IInitializationHandler
{
public InitializationHandler(IConfiguration configTech)
{
ConfigurationTech = configTech;
Initialize();
}
public void Initialize()
{
this.ldh = new LocalDataHandler(logger, this.ConfigurationTech);
this.config = new CConfigMasterBase();
LoadConfig();
}
public IEnumerable<CScopeMasterBase> GetScopes(string clientId)
{
IEnumerable<CScopeMasterBase> scopes = new List<CScopeMasterBase>();
scopes = this.ldh.GetScopes(clientId);
return scopes;
}
public CConfigMasterBase GetConfig()
{
return this.config;
}
private async Task LoadConfig()
{
this.config = this.ldh.GetConfig();
}
private LocalDataHandler ldh;
private CConfigMasterBase config;
private IEnumerable<CScopeMasterBase> scopes;
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(typeof(InitializationHandler));
public IConfiguration ConfigurationTech { get; private set; }
}
}
services.AddTransient<IInitializationHandler,InitializationHandler>();
services.AddTransient<IWorkingTicketHandler, WorkingTicketHandler>();

WebAPI + OWIN + SignalR + Autofac

I have been struggling on this issue for weeks now.
I have an app where i have configured owin backend with web api and autofac DI with background handfire jobs. I have alsmost looked at every question on Stackoveflow regarding this but nothing seems to work.
My app regarding OWIN/Hangfire/WebAPI all seems to work okay. Until it comes to SignalR push messages.
If i call any notification hub endpoint from js client push messages go okay and i can receive push messages on any other connected client. But when i wan to send message from my api controller or hangfire job it never reaches to any client.
Startup.cs
public void Configuration(IAppBuilder app)
{
//var signalRHelper = new SignalRHelper(GlobalHost.ConnectionManager.GetHubContext<NotificationHub>());
var constants = new Constants();
constants.Set(ConstantTypes.AllyHrNoReplyEmailAddress, Util.Constants.AllyHrNoReplyEmailAddress);
constants.Set(ConstantTypes.SendGridKey, Util.Constants.SendGridKey);
constants.Set(ConstantTypes.EncryptionKey, Util.Constants.EncryptionKey);
constants.Set(ConstantTypes.ApiUrl, Util.Constants.ApiUrl);
constants.Set(ConstantTypes.RootFolder, Util.Constants.RootFolder);
constants.Set(ConstantTypes.FrontEndUrl, Util.Constants.FrontEndUrl);
GlobalConfiguration.Configuration
.UseSqlServerStorage("AllyHrDb");
var config = System.Web.Http.GlobalConfiguration.Configuration;
var builder = new ContainerBuilder();
var jobBuilder = new ContainerBuilder();
var signalRBuilder = new ContainerBuilder();
var hubConfig = new HubConfiguration();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.Register(x => constants);
builder.RegisterModule(new ServiceModule());
jobBuilder.Register(x => constants);
jobBuilder.RegisterModule(new HangfireServiceModule());
signalRBuilder.RegisterModule(new SignalRServiceModule());
signalRBuilder.Register(x => constants);
signalRBuilder.RegisterType<AutofacDependencyResolver>().As<IDependencyResolver>().SingleInstance();
signalRBuilder.RegisterType<ConnectionManager>().As<IConnectionManager>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<NotificationHub>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<SignalRHelper>().PropertiesAutowired().ExternallyOwned().SingleInstance();
signalRBuilder.Register(context => context.Resolve<IDependencyResolver>().Resolve<IConnectionManager>().GetHubContext<NotificationHub, INotificationHub>()).ExternallyOwned().SingleInstance();
var hubContainer = signalRBuilder.Build();
builder.RegisterInstance(hubContainer.Resolve<IConnectionManager>());
builder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
builder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
builder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
jobBuilder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
jobBuilder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
jobBuilder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
var container = builder.Build();
var jobContainer = jobBuilder.Build();
var idProvider = new SignalRCustomUserIdProvider();
hubConfig.Resolver = new AutofacDependencyResolver(hubContainer);
hubConfig.Resolver.Register(typeof(IUserIdProvider), () => idProvider);
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider()
});
map.RunSignalR(hubConfig);
});
GlobalConfiguration.Configuration.UseAutofacActivator(jobContainer);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseHangfireServer();
app.UseHangfireDashboard();
ConfigureAuth(app);
app.UseWebApi(config);
}
I had to use different container because i have db set to InstancePerRequest scope.
All my services are being resolved in notification hub class, no problems there. The only issues is when i try and send message from hangfire service or even from api controller using hub context it never reaches to any client.
NotificationHub.cs
public interface INotificationHub
{
/// <summary>
///
/// </summary>
void pushNotification(string message);
/// <summary>
///
/// </summary>
/// <param name="model"></param>
void getNotification(object model);
void getMessage(object model);
}
/// <summary>
/// Notification Hub
/// </summary>
[HubName("NotificationHub")]
[Authorize]
public class NotificationHub : Hub<INotificationHub>
{
/// <summary>
///
/// </summary>
public static IHubContext<INotificationHub> GlobalContext { get; private set; }
private readonly IChatMessagingService _chatMessagingService;
private readonly IUserService _userService;
private Guid LoggedInUserId
{
get
{
var claims = ((ClaimsIdentity)Context.User.Identity).Claims.ToArray();
var userIdClaim = claims.FirstOrDefault(x => x.Type.Equals("UserId"));
if (userIdClaim == null) return Guid.Empty;
return Guid.Parse(userIdClaim.Value);
}
}
/// <summary>
/// Consructor
/// </summary>
/// <param name="lifetimeScope"></param>
/// <param name="context"></param>
public NotificationHub(ILifetimeScope lifetimeScope, IHubContext<INotificationHub> context)
{
GlobalContext = context;
try
{
var childScope = lifetimeScope.BeginLifetimeScope();
_chatMessagingService = childScope.Resolve<IChatMessagingService>();
_userService = childScope.Resolve<IUserService>();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <summary>
/// Notifications
/// </summary>
public void Notifications()
{
Clients.All.pushNotification("AllyHr" + LoggedInUserId);
}
/// <summary>
/// Send Message
/// </summary>
/// <param name="model"></param>
public void SendMessage(SendChatMessageBindingModel model)
{
var chatMessage = _chatMessagingService.SendMessageToGroup(LoggedInUserId, model.GroupId, model.Message);
var recipientIds = _chatMessagingService.GetChatMembersByGroupId(LoggedInUserId, model.GroupId);
var stringUserIds = new List<string>();
var chatGroup = _chatMessagingService.GetChatGroupById(model.GroupId);
foreach (var recipientId in recipientIds)
{
stringUserIds.Add(recipientId.ToString());
}
Clients.Users(stringUserIds).getNotification(new
{
message = "A new Message is Recieved in Chat Group: " + chatGroup.Name,
groupId = chatGroup.Id
});
var chatMessageVm = chatMessage.Map<ChatMessage, ChatMessageViewModel>();
chatMessageVm.Sender = _userService.Get(chatMessageVm.SenderId).Map<User, UserViewModel>();
stringUserIds.Add(LoggedInUserId.ToString());
Clients.Users(stringUserIds).getMessage(chatMessageVm);
}
}
signalRhelper.cs use to call from api or from Hangfire services
public class SignalRHelper
{
public IConnectionManager ConnectionManager { get; set; }
public IHubContext<INotificationHub> HubContext { get; set; }
/// <summary>
/// Send Notifications to Users
/// </summary>
/// <param name="message"></param>
/// <param name="userIds"></param>
public void GetNotification(object message, IList<string> userIds)
{
HubContext.Clients.Users(userIds).getNotification(message);
}
/// <summary>
/// Get LoggedInUser Id for SignalR
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public static Guid GetLoggedInUserId(IPrincipal user)
{
var claim = GetLoggedinUserClaim(user);
if (claim == null) return Guid.Empty;
return Guid.Parse(claim.Value);
}
private static Claim GetLoggedinUserClaim(IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).Claims.ToArray();
return claim.FirstOrDefault(x => x.Type.Equals("UserId"));
}
}
Could this be related to Autofac creating a new lifetimescope for your call, but you were expecting to continue using the existing scope? Maybe check your autofac registrations for singleinstance / instanceperlifetimescope
Just saying, but have you registered any static classes? They can keep your scope alive for far too long.
I see you're using multiple containerbuilders - that's not something we do over here, we have one 'massive' containerbuilder for each app. I'm curious why you're doing that? To satisfy my curiosity, could you try using a single containerbuilder and registering everything on that single builder? (Although it looks like this is a pattern for SignalR and autofac)
The documentation says: " a common error in OWIN integration is the use of GlobalHost."
It looks like you're doing exactly that.

How to implement an event if I have event in interface, also how I can use it in dependency Injection

I have created a separate class to handle mail sending. To make it hassle free from future changes I have used dependency injection to design it. Now, when I try to use SendMessageAsync() method, I want to check SendMailCompleted event to see status of message sent failed/succeeded. I am confused how I'm supposed to implement the event in the class that is implementing it. Without mentioning this event in the interface I won't be able to catch it in the injection class. Can any one suggest how to solve this? My interface looks like as follows
public interface IMailing
{
string Host { get; set; }
int Port { get; set; }
Task SendMailAsync(string toAddress, string subject, string body);
event SendCompletedEventHandler OnEmailSendComplete;
}
The class that is implementing the interface is as follows -
public class Mailing : IMailing
{
private SmtpClient client = new SmtpClient();
MailMessage mm = null;
public string Host{ get; set; }
public int Port { get; set; }
// do i need this? without event being in the interface I would have had this //two following lines to manually raise the event
public event SendCompletedEventHandler OnEmailSendComplete;
public delegate void SendCompletedEventHandler(object source, AsyncCompletedEventArgs e);
// following lines were generated from the vs2017 IDE
// how do I use it when actual Send mail completed event fires?
event System.Net.Mail.SendCompletedEventHandler IMailing.OnEmailSendComplete
{
add
{
throw new NotImplementedException();
}
remove
{
throw new NotImplementedException();
}
}
public async Task SendMailAsync(string toAddress, string subject, string body)
{
mm = new MailMessage(User, toAddress, subject, body);
client.SendCompleted += Client_SendCompleted;
await client.SendMailAsync(mm).ConfigureAwait(false);
}
private void Client_SendCompleted(object sender, AsyncCompletedEventArgs e)
{
OnEmailSendComplete?.Invoke(sender, e);
}
}
Now, the injection class uses constructor injection, which looks like follows -
public class MailingInjection
{
IMailing mailing = null;
private MailingInjection()
{ }
public MailingInjection(IMailing imail)
{
mailing = imail;
}
public async Task SenMailAsync(string to, string subject, string body)
{
mailing.OnEmailSendComplete += EmailSendCompleted;
await mailing.SendMailAsync(to, subject,body).ConfigureAwait(false);
}
private void EmailSendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
mailing.OnEmailSendComplete -= EmailSendCompleted;
}
}
I tried to put as less code as possible to explain my confusion so this code will not work in real scenarios but has the structure I believe. Please let me know if I have over exaggerated it. I appreciate any help.
Your code is conflating two different asynchronous patterns: the older event-based asynchronous pattern and the new task-based asynchronous pattern. In particular, SmtpClient.SendCompleted is only used with SmtpClient.SendAsync. Your code isn't using SendAsync, so it doesn't need SendCompleted. It can just use SmtpClient.SendMailAsync instead:
public interface IMailing
{
string Host { get; set; }
int Port { get; set; }
Task SendMailAsync(string toAddress, string subject, string body);
// No event necessary.
}
public class Mailing : IMailing
{
private SmtpClient client = new SmtpClient();
MailMessage mm = null;
public string Host{ get; set; }
public int Port { get; set; }
public async Task SendMailAsync(string toAddress, string subject, string body)
{
mm = new MailMessage(User, toAddress, subject, body);
await client.SendMailAsync(mm).ConfigureAwait(false);
}
}
Usage:
public class MailingInjection
{
...
public async Task SenMailAsync(string to, string subject, string body)
{
await mailing.SendMailAsync(to, subject,body).ConfigureAwait(false);
}
}
As a final reminder, if your abstraction has only one implementation, it's not an abstraction. ;)

Categories