i have a base class which has 2 derived classes and would like to map one field of the output to a "oneof" statement.
public class ClothingCollection
{
public string Name { get; set; }
public string Id { get; set; }
}
public abstract class ClothingCollection<T> : ClothingCollection
{
public abstract string Type { get; }
public abstract List<T> Stuff { get; }
}
public class PantsCollection : ClothingCollection<PantsModel>
{
public override string Type { get; } = "PANTS";
public override List<PantsModel> Stuff { get; }
}
public class ShirtCollection : BaseItem<ShirtModel>
{
public override string Type { get; } = "SHIRT";
public List<BarChartData> Data { get; }
}
public class PantsModel
{
public bool IsJeans { get; set; }
public string owner { get; set; }
}
public class ShirtModel
{
public string Brand { get; set; }
public int Quantity { get; set; }
}
and set the controller response as such ...
[ProducesResponseType(typeof(List<ClothingCollection>), 200)]
[HttpGet("HelloWorld")]
public IActionResult HelloWorld()
{
....
}
and now would like the resulting swagger documentation to be List that looks somewhat like the following (if it's even possible)
{
"Name": "string",
"Id": "string",
"type": "string",
"Stuff":[
anyof -> PantsCollection,
ShirtCollection
]
}
The concept where you have exactly one value of one of several fixed types is called a tagged union, discriminated union, or sometimes a either monad.
You could create a class that has either shirts or pants:
public class ShirtOrPants
{
public ShirtCollection Shirts { get; }
public PantsCollection Pants { get; }
public ShirtOrPants(ShirtCollection shirts, PantsCollection pants)
{
Shirts = shirts;
Pants = pants;
if ((shirts == null) == (pants == null))
{
throw new InvalidOperationException("Must have shirts OR pants!");
}
}
}
There are as far as I know no built in support for tagged unions in c# or any of the more popular serialization languages, so you would need some custom solution. You would typically add various methods to process either shirts or pants depending on which one has a value.
There are generic implementations available, but they might not be very helpful if the goal is to provide readable API definitions.
Related
On one hand I have a list of capabilities, for example:
public interface ICapability
{
public string Name { get; }
}
public class RangeCapability<T> : ICapability
{
public string Name { get; set; }
public T Min { get; set; }
public T Max { get; set; }
}
public class SetCapability<T>
{
public string Name { get; set; }
public HashSet<T> Set { get; set; }
}
On the other hand I have a list of requirements
public interface IRequirement
{
public string Name { get; }
}
public class Requirement<T> : IRequirement
{
public string Name { get; set; }
public T Value { get; set; }
}
Both capability list may contain capabilities of different types T and requirement list may contain requirements of different types. The important thing is that if for a given name the underlying types match I should check if value is between min and max (for range class) or in a set like in the example below:
public class Entity
{
List<ICapability> Capabilities { get; set; }
public bool IsSatisfying(List<IRequirement> requirements)
{
foreach(var requirement in requirements)
{
var capability = Capabilities.FirstOrDefault(x => x.Name == requirement .Name);
//how to check if here if types match and if req. within range or in collection?
}
}
}
I am not sure how to match generic types of two different classes and then do the check suitable for the apropriate implementation (is within range/is present in set). Can somebody point me in the right direction how could I make it work?
I believe this is what you're looking for. Make the interfaces generic and also make the Entity class generic.
public interface INamed<T>
{
string Name { get; }
}
public interface ICapability<T> : INamed<T>
{
}
public class RangeCapability<T> : ICapability<T>
{
public string Name { get; set; }
public T Min { get; set; }
public T Max { get; set; }
}
public class SetCapability<T>
{
public string Name { get; set; }
public HashSet<T> Set { get; set; }
}
public interface IRequirement<T> : INamed<T>
{
}
public class Requirement<T> : IRequirement<T>
{
public string Name { get; set; }
public T Value { get; set; }
}
public class Entity<T>
{
List<ICapability<T>> Capabilities { get; set; }
public bool IsSatisfying(List<IRequirement<T>> requirements)
{
foreach (var requirement in requirements)
{
var capability = Capabilities.FirstOrDefault(x => x.Name == requirement.Name);
//how to check if here if types match and if req. within range or in collection?
if(capability is INamed<T>)
{
Console.WriteLine("types match");
}
}
}
}
I broke my head over this already. So here is the situation. I have two types of documents with similar properties. High-level (base-level) properties (Name, Date) are required in one place, "Rows" are required to create specific document to send to another system. How it is implemented now:
Data classes:
public abstract class BaseDocument
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
public abstract class BaseDocument<TRowType> : BaseDocument
{
public abstract List<TRowType> Rows { get; set; }
}
public class DocumentTypeOne : BaseDocument<RowTypeOne>
{
public override List<RowTypeOne> Rows { get; set; }
}
public class DocumentTypeTwo : BaseDocument<RowTypeTwo>
{
public override List<RowTypeTwo> Rows { get; set; }
}
public class RowTypeOne
{
public int Cost { get; set; }
}
public class RowTypeTwo
{
public int Change { get; set; }
}
ProcessorClass:
public class DocumentsProcessor
{
public void ProcessDocument(BaseDocument doc)
{
switch (doc)
{
case DocumentTypeOne t1:
ProcessDocumentTypeOne((DocumentTypeOne)doc);
break;
case DocumentTypeTwo t2:
ProcessDocumentsTypeTwo((DocumentTypeTwo)doc);
break;
default:
throw new ArgumentException($"Unhandled type {nameof(doc)}");
}
}
public void ProcessDocumentTypeOne(DocumentTypeOne docOne)
{
// specific actions
}
public void ProcessDocumentsTypeTwo(DocumentTypeTwo docTwo)
{
// other specific actions
}
}
I know that downcasting is not good. But I have no ideas how to change it.
I can make base class with generic parameter but then I'll lost ability to work with only base-level properties. And this will require to rewrite class that return List.
What's the way to solve it? And is it needed to be solved?
You might wanna use interfaces.
public interface IBaseDocument
{
string Name { get; set; }
DateTime Date { get; set; }
}
public interface IDocumentWithRows<T>
{
List<T> Rows { get; set; }
}
public class DocumentTypeOne: IBaseDocument, IDocumentWithRows<RowTypeOne>
{
string IBaseDocument.Name { get; set; }
DateTime IBaseDocument.Date { get; set; }
List<RowTypeOne> IDocumentWithRows<RowTypeOne>.Rows { get; set; }
}
public class DocumentProcessor
{
public void ProcessDocument(IBaseDocument doc)
{
switch (doc)
{
case DocumentTypeOne docTypeOne:
ProcessDocumentTypeOne(docTypeOne);
break;
case DocumentTypeTwo docTypeTwo:
ProcessDocumentTypeTwo(docTypeTwo);
break;
}
}
}
I am writing some integration with a third-party eLearning platform that returns a variety of responses in different schemas depending on the function of my restful API call. Since these responses come back in several different schemas, I'm trying to create a series of response object classes that inherit a base Response object class that would contain the common JSON sections (aka "data" and "message") and allow each individual response object to override or have additional members/classes based on the response being returned.
Here are a couple examples of how the schemas may differ.
Class Creation Return:
{
"data": [
{
"row_index": 0,
"success": true,
"message": "string"
}
]
}
User Creation Return:
{
"data": [
{
"message": [
{
"id": "string",
"message": "string"
}
],
"success": true,
"user_id": 0
}
]
}
As you can see, the different responses have different schemas. The Class Create only returns a message member within the data object, and the User Create has a separate message object altogether.
Since I can't have a class called data within each object because of ambiguity, I'm thinking I need to create a Base Response Object that contains the common members and allows me to override or add on the fly as necessary.
I've tried to create a Base Class:
public class BaseResponse
{
public List<Data> data;
public class Data
{
public bool success { set; get; }
}
}
as well as an example derived class:
public class ClassroomResponse : BaseResponse
{
public class Data
{
public int row_index { get; set; }
public string message { get; set; }
}
}
I'm not sure if this is only possible with functions and not classes as I'm trying to do above? Is there a way to add additional members to the derived object's (row_index and message are not members of all responses, so I'd like to be able to grab those as needed)
You could either create individual, distinct classes for each type, which might be the right option here depending on the other variants you haven't shown. Or you can use generics. There's a few ways you can do this, but here is one way you might do it.
A base class for the overall response:
public abstract class Response<TData>
{
[JsonProperty("data")]
public TData Data { get; set; }
}
A base class for the Data objects:
public abstract class BaseData<TMessage>
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("message")]
public TMessage Message { get; set; }
}
The response type for class creation:
public class ClassData : BaseData<string>
{
[JsonProperty("row_index")]
public int RowIndex { get; set; }
}
The response types for user creation:
public class UserData : BaseData<UserMessage>
{
[JsonProperty("user_id")]
public int UserId { get; set; }
}
public class UserMessage
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
And finally the overall response types:
public class ClassResponse : Response<ClassData>
{ }
public class UserResponse : Response<UserData>
{ }
And now you can use the objects like you would normally:
var classData = new ClassResponse {Data = new ClassData {Message = ""}};
var classJson = JsonConvert.SerializeObject(classData);
var userData = new UserResponse {Data = new UserData {Message = new UserMessage {Message = ""}}};
var userJson = JsonConvert.SerializeObject(userData);
I will go by the generics route
Let's say that we have a response for Class creation as
public class ClassResponseObject {
public int row_index { get; set; }
public bool success { get; set; }
public string message { get; set; };
}
and for the User creation:
public class UserResponseObject {
public int user_id { get; set; }
public bool success { get; set; }
public MessageResponseObject message { get; set; };
}
and for the Message
public class MessageResponseObject {
public string id { get; set; }
public string message { get; set; };
}
after seeing the above code we are able to find that we have success property common in both the responses, so lets create a base class with that property and inherit these classes with that.
public class BaseResponseObject {
public bool success { get; set; }
}
public class ClassResponseObject : BaseResponseObject {
public int row_index { get; set; }
public string message { get; set; };
}
public class UserResponseObject : BaseResponseObject {
public int user_id { get; set; }
public MessageResponseObject message { get; set; };
}
at this point another common property we see is message, but both have different types. This can be solved with the use of generics. I am considering that there might be more types for the message property of the response, but it should work in either case.
for this let modify our BaseResponseObject and move the message property there
public class BaseResponseObject<TMessage> {
public bool success { get; set; }
public TMessage message { get; set; }
}
so our response objects will become something like this:
public class ClassResponseObject : BaseResponseObject<String> {
public int row_index { get; set; }
}
public class UserResponseObject : BaseResponseObject<MessageResponseObject> {
public int user_id { get; set; }
}
as the last step we need to define the final class for the actual response
public class APIResponse<TResponse> {
public List<TResponse> data { get; set; }
}
now when you are capture the response for the Class creation you can simply capture it in
APIResponse<ClassResponseObject>
similarly for the User creation, capture it in
APIResponse<UserResponseObject>
I hope this helps.
I'm looking for the best approach of working with different types identically.
I have a web service that goes to specific resource, makes some research and returns an object WebResult, that contains all information about completed operations.
And now I'd like to build a set of different metrics, that will describe all received results. These metrics should provide
different types of data
easy way to collect it
possibility to deserialize it.
Example 1
First I've created separate classes for different metrics
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string ExceptionMessage { get; set; }
public abstract void FillAllMetrics(WebResult result);
}
public class Resource1Statistic : AbstractStatistic
{
public string Title { get; set; }
public string[] Table1_Header { get; set; }
public int Table1_RowCount { get; set; }
public string[] Table2_Header { get; set; }
public int Table2_RowCount { get; set; }
public override void FillAllMetrics(WebResult result)
{
this.Url = result.url;
this.Title = result.data["title"];
this.Table1_Header = result.data["table1.header"].ToObject<string[]>();
//...
}
}
It works, but I'd like to make it in more standard way. One of the reason is that in this approach I have to create separate web form for each metrics.
Example 2
Second working example is universal but redundant: create an abstraction of any datatype
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string Exception { get; set; }
public Dictionary<string, Metric> Metrics { get ;set;}
public abstract void FillAllMetrics(WebResult webResult);
}
public class Metric // Specific class for data
{
public string StringValue { get; set; }
public int? IntegerValue { get; set; }
public string[] ArrayValue { get; set; }
public DateTime? DateTimeValue { get; set; }
}
public class Resource1Statistic : AbstractStatistic
{
public override void FillAllMetrics(WebResult result)
{
this.Metrics.Add("title",
new Metric() { StringValue = result.data["title"].ToString() });
this.Metrics.Add("Table1 Header",
new Metric() { ArrayValue = result.data["table1.header"].ToObject<string[]>() });
//...
}
It works, but I'm sure there is more elegant solution. I don't like to take all these null values in json.
Examples 3
Generic solution (regarding to Adwaenyth)
public abstract class AbstractStatistic
{
public string Url { get; set; }
public string Exception { get; set; }
public List<AbstractMetric> Metrics { get ;set;}
public abstract void FillAllMetrics(WebResult webResult);
}
public abstract class AbstractMetric{}
public class Metric<T> : AbstractMetric
{
public string Name { get; set; }
public T Value { get; set; }
public string Type { get; private set; }
public Metric()
{
this.Type = typeof(T).ToString();
}
}
public class Resource1Statistic : AbstractStatistic
{
public override void FillAllMetrics(WebResult result)
{
this.Metrics.Add(new Metric<string>()
{ Name = "title",
Value = result.data["title"].ToString() });
this.Metrics.Add(new Metric<string[]>()
{ Name = "Table1 Header",
Value = result.data["table1.header"].ToObject<string[]>() });
//...
}
This solution looks nice, but I have to write custom deserializer.
What do you think, is there some good pattern that fits to my task? Or what's the best approach?
I have a number of classes that are all related conceptually, but some more-so at the details level than others. For example, these three classes have nearly identical properties (although member functions will vary):
public class RelatedA : IRelatedType
{
public string Name { get; set; }
public string Value { get; set; }
public DateTime Stamp { get; set; }
}
public class RelatedB : IRelatedType
{
public string Name { get; set; }
public string Value { get; set; }
public DateTime Stamp { get; set; }
}
public class RelatedC : IRelatedType
{
public string Name { get; set; }
public string Value { get; set; }
public DateTime Stamp { get; set; }
public int Special { get; set; }
}
There are a couple of other classes that are conceptually related to the above 3, but can be a bit different implementation-wise:
public class RelatedD : IRelatedType
{
public string Name { get; set; }
public string Statement { get; set; }
}
public class RelatedE : IRelatedType
{
public string Name { get; set; }
public string Statement { get; set; }
public bool IsNew { get; set; }
}
Instances of these can be created by a factory based on some sort of "type" enumerated value. The problem is that later on when these objects are being used (in a business layer, for example), there could be a lot of code like this:
IRelatedType theObject = TheFactory.CreateObject(SomeEnum.SomeValue);
if (theObject is RelatedC)
{
RelatedC cObject = theObject as RelatedC;
specialVal = cObject.Special;
}
else if (theObject is RelatedD)
{
RelatedD dObject = theObject as RelatedD;
statementVal = dObject.Statement;
}
else if (theObject is RelatedE)
{
RelatedE eObject = theObject as RelatedE;
statementVal = eObject.Statement;
isNewVal = eObject.IsNew;
}
This could be repeated in many places. Is there a better approach to the design that I should be using (there's got to be)?
You could try and factor the differences into seperate classes that are then provided so for example:
IRelatedType theObject = TheFactory.CreateObject(SomeEnum.SomeValue);
RelatedTypeHelper theHelper=TheFactory.CreateHelper(theObject);
theHelper.DoSpecialThing(theObject);
Now you won't have to have all the if else blocks, and if you add a new type which requires new handling, you just whip up a new helper implement the required pieces and you should be good to go. The helper should help document this process.
I would also question why a single method would have such a different implementation for specialVal and StatementVal could be your sample, but It makes me curious what your really doing here. can you simplify things back taking a step back and questioning the point of these being included in this specific hierarchy.