SSRS Report credential issue - reporting-services

Hi I get the following error when try to accessing the report server url. How do I pass the windows credential to the ssrs report. For your info, the report server is configure in different server therefore it will ask for the authentication to login. Please help. Thanks
System.Net.WebException: The request failed with HTTP status 401:
Unauthorized. at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.GetSecureMethods()
at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.IsSecureMethod(String
methodname) at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.SetConnectionSSLForMethod(String
methodname) at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.ProxyMethodInvocation.Execute[TReturn](RSExecutionConnection
connection, ProxyMethod1 initialMethod, ProxyMethod1 retryMethod) at
Microsoft.Reporting.WebForms.Internal.Soap.ReportingServices2005.Execution.RSExecutionConnection.LoadReport(String
Report, String HistoryID) at
Microsoft.Reporting.WebForms.SoapReportExecutionService.LoadReport(String
report, String historyId) at
Microsoft.Reporting.WebForms.ServerReport.EnsureExecutionSession() at
Microsoft.Reporting.WebForms.ServerReport.SetParameters(IEnumerable`1
parameters)

You need to set
ReportViewer1.ServerReport.ReportServerCredentials = new MyReportServerConnection();
where your MyReportServerConnection implementation is something like this:
private sealed class MyReportServerConnection : IReportServerConnection2
{
[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken);
public WindowsIdentity ImpersonationUser
{
get
{
// Use credentials from config file
IntPtr userToken = IntPtr.Zero;
bool success = LogonUser(
"reportusername",
"reportuserdomain",
"reportuserpassword",
9,//LOGON_TYPE_NEW_CREDENTIALS
3,//LOGON32_PROVIDER_WINNT50
out userToken);
if (!success)
{
throw new Exception("Logon user failed");
}
return new WindowsIdentity(userToken);
}
}
public ICredentials NetworkCredentials
{
get
{
return null;
}
}
public bool GetFormsCredentials(out Cookie authCookie, out string userName, out string password, out string authority)
{
authCookie = null;
userName = null;
password = null;
authority = null;
// Not using form credentials
return false;
}
public Uri ReportServerUrl
{
get
{
return new Uri("http://reportserverurl/ReportServer");
}
}
public int Timeout
{
get
{
return 60000; // 60 seconds
}
}
public IEnumerable<Cookie> Cookies
{
get
{
// No custom cookies
return null;
}
}
public IEnumerable<string> Headers
{
get
{
// No custom headers
return null;
}
}
}

Related

How to set JWT type in JWT Header

Not able to set the JWT token typ in token header.
This is for making secure API's which i have already developed in JAX-RS. Basically i have generating a token by Jwts.builder() method, and in return i am getting token in APPLICATION_JSON, ant i paste this token at https://jwt.io/ Debugger. So i got to know that there is no token type specified token header,there is only {
"alg": "HS512"
}
Maybe this could be a reason that i cannot access secured API's. When i try to access secured API's then i got "Signed Claims JWSs are not supported" exception.
AuthenticationService.java
private String issueToken(String login, String password) {
LocalDateTime now = LocalDateTime.now().plusMinutes(10L);
Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
Date jwtExpiry = Date.from(instant);
String jwtToken = Jwts.builder().setSubject(login).setIssuer("XYZ").setIssuedAt(new Date())
.setExpiration(jwtExpiry).signWith(SignatureAlgorithm.HS512, "secretKey").compact();
return jwtToken;
}
public class JWTTokenNeededFilter implements ContainerRequestFilter
{
public static final Logger logger = Logger.getLogger(JWTTokenNeededFilter.class);
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String token = requestContext.getHeaderString("userToken");
if (token == null) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
try {
Jwts.parser().setSigningKey("secretKey").parseClaimsJwt(token);
logger.info("Valid Token " + token);
} catch (ExpiredJwtException expiredJwtException) {
logger.info("Token Expires " + expiredJwtException);
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
catch (Exception exception) {
logger.info("Exceptioin " + exception);
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
}
I am expecting token header like this
{
"alg": "HS512",
"typ": "JWT"
}
You can set the header just using the setHeaderParam("typ","JWT") jwt builder method.
https://github.com/jwtk/jjwt#header-parameters
String token = Jwts.builder()
.setHeaderParam("typ","JWT")
.setSubject(user.getUserId().toString())
.claim("Roles",authResult.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_TIME))
.signWith(Keys.hmacShaKeyFor(key))
.compact();
You can set using header as explained here : https://github.com/jwtk/jjwt/issues/174
Header header = Jwts.header();
header.setType("JWT");
Then set header to builder (I haven't compiled the code)
private String issueToken(String login, String password) {
LocalDateTime now = LocalDateTime.now().plusMinutes(10L);
Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
Date jwtExpiry = Date.from(instant);
Header header = Jwts.header();
header.setType("JWT");
//set additional headers
String jwtToken =
Jwts.builder().setHeader((Map<String, Object>)
header).setSubject(login).setIssuer("XYZ").setIssuedAt(new
Date())
.setExpiration(jwtExpiry).signWith(SignatureAlgorithm.HS512,
"secretKey").compact();
return jwtToken;
}
you can use Header.JWT_TYPE static field in Header interface. This is more cleanable and avoid boilerplate code: For example:
private static String generateToken() {
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256, secretKey)
.setHeaderParam("typ", Header.JWT_TYPE)
.compact();
}
and output which you want:
{
"typ": "JWT",
"alg": "HS256"
}

Get data array from sqlite database and post to API via json object... can be possible?

newbie here... i was developing app that send my data to api via retrofit. my code was working but it sends 1 data input only at the time.... in my case, i've like to do is I want to get more saved data in my sqlite (example 5 data saved) and send it all on api via json object.
This is my Activity:
DatabaseHelper databaseHelper2 = new
DatabaseHelper(getApplicationContext());
SQLiteDatabase db2 =
databaseHelper2.getWritableDatabase();
Cursor cursor =
databaseHelper2.retrieveSettingFromLocalDatabase(db2);
while (cursor.moveToNext()) {
ADDRESS =
cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_ADDRESS));
PORT =
cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_PORT));
TIMEINTERVAL=cursor.getString
(cursor.getColumnIndex(DatabaseHelper.SETTING_TIME_INTERVAL));
}
portInts=Integer.parseInt(PORT);
MapDetails mapDetails = new MapDetails(gg, lat, lon,
well, "0", portInts); //Datas ive get to send in api
List<MapDetails> data = new ArrayList<>();
data.add(mapDetails);
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("http://" + ADDRESS + ":" + PORT)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
Api locate = retrofit.create(Api.class);
Call<MapDetails> call = locate.mapDetailLocation(data);
call.enqueue(new Callback<MapDetails>() {
#Override
public void onResponse(Call<MapDetails> call, Response<MapDetails> response) {
Snackbar.make(view, "" + response,
Snackbar.LENGTH_INDEFINITE)
.setAction("Action", null).show();
}
#Override
public void onFailure(Call call, Throwable t) {
Snackbar.make(view, "" + t.getMessage(),
Snackbar.LENGTH_INDEFINITE)
.setAction("Action", null).show();
}
});
This is my code in API:
public interface Api {
#POST("/api/Database/NewLocation")
Call<MapDetails> mapDetailLocation(#Body List<MapDetails> mapDetails)
}
This is my sample Client:
public class MapDetails {
#SerializedName("SerialNumber")
#Expose
private String SerialNumber;
#SerializedName("Coordinate1")
#Expose
private String Coordinate1;
#SerializedName("Coordinate2")
#Expose
private String Coordinate2;
#SerializedName("DateTime")
#Expose
private String DateTime;
#SerializedName("Speed")
#Expose
private String Speed;
#SerializedName("Port")
#Expose
private int Port;
public MapDetails(String serialNumber, String coordinate1, String
coordinate2, String dateTime, String speed, int port) {
SerialNumber = serialNumber;
Coordinate1 = coordinate1;
Coordinate2 = coordinate2;
DateTime = dateTime;
Speed = speed;
Port = port;
}
public String getSerialNumber() {
return SerialNumber;
}
public void setSerialNumber(String serialNumber) {
SerialNumber = serialNumber;
}
public String getCoordinate1() {
return Coordinate1;
}
public void setCoordinate1(String coordinate1) {
Coordinate1 = coordinate1;
}
public String getCoordinate2() {
return Coordinate2;
}
public void setCoordinate2(String coordinate2) {
Coordinate2 = coordinate2;
}
public String getDateTime() {
return DateTime;
}
public void setDateTime(String dateTime) {
DateTime = dateTime;
}
public String getSpeed() {
return Speed;
}
public void setSpeed(String speed) {
Speed = speed;
}
public int getPort() {
return Port;
}
public void setPort(int port) {
Port = port;
}
}
this is my sqlite database ive like to retrieve:
this is the sample posting ive created at the top
but in my case, ive like to do is this one, getting the saved data from my database and send it like this,:
The reason why only one is being sent is that you are sending outside of the while loop that traverses the Cursor, so only the last is sent.
That is you have :-
while (cursor.moveToNext()) {
ADDRESS = cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_ADDRESS));
PORT = cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_PORT));
TIMEINTERVAL=cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_TIME_INTERVAL));
}
So say the query extracted a Cursor with 10 rows as address 1,2,3....10 (for explantory purposes) then
The loop is entered ADDRESS is set to 1, the next iteration sets it to 2, the next to 3 ..... and finally ADDRESS is set to 10 (same for PORT and TIMEINTERVAL)
After the loop the data is sent so only one is sent (ADDRESS 10).
What you need is along the lines of :-
List<MapDetails> data = new ArrayList<>();
MapDetails mapDetails
Retrofit.Builder builder;
Retrofit retrofit;
Call<MapDetails> call;
Api locate;
while (cursor.moveToNext()) {
ADDRESS = cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_ADDRESS));
PORT = cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_PORT));
TIMEINTERVAL=cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_TIME_INTERVAL));
portInts=Integer.parseInt(PORT);
mapDetails = new MapDetails(gg, lat, lon, well, "0", portInts);
data.clear(); //<<<<<<<< remove previous entries if required????
data.add(mapDetails);
builder = new Retrofit.Builder()
.baseUrl("http://" + ADDRESS + ":" + PORT)
.addConverterFactory(GsonConverterFactory.create());
retrofit = builder.build();
locate = retrofit.create(Api.class);
call = locate.mapDetailLocation(data);
call.enqueue(new Callback<MapDetails>() {
#Override
public void onResponse(Call<MapDetails> call, Response<MapDetails> response) {
Snackbar.make(view, "" + response,
Snackbar.LENGTH_INDEFINITE).setAction("Action", null).show();
}
#Override
public void onFailure(Call call, Throwable t) {
Snackbar.make(view, "" + t.getMessage(),
Snackbar.LENGTH_INDEFINITE).setAction("Action", null).show();
}
}
Note the above is in-principle code. it has not been checked or tested and may therefore contain some errors.
It may be that you can send an entire set e.g. data with populated in which case you may only need up to data.add(mapDetails); in the loop and then have the following code outside the loop.
If I understood your question properly, you need to send JSONArray as payload for the request. But, you made a little mistake while preparing payload from SQLite database. #Mike T pointed out that mistake in his answer to your question.
Follow these codes to fix the problem.
DatabaseHelper databaseHelper2 = new DatabaseHelper(getApplicationContext());
SQLiteDatabase db2 = databaseHelper2.getWritableDatabase();
Cursor cursor = databaseHelper2.retrieveSettingFromLocalDatabase(db2);
List<MapDetails> data = new ArrayList<>(); // declare ArrayList outside and before while loop
while (cursor.moveToNext()) {
ADDRESS = cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_ADDRESS));
PORT = cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_PORT));
TIMEINTERVAL = cursor.getString(cursor.getColumnIndex(DatabaseHelper.SETTING_TIME_INTERVAL));
// pass arguments to MapDetails class constructor
portInts = Integer.parseInt(PORT);
MapDetails mapDetails = new MapDetails(gg, lat, lon, well, "0", portInts); //Datas ive get to send in api
// add prepared data to ArrayList
data.add(mapDetails);
}
// and finally execute network call
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("http://" + ADDRESS + ":" + PORT)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
Api locate = retrofit.create(Api.class);
Call<MapDetails> call = locate.mapDetailLocation(data);
call.enqueue(new Callback<MapDetails>() {
#Override
public void onResponse(Call<MapDetails> call, Response<MapDetails> response) {
Snackbar.make(view, "" + response, Snackbar.LENGTH_INDEFINITE).setAction("Action", null).show();
}
#Override
public void onFailure(Call call, Throwable t) {
Snackbar.make(view, "" + t.getMessage(), Snackbar.LENGTH_INDEFINITE).setAction("Action", null).show();
}
});
PS: I'm not sure why are you taking ADDRESS and PORT from SQLite database. If they're same in every single row you don't need to take it from database right?

sending arraylist from a rest service

I am new to webservices and also REST. I am trying to send a message as a post request to a rest service using rest java client.I am trying to get response of previous requests also(everything in json format). So, am storing the message objects into an arraylist and sending the list as a reponse. But I am not able to get the previous messages. Please tell me if am doing anything wrong.
This is my message model class.
public class Messages {
private String id;
private String message;
public Messages() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
the following is my webservice to receive a message object and return a json array.
#Path("/json/messages")
public class JSONMessages {
public List<Messages> list = new ArrayList<Messages>();
List<Messages> getAllMessages(Messages m){
list.add(m);
return list;
}
#POST
#Path("/post")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response MessageListInJSON(Messages msg) {
System.out.println("message saved");
if(!(msg.getId().equals("1"))){
String output ="Invalid User";
return Response.ok(output).build();
}
else{
return Response.ok(getAllMessages(msg)).build();
}
}
}
Finally, the following is my client side code
public class ClientPost {
public static void main(String[] args) {
try {
ClientConfig clientConfig = new DefaultClientConfig();
Client client = Client.create(clientConfig);
WebResource webResource = client
.resource("http://localhost:8050/lab.rest.webservices/rest/json/messages/post");
//for(int i=0;i<5;i++){
String input = "{\"id\":\"1\", \"message\":\"hey there!\"}";
ClientResponse response = webResource.accept("application/json").type("application/json")
.entity(input).post(ClientResponse.class);
if (response.getStatus() !=200 ) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.getStatus());
}
System.out.println("Output from Server .... \n");
String output = response.getEntity(String.class);
System.out.println(output+"\n");
}
catch (Exception e) {
e.printStackTrace();
}
} }
Now, what I am expecting to see is the message I sent along with the previous responses stored in the array list(which were sent by running the client multiple times manually for now) but always am ending up with only the current message.
output:
Output from Server ....
[{"id":"1","message":"hey there!"}]
To be precise, what I want as output when i run my client several times(or put the try block in loop) is as follows which i am unable to get.
Output from Server ....
[{"id":"1","message":"hey there!"},{"id":"1","message":"hey there!"},{"id":"1","message":"hey there!"},{"id":"1","message":"hey there!"}] .
Resources in JAXRS aren't singletons. That means that for each request, the class JSONMessages is instantiated. So you lose the content of the attribute list. Changing it to static could fix your problem.
There is an annotation Singleton to change this behavior. In this case the resource will be managed as singleton and not in request scope. Here is a sample:
#Singleton
#Path("/json/messages")
public class JSONMessages {
(...)
}
Otherwise, be careful of concurrent accesses on your list. See this question for more details: java concurrent Array List access.
Hope it helps you,
Thierry

WcfFacility and Sequence contains no elements error?

I have wcf library with service contracts and implementations.
[ServiceContract]
public interface IServiceProtoType
{
[OperationContract]
Response GetMessage(Request request);
[OperationContract]
String SayHello();
}
[DataContract]
public class Request
{
private string name;
[DataMember]
public string Name
{
get { return name; }
set { name = value; }
}
}
[DataContract]
public class Response
{
private string message;
[DataMember]
public string Message
{
get { return message; }
set { message = value; }
}
}
public class MyDemoService : IServiceProtoType
{
public Response GetMessage(Request request)
{
var response = new Response();
if (null == request)
{
response.Message = "Error!";
}
else
{
response.Message = "Hello, " + request.Name;
}
return response;
}
public string SayHello()
{
return "Hello, World!";
}
}
I have windows service project that references this library, where MyService is just an empty shell that inherits ServiceBase. This service is installed and running under local system.
static void Main()
{
ServiceBase.Run(CreateContainer().Resolve());
}
private static IWindsorContainer CreateContainer()
{
IWindsorContainer container = new WindsorContainer();
container.Install(FromAssembly.This());
return container;
}
public class ServiceInstaller : IWindsorInstaller
{
#region IWindsorInstaller Members
public void Install(IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
string myDir;
if (string.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
{
myDir = AppDomain.CurrentDomain.BaseDirectory;
}
else
{
myDir = AppDomain.CurrentDomain.RelativeSearchPath;
}
var wcfLibPath = Path.Combine(myDir , "WcfDemo.dll");
string baseUrl = "http://localhost:8731/DemoService/{0}";
AssemblyName myAssembly = AssemblyName.GetAssemblyName(wcfLibPath);
container
.Register(
AllTypes
.FromAssemblyNamed(myAssembly.Name)
.InSameNamespaceAs<WcfDemo.MyDemoService>()
.WithServiceDefaultInterfaces()
.Configure(c =>
c.Named(c.Implementation.Name)
.AsWcfService(
new DefaultServiceModel()
.AddEndpoints(WcfEndpoint
.BoundTo(new WSHttpBinding())
.At(string.Format(baseUrl,
c.Implementation.Name)
)))), Component.For<ServiceBase>().ImplementedBy<MyService>());
}
#endregion
}
In Client Console app I have the following code and I am getting the following error:
{"Sequence contains no elements"}
static void Main(string[] args)
{
IWindsorContainer container = new WindsorContainer();
string baseUrl = "http://localhost:8731/DemoService/{0}";
container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero);
container
.Register(
Types
.FromAssemblyContaining<IServiceProtoType>()
.InSameNamespaceAs<IServiceProtoType>()
.Configure(
c => c.Named(c.Implementation.Name)
.AsWcfClient(new DefaultClientModel
{
Endpoint = WcfEndpoint
.BoundTo(new WSHttpBinding())
.At(string.Format(baseUrl,
c.Name.Substring(1)))
})));
var service1 = container.Resolve<IServiceProtoType>();
Console.WriteLine(service1.SayHello());
Console.ReadLine();
}
I have an idea what this may be but you can stop reading this now (and I apologize for wasting your time in advance) if the answer to the following is no:
Is one (or more) of Request, Response, or MyDemoService in the same namespace as IServiceProtoType?
I suspect that Windsor is getting confused about those, since you are doing...
Types
.FromAssemblyContaining<IServiceProtoType>()
.InSameNamespaceAs<IServiceProtoType>()
... and then configuring everything which that returns as a WCF client proxy. This means that it will be trying to create proxies for things that should not be and hence a Sequence Contains no Elements exception (not the most useful message IMHO but crushing on).
The simple fix would be just to put your IServiceProtoType into its own namespace (I often have a namespace like XXXX.Services for my service contracts).
If that is not acceptable to you then you need to work out another way to identify just the service contracts - take a look at the If method for example or just a good ol' Component.For perhaps.

Force WCF Rest client to use Json deserializer regardless of content-type

How to force WCF Rest client to use Json deserializer regardless of content-type?
I am invoking a REST based web service through WCF.
The service returns JSON body, but has content-type "Application/xml". The WCF framework is now giving me the XmlException.
public class MessageFormatter : IClientMessageFormatter
{
private readonly IClientMessageFormatter _formatter;
public MessageFormatter(IClientMessageFormatter formatter)
{
_formatter = formatter;
}
public object DeserializeReply(System.ServiceModel.Channels.Message message, object[] parameters)
{
return _formatter.DeserializeReply(message, parameters);
}
}
that _formatter.DeserializeReply is throwing XmlException. I can't find any example anywhere to force json deserialization on reply.
Edit - The "message" object when moused over is throwing "{... Error reading body: System.Xml.XmlException: The data at the root level is invalid. Line 1, position 1. ...}"
That same object in another one of my project that communicate with a different REST service (Picasa web services) has a what seems like a xml serialised version of JSON object?? So the problem seems further up the stream. I need to find where this object is originating from. I'll go play around with MessageEncoder class.
Edit - (Adding more info)
public class MyBinding : WebHttpBinding
{
public MyBinding(WebHttpSecurityMode mode)
: base(mode)
{
}
public override BindingElementCollection CreateBindingElements()
{
var result = base.CreateBindingElements();
var replacements = result.OfType<MessageEncodingBindingElement>().ToList();
foreach (var messageEncodingBindingElement in replacements)
{
var index = result.IndexOf(messageEncodingBindingElement);
result.Remove(messageEncodingBindingElement);
result.Insert(index, new MyMessageEncodingBindingElement(messageEncodingBindingElement));
}
return result;
}
}
public class MyMessageEncodingBindingElement : MessageEncodingBindingElement
{
private readonly MessageEncodingBindingElement _element;
public MyMessageEncodingBindingElement(MessageEncodingBindingElement element)
{
_element = element;
}
public override BindingElement Clone()
{
var result = _element.Clone();
if (result is MessageEncodingBindingElement)
return new MyMessageEncodingBindingElement(result as MessageEncodingBindingElement);
return result;
}
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new MyMessageEncoderFactory(_element.CreateMessageEncoderFactory());
}
}
The method CreateMessageEncoderFactory() is never called even when the constructor and Clone method are hit when breakpoints are set. Any help? I'm trying to set a custom MessageEncoder and MessageEncoderFactory class to modify the instantiation process of the Message object.
You can use a WebContentTypeMapper for that. That's a property of the WebHttpBinding, and you can customize how the deserialization will be done by the encoder from that binding, including forcing it to always use the JSON deserializer, regardless of the incoming message's Content-Type. The code below shows how this can be done.
public class StackOverflow_13225272
{
[DataContract]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
public override string ToString()
{
return string.Format("Person[Name={0},Age={1}]", Name, Age);
}
}
[ServiceContract]
public interface ITest
{
[WebGet(ResponseFormat = WebMessageFormat.Json)]
Person GetPerson(string responseContentType);
}
public class Service : ITest
{
public Person GetPerson(string responseContentType)
{
WebOperationContext.Current.OutgoingResponse.ContentType = responseContentType;
return new Person { Name = "John Doe", Age = 29 };
}
}
class AllJsonContentTypeMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Json;
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
#if USE_NETFX4
// This works on .NET 4.0 and beyond
WebHttpBinding binding = new WebHttpBinding();
binding.ContentTypeMapper = new AllJsonContentTypeMapper();
#else
// This works on .NET 3.5
CustomBinding binding = new CustomBinding(new WebHttpBinding());
binding.Elements.Find<WebMessageEncodingBindingElement>().ContentTypeMapper = new AllJsonContentTypeMapper();
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress));
#endif
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
ITest proxy = factory.CreateChannel();
Console.WriteLine("With JSON: {0}", proxy.GetPerson("application/json"));
Console.WriteLine("With XML: {0}", proxy.GetPerson("application/xml"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
This might work.
public class ForceJsonClientMessageFormatter : IClientMessageFormatter
{
private readonly DataContractJsonSerializer _jsonSerializer;
public ForceJsonClientMessageFormatter(Type responseType)
{
_jsonSerializer = new DataContractJsonSerializer(responseType);
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
throw new NotImplementedException("This client message formatter is for replies only!");
}
public object DeserializeReply(Message message, object[] parameters)
{
string messageBody = message.GetBody<string>();
using (MemoryStream messageStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)))
{
messageStream.Seek(0, SeekOrigin.Begin);
object deserializedObject = _jsonSerializer.ReadObject(messageStream);
return deserializedObject;
}
}
}
public class ForceJsonWebHttpBehavior : WebHttpBehavior
{
protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
return new ForceJsonClientMessageFormatter(operationDescription.Messages[1].Body.ReturnValue.Type);
}
}
I haven't tried it, but I think this will work. You can create a custom IClientMessageFormatter which overwrites the message format to Json, wrap that in a behavior, and then apply that behavior to your client endpoint configuration.
public class ForceJsonClientMessageFormatterDecorator : IClientMessageFormatter
{
private readonly IClientMessageFormatter _decoratedFormatter;
public ForceJsonClientMessageFormatterDecorator(IClientMessageFormatter decoratedFormatter)
{
_decoratedFormatter = decoratedFormatter;
}
public object DeserializeReply(Message message, object[] parameters)
{
message.Properties[WebBodyFormatMessageProperty.Name] = new WebBodyFormatMessageProperty(WebContentFormat.Json);
return _decoratedFormatter.DeserializeReply(message, parameters);
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
return _decoratedFormatter.SerializeRequest(messageVersion, parameters);
}
}
public class ForceJsonWebHttpBehavior : WebHttpBehavior
{
protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
IClientMessageFormatter decoratedFormatter = base.GetReplyClientFormatter(operationDescription, endpoint);
return new ForceJsonClientMessageFormatterDecorator(decoratedFormatter);
}
}