3

I am creating a web service, and want to be a bit more elegant with the return data, instead of having lots of properties that the consumer needs to check.

Depending on what data is generated behind the scenes, I need to be able to return error data, or the data the consumer was expecting.

Instead of having a large flat object, and filling the properties when needed, and letting the user check a 'success' flag, I'd like a single property, Data, to be either an instance of an error class, or an instance of a success class.

This is kind of what I want to do:

class ItemResponse
{
    public bool Success { get; set; }
    public T Data{ get; set; }
}

if( /*acceptance criteria*/ )
{
    ItemResponse<SuccessData> resp = new ItemResponse<SuccessData>();
    resp.Data = new SuccessData();
}
else
{
    ItemResponse<ErrorData> resp = new ItemResponse<ErrorData>();
    resp.Data = new ErrorData();
}

return resp;


public class SuccessData
{

}

public class ErrorData
{

}

Then have the web method return the object, with the generic property.

Is this possible, and if so, how would I do it, given that the webmethod return type has to be typed correctly?

Sam Delaney
  • 335
  • 3
  • 16
  • Do you have control over the code that consumes this service? Can you stream the results? Use JSON? – Dan Pichelman Apr 16 '13 at 13:26
  • I will not be the consumer, no. The result will need to be left as xml. – Sam Delaney Apr 16 '13 at 13:29
  • a web service is basically a function that is exposed as XML. Since a function cannot have different types I don't think this can. Would be interesting to have a super class with the one property and have two sub classes but not sure if that is allowed – tgkprog Apr 16 '13 at 13:33
  • I'm fairly sure I've seen this done before, but I don't have any code to refer to or to verify it did what I think it did. Essentially, the consumer checked the success flag, and then cast the Data property depending on what the value was. – Sam Delaney Apr 16 '13 at 13:38

2 Answers2

4

Generics are a tool for adding type safety during compile time. Consequently, the concrete type used by the consumer of the class must be known at compile time. If you create a function

List<T> myFunction<T>() {...}

...then you need to specify T when calling it, e.g.

var myResult = myFunction<int>();

...which makes the concrete type known at compile time. (Even if you don't specify T because it can be infered, the concrete type is also known at compile time.)

You, however, want the generic type of Data to be determined at run-time: If an error occured, you return an ItemResponse<SuccessData>, otherwise an ItemResponse<ErrorData>. That's just not how generics work.

Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • Thanks. Something didn't feel right when I was trying to work through the logic of it. All the answers here are confirming that. – Sam Delaney Apr 16 '13 at 14:59
2

Short version, you can't do what you're suggesting as you've laid it out.

Long(er) version Part A:

A web service can be considered like a class' method, and actually is a method off of your web service class. I would recommend going over some web service tutorials in order to get a better grasp of the mechanics behind setting up a web service. MSDN has a number of Microsoft stack specific tutorials that are easily found with your favorite search engine.

The return object off of a method is not allowed to have polymorphic behavior, which is essentially what your asking for.

This pseudo code is equivalent to what you're trying to create and that's why the compiler isn't allowing it. It doesn't know which DoSomething() you're attempting to call.

class myFoo
{
     public SuccessResponse DoSomething() {....}  
     public ErrorResponse DoSomething() {....}
}

Alternatively, you could envisage something like this:
public [SuccessResponse | ErrorResponse] DoSomething()
but that fails for what should be obvious reasons as well. C# simply doesn't support polymorphic returns.

Part B Even if we focus on just resp.Data, that object still has to be declared as some sort of type.

class Response
{
     public Collection<someType> Data;
}

If your SuccessData and ErrorData implement the same interface then someType could simply be IyourInterface but that raises another issue. Namely, how will the end user know whether they were given good data in Data or whether there is an error response tucked in there instead.

WCF, I believe, will be nice enough to serialize IyourInterface for you so long as you declare it as a public part of the WCF service object. But that still doesn't resolve how your end user will know what to do.


If you're willing for a little less elegance in the response, a classic pattern is to simply bundle your success data and error objects together into another response class like this:

class myResponse
{
    public SuccessResponse myRespData;
    public ErrorResponse myError
}

Now, the end user checks to see if there's an error if they care. Presuming no error, then they go and look into the response data.


Based upon your comment, yes, you can do the following too:

class Response
{
    public List<IYourData> Data;
    public YourEnum ReturnType;
}

public class ResponseData : IYourData { ... }  
public class ErrorData : IYourData { ... }  

And then on the client, you can perform a simple check like this:

if( ReturnType == YourEnum.Success ) { ... }  
else if( ReturnType == YourEnum.Error ) { ... }  
else ... 

WCF will handle the serialization of List for you. It'll either convert to an array or pass the collection directly depending upon what settings you have in place. There are some SO Q&A's that handle that particular aspect.

  • "Essentially, the consumer checked the success flag, and then cast the Data property depending on what the value was." - this is what I think the example I had seen did. I think for simplicity, have two objects, where the consumer can check if an error response exists first, would be the best. No explanation would then be necessary to use it. Perhaps what I had seen earlier (it must have used the implementing the same interface usage) is overkill and over engineering. Out of interest, could you show how that would work? Thanks for the well written response. – Sam Delaney Apr 16 '13 at 13:47
  • @GlenH7 - I edited to fix the formatting issues you ran into. You can display code by indenting four spaces, and then all the special symbols like `<>` show up, and it attempts syntax coloring. – Bobson Apr 16 '13 at 14:16
  • @Bobson Thanks! I still struggle with that aspect... –  Apr 16 '13 at 14:19
  • I think you can also use `` tags instead of `
    ` tags, but I'm not positive.
    – Bobson Apr 16 '13 at 14:21
  • @Bobson - in my edit on P.SE (which died in the migration) I was trying to use the `` tags, which allowed for the `<>` to show up correctly, but it wasn't showing the code formatting despite using the `` or `` tags. Clearly I need more practice writing code on StackExchange... –  Apr 16 '13 at 14:27
  • @SamDelaney - See [Heinzi's answer](http://stackoverflow.com/a/16039394/1345223) as well for another aspect to consider. You can use a generic on the return object of a service, but you'll still need to consider how the end user will identify what type of data it is. To make that work, you could wrap `YourEnum` as a member of `IYourDataInt`. –  Apr 16 '13 at 14:33
  • -1 for posting a link to legacy ASMX services, which should not be used for new development. – John Saunders Apr 16 '13 at 14:55
  • Consider using a SOAP Fault to indicate errors instead of passing an error code the user will have to check. – John Saunders Apr 16 '13 at 14:56
  • @JohnSaunders Hi John, I've been specifically asked to use the ASMX services, so this answer addresses this for me. WCF may be the preferred way, but it is correct to say they should not be used? – Sam Delaney Apr 16 '13 at 15:17
  • @JohnSaunders - Apologies for adding a dated tutorial. When I first wrote this answer on P.SE, the question was more vague and a general "here's how to jump into this services stuff" tutorial was an appropriate type of link. Since my initial answer, the question was updated and then migrated here to SO. –  Apr 16 '13 at 16:04
  • A tutorial on obsolete technology was not appropriate. – John Saunders Apr 16 '13 at 16:27
  • @SamDelaney: ASMX is a legacy technology, and should not be used for new development. WCF should be used for all new development of web service clients and servers. One hint: Microsoft has retired the [ASMX Forum](http://social.msdn.microsoft.com/Forums/en-US/asmxandxml/threads) on MSDN. See also "[Microsoft says: ASMX Web Services are a “Legacy Technology”](http://johnwsaunders3.wordpress.com/2009/07/03/microsoft-says-asmx-web-services-are-a-%E2%80%9Clegacy-technology%E2%80%9D/)". – John Saunders Apr 16 '13 at 19:36
  • "WCF, I believe, will be nice enough to serialize IyourInterface for you so long as you declare it as a public part of the WCF service object. But that still doesn't resolve how your end user will know what to do.". If WCF is the better option, and given I can get my boss to agree to use it, what is the best approach to what I wanted to do in ASMX, in WCF? – Sam Delaney Apr 17 '13 at 09:33
  • @SamDelaney - I would encourage some additional research before concluding that ASMX can't support the collection you would like to use. I said that WCF can handle it because I have some code doing so, but that doesn't preclude ASMX from doing so. Check out this SO question on [ASMX vs WCF](http://stackoverflow.com/q/2448472/1345223). Not everyone agrees with Saunders' conclusions and this may not be the appropriate point to migrate your existing services technology stack. In short, further evaluate your choices before switching. –  Apr 17 '13 at 10:45