I have one interface and two classes that implements it.
namespace FirebaseNet.Messaging
{
public interface INotification
{
string Title { get; set; }
}
public class AndroidNotification : INotification
{
public string Title { get; set; }
}
public class IOSNotification : INotification
{
public string Title { get; set; }
}
}
Now I have another class like this.
public class Message
{
public INotification Notification { get; set; }
}
The Message parameter is passed to the class like this
[HttpPost]
public async Task<JsonResult> SendMessage(Message message)
This parameter can be either
var message = new Message()
{
Notification = new AndroidNotification()
{
Title = "Portugal vs. Denmark"
}
};
or
var message = new Message()
{
Notification = new IOSNotification()
{
Title = "Portugal vs. Denmark"
}
};
So far, all works. Now I want to AJAX POST to SendMessage. I tried using this DTO.
JavaScript Code
var message = {
Notification : {
Title : "Portugal vs. Denmark"
}
};
This obviously failed with
Cannot create an instance of an interface.
What would be an ideal workaround for it?
P.S: Thought of changing the Message class to
public class Message
{
public INotification Notification { get; set; }
public AndroidNotification AndroidNotification { get; set; }
public IOSNotification IOSNotification { get; set; }
}
It's a third party DLL and I don't wanna touch it ideally.
One way to achieve that would be to write a custom model binder:
public class NotificationModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
if (!typeof(INotification).IsAssignableFrom(type))
{
throw new InvalidOperationException("Bad Type");
}
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
that you would register when bootstrapping your application:
ModelBinders.Binders.Add(
typeof(INotification),
new NotificationModelBinder()
);
this would now allow the client to specify the concrete type of the notification:
var message = {
ModelType: "WebApplication1.Models.AndroidNotification",
Notification : {
Title : "Portugal vs. Denmark"
}
};
or:
var message = {
ModelType: "WebApplication1.Models.IOSNotification",
Notification : {
Title : "Portugal vs. Denmark"
}
};
Of course you could tweak the model binder for the exact property name that indicates the concrete type and the possible values. In my example the fully qualified type name should be used but you could have some mapping with friendlier names.
Related
This question already has answers here:
Generic Class Covariance
(3 answers)
Closed 1 year ago.
This is the code I would like to write at the end:
var notification = NotificationFactory.FromJson(jsonString);
if (notification is UserUpdateProfileNotification)
{
// notification is of type UserUpdateProfileNotification so I can access notification.Payload.AccoundId
}
This is what I tried so far:
public abstract class Notification<T>
{
public string Type { get; set; }
public T Payload { get; set; }
}
public class UserUpdateProfileNotificationPayload
{
public Guid AccountId { get; set; }
}
public class UserUpdateProfileNotification: Notification<UserUpdateProfileNotificationPayload>
{
public UserUpdateProfileNotification(UserUpdateProfileNotificationPayload payload)
{
Type = "USER_UPDATE_PROFILE";
Payload = payload;
}
}
public static class NotificationFactory
{
public static Notification<object> FromJson(string notification)
{
var parsedNotif = JsonSerializer.Deserialize<Notification<object>>(notification);
if (parsedNotif.Type == "USER_UPDATE_PROFILE")
{
var concreteType = JsonSerializer.Deserialize<UserUpdateProfileNotification>(notification);
return new UserUpdateProfileNotification(concreteType.Payload);
}
throw new NotImplementedException();
}
}
The error I got:
Cannot implicitly convert type 'UserUpdateProfileNotification' to 'Notification'
For me, converting Notification<x> to Notification<object> with x inherit from object should be "automatic". But it's not. What's the best way to express this idea in C# (10) ?
I got something to work (I use dynamic instead of Notification and pattern matching work)
Factory
public class NotificationFactory
{
public static dynamic FromJson(string notification)
{
var parsedNotif = JsonSerializer.Deserialize<Notification<dynamic>>(notification);
if (parsedNotif.Type == "USER_UPDATE_PROFILE")
{
var concreteType = JsonSerializer.Deserialize<UserUpdateProfileNotification>(notification);
return new UserUpdateProfileNotification(concreteType.Payload);
}
throw new NotImplementedException();
}
}
How I use:
var notification = NotificationFactory.FromJson(item);
if (notification is UserUpdateProfileNotification notif)
{
log.LogInformation($"{notif.Payload.AccountId}");
}
Double parsing is not optimal but it is acceptable in my case
According to the following object definitions:
public interface IMessage
{
Guid Identifier { get; set; }
}
public class Message : IMessage
{
public Guid Identifier { get; set; }
}
public interface IEventMessage<TPayload> : IMessage
{
TPayload Payload { get; set; }
}
public class EventMessage<TPayload> : Message, IEventMessage<TPayload>
{
public TPayload Payload { get; set; }
}
public interface ICommandMessage<TParameters> : IMessage
{
TParameters Parameters { get; set; }
}
public class CommandMessage<TParameters> : Message, ICommandMessage<TParameters>
{
public TPayload Payload { get; set; }
}
I would like to filter a collection of Messages according to their type without taking into account their generic type
var messages = new List<IMessage>
{
new CommandMessage<string>() { Name = "Command Message String" },
new CommandMessage<int>() { Name = "Command Message Int" },
new EventMessage<string>() { Name = "Event Message String" },
new EventMessage<int>() { Name = "Event Message Int" }
};
var onlyStringCommandMessages = messages.OfType<CommandMessage<string>>(); // OK
var onlyIntCommandMessages = messages.OfType<CommandMessage<int>>(); // OK
var onlyCommandMessages = messages.OfType<CommandMessage<object>>(); // KO
var onlyCommandMessages2 = messages.OfType<CommandMessage<dynamic>>(); // KO
I have tried other techniques such as:
var onlyCommandMessages3 = messages.Where(x =>
x.GetType()
.GetInterfaces()
.Any(i => i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(ICommandMessage<>)))); // OK but return IMessage types
It seems to work but it returns me IMessage types. How can I cast to ICommandMessage at the same time?
It would be very useful to have a any type like in Typescript. ICommandMessage<any>
I have found another subject related to this problem but it doesn't help me.
Any thoughts?
Not exactly what you ask, but (inspired by IEnumerator and Enumerator<T>) you use a non generic interface like :
public interface ICommandMessage : IMessage
{
object Parameters { get; set; }
}
public interface ICommandMessage<TParameters> : ICommandMessage
{
TParameters Parameters { get; set; }
}
public class CommandMessage<TParameters> : Message, ICommandMessage<TParameters>
{
public TParameters Parameters { get; set; }
object ICommandMessage.Parameters
{
get => Parameters;
set => Parameters = (TParameters)value;
}
}
Then you can:
var onlyCommandMessages = messages.OfType<ICommandMessage>();
Edit from #Flater comment :
With this structure, you have no guarantee the class that implement ICommandMessage<TParameters> will return the same object to ICommandMessage<TParameters>.Parameter and ICommandMessage.Parameter. It's the same problem with IEnumerator<T>.
Like #Flater suggest, you can minimize ICommandMessage to correspond your need.
I have the following model:
public class Network {
public string Network { get; set; }
public string Cidr { get; set; }
public string TenantId { get; set; }
}
And the following endpoint:
public async Task<ActionResult<Network>> PostNetwork(Network network)
{
...
}
And would like to know if it's possible to bind Network and Cidr with values from my post Body, and the TenantId with the value from a specific header.
I don't wan't to put entity.TenantId = HttpContext.Request.Headers["TenantId"]; all over my controllers endpoint because this pattern is necessary to several entities/endpoints.
I've tried to create a custom middle-ware to edit my Body content, but the changes wasn't reflected in my controller. Also tried to use a custom DataBinder but without success.
So you'll need to create a custom model binder and add an attribute to your class.
The model binder will inherit from IModelBinder and assuming your data is JSON
public class NetworkModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
Network model;
string bodyAsText = await new StreamReader(bindingContext.HttpContext.Request.Body).ReadToEndAsync();
model = JsonConvert.DeserializeObject<Network>(bodyAsText);
model.TenantId = bindingContext.HttpContext.Request.Headers["TenantId"];
bindingContext.Result = ModelBindingResult.Success(model);
}
}
Then add the ModelBinder attribute to the class to tell it to use that model binder
[ModelBinder(typeof(NetworkModelBinder))]
public class Network
{
public string Network { get; set; }
public string Cidr { get; set; }
public string TenantId { get; set; }
}
My task was to fill the property of each object which implements the specific interface from a header. I resolved it by decorating the default ModelBinderFactory.
public sealed class CustomModelBinderFactory : ModelBinderFactory, IModelBinderFactory
{
public CustomModelBinderFactory(
IModelMetadataProvider metadataProvider,
IOptions<MvcOptions> options,
IServiceProvider serviceProvider)
: base(metadataProvider, options, serviceProvider)
{
}
public new IModelBinder CreateBinder(ModelBinderFactoryContext context)
{
var binder = base.CreateBinder(context);
return new NodeIdBinder(binder);
}
/// <summary>
/// Wrapper on the resolved by <see cref="ModelBinderFactory"/> binder.
/// </summary>
private sealed class NodeIdBinder : IModelBinder
{
private readonly IModelBinder _originalBinder;
public NodeIdBinder(IModelBinder originalBinder)
{
_originalBinder = originalBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
await _originalBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.Model is IQueryWithNode model)
{
var nodeId = bindingContext.HttpContext.Request.Headers["X-Node"];
model.NodeId = nodeId;
}
}
}
}
The interface example
public interface IQueryWithNode
{
public string NodeId { get; set; }
}
This factory should be added to a container. I didn't find an official way to do it, so I used
services.AddSingleton<IModelBinderFactory, CustomModelBinderFactory>();
Now any request which consumes the object that implements this interface will have the NodeId property filled.
I prefer this solution because I feel it's simpler & easier to read and maintain.
That said...
This should work 95% of the time.
However, truly "Complex Objects" may need their own JSON Deserializer class.
[HttpPost]
public IActionResult Save([ModelBinder(typeof(EmployeeModelBinder))] Employee entity)
{
// Do awesome stuff here
}
[HttpPost]
public IActionResult Find([ModelBinder(typeof(EmployeeModelBinder))] EmployeeFilter filter)
{
// Do awesome stuff here
}
public class EmployeeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var key = bindingContext.ModelName;
var result = bindingContext.ValueProvider.GetValue(key);
// Exists?
if (result.Length == 0)
return Task.CompletedTask;
// Deserialize
var json = result.ToString();
switch (key)
{
case "entity":
var entity = JsonConvert.DeserializeObject<Employee>(json);
bindingContext.Result = ModelBindingResult.Success(entity);
break;
case "filter":
var filter = JsonConvert.DeserializeObject<EmployeeFilter>(json);
bindingContext.Result = ModelBindingResult.Success(filter);
break;
}
return Task.CompletedTask;
}
}
I have the below code that gets an object from the web api service.
At the following line of code
response.Content.ReadAsAsync<CMLandingPage>().Result;
I get the following exception :
InnerException = {"Could not create an instance of type MLSReports.Models.IMetaData. Type is an interface or abstract class and cannot be instantiated. Path 'BaBrInfo.Series[0].name', line 1, position 262."}
Any pointers are much appreciated.
CMLandingPage lpInfo = new CMLandingPage();
try
{
using (HttpClient client = new HttpClient())
{
// Add an Accept header for JSON format
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
response = client.PostAsJsonAsync(LPChartinfoapiURL, criteria.Mlsnums).Result;
}
// Throw exception if not a success code.
response.EnsureSuccessStatusCode();
// Parse the response body.
lpInfo = response.Content.ReadAsAsync<CMLandingPage>().Result;
}
CMLandingPage :
namespace MLSReports.Models
{
public class CMLandingPage
{
public CMLandingPage() { }
public CMColumn BaBrInfo { get; set; }
}
public class CMColumnItem<T> : IMetaData
{
#region Constructors and Methods
public CMColumnItem() { }
#endregion
#region Properties and Fields
public string name { get; set; }
public List<T> data { get; set; }
public string color { get; set; }
#endregion
}
public class CMColumn
{
#region Constructor and Method
public CMColumn()
{
Series = new List<IMetaData>();
}
#endregion
#region Properties and Fields
public string ChartType { get; set; }
public string ChartTitle { get; set; }
public List<IMetaData> Series { get; set; }
#endregion
}
}
Your CmColumn class has this property on it:
public List<IMetaData> Series { get; set; }
The WebApi controller is evidently trying to construct an object based on the values of the parameters you're sending it. When it gets to the value with the name "BaBrInfo.Series[0].name", it knows it should create a new IMetaData object so that it can set its name and add it to the Series property, but IMetaData is just an interface: it doesn't know what type of object to construct.
Try changing IMetaData to some concrete type that implements that interface.
You need to get the serializer to include the type in the json payload and the deserializer to use the included type:
//Serialization
var config = new HttpConfiguration();
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
return Request.CreateResponse(HttpStatusCode.OK, dto, config);
//Deserialization
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings = { TypeNameHandling = TypeNameHandling.Auto }
};
response.Content.ReadAsAsync<CMLandingPage>(new [] { formatter }).Result;
POLYMORPHIC SERIALIZATION USING NEWTON JSON.NET IN HTTPCONTENT
I am trying to use MessagePack to serialize an object that has a property of an interface type. When I call Pack, it throws SerializationException that says a serializer is not defined for the interface.
Code example:
namespace ConsoleApplication1
{
// interfaces and classes declaration
public interface IDummyInterface { }
public class DummyObject : IDummyInterface
{
public string Value { get; set; }
}
public class SmartObject
{
public string Name { get; set; }
IDummyInterface DummyOne { get; set; }
}
// in main
var mySmartObject = new SmartObject() { Name = "Yosy", DummyOne = new DummyObject() { Value = "Value"} };
using(var stream = new MemoryStream())
{
var serializer = MessagePackSerializer.Create<SmartObject>();
serializer.Pack(mySmartObject, stream); // => This code throws the exception
}
}
Can I tell MessagePack which serializer to use for IDummyInterface and tell it to act as DummyObject?
It seems to me you are using msgpack-cli. To make it work, basically there are two ways to do it.
1. Use MessagePackKnownTypeAttribute
This one is easy and straightforward.
public class SmartObject
{
public string Name { get; set; }
[MessagePackKnownType("d", typeof(DummyObject))]
public IDummyInterface DummyOne { get; set; } // Need to make this property public
}
2. Implement custom serializer
If you want a clean model class without reference to MsgPack library, you can do the following, but you need to figure out a way to serialize/deserialize SmartObject (efficiently).
public class SmartObjectSerializer : MessagePackSerializer<SmartObject>
{
public SmartObjectSerializer(SerializationContext ownerContext) : base(ownerContext)
{
}
protected override void PackToCore(Packer packer, SmartObject objectTree)
{
var str = ToString(objectTree); // TODO: Just an example
packer.Pack(str);
}
protected override SmartObject UnpackFromCore(Unpacker unpacker)
{
var str = unpacker.LastReadData.AsStringUtf8(); // TODO: Just an example
return new SmartObject
{
// TODO: Initialize based on str value
};
}
}
// In main
var context = new SerializationContext();
context.Serializers.RegisterOverride(new SmartObjectSerializer(context));
var serializer = MessagePackSerializer.Get<SmartObject>(context);
// The rest is the same
There are some sample codes you may be interested to take a look.
CustomSerializer
Polymorphism