We are trying to build a web api request to handle different type of business actions in a common endpoint. We have got a parent model class which holds common fields, and each business type has its own class inheriting from base model class. Controller input type will be the base class and client must specify the type being sent in the request (designated enum value for each type, not the fully qualified type name). We have created a custom de-serialiser to convert the received object to actual type based on the specified type.
Like model classes, we have got base interface inherited by each business type. Since the actions are same across different business types and only the input and output types of each action varies, we defined a generic base interface holding all the actions. Each business specific class inherits interface by specifying relevant model type during inheritance.
Problem trying to solve:
I know the model type through custom de-serialiser. Based on this type I must get the instance of class inheriting the interface for the identified type and call the method of that service. How to achieve this?
Written below method but getting runtime exception for invalid casting
private static ITransactionService<Transaction> GetServiceInstance(Transaction transaction)
{
return transaction switch
{
Commitment => (ITransactionService<Transaction>)new CommitmentService(),
Drawdown => (ITransactionService<Transaction>)new DrawdownService(),
_ => null
};
}
Below is the complete code structure for reference:
public class Transaction
{
public int Id { get; set; }
public DateTime Date { get; set; }
}
public class Commitment : Transaction
{
public string Investor { get; set; }
public int Quantity { get; set; }
}
public class Drawdown : Transaction
{
public decimal Amount { get; set; }
}
public interface ITransactionService<T> where T : Transaction
{
T Create(T transaction);
T Update(T transaction);
T Apporve(T transaction);
}
public class CommitmentService : ITransactionService<Commitment>
{
public Commitment Apporve(Commitment transaction)
{
throw new NotImplementedException();
}
public Commitment Create(Commitment transaction)
{
throw new NotImplementedException();
}
public Commitment Update(Commitment transaction)
{
throw new NotImplementedException();
}
}
public class DrawdownService : ITransactionService<Drawdown>
{
public Drawdown Apporve(Drawdown transaction)
{
throw new NotImplementedException();
}
public Drawdown Create(Drawdown transaction)
{
throw new NotImplementedException();
}
public Drawdown Update(Drawdown transaction)
{
throw new NotImplementedException();
}
}
The exception I am getting:
Basically we are trying to reduce number of classes and endpoints need to be created for each type.
CodePudding user response:
You have a variance problem, ITransactionService<Commitment>
can't be cast to ITransactionService<Transaction>
. Some variance problems can be solved by adding in
or out
keywords.
But in general all variance problems can be solved with either more generics. Forcing the caller to either know the actual type, or invoke the methods via reflection;
private static ITransactionService<T> GetServiceInstance<T>(T transaction)
Or more types with less generics. Adding methods that allow the caller to access features without needing to know the actual type;
public interface ITransactionService
{
Transaction Create(Transaction transaction);
Transaction Update(Transaction transaction);
Transaction Apporve(Transaction transaction);
}
public interface ITransactionService<T> : ITransactionService where T : Transaction
{
T Create(T transaction);
T Update(T transaction);
T Apporve(T transaction);
}
public abstract class BaseService<T> : ITransactionService<T> where T : Transaction
{
Transaction ITransactionService.Apporve(Transaction transaction)
=> Apporve((T)transaction);
public abstract T Apporve(T transaction);
Transaction ITransactionService.Create(Transaction transaction)
=> Create((T)transaction);
public abstract T Create(T transaction);
Transaction ITransactionService.Update(Transaction transaction)
=> Update((T)transaction);
public abstract T Update(T transaction);
}
Now every ITransactionService<T>
has a known base type, ITransactionService
that you can cast.