WCF OperationContract and dynamic parameter - c#

I have a WCF Service that based on Writing Highly Maintainable WCF Services. Requests are processed using a CommandService:
[WcfDispatchBehaviour]
[ServiceContract(Namespace="http://somewhere.co.nz/NapaWcfService/2013/11")]
[ServiceKnownType("GetKnownTypes")]
public class CommandService
{
[OperationContract]
public object Execute(dynamic command)
{
Type commandHandlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
dynamic commandHandler = BootStrapper.GetInstance(commandHandlerType);
commandHandler.Handle(command);
return command;
}
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
var coreAssembly = typeof(ICommandHandler<>).Assembly;
var commandTypes =
from type in coreAssembly.GetExportedTypes()
where type.Name.EndsWith("Command")
select type;
return commandTypes.ToArray();
}
}
Everything works great (thanks Steve) but now I need to add the ability to upload a file to the service. From what I've read and based on errors received during testing, WCF needs to use a [MessageContract] when uploading a file using a Stream. So I've decorated my command class and put the non-Stream members into the message header, and updated my binding definition to use streaming:
[MessageContract]
public class AddScadaTileCommand
{
[MessageHeader(MustUnderstand = true)]
public int JobId { get; set; }
[MessageHeader(MustUnderstand = true)]
public string MimeType { get; set; }
[MessageHeader(MustUnderstand = true)]
public string Name { get; set; }
[MessageBodyMember(Order = 1)]
public Stream Content { get; set; }
}
Unfortunately when I call the service with a file to upload I get an error:
There was an error while trying to serialize parameter
http://somewhere.co.nz/NapaWcfService/2013/11:command. The
InnerException message was 'Type 'System.IO.FileStream' with data
contract name
'FileStream:http://schemas.datacontract.org/2004/07/System.IO' is not
expected.
So I added a new method to the service specifically for the file upload request:
[OperationContract]
public void Upload(AddScadaTileCommand addScadaTileCommand)
{
Type commandHandlerType = typeof(ICommandHandler<>).MakeGenericType(typeof(AddScadaTileCommand));
dynamic commandHandler = BootStrapper.GetInstance(commandHandlerType);
commandHandler.Handle(addScadaTileCommand);
}
This works perfectly, unless I change the AddScadaTileCommand parameter to dynamic in the method definition, in which case I get the same error as above. This appears to indicate that the [MessageContract] attributes are not applied or ignored when using dynamic as the type of the parameter. Is there any way to resolve this or will I need to create separate methods for requests that involve streams?

Related

ServiceStack C# strongly typed client DTO

Here: Recommended ServiceStack API Structure and here: https://github.com/ServiceStack/ServiceStack/wiki/Physical-project-structure are recommendations for how to structure your projects for C# clients to reuse DTOs.
Apparently this is done by including a dll of the DTO assembly. I have searched the web for one example, just Hello World that uses a separate assembly DTO for a C# client in ServiceStack. Perhaps I should be able to break this out myself but so far it has not proven that easy.
Almost all client descriptions are for generic and non-typed JSON or other non-DTO based clients. No one appears interested in typed C# clients like I am (even the ServiceStack documentation I have found). So I thought this would be a good question even if I figure it out myself in the end.
To be clear, I have built and run the Hello World example server. I have also used a browser to attach to the server and interact with it. I have also created a client empty project that can call
JsonServiceClient client = new JsonServiceClient(myURL);
Then I tried to copy over my DTO definition without the assembly DLL as I don't have one. I get ResponseStatus is undefined.
Clearly there is something missing (it appears to be defined in ServiceStack.Interfaces.dll) and if I could create a dll of the DTO I think it would resolve all references.
Can anyone give insight into how to create the DTO assembly for the simple Hello World?
Edited to add code:
using ServiceStack.ServiceClient.Web;
namespace TestServiceStack
{
class HelloClient
{ public class HelloResponse
{
public string Result { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
//Request DTO
public class Hello
{
public string Name { get; set; }
}
HelloResponse response = client.Get(new Hello { Name = "World!" });
}
}
Where the ResponceStatus is undefined.
I was able to find the missing symbol ResponseStatus by adding:
using ServiceStack.ServiceInterface.ServiceModel;
Here is the full code that built. Keep in mind that I found out something else in the process. Once this built it then failed because I was using a DTO from a .NET 4.0 environment in a .NET 3.5 environment. But that is an unrelated issue. Also note that this test code does nothing with the response, it is just an example to get the build working.
using ServiceStack.ServiceClient;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;
using ServiceStack.Service;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost;
using ServiceStack;
using ServiceStack.ServiceClient.Web;
using RestTestRoot; // This is the name of my DTO assembly. You will need to insert your own here.
using ServiceStack.ServiceInterface.ServiceModel;
namespace WebApplicationRoot
{
class HelloClient
{
JsonServiceClient hello_client;
//Request DTO
public class Hello
{
public string Name { get; set; }
}
//Response DTO
public class HelloResponse
{
public string Result { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
//Can be called via any endpoint or format, see: http://mono.servicestack.net/ServiceStack.Hello/
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
//REST Resource DTO
[Route("/todos")]
[Route("/todos/{Ids}")]
public class Todos : IReturn<List<Todo>>
{
public long[] Ids { get; set; }
public Todos(params long[] ids)
{
this.Ids = ids;
}
}
[Route("/todos", "POST")]
[Route("/todos/{Id}", "PUT")]
public class Todo : IReturn<Todo>
{
public long Id { get; set; }
public string Content { get; set; }
public int Order { get; set; }
public bool Done { get; set; }
}
public HelloClient(){
// ServiceStack gateway = new ServiceStack.ClientGateway(
// location.protocol + "//" + location.host + '/ServiceStack.Examples.Host.Web/ServiceStack/');
hello_client = new JsonServiceClient("http://tradetree2.dnsapi.info:8080/");
hello_client.Get<HelloResponse>("/hello/MyTestWorld!");
}
}
}

Call WCF method from client c#

I have created one WCF service Application. There are few methods in Service1.svc.
Here is my IService1.cs
[OperationContract]
GetUserDetailsByEmail_Result GetUserDetailsByEmail(string email);
Here is my Service.svc.cs
public class Service1 : IService1
{
#region GetUserDetails
public GetUserDetailsByEmail_Result GetUserDetailsByEmail(string email)
{
return (new UserManager()).GetUserDetailsByEmail(email);
}
#endregion
}
Here GetUserDetailsByEmail_Result is Complex type created in DemoModel.edmx. It contain some Scalar Property.
Basically what I am trying to do is, I want to call this method from Client(c#) side. Here is my Client Side code
//svc.GetUserDetailsByEmailCompleted += new EventHandler<GetUserDetailsByEmailCompletedEventArgs>(svc_GetUserDetailsByEmailCompleted);
GetUserDetailsByEmail_Result dtbUserDetails = svc.GetUserDetailsByEmailAsync(loginName);
Here svc is the object of Service1Client. Here I am simply calling wcf method. It gives me an error
Cannot implicitly convert type 'void' to 'Demo.DemoServiceReference_Client.GetUserDetailsByEmail_Result'
It works when I use svc_GetUserDetailsByEmailCompleted method. But I want the return data directly in dtbUserDetails. How can I achieve this? Is there any changes in my WCF service or in my client side? Or in WCF method declaration?
You either need to create an object and bind the data to it like some of the people in the comments suggested then mark each property like so:
[DataContract(Namespace = "MyServiceContract.Service1.ComplexObject")]
public class ComplexObject
{
[DataMember(Order = 1, IsRequired = true)]
public String DbItem1{ get; private set; }
[DataMember(Order = 2, IsRequired = false)]
public ComplexBlobData DbItem2{ get; set; }
}
Or if you can open up the DemoModel.edmx(Code Behind) and mark it all with data contract the same way you would mark your own object.
Bottom line anything not marked is not going over the wire.

WCF - Cannot create abstract class

I'm writing a WCF webservice and passing in a complex type as a parameter of the method. The complex type looks like this:
[DataContract(Namespace = "")]
public class MyRequest
{
[DataMember()]
public string TransactionId { get; set; }
[DataMember(IsRequired = true)]
public bool IsRollback { get; set; }
[DataMember(IsRequired = true)]
public OrderType OrderType { get; set; }
[DataMember(IsRequired = true)]
public ICustomerId CustomerId { get; set; }
[DataMember()]
public long OrderId { get; set; }
[DataMember()]
public AnotherComplexType PurchaseInfo { get; set; }
The webservice method looks like this:
[ServiceKnownType(typeof(CustomerIdByName))]
[ServiceKnownType(typeof(CustomerIdByAccount))]
public OrderResult Execute(MyRequest order) {
}
The Interface looks like this:
[KnownType(typeof(CustomerIdByAccount))]
[KnownType(typeof(CustomerIdByName))]
public interface ICustomerId{
string GetId();
}
When I make a request using the SOAP end point, everything works just great. But when passing the request to the REST end point, I get the serialization error.
This is the request I'm using
<MyRequest>
<CustomerId>
<AccountId>59251</AccountId>
</CustomerId>
<IsRollback>false</IsRollback>
<OrderId>0</OrderId>
<OrderType>OrderSubscription</OrderType>
<PurchaseInfo>
<ObjectId>196521</ObjectId>
</PurchaseInfo>
<TransactionId>ABC123</TransactionGuid>
</MyRequest>
Since I had been stuck at this point for too long, I then changed the ICustomerId member to be an abstract class that implements ICustomerId. Again the SOAP end point works fine but sending the request to the rest end point I get an error that states "Cannot create abstract class"
What am I missing or doing wrong here?
Is this failing because the interface is nested in the complex type and not a direct parameter of the webservice method? I've used webservices that receive interfaces as parameter and with the KnownType decorators they work just fine. Same question applies to the abstract class, is this not working because the abstract class is nested within a member of the MyRequest complex type?
This is the error message I am getting:
Element CustomerId from namespace cannot have child contents to be deserialized as an object. Please use XmlNode[] to deserialize this pattern of XML
Have you tried decorating your interface as a RESTful method?
[ServiceContract]
public interface IMyRequest
{
[OperationContract]
[WebInvoke(
UriTemplate = "Requests/GetID",
Method = "POST",
BodyStyle = WebMessageBodyStyle.Wrapped)]
string GetId(MyRequest myRequest);
...
Also:
make sure that the [DataMember] properties match your request payload. Everything that gets passed in your XML request payload must be caught by the serializer in the service. I recommend keeping the naming the same, but you can map it using [DataMember(name="MyProperty")]. Also, your [DataContract] must be mapped to match the name of the parent node of your XML payload like [DataContract(Name="MyRequest")] but only if the class is named differently than the xml node. Do this, and it will deserialize your xml into the server side object/dto
The error you're getting sounds like it's complaining about the complex type inside of your DataContract. Your complex type needs to be decorated for serialization the same as your MyRequest type.
Also ensure your REST endpoint is bound to webHttpBinding

How can I convert an XML file to an instance of a MessageContract class?

I'm attempting to test a [MessageContract] class against an existing sample message, and I'm looking for a way to simplify development by reading the sample message file into an instance of my [MessageContract] class and seeing how it worked out (I'm dealing with a particularly complex contract here, of non-WCF origin).
My [MessageContract] class looks something like this:
[MessageContract(IsWrapped = true, WrapperName = "wrapper", WrapperNamespace = "somens")]
public class RequestMessage
{
[MessageHeader(Name = "HeaderElem", Namespace = "otherns")]
public XElement CorrelationTimeToLive { get; set; }
[MessageBodyMember(Name = "id", Namespace = "somens")]
public XElement id { get; set; }
}
I can read the file into an instance of the Message class, using code such as the following:
var xr = XmlReader.Create("sample_message.xml");
var msg = Message.CreateMessage(xr, int.MaxValue, MessageVersion.Soap12);
That's not particulary helpful, however, because it doesn't allow me to test my [MessageContract] class at all.
Somewhere in the guts of WCF is a system for turning this Message instance into an instance of a particular [MessageContract] class, but what is it?
I just learned how to do this the other day following a talk with a collegue. I think this is what you were asking to do.
namespace MessageContractTest
{
class Program
{
static void Main(string[] args)
{
string action = null;
XmlReader bodyReader = XmlReader.Create(new StringReader("<Example xmlns=\"http://tempuri.org/\"><Gold>109</Gold><Message>StackOverflow</Message></Example>"));
Message msg = Message.CreateMessage(MessageVersion.Default, action, bodyReader);
TypedMessageConverter converter = TypedMessageConverter.Create(typeof(Example), "http://tempuri.org/IFoo/BarOperation");
Example example = (Example)converter.FromMessage(msg);
}
}
[MessageContract]
public class Example
{
[MessageHeader]
public string Hello;
[MessageHeader]
public double Value;
[MessageBodyMember]
public int Gold;
[MessageBodyMember]
public string Message;
}
}
You will need to deserialize the XML into an instance of your data contract. This is what WCF is doing for you under the covers.
Here is a quick tutorial that will show you how to invoke the DataContractSerializer manually for your XML.

Can't deserialize XML in WCF REST service

I've just started playing with the REST starter kit, and I've hit a road block trying to build my own service. I'm trying to create a service for account management, and I can't get the service to serialize my objects, throwing the following error:
Unable to deserialize XML body with root name 'CreateAccount' and root namespace '' (for operation 'CreateAccount' and contract ('Service', 'http://tempuri.org/')) using DataContractSerializer. Ensure that the type corresponding to the XML is added to the known types collection of the service.
Here's the actual code for the service (based off of the 'DoWork' method that came with the project):
[WebHelp(Comment = "Creates a Membership account")]
[WebInvoke(UriTemplate = "CreateAccount", RequestFormat = WebMessageFormat.Xml)]
[OperationContract]
public ServiceResponse CreateAccount(CreateAccount request)
{
try
{
// do stuff
return new ServiceResponse()
{
Status = "SUCCESS",
ErrorMessage = ""
};
}
catch (Exception ex)
{
return new ServiceResponse()
{
Status = "ERROR",
ErrorMessage = ex.Message + "\n\n" + ex.StackTrace
};
}
}
And last, but not least, here's the object that's causing all the trouble:
public class CreateAccount
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public bool SignUpForNewsletter { get; set; }
public string Password { get; set; }
}
Am I missing anything stupid?
Thanks in advance!
It turns out I was missing an extra value in the [DataContract] attribute on the business object.
Should be [DataContract(Namespace = "")]
It appears the problem is a namespace clash between your method name "CreateAccount" and your input type "CreateAccount".
Also, you have to mark your CreateAccount type as a DataContract like so:
[DataContract]
public CreateAccount
{
[DataMember]
public string LastName { get; set; }
...
}
If you want to keep the same name, you can specify a namespace for the CreateAccount class.
I noticed you have a return type as well. Ensure the return type is marked with the DataContract attribute as well. Also, specify the return format like so:
ResponseFormat = WebMessageFormat.Xml
If you don't have it already, I think a [DataContract] attribute above your CreatAccount class.
I had a similar problem, but I did have the DataContract attribute. What I was missing though was the xmlns="http://uri.org" attribute from the root element when trying to read the xml back into the object.
e.g.
<Root_Element xmlns="http://uri.org"><Child_Element/>...</Root_Element>

Categories

Resources