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();
}
Related
I used the ABP vNext framework.I need to publish a domain event when saved an aggregated specified property to the database successfully,but now, the domain event is always triggered before the unit of work is completed.
Code
public class Meeting : AggregateRoot<Guid>{
public Meeting(Guid id, Guid applicantId) : base(id) {
ApplicantId = applicantId;
Phase = MeetingPhase.Draft;
Type = MeetingType.Default;
TimeAndPlaces = new Collection<TimeAndPlace>();
InsideAttendees = new Collection<InsideAttendee>();
MeetingFiles = new Collection<MeetingFile>();
}
/// <summary>
/// 取消会议
/// </summary>
public void Cancel() {
Phase = MeetingPhase.Draft;
Type = MeetingType.Canceled;
var cancelData = new MeetingCancelData(this);
AddLocalEvent(cancelData);
}
}
You can contribute to IUnitOfWork.CompletedHandlers handlers.
See https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs#L194
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
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>();
I have .Net Core Web API application. There is some Get method in controller and injected IRepositoryProviderFactory. Nothing special.
[ApiController]
public class DataController : ControllerBase
{
private readonly ILogger<DataController> _logger;
private readonly IRepositoryProviderFactory _repositoryProviderFactory;
#region ctor
/// <summary>
/// ctor
/// </summary>
public DataController(ILogger<DataController> logger, IRepositoryProviderFactory repositoryProviderFactory)
{
_logger = logger;
_repositoryProviderFactory = repositoryProviderFactory;
}
[HttpPost]
public async Task<IActionResult> GetData([FromBody] SearchModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
var data = await _repositoryProviderFactory.GetDataAsync(model);
return Ok(data);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
There are repositories based on the same Interface to get data from different data sources.
public Repository1: IDataRepository {}
public Repository2: IDataRepository {}
public Repository3: IDataRepository {}
I'm using DI so all parts are registered in services as Scoped or Transients. Some repositories are using EntityFramework.
services.AddScoped<IDataRepository, Repository1>();
services.AddScoped<IDataRepository, Repository2>();
services.AddScoped<IDataRepository, Repository3>();
Ok, now I need to implement RepositoryProviderFactory to return repository. But there is one required functionality: it must return for every call different repository.
I have injected IEnumerable and I need to return Repository1, Repository2, Repository3 and again Repository1, … etc. So all repositores are used the same time.
/// <summary>
/// ctor
/// </summary>
public RepositoryProviderFactory(
ILogger<RepositoryProviderFactory> logger,
IEnumerable<IDataRepository> dataRepositories)
{
_logger = logger;
_dataRepositories = dataRepositories;
}
public IDataRepository GetRepository()
{
// TODO: Logic to cycle repositories
var instance = dataRepositories.Where();
return instance;
}
How to do this?
Factory can't be registered as Singleton, because repositories have another dependencies that have Scoped Lifetime (DbContext etc.)
How can I create some thread safe singleton object to be able persists last used repository and serve another one, for another call?
Well, I did it this way.
I made class GlobalAppData and registered is as Singleton.
services.AddSingleton<GlobalAppData>();
Then I made simple implementation (not finished yet).
public class GlobalAppData
{
private IDataRepository[] _availableRepositories;
private IDataRepository_lastUsedRepository;
/// <summary>
/// ctor
/// </summary>
public GlobalAppData()
{
_availableRepositories = new IDataRepository[0];
}
public void TryRegisterRepositories(IEnumerable<IDataRepository> repositories)
{
if (_availableRepositories.Length > 0)
{
return;
}
_availableRepositories = repositories.ToArray();
_lastUsedRepository = null;
}
public IDataRepository GetNextRepository()
{
if (_lastUsedRepository == null)
{
_lastUsedRepository = _availableRepositories[0];
return _lastUsedRepository;
}
var lastUsedIndex = _availableRepositories.IndexOf(_lastUsedRepository);
if (lastUsedIndex < _availableRepositories.Length - 1)
{
lastUsedIndex++;
}
else
{
lastUsedIndex = 0;
}
_lastUsedRepository = _availableRepositories[lastUsedIndex];
return _lastUsedRepository;
}
}
Then because of DI there will be stored original instances, not injected ones, I made just simple compare in factory.
var instanceData = _globalAppData.GetNextRepository();
var instance = _repositories.SingleOrDefault(r => r.GetType() == instanceData.GetType());
Not perfect, but it works as a start.
I am building a small project using MvvMCross within a Xamarin PCL project and having issue with an async Task that I am calling within a command that is bound to a button.
I have a fake web service where-in I simply call Task.Delay(3000). When the process gets to this point it simply sits and does nothing.
I originally had the command call using the .wait() call but read somewhere that this was a blocking call and cant be miced with "async / wait"
Could someone help and possible give me a hint as to where I am going wrong on the command binding please ?
https://bitbucket.org/johncogan/exevaxamarinapp is the public git repo, the specific command is
public ICommand SaveProfile
within the ProfileViewModel.cs file.
The specific code is:
public ICommand SaveProfile
{
get
{
return new MvxCommand(() =>
{
if (_profile.IsValidData())
{
// Wait for task to compelte, do UI updates here
// TODO Throbber / Spinner
EnumWebServiceResult taskResult;
Mvx.Resolve<IProfileWebService>().SendProfileToServer(_profile).Wait();
if(_profileWebService.getLastResponseResult() == true){
taskResult = EnumWebServiceResult.SUCCESS;
}else{
taskResult = EnumWebServiceResult.FAILED_UNKNOWN;
}
//_profileWebService.SendProfileToServer(_profile).Wait();
// Close(this);
}
});
}
}
The web service class () is:
using System;
using System.Threading.Tasks;
using ExevaXamarinApp.Models;
namespace ExevaXamarinApp.Services
{
public class FakeProfileWebService : IProfileWebService
{
public int _delayPeriod { get; private set; }
public bool? lastResult;
/// <summary>
/// Initializes a new instance of the <see cref="T:ExevaXamarinApp.Enumerations.FakeProfileWebService"/> class.
/// </summary>
/// 3 second delay to simulate a remote request
public FakeProfileWebService()
{
_delayPeriod = 3000;
lastResult = null;
}
private Task Sleep()
{
return Task.Delay(3000);
}
public bool? getLastResponseResult(){
return lastResult;
}
/// <summary>
/// Sends the profile to server asynchronously
/// </summary>
/// <returns>EnumWebServiceResultFlag value</returns>
/// <param name="profileObject">Profile model object</param>
public async Task SendProfileToServer(Profile profileObject)
{
// Validate arguments before attempting to use web serivce
if (profileObject.IsValidData())
{
// TODO: Return ENUM FLAG that represents the state of the result
await Sleep();
lastResult = true;
}else{
lastResult = false;
}
}
}
}
Please try this:
public ICommand SaveProfile
{
get
{
return new MvxCommand(async () => // async added
{
if (_profile.IsValidData())
{
// Wait for task to compelte, do UI updates here
// TODO Throbber / Spinner
EnumWebServiceResult taskResult;
await Mvx.Resolve<IProfileWebService>().SendProfileToServer(_profile).ConfigureAwait(false); // await, confi.. added
if(_profileWebService.getLastResponseResult() == true){
taskResult = EnumWebServiceResult.SUCCESS;
}else{
taskResult = EnumWebServiceResult.FAILED_UNKNOWN;
}
//_profileWebService.SendProfileToServer(_profile).Wait();
// Close(this);
}
});
}
}
private async Task Sleep() // async added
{
return await Task.Delay(3000).ConfigureAwait(false); // await, confi... added
}
public async Task SendProfileToServer(Profile profileObject)
{
// Validate arguments before attempting to use web serivce
if (profileObject.IsValidData())
{
// TODO: Return ENUM FLAG that represents the state of the result
await Sleep().ConfigureAwait(false); // await, confi... added
lastResult = true;
}else{
lastResult = false;
}
}
The problem is, that the context from the UI and the async cause a deadlock.