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.
Related
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
I'm looking to retain a ton of functionality I used to have in my codebase from the service layer that I exposed previously using OData services but through ServiceStack, assuming I implement the service logic, I don't want to have to make a ton of new DTO's for requests when this is essentially what i'm trying to achieve unless the framework "forces" me to declare a bunch of extra classes for no functional gain ...
[Route("~/{Type}")]
public class GetRequest
{
public string Type {get; set; }
public string Select { get; set; }
public string Expand { get; set; }
public string Filter { get; set; }
public string GroupBy { get; set; }
public string OrderBy { get; set; }
}
public ServiceBase<T> : Service
{
public virtual IEnumerable<T> Get(GetRequest<T> request) { ... }
}
public FooService : ServiceBase<Foo>
{
public override IEnumerable<Foo> Get(GetRequest<Foo> request) { ... }
}
The only other way I can see to implement this is to basically have to create a FooRequest DTO that inherits from the generic one here and adds nothing.
Whilst this might be the case in some scenarios, for the bulk of the hundreds of endpoints I have to migrate this just seems wasteful and likely will require to me having to result to code generation, something Service Stack claims "isn't needed".
My situation is made worse because I have "multiple data contexts" to consider for example ...
// base implementation for all services, derives from ServiceStack Service
public abstract class ServiceBase<T> : Service { ... }
// core service then one concrete implementation off that
public class CoreService<T> : ServiceBase<T> { ... }
public CoreFooService : CoreService<Foo> { ... }
/// b2b service then one concrete implementation off of that
public class B2BService<T> : ServiceBase<T> { ... }
public class BarB2BService : B2BService<Bar> { ... }
... with my OData based implementation I only need to add each new class to add a point of customisation for that type of data in the stack.
With ServiceStack this still seems to be possible regarding service classes (i think, but i'm not clear on how the routing works) ... where I get confused is understanding the request DTOs which are basically the same in all get requests but seemingly not routeable based on some tpye information in the URL.
Ideally I would like to route a standard Request DTO to a service method by a combination of the HTTP verb used and then something like [Route("~/{Context}/{Type}")] in the url (with that being the attribute usage on the DTO).
I get the feeling though that ServiceStack doesn't work like this and is going to require me to define a new DTO for literally every method on every service and i'm going to have to define a bunch of new services that don't exist with no new implementation details in them just to satisfy the frameworks needs.
Or am i missing some trick in how to use the framework here to avoid this work?
You can have multiple Service base classes but your Request DTO cannot be generic, it has to be a concrete Request DTO, but it can inherit base classes, e.g. All AutoQuery RDBMS Services inherit from QueryDb<T> or QueryDb.
Your Route should start with / (i.e. not ~/) and you could have a single Parameter that accepts any Type:
[Route("/data/{Type}")]
public class GetData
{
public string Type {get; set; }
public string Select { get; set; }
public string Expand { get; set; }
public string Filter { get; set; }
public string GroupBy { get; set; }
public string OrderBy { get; set; }
}
That can be called with:
GET /data/Anything
But your Service should have the same return Type (i.e. adhere to its Service Contract) so a wildcard Service is not going to be useful unless you return the same unstructured Data response like Dictionary<string,object>, List<object>, etc.
I get the feeling though that ServiceStack doesn't work like this and is going to require me to define a new DTO for literally every method on every service and i'm going to have to define a bunch of new services that don't exist with no new implementation details in them just to satisfy the frameworks needs.
Yes ServiceStack Requires every Service is defined by its Request DTO which is the master authority describing that Services contract. This is not just a requirement to appease the Framework, the Request DTO is the message that invokes a Service, which is the only thing generic Service Clients need to send to invoke a Service, which it can't send if it doesn't exist, nor can it have a Typed API (without code-gen) if there are no types.
I have a class which has various child objects:
public class ApplicationPayload
{
public Quote Quote { get; set; }
public IApplication Application { get; set; }
public DeliveryPreferences DeliveryPreferences { get; set; }
}
I have an api controller method which accepts this model:
public async Task<IActionResult> LtdCompanyPost([FromBody] ApplicationPayload payload)
{
}
When submitting to the controller method, properties within classes that implement IApplication are not being validated (validation seems to be being ignored), however, the other objects (Quote / DeliveryPreferences) are being validated as expected.
Is it possible to have my objects implementing IApplication validatable, or is this structure simply not going to work for me?
(I tested the objects implementing IApplication by having them at the same level as Quote/DeliveryPreferences, having removed the interface implementation, and the validation worked as expected, so the validation rules themselves are not the issue).
Any advice? I can give more examples if necessary.
My guess is you are falling foul of the below check in the ComplexModelBinder, an interface has no constructor.
But more broadly, how would it know what implementation of the interface to instantiate?
if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null)
{
var metadata = bindingContext.ModelMetadata;
switch (metadata.MetadataKind)
{
case ModelMetadataKind.Parameter:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter(
modelTypeInfo.FullName,
metadata.ParameterName));
case ModelMetadataKind.Property:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty(
modelTypeInfo.FullName,
metadata.PropertyName,
bindingContext.ModelMetadata.ContainerType.FullName));
case ModelMetadataKind.Type:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType(
modelTypeInfo.FullName));
}
}
https://github.com/aspnet/Mvc/blob/24eaa740f5b1736700d8d91053f60d690f4fc17e/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs#L366,
I got an Employee class and each employee has a list of applied leaves. Is it possible to have the list AppliedLeave as a [DataMember] in WCF?
[DataContract]
public class Employee
{
[DataMember]
public string UserID { get; set; }
[DataMember]
public int EmployeeNumber { get; set; }
[ForeignKey("EmployeeUserID")]
[DataMember]
public List<Leave> AppliedLeave
{
get { return _appliedLeaves; }
set { _appliedLeaves = value; }
}
private List<Leave> _appliedLeaves = new List<Leave>();
...
}
Is there any other way to do this?
thank you for your consideration of this matter
I extend my Question
This is my Leave Class:
[DataContract]
public class Leave
{
[Key()]
[DataMember]
public Guid LeaveId { get; set; }
[DataMember]
public string LeaveType { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public string EmployeeUserID { get; set; }
}
this shows ServiceContract ---->
[ServiceContract]
public interface IEmployeeService
{
[OperationContract]
Employee GetEmployeeByUserId(string userId);
[OperationContract]
void AssignSupervisor(string userId, string supervisorUserId);
[OperationContract]
void DeleteEmployeeByUserId(string userId);
....
}
In Client application,
EmployeeServiceClient employeeService = new EmployeeServiceClient();
Employee employee = employeeService.GetEmployeeByUserId(id);
But when Employee gathered from the service its shows Null for leaves,
Can somebody help me? what have I done wrong here?
Yes, it is possible to return generics from WCF service operations.
But by default they are casted to Array on client side. This can be customized while proxy generation.
WCF: Serialization and Generics
Also you have to decorate the service with all the types to which generics can be resolved, using KnownTypeAttribute.
Known Types and the Generic Resolver
I also found my server side list would always arrive at the client as a null pointer. After browsing around a lot for this problem it strikes me it is nearly always denied at first ("your code should work")
Found the issue.. I had configured my solution using one "WCF Service" project and one "Winforms app" project with a generated service reference. Both interface and implementation of Service1 were in the WCF service project, as expected. But any list member returned null.
When I put my IService1.cs = the interface only = in a separate class library instead, reference the class library on both sides (using) and generate the service reference again, my list does work ! The generated code on the client side looks much simpler.
I did not need any special attributes, change service reference configuration, or interface references for this.
You could use IList<T> instead of List<T>.
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.