Order process using SignalR asp.net core best practice - c#

Below is the order process which i have implemented thorough signalR in asp.net core web api. Every thing is working fine except one scenario(Problem scenario given below) i need the best possible solution.
System Overview:
1) Customer Place new order (Client wait until order being processed by admin client).
2) The order is saved to the DB with ‘status=unknown ‘.
3) Admin is notified through hub about new order. (on a dashboard)
4) Admin accepts or decline new order then Order status is updated in database.
5) Customer is notified about the order, if is accepted or declined through SignalR
Problem scenario
The business rule that we have to implement is that the order should be automatically declined after 2 minutes if the Admin does not respond. In this case the server should automatically decline the order and the customer should be notified.
Solution 1: We thought of adding a timer on the Customer and Admin side, but we prefer the Timer to be somewhere on the server so we don't have to implement the timers on the customer and admin side.
Base Hub Controller
public abstract class ApiHubController<T> : Controller
where T : Hub
{
private readonly IHubContext _hub;
public IHubConnectionContext<dynamic> Clients { get; private set; }
public IGroupManager Groups { get; private set; }
protected ApiHubController(IConnectionManager signalRConnectionManager)
{
var _hub = signalRConnectionManager.GetHubContext<T>();
Clients = _hub.Clients;
Groups = _hub.Groups;
}
}
public class BaseHubController : ApiHubController<Broadcaster>
{
public BaseHubController(IConnectionManager signalRConnectionManager) : base(signalRConnectionManager)
{
}
}
Server side code (Place Order)
public class OrderController : BaseHubController
{
public async Task SendNotification([FromBody]NotificationDTO notify)
{
await Clients.Group(notify.AdminId.ToString()).SendNotificationToDashboard(notify); //notifing to admin for about //new order
}
public async Task NotifyDashboard(NotificationDTO model)
{
var sendNotification = SendNotification(model);//sending notification to admin dashboard
}
[HttpPost]
[Route("PlaceOrder")]
public IActionResult PlaceOrder([FromBody]OrderDTO order)//Coustomer place order
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var orderCode = _orderProvider.PlaceOrder(order, ValidationContainer);//save new order in database
var notify = order.GetNotificationModel();
notify.OrderId = orderCode;
NotifyDashboard(notify);
//Other code
return new OkObjectResult(new { OrderCode = orderCodeString, OrderId = orderCode });
}
}

Related

Store implementation of generic interface in list

I'm trying to create a service which manages different account providers and accounts within our application (WPF desktop app).
My idea was to have account providers, which would be external web services such as Jira, Gitlab, etc. Each provider has a list with accounts.
Depending on the provider it can have different means to authorize our software to use a users account. Our first implementation is using OAuth tokens for authorization and a Rest API to interact with a web service.
Obviously, we need some restrictions e.g. an OAuthProvider will only accept OAuthAccounts in its Accounts list.
The ideal case would be if we could store all providers no matter their accounts type in a single list of the form List<IAccountProvider<IAccount>> someList. However I couldn't get it to compile. The error is:
Error CS1503 Argument 1: cannot convert from 'TestProject.Program.JiraProvider' to 'TestProject.Program.IAccountProvider<TestProject.Program.IAccount>'
I tried to make IAccountProvider covariant but this in turn restricts me very much in using the interface...
Is there some way to get it to compile using this class hierarchy? If you have a better idea how to setup the hierarchy then I'd be glad to hear your suggestions :).
static int Main(string[] args)
{
var jiraProv = new JiraProvider();
//var someList = new List<IAccountProvider<OAuthAccount>>(); // this would work - but it's not what I want...
var someList = new List<IAccountProvider<IAccount>>(); // this doesn't seem to work
someList.Add(jiraProv); // error CS1503 cannot convert from X to Y
return 0;
}
//-----------------
// Interfaces - details left out for brevity...
// Providers
public interface IAccountProvider<T>
where T : IAccount
{
List<T> Accounts { get; }
T CurrentAccount { get; }
void Login(T account);
}
public interface IOAuthProvider<T> : IAccountProvider<T>
where T : IOAuthAccount
{ }
// Accounts
public interface IAccount { }
public interface IOAuthAccount : IAccount { }
//-----------------
// Implementations - details left out for brevity...
public class OAuthAccount : IOAuthAccount { }
public class JiraProvider : IOAuthProvider<OAuthAccount>
{
public List<OAuthAccount> Accounts { get; set; }
public OAuthAccount CurrentAccount { get; set; }
public void Login(OAuthAccount account) { }
}

Register same object multiple times with different configuration

I have this api client ICommunicationClient(url, tenant) registered in my IoC container. Now I'm facing the scenario where I can have 1 to n api clients. I need to register all of them and I'm not sure how to handle that. I've seen there's this RegisterCollection in SI though.
I'm considering use a ICommunicationClientProvider as a wrapper around the actual clients. It contains a list with all the registered clients and methods to retrieve them. I feel this is not the best approach and of course, it "forces" me to touch other pieces of the app.
public class CommunicationClientProvider : ICommunicationClientProvider
{
public CommunicationClientCollection CommunicationClientsCollection { get; set; }
public string Tenant { get; set; }
public ICommunicationClient GetClients()
{
return CommunicationClientsCollection[Tenant];
}
public void SetClients(CommunicationClientCollection clients)
{
CommunicationClientsCollection = clients;
}
}
public interface ICommunicationClientProvider
{
ICommunicationClient GetClients();
void SetClients(CommunicationClientCollection clients);
}
This to host the collection
public class CommunicationClientCollection : Dictionary<string, ICommunicationClient>
{
}
Here I register the collection against SI
var clients = new CommunicationClientProvider();
foreach (var supportedTenant in supportedTenants)
{
clients.CommunicationClientsCollection
.Add(supportedTenant, new CommunicationClient(
new Uri(configuration.AppSettings["communication_api." + supportedTenant]),
new TenantClientConfiguration(supportedTenant)));
}
container.RegisterSingleton<ICommunicationClientProvider>(clients);
Do you know a better way of doing this? This is a normal scenario for example when you have multiple databases.
UPDATE: - ITenantContext part -
This is basically how my tenant context interface looks like:
public interface ITenantContext
{
string Tenant { get; set; }
}
and this is where I'm making my call to communication api:
public class MoveRequestedHandler : IHandlerAsync<MoveRequested>
{
private readonly IJctConfigurationService _communicationClient;
private readonly ITenantContext _tenantContext;
public MoveRequestedHandler(IJctConfigurationService communicationClient, ITenantContext tenantContext)
{
_communicationClient = communicationClient;
_tenantContext = tenantContext;
}
public async Task<bool> Handle(MoveRequested message)
{
_tenantContext.Tenant = message.Tenant;
_communicationClient.ChangeApn(message.Imei, true);
return await Task.FromResult(true);
}
}
here I register the ITenantContext
container.RegisterSingleton<ITenantContext, TenantContext>();
The tenant is defined within the MoveRequested object (message.Tenant).
How can I make CommunicationClient aware of that tenant?
If adding an ICommunicationClientProvider abstraction causes you to make sweeping changes throughout your application, there is clearly something wrong. You should typically be able to add features and make changes without having to do sweeping changes. And as a matter of fact, I think your current design already allows this.
Your ICommunicationClientProvider) acts like a factory, and factories are hardly ever the right solution. Instead, your are much better of using the Composite design pattern. For instance:
sealed class TenantCommunicationClientComposite : ICommunicationClient
{
private readonly ITenantContext tenantContext;
private readonly Dictionary<string, ICommunicationClient> clients;
public TenantCommunicationClientComposite(ITenantContext tenantContext,
Dictionary<string, ICommunicationClient> clients) {
this.tenantContext = tenantContext;
this.clients = clients;
}
object ICommunicationClient.ClientMethod(object parameter) =>
this.clients[this.tenantContext.CurrentTenantName].ClientMethod(parameter);
}
You can register this class as follows:
var dictionary = new Dictionary<string, ICommunicationClient>();
foreach (var supportedTenant in supportedTenants) {
dictionary.Add(supportedTenant, new CommunicationClient(
new Uri(configuration.AppSettings["communication_api." + supportedTenant]),
new TenantClientConfiguration(supportedTenant)));
}
container.RegisterSingleton<ICommunicationClient>(
new TenantCommunicationClientComposite(
new AspNetTenantContext(),
dictionary));
Here the ITenantContext is an abstraction that allows you to get the current tenant on who's behalf the current request is running. The AspNetTenantContext is an implementation that allows you to retrieve the current tenant in an ASP.NET application. You probably already have some code to detect the current tenant; you might need to move that code to such AspNetTenantContext class.

Locking custom list of users in SignalR Hub class

I have a SignalR Hub subclass, where I keep a static list of users that joined the hub. Is there a need for locking that list in methods where it is used, and what is a proper way to do this - is this a proper way to do this?
public class MessageHub : Hub
{
private static List<HubUser> users = new List<HubUser>();
public void Join(string UserName)
{
lock(users){/*some code to validate and store user*/}
}
public List<HubUser> GetOnlineUsers()
{
lock (users)
{
return users.ToList<HubUser>();
}
}
//other methods such as GetUsersCount(), IsUserOnline(string User)....
}

SignalR C# MVC Mapping Anonymous User to Client ID

Question: How do I manage anonymous users so that multiple tabs in a single browser are all updated when the Hub sends out a response?
The scenario is as follows:
I would like to integrate SignalR into a project so that anonymous users can live chat with operators. Obviously user's that have authenticated with iIdentity are mapped via the Client.User(username) command. But currently say an anonymous user is browsing site.com/tools and site.com/notTools I can not send messages to all tabs with only a connectionID. Only one tab gathers the response.
I have tried using IWC patch but that tool doesn't account for saving chat information into a database and I think passing variables via ajax isn't a secure way to read/write to a database.
I have also looked in the following: Managing SignalR connections for Anonymous user
However creating a base such as that and using sessions seems less secure than Owin.
I had the idea to use client.user() by creating a false account each time a user connects and delete it when they disconnect. But I would rather not fill my aspnetusers db context with garbage accounts it seems unnecessary.
Is it possible to use UserHandler.ConnectedIds.Add(Context.ConnectionId); to also map a fake username? Im at a loss.
Would it make sense to use iUserId provider?
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
Then create a database that stores IP addresses to connectionIDs and single usernames?
database:
Users: With FK to connection IDs
|userid|username|IP |connectionIDs|
| 1 | user1 |127.0.0.1| 1 |
| 2 | user2 |127.0.0.2| 2 |
connectionIDs:
|connectionID|SignalRID|connectionIDs|
| 1 | xx.x.x.x| 1 |
| 2 | xx.xxx.x| 2 |
| 3 | xx.xxxxx| 2 |
| 4 | xxxxx.xx| 2 |
Then Possibly write logic around the connection?
public override Task OnConnected()
{
if(Context.Identity.UserName != null){
//add a connection based on the currently logged in user.
}
else{
///save new user to database?
}
}
but the question still remains how would I handle multiple tabs with that when sending a command on the websocket?
update
To be clear, my intent is to create a live chat/support tool that allows for in browser anonymous access at all times.
The client wants something similar to http://www.livechatinc.com
I have already created a javascript plugin that sends and receives from the hub, regardless of what domain it is on. (my client has multisites) the missing piece to the puzzle is the simplest, managing the anonymous users to allow for multi-tabbed conversations.
I'm not following the false user account idea (don't know if it works), but will develop an alternative.
The goal could be achieved through a ChatSessionId cookie shared by all browser tabs and creating Groups named as this ChatSessionId.
I took basic chat tutorial from asp.net/signalr and added functionality to allow chat from multiple tabs as the same user.
1) Assign a Chat Session Id in "Chat" action to identify the user, as we don't have user credential:
public ActionResult Chat()
{
ChatSessionHelper.SetChatSessionCookie();
return View();
}
2) Subscribe to chat session when enter the chat page
client side
$.connection.hub.start()
.then(function(){chat.server.joinChatSession();})
.done(function() {
...
server side (hub)
public Task JoinChatSession()
{
//get user SessionId to allow use multiple tabs
var sessionId = ChatSessionHelper.GetChatSessionId();
if (string.IsNullOrEmpty(sessionId)) throw new InvalidOperationException("No chat session id");
return Groups.Add(Context.ConnectionId, sessionId);
}
3) broadcast messages to user's chat session
public void Send(string message)
{
//get user chat session id
var sessionId = ChatSessionHelper.GetChatSessionId();
if (string.IsNullOrEmpty(sessionId)) throw new InvalidOperationException("No chat session id");
//now message will appear in all tabs
Clients.Group(sessionId).addNewMessageToPage(message);
}
Finally, the (simple) ChatSessionHelper class
public static class ChatSessionHelper
{
public static void SetChatSessionCookie()
{
var context = HttpContext.Current;
HttpCookie cookie = context.Request.Cookies.Get("Session") ?? new HttpCookie("Session", GenerateChatSessionId());
context.Response.Cookies.Add(cookie);
}
public static string GetChatSessionId()
{
return HttpContext.Current.Request.Cookies.Get("Session")?.Value;
}
public static string GenerateChatSessionId()
{
return Guid.NewGuid().ToString();
}
}
a solution widely adopted is to make the user register with his some kind of id with connection back , on the onConnected.
public override Task OnConnected()
{
Clients.Caller.Register();
return base.OnConnected();
}
and than the user returns with a call with some kind of your own id logic
from the clients Register Method
public void Register(Guid userId)
{
s_ConnectionCache.Add(userId, Guid.Parse(Context.ConnectionId));
}
and you keep the user ids in a static dictionary ( take care of the locks since you need it to be thread safe;
static readonly IConnectionCache s_ConnectionCache = new ConnectionsCache();
here
public class ConnectionsCache :IConnectionCache
{
private readonly Dictionary<Guid, UserConnections> m_UserConnections = new Dictionary<Guid, UserConnections>();
private readonly Dictionary<Guid,Guid> m_ConnectionsToUsersMapping = new Dictionary<Guid, Guid>();
readonly object m_UserLock = new object();
readonly object m_ConnectionLock = new object();
#region Public
public UserConnections this[Guid index]
=>
m_UserConnections.ContainsKey(index)
?m_UserConnections[index]:new UserConnections();
public void Add(Guid userId, Guid connectionId)
{
lock (m_UserLock)
{
if (m_UserConnections.ContainsKey(userId))
{
if (!m_UserConnections[userId].Contains(connectionId))
{
m_UserConnections[userId].Add(connectionId);
}
}
else
{
m_UserConnections.Add(userId, new UserConnections() {connectionId});
}
}
lock (m_ConnectionLock)
{
if (m_ConnectionsToUsersMapping.ContainsKey(connectionId))
{
m_ConnectionsToUsersMapping[connectionId] = userId;
}
else
{
m_ConnectionsToUsersMapping.Add(connectionId, userId);
}
}
}
public void Remove(Guid connectionId)
{
lock (m_ConnectionLock)
{
if (!m_ConnectionsToUsersMapping.ContainsKey(connectionId))
{
return;
}
var userId = m_ConnectionsToUsersMapping[connectionId];
m_ConnectionsToUsersMapping.Remove(connectionId);
m_UserConnections[userId].Remove(connectionId);
}
}
a sample call to Register form an android app
mChatHub.invoke("Register", PrefUtils.MY_USER_ID).get();
for JS it would be kind of the same
chat.client.register = function () {
chat.server.register(SOME_USER_ID);
}

IoC container implementation in controller

I want to use the IoC container in a method to check a logged in users company code when they submit a payment. I have two certificates in my settings class and an IF else statement to differentiate between each one.
public static string FDGCreditCardUserID
{
get
{
if (BillingController.currentcompanycode == 5)
return ConfigurationManager.AppSettings["5FDGCreditCardUserID"];
else
return ConfigurationManager.AppSettings["6FDGCreditCardUserID"];
}
}
public static string FDGCreditCardPassword
{
get
{
if (BillingController.currentcompanycode == 5)
return ConfigurationManager.AppSettings["5FDGCreditCardPassword"];
else
return ConfigurationManager.AppSettings["6FDGCreditCardPassword"];
}
}
Then in my IoC container
x.For<IFDGService>().Use<FDGService>().SetProperty(s =>
{
s.Url = Settings.FDGURL;
s.UserID = Settings.FDGCreditCardUserID;
s.Password = Settings.FDGCreditCardPassword;
s.Certificate = Settings.FDGCreditCardCertFilePath;
});
I have an FDGService that checks credentials but does not return to the IoC on payment submit to check the company code and apply the correct certificate.
SubmitPayment Method where the creditcard control contains the correct company code when i run it.
How do i get my application to select the correct certificate based on the updated company code. Seeing as users can have different company codes based on policies selected for payment. One company code at the moment can either be 5 or 6.
public ActionResult SubmitPayment([ConvertJSON]List<PayModel> payments)
{
List<TransactionModel> transactions = new List<TransactionModel>();
foreach (var pymt in payments)
{
var policyNumber = pymt.PolicyNumber.Trim();
TransactionModel trans = new TransactionModel() { Payment = pymt };
if (pymt.Selected)
{
var creditCardControl = UpdateCreditCardControl(policyNumber);
If you are using StructureMap it uses "Greedy Initialization", meaning when the constructor is called it will call the constructor with the most amount of arguments or parameters passed in.
private IFDGService service;
public MyController(IFDGService service)
{
this.service = service;
}
Then service will be available after IoC.Configure() is called.
Call IoC.Configure() whereever the application is started. google "where does Mvc start" or something like that.
to change the company code set it somewhere other than an instance variable in the controller, like a static class, I know static is bad, get it working and then make it better, since that would be complex to modify, and then get; set; when you need to.
I have to go to meeting, kinda rushed, hope that helps

Categories

Resources