Exception handling in GRPC - return common object - c#

I googled a lot C# articles how to proceed with that with interceptors. I can divide them on 2 types:
Rethrow RPCException
return default
My problem, i want to return back some common API response object.
public class GrpcResponseBase
{
public int StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
public class GrpcResponse<TData> : GrpcResponseBase
{
public TData Data { get; set; }
...
}
So all the objects i return to client need to be based on that. And the object returned from exception handler too.
But that's the problem. The response objects are autogenerated by protobuf compiler from proto files. Proto doesn't support inheritance, and i don't want to copy-paste those 2-3 fields each time for each "message". And i think it doesn't support generics too.
What can i do ? Maybe don't use interceptors, but use something else ? Please suggest

Related

Managing multiple versions of object in JSON

I have a class in C#, that has a number of variables. Let's call it "QuestionItem".
I have a list of this object, which the user modifies, and then sends it via JSON serialization (with Newtonsoft JSON library) to the server.
To do so, I deserialize the objects that are already in the server, as a List<QuestionItem>, then add this new modified object to the list, and then serialize it back to the server.
In order to display this list of QuestionItems to the user, I deserialize the JSON as my object, and display it somewhere.
Now, the problem is - that I want to change this QuestionItem and add some variables to it.
But I can't send this NewQuestionItem to the server, because the items in the server are of type OldQuestionItem.
How do I merge these two types, or convert the old type to the new one, while the users with the old version will still be able to use the app?
You are using an Object Oriented Language, so you might aswell use inheritance if possible.
Assuming your old QuestionItem to be:
[JsonObject(MemberSerialization.OptOut)]
public class QuestionItem
{
[JsonConstructor]
public QuestionItem(int Id, int Variant)
{
this.Id = Id;
this.Variant = Variant;
}
public int Id { get; }
public int Variant { get; }
public string Name { get; set; }
}
you can extend it by creating a child class:
[JsonObject(MemberSerialization.OptOut)]
public class NewQuestionItem : QuestionItem
{
private DateTime _firstAccess;
[JsonConstructor]
public NewQuestionItem(int Id, int Variant, DateTime FirstAccess) : base(Id, Variant)
{
this.FirstAccess = FirstAccess;
}
public DateTime FirstAccess { get; }
}
Note that using anything different than the default constructor for a class requires you to use the [JsonConstructor] Attribute on this constructor and every argument of said constructor must be named exactly like the corresponding JSON properties. Otherwise you will get an exception, because there is no default constructor available.
Your WebAPI will now send serialized NewQuestionItems, which can be deserialized to QuestionItems. In fact: By default, JSON.NET as with most Json libraries, will deserialize it to any object if they have at least one property in common. Just make sure that any member of the object you want to serialize/desreialize can actually be serialized.
You can test the example above with the following three lines of code:
var newQuestionItem = new NewQuestionItem(1337, 42, DateTime.Now) {Name = "Hello World!"};
var jsonString = JsonConvert.SerializeObject(newQuestionItem);
var oldQuestionItem = JsonConvert.DeserializeObject<QuestionItem>(jsonString);
and simply looking at the property values of the oldQuestionItem in the debugger.
So, this is possible as long as your NewQuestionItem only adds properties to an object and does neither remove nor modify them.
If that is the case, then your objects are different and thus, requiring completely different objects with a different URI in your API, as long as you still need to maintain the old instance on the existing URI.
Which brings us to the general architecture:
The most clean and streamline approach to what you are trying to achieve is to properly version your API.
For the purpose of this link I am assuming an Asp.NET WebApi, since you are handling the JSON in C#/.NET. This allows different controller methods to be called upon different versions and thus, making structural changes the resources your API is providing depending on the time of the implementation. Other API will provide equal or at least similar features or they can be implemented manually.
Depending on the amount and size of the actual objects and potential complexity of the request- and resultsets it might also be worth looking into wrapping requests or responses with additional information. So instead of asking for an object of type T, you ask for an Object of type QueryResult<T> with it being defined along the lines of:
[JsonObject(MemberSerialization.OptOut)]
public class QueryResult<T>
{
[JsonConstructor]
public QueryResult(T Result, ResultState State,
Dictionary<string, string> AdditionalInformation)
{
this.Result = result;
this.State = state;
this.AdditionalInformation = AdditionalInformation;
}
public T Result { get; }
public ResultState State { get; }
public Dictionary<string, string> AdditionalInformation { get; }
}
public enum ResultState : byte
{
0 = Success,
1 = Obsolete,
2 = AuthenticationError,
4 = DatabaseError,
8 = ....
}
which will allow you to ship additional information, such as api version number, api version release, links to different API endpoints, error information without changing the object type, etc.
The alternative to using a wrapper with a custom header is to fully implement the HATEOAS constraint, which is also widely used. Both can, together with proper versioning, save you most of the trouble with API changes.
How about you wrapping your OldQuestionItem as a property of QuestionItem? For example:
public class NewQuestionItem
{
public OldQuestionItem OldItem { get; set; }
public string Property1 {get; set; }
public string Property2 {get; set; }
...
}
This way you can maintain the previous version of the item, yet define new information to be returned.
Koda
You can use something like
public class OldQuestionItem
{
public DateTime UploadTimeStamp {get; set;} //if less then DateTime.Now then it QuestionItem
public string Property1 {get; set; }
public string Property2 {get; set; }
...
public OldQuestionItem(NewQuestionItem newItem)
{
//logic to convert new in old
}
}
public class NewQuestionItem : OldQuestionItem
{
}
and use UploadTimeStamp as marker to understand, what Question is it.

Will changing interface break existing clients

Preface: This code is used within a windows desktop application, client / server application, where the server sends and receives messages to/from the client using SMessage based classes
I have the following interface
public interface IMessage
{
string ID { get; }
string R_ID { get; set; }
DateTime Send { get; }
}
Here is the concrete implementation of this interface:
[Serializable]
public class SMessage : IMessage
{
public string ID { get; set; }
public string R_ID { get; set; }
public DateTime Send{ get; set;}
public SMessage()
{
R_ID = "";
ID = Guid.NewGuid().ToString();
Send = DateTime.UtcNow;
}
public SMessage(SMessage msg)
{
ID = msg.ID;
Send = msg.UTCSend;
R_ID = msg.R_ID;
}
}
I have released software to the world using the above interface and now I need to add a piece of additional data to this interface "Where"
public interface IMessage
{
string ID { get; }
string R_ID { get; set; }
DateTime Send { get; }
string Where { get; }
}
My question : Will adding this piece of data break existing clients in the field?
If so, how I can I update the interface / concrete classes so existing clients don't break?
Thanks
Additional info:
The SMessage is the base class for other messages that are sent within the application:
public class InstallMessage : SMessage
{
}
public class ChangeState : SMessage
{
}
How can I keep from breaking existing clients?
So, if I do this:
public interface IMessage2 : IMessage
{
string Where { get; }
}
And this:
public class SMessage : IMessage2
{
// the correct implementation for IMessage2 is added and omitted here for brevity
}
So what I am unsure about is how do I handle the case where I don't know if the message is from IMessage2 or not? ( NOTE: this code is in the client and server applications )
EXISTING CODE IN THE FIELD:
public void ReceiveChange( ChangeState msg )
{
string x = msg.ID.ToString();
}
NEW CODE THAT WILL BE SENT OUT WITH NEXT VERSION:
public void ReceiveChange( ChangeState msg )
{
string x = msg.ID.ToString();
// do I need to do some converting to keep from breaking ?
IMessage2 iMsg = msg as IMessage2;
if( iMsg2 != null )
{
string y = iMsg2.Where;
}
}
Thanks
Your interface's consumers won't complain, but the implementations will.
If you want to avoid this, then create a new interface that extends from the old one:
public interface INewMessage : IMessage
{
string Where { get; set; }
}
If in an WebAPI scenario.
It will only break existing clients if you have a dependency upon the newly added fields in a method that accepts IMessage as a parameter.
public void ServiceMethod(IMessage message) {
if (message.Where == null)
throw new ArgumentException("message.Where is null");
}
you can add things to interfaces and as long as you have code to properly handle the missing information existing clients will be fine.
The proper way to handle this though is to 'version' your services and data contracts. I find namespace versioning the easiest to maintain. You would define a new namespace (say v2) and redefine everything that actually changes, methods, data contracts, etc. And then in your routing, route the v2 messages (http://acme.com/api/v2/messages) to the new namespace or if not specially routed (http://acme.com/api/messages) route it to the old namespace.
If in a directly referenced library.
Then yes - it will break existing clients. Unless your factory that produces concrete implementations can determine which the client wants. Something similar to the WebAPI routing - but for directly referenced libraries. But this is extremely difficult.
Yes, it will break the existing clients if they implmented their own classes that use IMessage that do not derive from SMessage. This is the reason why Microsoft has not updated interfaces in the .NET framework between versions to add new features. For example in .NET 4.5 DbDataReader got new async methods that returned tasks but they could not update IDataReader because that would have broken anyone who implemented IDataReader without deriving from DbDataReader.
If you don't want to break the code of people who created classes with IMessage but without using SMessage you must either create a new derived interface that has the additional field (For example this is what COM objects do, you will often see ISomeInterface, ISomeInterface2, ISomeInterface3 etc.) or not update the interface at all and only update concrete implementations that other people may have derived from.

Save methods and results

I currently have a method to save a user.
public PersonDto Save(PersonDto personDto)
However, if, while saving, I find there is a duplicate username, or some other issue with the data - my only way to respond to this is to throw an exception.
throw new Exception("Username exists");
I have read that exceptions shouldn't be used for 'Business Requirement Transgressions'.
Is there a better way to return results to my calling methods? I need to return to PersonDto, but also, some information about any issues. Is there a common practice or model for doing this? Maybe return a 'SaveResult' object, which contains a Object SavedObject (in this case, my PersonDto), as well as some other properties like string SaveResult and bool Success?
Maybe implement a generic wrapper for use with all such requests.
enum Disposition
{
OK,
Warning,
Error
}
class Response<T>
{
public T Result { get; set; }
public Disposition Disposition { get; set; }
public string Message { get; set; }
}
i.e.:
public Response<PersonDto> Save(PersonDto personDto)
This way you can specify some metadata for each of your return values.
I recommend using a SaveResult approach to your problem e.g.
public class SaveResult
{
public PersonDto { get; set; }
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
var result = Save(person);
if (!result.Success)
{
Console.WriteLine(result.ErrorMessage);
}
Or something like that. This will allow you to pass back the PersonDto, but also signal to the API caller that an error occurred when trying to save.
There's nothing really wrong with throwing an exception, but as you also want to return the person DTO, then you might as well use a specific method return type for that purpose.

MongoDb C# failed to serialize the response

Class (Entity)
public class Entity
{
public ObjectId Id { get; set; }
public Entity()
{
Id = ObjectId.GenerateNewId();
}
}
Class (Member)
public class Member : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string MobileNumber { get; set; }
}
Action
public dynamic Get()
{
var response = UnitOfWork.MemberRepository.GetMembers();
return response;
}
I'm building a API using .NET WebAPI and using mongodb as a datastore, I'm have some troubles serializing the responding object from the database.
Can't understand why, search the internet a while and found similar problems with no solutions. Either I'm a bad google searcher or there answer is hidden somewhere deep:)
Full stack trace: http://pastie.org/8389787
This is little guessing, but the code really isn't too telling.
I'm pretty sure this is because the C# Mongo driver's BsonDocument exposes a ton of properties like AsBoolean, AsInt, AsString, etc. Calling those getters on data that isn't convertible to the respective type causes an exception. While I don't see them in the stack trace, that might be a compiler optimization.
One solution is to make the code strongly-typed (if it isn't already). I don't know what UnitOfWork.MemberRepository.GetMembers(); is, but it hides what you're doing and it's also not clear what it returns. You're losing a lot of the advantages of the C# driver. The Collection<T> class is pretty much a repository pattern already by the way.
A cleaner approach (they aren't mutually exclusive) is to not serialize the database object to the outside world, but use DTO for the WebAPI side and translate between them, for instance using AutoMapper. I would always do this, because you're throwing an object that might be decorated with DB-Attributes in a serializer you don't know - that could lead to all sorts of problems. Also, you often want to hide certain information from the outside, or make it read-only.
Another option is to use ServiceStack.Text as a JSON-serializer instead, which tends to cause less trouble in my experience.

Serializing complex objects for WCF

I'm trying to pass a complex object via Windows Communication Foundation, but I get Read errors. I'm able to binaryFormat the object to a file and reload and deserialize it. All the components/ referenced component Classes are marked with the Serializable attribute. As a work round I have serialized the object to a memory stream, passed the memory stream over WCF and then deSerialized the memory stream at the other end. Although I could live with this solution it doesn't seem very neat. I can't seem to work out what the criteria are for being able to read from the proxy. Relatively simple objects, even ones that include a reference to another class can be be passed and read without any attribute at all. Any advice welcomed.
Edit: Unrecognised error 109 (0x6d) System.IO.IOException the Read Operation Failed.
Edited As Requested here's the class and the base class. Its pretty complicated that's why I didn't include code at the start, but it binary serializes fine.
[Serializable]
public class View : Descrip
{
//MsgSentCoreDel msgHandler;
public Charac playerCharac { get; internal set;}
KeyList<UnitV> unitVs;
public override IReadList<Unit> units { get { return unitVs; } }
public View(Scen scen, Charac playerCharacI /* , MsgSentCoreDel msgHandlerI */)
{
playerCharac = playerCharacI;
//msgHandler = msgHandlerI;
DateTime dateTimeI = scen.dateTime;
polities = new PolityList(this, scen.polities);
characs = new CharacList(this, scen.characs);
unitVs = new KeyList<UnitV>();
scen.unitCs.ForEach(i => unitVs.Add(new UnitV(this, i)));
if (scen.map is MapFlat)
map = new MapFlat(this, scen.map as MapFlat);
else
throw new Exception("Unknown map type in View constructor");
map.Copy(scen.map);
}
public void SendMsg(MsgCore msg)
{
msg.dateT = dateTime;
//msgHandler(msg);
}
}
And here's the base class:
[Serializable]
public abstract class Descrip
{
public DateTime dateTime { get; set; }
public MapStrat map { get; set; }
public CharacList characs { get; protected set; }
public PolityList polities { get; protected set; }
public abstract IReadList<Unit> units { get; }
public GridElList<Hex> hexs { get { return map.hexs; } }
public GridElList<HexSide> sides { get { return map.sides; } }
public Polity noPolity { get { return polities.none; } }
public double hexScale {get { return map.HexScale;}}
protected Descrip ()
{
}
public MapArea newMapArea()
{
return new MapArea(this, true);
}
}
I suggest that you take a look at the MSDN documentation for DataContracts in WCF since that provides some very helpful guidance.
Update
Based on the provided code and exception information, there are two areas of suspicion:
1) Collections and Dictionaries, especially those that are generics-based, always give the WCF client a hard time since it will not differentiate between two of these types of objects with what it considers to be the same signature. This will usually result in a deserialization error on the client, though, so this may not be your problem.
If it is your problem, I have outlined some of the steps to take on the client in my answer to this question.
2) You could have, somewhere in your hierarchy, an class that is not serializable.
If your WCF service is hosted in IIS, then the most invaluable tool that I have found for tracking down this kind of issue is the built-in WCF logger. To enable this logging, add the following to your web.config file in the main configuration section:
After you have generated the error, double-click on the svclog file and the Microsoft Service Trace Viewer will be launched. The items in red on the left-hand side are where exceptions occur and after selecting one, you can drill into its detail on the right hand side and it usually tells you exactly which item it had a problem with. Once we found this tool, tracking down these issues went from hours to minutes.
You should use DataContract and DataMember attributes to be explicit about which fields WCF should serialise, else also implement ISerializable and write (de-)serialisation yourself.

Categories

Resources