I cannot find any resource, that would say I cannot do that.
I have all setup hub/client and tested when parameter is decimal, but once I use generic class, then the server wont react.
SignalRMessage:
public class SignalRMessage<T>
{
public SignalRMessage(T value, string text)
{
Value = value;
Text = text ?? string.Empty;
}
public T Value { get; set; }
public string Text { get; set; }
}
Hub (OnConnected gets a hit):
public class JobHeaderHub : Hub
{
public override Task OnConnectedAsync()
{
Debug.WriteLine(Clients.Caller);
return base.OnConnectedAsync();
}
public async Task JobHeaderUpdated(SignalRMessage<decimal> message)
{
await Clients.Others.SendAsync("ReceiveJobHeaderUpdated", message);
}
public async Task JobHeaderCreated(SignalRMessage<decimal> message)
{
await Clients.Others.SendAsync("ReceiveJobHeaderCreated", message);
}
}
Client:
public class JobHeaderSingalRClient
{
private HubConnection connection;
public JobHeaderSingalRClient()
{
// connection = new HubConnectionBuilder().WithUrl(#"").WithAutomaticReconnect().Build();
connection = new HubConnectionBuilder().WithUrl(#"http://localhost:5000/jobheader").WithAutomaticReconnect().Build();
connection.On<SignalRMessage<decimal>>("ReceiveJobHeaderUpdated", message => JobHeaderUpdated?.Invoke(message));
connection.On<SignalRMessage<decimal>>("ReceiveJobHeaderCreated", message => JobHeaderCreated?.Invoke(message));
}
public static async Task<JobHeaderSingalRClient> CreateConnectedClient()
{
var client = new JobHeaderSingalRClient();
await client.ConnectAsync();
return client;
}
public async Task<JobHeaderSingalRClient> ConnectAsync()
{
await connection.StartAsync();
return this;
}
public event Action<SignalRMessage<decimal>> JobHeaderUpdated;
public async Task SendJobHeaderUpdated(decimal id, string message = null)
{
await connection.SendAsync("JobHeaderUpdated", new SignalRMessage<decimal>(id, message));
}
public event Action<SignalRMessage<decimal>> JobHeaderCreated;
public async Task SendJobHeaderCreated(decimal id, string message = null)
{
await connection.SendAsync("JobHeaderCreated", new SignalRMessage<decimal>(id, message));
}
}
I have no idea why when parameter is SignalRMessage<decimal> then the methods on server are not getting hit. Anyone knows? Thanks.
I had this sort of issues too when I was using constructors with parameters. All of them disappeared after adding a default parameterless constructor.
This is most probably not related to signalR, but to the underlying JSON serialization.
The type has to be specified in order to be able to serialize the objects.
I had similar issues when using objects of type object as parameters.
To troubleshoot turn on verbose error messages in signalR and see if there are any errors logged.
services.AddSignalR(options =>
{
options.Hubs.EnableDetailedErrors = true;
});
Related
I'm currently working on a ASP.NET Core 2 application using SignalR Core. I was wondering if it is possible to receive a complex object within the Hub class instead of a simple string or simple data structure.
Works - This example works fine: string message
public class MyHub : Hub
{
public Task SendMessage(string message)
{
// ... some logic
}
}
Works - This example works fine as well: List<Dictionary<string, object>> message
public class MyHub : Hub
{
public Task SendMessage(List<Dictionary<string, object>> message)
{
// ... some logic
}
}
Doesn't work correctly - It seems I cannot transfer complex objects via SignalR e.g. if I create a custom message class:
public class Message
{
public int MessageId { get; set; }
public List<Dictionary<string, object>> Items { get; set; }
public List<string> TextMessages { get; set; }
}
public class MyHub : Hub
{
public Task SendMessage(Message message)
{
// ... some logic
}
}
Do you know how to transfer complex objects via a SignalR RPC?
Thank you!
You can use the Newtonsoft.Json Nuget.
There you have a JsonConverter that can serializ your object.
So in your example:
public class MyHub : Hub
{
public Task SendMessage(Message message)
{
var messageJsonString = JsonConvert.SerializeObject<Message>(message);
// some logic
}
}
And on your client you can convert it back to an object. It´s have a nativ API so you just call
connection.on("ReceiveMessage", (message) => {
let messageObject = JSON.parse(message);
// Other code here
});
Now message is again the object you send from the server.
And of course you can use JsonConvert.DeserializeObject<T>() to convert a json string you recieve from the client into a Object.
Follow steps below for a working demo which passing Message between signalR Client and Server.
Server
public class TimeHub: Hub
{
public async Task UpdateTime(string message)
{
if (Clients != null)
{
await Clients?.All.SendAsync("ReceiveMessage", message);
}
}
public Task SendMessage(Message message)
{
// ... some logic
return Task.CompletedTask;
}
}
Client
private static async void Connect()
{
var hubConnectionBuilder = new HubConnectionBuilder();
#region Worked
var hubConnection = hubConnectionBuilder.WithUrl("https://localhost:44381/timeHub", options =>
{
}).Build();
#endregion
await hubConnection.StartAsync();
await hubConnection.SendAsync("UpdateTime", $"From Client");
var item1 = new Dictionary<string, object> {
{ "T1", new { Name = "TT1" } },
{ "T2", new { Name = "TT2" } },
{ "T3", new { Name = "TT3" } },
};
var item2 = new Dictionary<string, object> {
{ "T11", new { Name = "TT11" } },
{ "T12", new { Name = "TT12" } },
{ "T13", new { Name = "TT13" } },
};
await hubConnection.SendAsync("SendMessage", new Message {
MessageId = 1,
Items = new List<Dictionary<string, object>> {
item1,
item2
},
TextMessages = new List<string> {
"H1",
"H2"
}
});
var on = hubConnection.On("ReceiveMessage", OnReceivedAction);
Console.WriteLine($"Client is Start");
Console.ReadLine();
on.Dispose();
await hubConnection.StopAsync();
}
If you are considering using JSON parsing just for the sake of passing multiple objects/parameters to client side, there is an alternative.
Server side (C#). You can pass any number of parameters to the anonymous object array.
SendCoreAsync("MethodName", new object[] {someObject, someNumber, someString });
Client side (Typescript)
private alertHandler = (someObject: any, someNumber: number, someString: string) => {
console.log(someObject, someNumber, someString);
};
I have a slightly more detailed answer here
How can I get user input before building the form. For example if the user typed "exit" at any time during the formflow, I want to save the user input into a status variable and check if it equals "exit" and if it does then return null or do some code.
namespace MyBot.Helpers
{
public enum Person
{
// [Describe("I am a Student")]
IAmStudent,
// [Describe("I am an Alumni")]
IAmAlumni,
// [Describe("Other")]
Other
};
public enum HardInfo { Yes, No };
[Serializable]
public class FeedBackClass
{
public bool AskToSpecifyOther = true;
public string OtherRequest = string.Empty;
[Prompt("May I Have Your Name?")]
[Pattern(#"^[a-zA-Z ]*$")]
public string Name { get; set; }
[Prompt("What is your Email Address?")]
public string Email { get; set; }
[Prompt("Please Select From The Following? {||}")]
[Template(TemplateUsage.NotUnderstood, "What does \"{0}\" mean?", ChoiceStyle = ChoiceStyleOptions.Auto)]
public Person? PersonType { get; set; }
[Prompt("Please Specify Other? {||}")]
public string OtherType { get; set; }
[Prompt("Was The Information You Are Looking For Hard To Find? {||}")]
[Template(TemplateUsage.NotUnderstood, "What does \"{0}\" mean?", ChoiceStyle = ChoiceStyleOptions.Auto)]
public HardInfo? HardToFindInfo { get; set; }
public static IForm<FeedBackClass> MYBuildForm()
{
var status = "exit";
if (status == null) {
return null;
}
else
{
return new FormBuilder<FeedBackClass>()
.Field(nameof(Name), validate: ValidateName)
.Field(nameof(Email), validate: ValidateContactInformation)
.Field(new FieldReflector<FeedBackClass>(nameof(PersonType))
.SetActive(state => state.AskToSpecifyOther)
.SetNext(SetNext))
.Field(nameof(OtherType), state => state.OtherRequest.Contains("oth"))
.Field(nameof(HardToFindInfo)).Confirm("Is this your selection?\n{*}")
.OnCompletion(async (context, state) =>
{
await context.PostAsync("Thanks for your feedback! You are Awsome!");
context.Done<object>(new object());
})
.Build();
}
if the user typed "exit" at any time during the formflow, I want to save the user input into a status variable and check if it equals "exit" and if it does then return null or do some code.
It seems that you’d like to implement global handler to process "exit" command. Scorables can intercept every message sent to a Conversation and apply a score to the message based on logic you define, which can help you achieve it, you can try it.
For detailed information, please refer to Global message handlers using scorables or this Global Message Handlers Sample
The following code snippet work for me, you can refer to it.
ExitDialog:
public class ExitDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("This is the Settings Dialog. Reply with anything to return to prior dialog.");
context.Wait(this.MessageReceived);
}
private async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
if ((message.Text != null) && (message.Text.Trim().Length > 0))
{
context.Done<object>(null);
}
else
{
context.Fail(new Exception("Message was not a string or was an empty string."));
}
}
}
ExitScorable:
public class ExitScorable : ScorableBase<IActivity, string, double>
{
private readonly IDialogTask task;
public ExitScorable(IDialogTask task)
{
SetField.NotNull(out this.task, nameof(task), task);
}
protected override async Task<string> PrepareAsync(IActivity activity, CancellationToken token)
{
var message = activity as IMessageActivity;
if (message != null && !string.IsNullOrWhiteSpace(message.Text))
{
if (message.Text.ToLower().Equals("exit", StringComparison.InvariantCultureIgnoreCase))
{
return message.Text;
}
}
return null;
}
protected override bool HasScore(IActivity item, string state)
{
return state != null;
}
protected override double GetScore(IActivity item, string state)
{
return 1.0;
}
protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
{
var message = item as IMessageActivity;
if (message != null)
{
var settingsDialog = new ExitDialog();
var interruption = settingsDialog.Void<object, IMessageActivity>();
this.task.Call(interruption, null);
await this.task.PollAsync(token);
}
}
protected override Task DoneAsync(IActivity item, string state, CancellationToken token)
{
return Task.CompletedTask;
}
}
GlobalMessageHandlersBotModule:
public class GlobalMessageHandlersBotModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder
.Register(c => new ExitScorable(c.Resolve<IDialogTask>()))
.As<IScorable<IActivity, double>>()
.InstancePerLifetimeScope();
}
}
Register the module:
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new ReflectionSurrogateModule());
builder.RegisterModule<GlobalMessageHandlersBotModule>();
});
Test result:
I have a base class called ServicePluginBase that implements logging.
public class PluginLog
{
public int Id { get; set; }
public int? ServiceId { get; set; }
public string Event { get; set; }
public string Details { get; set; }
public DateTime DateTime { get; set; }
public string User { get; set; }
}
public class SQLPluginLogger : IPluginLogger
{
//EFLogginContext maps PluginLog like so:
// modelBuilder.Entity<PluginLog>().ToTable("log").HasKey(l => l.Id)
private EFLoggingContext _logger = new EFLoggingContext();
public IQueryable<PluginLog> LogItems
{
get { return _logger.LogItems; }
}
public void LogEvent(PluginLog item)
{
_logger.LogItems.Add(item);
_logger.SaveChanges();
}
}
public abstract class ServicePluginBase : IPlugin
{
private IPluginLogger _logger;
public ServicePluginBase(IPluginLogger logger)
{
_logger = logger;
}
protected LogEvent(string eventName, string details)
{
PluginLog _event = new PluginLog()
{
ServiceId = this.Id,
Event = eventName,
Details = details,
User = Thread.CurrentPrincipal.Identity.Name,
DateTime = DateTime.Now
};
_logger.LogEvent(_event);
}
}
Now, within my concrete class, I log events as they happen. In one class, I have some asynchronous methods running -- and logging. Sometimes this works great. Other times, I get the error stating that "Property 'Id' is part of the object's key and cannot be updated." Interestingly enough, I have absolutely no code that updates the value of Id and I never do Updates of log entries -- I only Add new ones.
Here is the async code from one of my plugins.
public class CPTManager : ServicePluginBase
{
public override async Task HandlePluginProcessAsync()
{
...
await ProcessUsersAsync(requiredUsersList, currentUsersList);
}
private async Task ProcessUsersAsync(List<ExtendedUser> required, List<ExtendedUser> current)
{
using (var http = new HttpClient())
{
var removals = currentUsers.Where(cu => !required.Select(r => r.id).Contains(cu.id)).ToList();
await DisableUsersAsync(removals http);
await AddRequiredUsersAsync(requiredUsers.Where(ru => ru.MustAdd).ToList());
}
}
private async Task DisableUsersAsync(List<ExtendedUser> users, HttpClient client)
{
LogEvent("Disable Users","Total to disable: " + users.Count());
await Task.WhenAll(users.Select(async user =>
{
... //Http call to disable user via Web API
string status = "Disable User";
if(response.status == "ERROR")
{
EmailFailDisableNotification(user);
status += " - Fail";
}
LogEvent(statusText, ...);
if(response.status != "ERROR")
{
//Update FoxPro database via OleDbConnection (not EF)
LogEvent("ClearUDF", ...);
}
}));
}
private async Task AddRequiredUsersAsync(List<ExtendedUser> users, HttpClient client)
{
LogEvent("Add Required Users", "Total users to add: " + users.Count());
await Task.WhenAll(users.Select(async user =>
{
... //Http call to add user via web API
LogEvent("Add User", ...);
if(response.status != "ERROR")
{
//Update FoxPro DB via OleDBConnection (not EF)
LogEvent("UDF UPdate",...);
}
}));
}
}
First, I'm confused why I'm getting the error mentioned above -- "Id can't be updated" ... I'm not populating the Id field nor am I doing updates to the Log file. There are no related tables -- just the single log table.
My guess is that I'm doing something improperly in regards to asynchronous processing, but I'm having trouble seeing it if I am.
Any ideas as to what may be going on?
I have a DialogViewModel class with async Task LoadData() method. This method loads data asynchronously and shows this dialog, which notifies user about loading. Here is the code:
try
{
var dialog = new DialogViewModel();
var loadTask = dialog.LoadData();
WindowManager.ShowDialog(dialog);
await loadTask;
}
catch (Exception ex)
{
Logger.Error("Error in DialogViewModel", ex);
// Notify user about the error
}
When LoadData throws an exception, it isn't handled until user exits the dialog. It happens because exception is handled when calling await, and it's not happening until WindowManager.ShowDialog(dialog) completes.
What is the correct way to show a dialog with async loading? I've tried this ways:
Call LoadData() in OnShow(), constructor or similar. But this won't work if I'll need to show this dialog without any data
Call await LoadData() before showing the dialog. This way user have to wait for data to load before actually seeing the window, but I want the window to show up instantly with a loading indicator.
Why is there an explicit public LoadData method?
If this has to happen then do it inside the constructor asynchronously using Task<T> with a ContinueWith to process any exception generated by checking the IsFaultedproperty on the returned task.
This would address both issues you've highlighted.
A very simple example is shown below, obivously you're implementation will be more complicated.
public class DialogViewModel
{
private Task _task;
public DialogViewModel()
{
var context = TaskScheduler.FromCurrentSynchronizationContext();
_task = Task.Factory.StartNew(() =>
{
var data = GetDataCollection();
return data;
})
.ContinueWith(t =>
{
if (t.IsFaulted)
{
HasErrored = true;
ErrorMessage = "It's borked!";
}
else
{
Data = t.Result;
}
}, context);
}
public IEnumerable<string> Data { get; private set; }
public bool HasErrored { get; private set; }
public string ErrorMessage { get; private set; }
private static IEnumerable<string> GetDataCollection()
{
return new List<string>()
{
"John",
"Jack",
"Steve"
};
}
}
Or if you don't want to use Task<T> explicitly and want to use async\await functionality you could use a slightly different approach because you can't use async\await with a class constructor:
public class DialogViewModel
{
public IEnumerable<string> Data { get; private set; }
public bool HasErrored { get; private set; }
public string ErrorMessage { get; private set; }
async public static Task<DialogViewModel> BuildViewModelAsync()
{
try
{
var data = await GetDataCollection();
return new DialogViewModel(data);
}
catch (Exception)
{
return new DialogViewModel("Failed!");
}
}
private DialogViewModel(IEnumerable<string> data)
{
Data = data;
}
private DialogViewModel(string errorMessage)
{
HasErrored = true;
ErrorMessage = errorMessage;
}
private async static Task<IEnumerable<string>> GetDataCollection()
{
// do something async...
return await Task.Factory.StartNew(() => new List<string>()
{
"John",
"Jack",
"Steve"
});
}
}
I have a system which uses AOP with ContextBoundObject.
This is used to intercept a method call and perform certain operations before and after the function. It all works fine until I make the 'function to be intercepted' async.
I understand that the C# compiler rewrites async methods into a state machine, which returns control to the sink as soon as 'await' is reached
So it continues into the interception and executes the code which is meant to be executed only after the Method.
I can see there is an "AsyncProcessMessage" in IMessageSink, but I can't find a way to invoke it, and I am not sure if it will work in the async/await scenario.
Is there a way to make Async/Await work with the ContextBoundObject? Is using another Aspect Oriented Programming approach the only option here?
The code sample below has the method to be intercepted decorated with the 'Audit' attribute and placed in the AuditFacade which is a ContextBoundObject. The SyncProcessMessage method in the AuditSink has the logic to be executed before and after the method.
[AuditBoundary]
public class AuditFacade : ContextBoundObject
{
[Audit]
public ResponseObject DoSomthing()
{
//Do something
return new ResponseObject();
}
/// <summary>
/// Async Method to be intercepted
/// </summary>
/// <returns></returns>
[Audit]
public async Task<ResponseObject> DoSomthingAysnc()
{
//Do something Async
await Task.Delay(10000);
return new ResponseObject();
}
}
[AttributeUsage(AttributeTargets.Method)]
public class AuditAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Class)]
public class AuditBoundaryAttribute : ContextAttribute
{
public AuditBoundaryAttribute()
: base("AuditBoundary" + Guid.NewGuid().ToString())
{
}
public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
{
ctorMsg.ContextProperties.Add(new AuditProperty());
}
}
public class AuditProperty : IContextProperty, IContributeObjectSink
{
public string Name
{
get { return "AuditProperty"; }
}
public bool IsNewContextOK(Context newCtx)
{
var p = newCtx.GetProperty("AuditProperty") as AuditProperty;
if (p == null)
return false;
return true;
}
public void Freeze(Context newContext)
{
}
public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)
{
return new AuditSink(nextSink);
}
}
public class AuditSink : IMessageSink
{
private IMessageSink nextSink;
public AuditSink(IMessageSink nextSink)
{
this.nextSink = nextSink;
}
public IMessage SyncProcessMessage(IMessage msg)
{
var message = msg as IMethodCallMessage;
IMethodReturnMessage returnMessage = null;
ResponseObject response;
//Some Pre Processing happens here
var newMessage = new MethodCallMessageWrapper(message);
//Invoke the Method to be Audited
returnMessage = nextSink.SyncProcessMessage(newMessage) as IMethodReturnMessage;
response = returnMessage.ReturnValue as ResponseObject;
//Some Post Processing happens here with the "response"
return returnMessage;
}
public IMessageSink NextSink
{
get { return this.nextSink; }
}
public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
{
return nextSink.AsyncProcessMessage(msg, replySink);
}
}
I don't know anything about ContextBoundObject, but I think that AsyncProcessMessage() has nothing to do with async-await and that the following should work using the normal SyncProcessMessage():
Do your preprocessing step.
Invoke the async method.
Add your postprocessing step as a continuation to the returned Task, using ContinueWith() or await.
Return the continuation Task to the caller.
If you're okay with your postprocessing executing on the thread pool, then ContinueWith() is probably simpler. If you need the postprocessing to execute on the original context, use await.
The await version could look like this:
var responseTask = (Task<ResponseObject>)returnMessage.ReturnValue;
Func<Task<ResponseObject>> postProcessTaskFunc = async () =>
{
var response = await responseTask;
// Some Post Processing happens here with the "response"
return response;
}
return new ReturnMessage(postProcessTaskFunc(), …);