Error sending object to .net signalr client - signalr

My understanding is that with signalr I can just send objects back and forth. I am trying to set up a .net client to receive notifications that orders have been placed on a web site. I am trying to set up a very simple example so that I understand the concepts. It works great when I am sending a string notification back to the client, but when I try to send an object I get an error:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was unhandled by user code
HResult=-2146233088
Message=The best overloaded method match for 'ConsoleHub.Program.DisplayOrder(ConsoleHub.Order)' has some invalid arguments
Source=Anonymously Hosted DynamicMethods Assembly
StackTrace:
at CallSite.Target(Closure , CallSite , Type , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
at ConsoleHub.Program.<Main>b__6(Object o) in c:\Working\OrderNotifier\ConsoleHub\Program.cs:line 23
at Microsoft.AspNet.SignalR.Client.Hubs.HubProxyExtensions.<>c__DisplayClass6`1.<On>b__4(JToken[] args)
at Microsoft.AspNet.SignalR.Client.Hubs.Subscription.OnData(JToken[] data)
at Microsoft.AspNet.SignalR.Client.Hubs.HubProxy.InvokeEvent(String eventName, JToken[] args)
at Microsoft.AspNet.SignalR.Client.Hubs.HubConnection.OnReceived(JToken message)
at Microsoft.AspNet.SignalR.Client.Connection.Microsoft.AspNet.SignalR.Client.IConnection.OnReceived(JToken message)
at Microsoft.AspNet.SignalR.Client.Transports.HttpBasedTransport.ProcessResponse(IConnection connection, String response, Boolean& timedOut, Boolean& disconnected)
InnerException:
My class:
public class Order
{
public int OrderId { get; set; }
public string Name { get; set; }
public string OrderItem { get; set; }
}
My hub:
using Microsoft.AspNet.SignalR.Hubs;
using OrderNotifier.Models;
using System.Linq;
using System.Threading.Tasks;
namespace OrderNotifier.Hubs
{
public class NotifierHub : Hub
{
OrderContext db = new OrderContext();
public void Hello()
{
Clients.Caller.Welcome("hello");
}
}
}
My controller action:
[HttpPost]
public ActionResult Create(Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.SaveChanges();
SendNotifier.SendOrderNotification(String.Format("{0} ordered {1}", order.Name, order.OrderItem), order);
return RedirectToAction("Index");
}
return View(order);
}
SendNotifier - which is a little weird because I am having it send both a string version and an object version for testing:
public class SendNotifier
{
public static void SendOrderNotification(string message, Order order)
{
var context = GlobalHost.ConnectionManager.GetHubContext<NotifierHub>();
context.Clients.All.Notify(message);
context.Clients.All.Order(order);
}
}
And my console application:
using Microsoft.AspNet.SignalR.Client.Hubs;
using OrderNotifier.Models;
using System;
namespace ConsoleHub
{
class Program
{
static void Main(string[] args)
{
var hubConnection = new HubConnection("http://localhost:60692");
var order = hubConnection.CreateHubProxy("NotifierHub");
//
// Set up action handlers
//
order.On("Welcome", message => Console.WriteLine(message));
order.On("Notify", message => Console.WriteLine(message));
order.On("Order", o => DisplayOrder(o));
hubConnection.Start().Wait();
order.Invoke("Hello").Wait();
Console.WriteLine("Initialized...");
Console.ReadLine();
}
public void DisplayOrder(Order o)
{
Console.WriteLine(String.Format("Order object received.../r/nOrderId: {0}/r/nName: {1}/r/nOrderItem: {2}", o.OrderId, o.Name, o.OrderItem));
//Console.WriteLine(o);
}
}
}
If I change the DisplayOrder parameter to be a string it works. I know I could probably manually deserialize it using Json.Net, but my understanding is that I should just be able to work with it as an object and let signalr deserialize. What am I missing?

You're using the dynamic object overload of On. You need to specify the type:
order.On<Order>("Order", DisplayOrder);

Related

Uno Platform WASM SignalR deserialization issues

I have a SignalR service and an Uno Platform WASM Client (with Prism). I want to call a hub method, which returns a model. The problem is, i have two identical models (Properties, Methods, etc.) but the client can only receive one of them upon calling the hub methods. With the other model a exception gets thrown on deserialization.
Hub:
using Microsoft.AspNetCore.SignalR;
using ReservationManagement.SignalRInterface.Model;
using System.Threading.Tasks;
namespace ReservationManagement.Service.Hubs
{
public class TestServiceHub: Hub
{
public async Task<LocationModel> LocationModel()
{
return new LocationModel() { Name = "Location" };
}
public async Task<TestModel> TestModel()
{
return new TestModel() { Name = "Test" };
}
}
}
Models:
namespace ReservationManagement.SignalRInterface.Model
{
public class TestModel
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
}
namespace ReservationManagement.SignalRInterface.Model
{
public class LocationModel
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
}
ViewModel:
using Microsoft.AspNetCore.SignalR.Client;
using Prism.Commands;
using ReservationManagement.SignalRInterface.Model;
namespace ReservationManagement.UnoPrism.ViewModels
{
class TestViewModel: ViewModelBase
{
private HubConnection HubConnection { get; set; }
public DelegateCommand Loaded { get; private set; }
public LocationModel LocationModel
{
get => _locationModel;
set { SetProperty(ref _locationModel, value); }
}
private LocationModel _locationModel;
public TestModel TestModel
{
get => _testModel;
set { SetProperty(ref _testModel, value); }
}
private TestModel _testModel;
public TestViewModel()
{
Loaded = new DelegateCommand(LoadedExecute);
}
private async void LoadedExecute()
{
HubConnection = new HubConnectionBuilder()
.WithUrl("http://localhost:5000/TestServiceHubAnyOrigin")
.WithAutomaticReconnect()
.Build();
await HubConnection.StartAsync();
LocationModel = await HubConnection.InvokeAsync<LocationModel>("LocationModel");
TestModel = await HubConnection.InvokeAsync<TestModel>("TestModel");
}
}
}
The call for LocationModel works fine and it is set to what the service returns. The call for TestModel results in an exception. If i switch the calling order (1. TestModel, 2. LocationModel) the exception will still be thrown on the TestModel-call.
Everything works perfectly fine when i build for Uwp.
Exception:
System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'ReservationManagement.SignalRInterface.Model.TestModel'. Path: $ | LineNumber: 0 | BytePositionInLine: 1. ---> System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'ReservationManagement.SignalRInterface.Model.TestModel'.
I also tried this, as the exception suggests, with a singular parameterized constructor and a parameterized constructor annotated with 'JsonConstructorAttribute', but still the same exception.
This is a generic issue related to the IL Linker step, which removes members that are detected as not used, in some cases incorrectly when reflection is used.
You will need to add linker descriptors to fix this in the LinkerConfig.xml file in the WebAssembly project, likely in the assembly that contains the ReservationManagement namespace.
You can find additional documentation about the linker configuration here.

Dependency Injection on API to API with AutoRest

I been following the Swagger in Azure App Service tutorial and I notice the AutoREST code generation. In the tutorial, theres is an API and a DataAPI.
The TodoListAPI is a normal Web API.
The TodoListDataAPI is the one that is connected to a datasource, it is also a Web API and it is being consumed by TodoListAPI.
Using swagger autogerated codes are being imported to the TodoListAPI
partial interface ITodoListDataAPI: IDisposable
{
Uri BaseUri
{
get; set;
}
ServiceClientCredentials Credentials
{
get; set;
}
IToDoList ToDoList
{
get;
}
....
/// this seems to be the interface that is needed to be injected in the Controller
public partial interface IToDoList
{
Task<HttpOperationResponse<object>> DeleteByOwnerAndIdWithOperationResponseAsync(string owner, int id, CancellationToken cancellationToken = default(System.Threading.CancellationToken));
Task<HttpOperationResponse<ToDoItem>> GetByIdByOwnerAndIdWithOperationResponseAsync(string owner, int id, CancellationToken cancellationToken = default(System.Threading.CancellationToken));
Then in the ToDoListAPI controller it is being used like this
public class ToDoListController : ApiController
{
private string owner = "*";
private static ITodoListDataAPINewDataAPIClient()
{
var client = new TodoListDataAPI(new Uri(ConfigurationManager.AppSettings["ToDoListDataAPIUrl"]));
return client;
}
// GET: api/ToDoItemList
public async Task<IEnumerable<ToDoItem>> Get()
{
using (var client = NewDataAPIClient())
{
var results = await client.ToDoList.GetByOwnerAsync(owner);
....
}
}
}
Now the problem in this pattern is it is not testable because it directly consumes the DataAPI.
My question is, How can I make ITodoList to be used as dependency injection on the controller.
public class ToDoListController : ApiController
{
private readonly ITodoListDataAPI _todoListData;
private ToDoListController (IToDoList todoListData)
{
_todoListData = todoListData;
}
}
I also don't know what Autofoca DI library to use, there is Autofac and Autofac.WebApi in the nuget gallery and I am not sure what to use in these instance.
Thanks,

SignalR send object - newbie

With Signal R, if trying to send an object, what is the syntax to pass the model?
private async void FormLoaded(object sender, RoutedEventArgs e)
{
//Connection stuff...
Proxy.On("sendHello", () => OnSendDataConnection(ModelBuilder()));
}
private void OnSendDataConnection(ConnectionModel model)
{
Dispatcher.Invoke(DispatcherPriority.Normal,model?????)
this.Dispatcher.Invoke((Action)(LoadW));
}
Looking at the question (text, and not the code) I understand you are trying to send Complex object to JS side and use it there? Is that correct?
In that case the solution is supposed to be simple:
From the ASP.NET.SignalR site:
You can specify a return type and parameters, including complex types
and arrays, as you would in any C# method. Any data that you receive
in parameters or return to the caller is communicated between the
client and the server by using JSON, and SignalR handles the binding
of complex objects and arrays of objects automatically.
Example C#:
public void SendMessage(string name, string message)
{
Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
}
JS:
var contosoChatHubProxy = $.connection.contosoChatHub;
contosoChatHubProxy.client.addMessageToPage = function (message) {
console.log(message.UserName + ' ' + message.Message);
});
Where ContosoChatMessage is:
public class ContosoChatMessage
{
public string UserName { get; set; }
public string Message { get; set; }
}
(read on for examples...)
So basically, in JS once 'model' received, you should be able to use 'model.XY', where XY is a member of the model complex object.
I hope it helps.
In my case, I needed to convert my fields to properties, otherwise, it doesn't send the information properly.
From
public string myField;
to
public string myField {get; set;}

Json.Net Deserialization - Web API and Nullable Date

Say you've got a model that looks like
public class UserModel
{
public string UserName { get; set; }
public DateTime? DateOfBirth { get; set; }
}
The DateOfBirth field isn't required, but could be specified. You have a Web API POST endpoint that looks like
[Route("")]
[AcceptVerbs("POST")]
public async Task<IHttpActionResult> Create(UserModel user)
{
}
And we've set the JSON serializer in start up like so,
public static void Register(HttpConfiguration config)
{
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
var settings = jsonFormatter.SerializerSettings;
settings.Converters.Add(new IsoDateTimeConverter());
settings.Error += (sender, args) => Console.WriteLine("This event is fired ok");
}
If we send some JSON to the endpoint that looks like this
{
"userName": "User1",
"dateOfBirth": "jhdgjhjfg"
}
...the error event is fired in the Serializer settings and the endpoint is called. At this point, the DateOfBirth field is null and I don't have any context that a deserialization error has occurred
Reading the JSON.Net documentation, because Handled == false in the Error event arguments of the Settings object, an exception should be raised into the application code - this doesn't happen? Is there a setting I haven't configured correctly for this?
How can I get context within the action so that I know a value was specified for a field and couldn't be deserialized? Even global behaviour would be fine, as long as I know this has happened and can return a 400.
UPDATE:
We can use a filter to check the Model state, then check the Model State errors for exceptions of type JsonReaderException. This lets you return a 400 with a list of violating fields
public class CheckJsonExceptionModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid)
{
return;
}
var fieldsInError = new List<string>();
foreach (var jsonException in
actionContext.ModelState.Keys.SelectMany(key => actionContext.ModelState[key].Errors)
.Select(error => error.Exception).OfType<JsonReaderException>())
{
Trace.TraceError(jsonException.Message);
fieldsInError.Add(jsonException.Path);
}
var apiError = new { ErrorMessages.BadRequestModel.Message, FieldsInError = new List<string>() };
foreach (var fieldError in fieldsInError)
{
apiError.FieldsInError.Add(fieldError);
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, apiError);
}
}
You have multiple options. But first, you are getting no exception because the WebApi handles this exception. Bad news.
Good news, you can handle it in at least two ways; use the ModelState.IsValid property - in your case it will be false. You can access them in your post-method. When you remove the invalid dateOfBirth it is true ;-)
Or you can use an ActionFilterAttribute to put it on your methods for re-use purposes.
For example:
public async Task<IHttpActionResult> Create(UserModel user) {
if (!ModelState.IsValid) {
// ModelState.Keys // Get all error-keys
}
}

ASP.Net (3.5) MVC partial class for rule validation

I have 2 projects. A data project, which contains my database and my Entity Framework model. I have table called 'User', and then have a generated EF class for user.
I am trying to add a partial class:
using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Text;
namespace Data
{
public partial class user
{
public bool isValid
{
get {
return (GetRuleViolations().Count()==0);
}
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
yield break;
}
partialvoid OnValidate(ChangeAction action)
{
if (isValid)
throw new ApplicationException("Rule violation prevents saving");
}
}
public class RuleViolation
{
public string ErrorMessage { get; private set; }
public string PropertyName { get; private set; }
public RuleViolation (string errorMessage)
{
ErrorMessage = errorMessage;
}
public RuleViolation(string errorMessage, string propertyName)
{
ErrorMessage = errorMessage;
PropertyName = propertyName;
}
}
}
This is following the MVC 1.0 NerdDinner example. However, I am getting a design time error on the OnValidate method:
partial void OnValidate(ChangeAction action)
{
if (isValid)
throw new ApplicationException("Rule violation prevents saving");
}
No definining declaration found for implimenting declaration of partial method 'void OnValidate(ChangeAction action)'
What am I doing wrong?
There should be another file containing the rest of the user class.
In this file you have to declare that there might be a partial method called OnValidate.
public partial class user
{
partial void OnValidate(ChangeAction action);
}
Some detail:
A partial method needs to have a signature specified somewhere. Once the signature has been defined then an optional implementation can be specified. Read http://msdn.microsoft.com/en-us/library/wa80x488.aspx for more information.

Resources