I have a web service that service an Excel file
public class ReportService : Service
{
public IReportRepository Repository {get; set;}
public object Get(GetInvoiceReport request)
{
var invoices = Repository.GetInvoices();
ExcelReport report = new ExcelReport();
byte[] bytes = report.Generate(invoices);
return new FileResult(bytes);
}
}
and I setup the object that is retured from the service as
public class FileResult : IHasOptions, IStreamWriter, IDisposable
{
private readonly Stream _responseStream;
public IDictionary<string, string> Options { get; private set; }
public BinaryFileResult(byte[] data)
{
_responseStream = new MemoryStream(data);
Options = new Dictionary<string, string>
{
{"Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{"Content-Disposition", "attachment; filename=\"InvoiceFile.xlsx\";"}
};
}
public void WriteTo(Stream responseStream)
{
if (_responseStream == null)
return;
using (_responseStream)
{
_responseStream.WriteTo(responseStream);
responseStream.Flush();
}
}
public void Dispose()
{
_responseStream.Close();
_responseStream.Dispose();
}
}
Now, the webservice works fine when tested through a browser; but it gives an error message when tested from a unit test. Below is the error message:
System.Runtime.Serialization.SerializationException : Type definitions
should start with a '{', expecting serialized type 'FileResult', got
string starting with:
PK\u0003\u0004\u0014\u0000\u0008\u0000\u0008\u0000�\u000b5K���%\u0001\u0000\u0000�\u0003\u0000\u0000\u0013\u0000\u0000\u0000[Content_Types].xml��
at
ServiceStack.Text.Common.DeserializeTypeRefJson.StringToType(TypeConfig
typeConfig, StringSegment strType, EmptyCtorDelegate ctorFn,
Dictionary2 typeAccessorMap) at
ServiceStack.Text.Common.DeserializeType1.<>c__DisplayClass2_0.b__1(StringSegment value) at ServiceStack.Text.Json.JsonReader1.Parse(StringSegment
value) at ServiceStack.Text.Json.JsonReader1.Parse(String value)
at ServiceStack.Text.JsonSerializer.DeserializeFromString[T](String
value) at
ServiceStack.Text.JsonSerializer.DeserializeFromStream[T](Stream
stream) at
ServiceStack.ServiceClientBase.GetResponse[TResponse](WebResponse
webResponse) at
ServiceStack.ServiceClientBase.Send[TResponse](String httpMethod,
String relativeOrAbsoluteUrl, Object request)
Below is the unit test I used to test the webservice:
[Test]
public void TestInvoiceReport()
{
var client = new JsonServiceClient("http://localhost/report/");
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "[User Name]",
Password = "[Password]"
});
var requestDTO = new GetInvoiceReport();
var ret = client.Get<FileResult>(requestDTO);
Assert.IsTrue(ret != null);
}
Edit:
I am including the definition for my request DTO class:
[Route("/invoices", "GET")]
public class GetInvoiceReport: IReturn<FileResult>
{
}
Any help is appreciated.
Note: if you're making a HTTP Request instead of calling the Service in code, it's an Integration Test instead of a Unit Test.
You haven't provided your GetInvoiceReport Request DTO definition, but if you're returning anything that's not a serialized DTO it should be specified it its IReturn<T> interface, e.g:
public class GetInvoiceReport : IReturn<byte[]> { ... }
Then you'll be able to download the raw bytes with:
byte[] response = client.Get(new GetInvoiceReport());
You can use the Service Clients Request Filters for inspecting the HTTP Response Headers.
I'd also recommend checking out ServiceStack's .NET Service Clients docs which contains extensive info for downloading raw Responses.
Related
We are testing Azure Communication Services in a new project. Specifically, we are looking at the Azure Communication Services for Calling documented here and the quick start project found here.
The general pattern to utilize the service is shown in the following code.
public string AppCallbackUrl => $"{AppBaseUrl}/api/outboundcall/callback?{EventAuthHandler.GetSecretQuerystring}"
// Defined the call with a Callback URL
var source = new CommunicationUserIdentifier(callConfiguration.SourceIdentity);
var target = new PhoneNumberIdentifier(targetPhoneNumber);
var createCallOption = new CreateCallOptions(
new Uri(AppCallbackUrl),
new List<MediaType> { MediaType.Audio },
new List<EventSubscriptionType> { EventSubscriptionType.DtmfReceived });
// Initiate the call
var call = await callClient.CreateCallConnectionAsync(
source, new List<CommunicationIdentifier>() { target }, createCallOption, reportCancellationToken)
.ConfigureAwait(false);
// Register for call back events
RegisterToCallStateChangeEvent(call.Value.CallConnectionId);
The example uses a configuration value or hardcoded secret key to authenticate the Callback Url, as shown below.
[Route("api/[controller]")]
[ApiController]
public class OutboundCallController : ControllerBase
{
[AllowAnonymous]
[HttpPost("callback")]
public async Task<IActionResult> OnIncomingRequestAsync()
{
// Validating the incoming request by using secret set in app.settings
if (EventAuthHandler.Authorize(Request))
{
...
}
else
{
return StatusCode(StatusCodes.Status401Unauthorized);
}
}
}
public class EventAuthHandler
{
private static readonly string SecretKey = "secret";
private static readonly string SecretValue;
static EventAuthHandler()
{
SecretValue = ConfigurationManager.AppSettings["SecretPlaceholder"] ?? "h3llowW0rld";
}
public static bool Authorize(HttpRequest request)
{
if (request.QueryString.Value != null)
{
var keyValuePair = HttpUtility.ParseQueryString(request.QueryString.Value);
return !string.IsNullOrEmpty(keyValuePair[SecretKey]) && keyValuePair[SecretKey].Equals(SecretValue);
}
return false;
}
public static string GetSecretQuerystring => $"{SecretKey}={HttpUtility.UrlEncode(SecretValue)}";
}
Is there a better way to do this in a production environment? How can I incorporate ASP.NET Core authentication with a Callback?
tl;dr: I'm having trouble mocking restease**
Also, I realize I may be totally on the wrong track, so any suggestions / nudges in the right direction would be of great help. I am quite new to this.
I'm making a small HTTP Client library, built around RestEase. RestEase is nice and easy to use, but I'm having trouble mocking the calls for the purpose of unit testing.
I want to use moq and NUnit, but I can't properly mock the RestClient. Example (shortened for brevity):
IBrandFolderApi - interface needed by restease to send calls
public interface IBrandFolderApi
{
[Post("services/apilogin")]
Task<LoginResponse> Login([Query] string username, [Query] string password);
}
BrandfolderClient.cs - the main class
public class BrandfolderClient : IBrandfolderClient
{
private IBrandFolderApi _brandFolderApi { get; set; }
public BrandfolderClient(string url)
{
_brandFolderApi = RestClient.For<IBrandFolderApi >(url);
}
public async Task<string> Login(string username, string password)
{
LoginResponse loginResponse = await _brandFolderApi .Login(username, password);
if (loginResponse.LoginSuccess)
{
....
}
....
return loginResponse.LoginSuccess.ToString();
}
}
The unit tests
public class BrandFolderTests
{
BrandfolderClient _brandfolderClient
Mock<IBrandFolderApi> _mockBrandFolderApii;
[SetUp]
public void Setup()
{
//The test will fail here, as I'm passing a real URL and it will try and contact it.
//If I try and send any string, I receive an Invalid URL Format exception.
string url = "https://brandfolder.companyname.io";
_brandfolderClient = new BrandfolderClient (url);
_mockBrandFolderApii= new Mock<IBrandFolderApi>();
}
....
}
So, I don't know how to properly mock the Restclient so it doesn't send an actual request to an actual URL.
The test is failing at the constructor - if I send a valid URL string, then it will send a call to the actual URL. If I send any other string, I get an invalid URL format exception.
I believe I haven't properly implemented something around the rest client, but I'm not sure where. I'm very stuck on this, I've been googling and reading like crazy, but I'm missing something and I don't know what.
So, I don't know how to properly mock the Restclient so it doesn't send an actual request to an actual URL.
You actually should not have any need to mock RestClient.
Refactor your code to depend explicitly on the abstraction you control
public class BrandfolderClient : IBrandfolderClient {
private readonly IBrandFolderApi brandFolderApi;
public BrandfolderClient(IBrandFolderApi brandFolderApi) {
this.brandFolderApi = brandFolderApi; //RestClient.For<IBrandFolderApi >(url);
}
public async Task<string> Login(string username, string password) {
LoginResponse loginResponse = await brandFolderApi.Login(username, password);
if (loginResponse.LoginSuccess) {
//....
}
//....
return loginResponse.LoginSuccess.ToString();
}
}
removing the tight coupling to static 3rd party implementation concerns will allow your subject to be more explicit about what it actually needs to perform its function.
This will also make it easier for the subject to be tested in isolation.
For example:
public class BrandFolderTests {
BrandfolderClient subject;
Mock<IBrandFolderApi> mockBrandFolderApi;
[SetUp]
public void Setup() {
mockBrandFolderApi = new Mock<IBrandFolderApi>();
subject = new BrandfolderClient(mockBrandFolderApi.Object);
}
//....
[Test]
public async Task LoginTest() {
//Arrange
LoginResponse loginResponse = new LoginResponse() {
//...
};
mockBrandFolderApi
.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(loginResponse);
//Act
string response = await subject.Login("username", "password");
//Assert
mockBrandFolderApi.Verify(x => x.Login(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
}
In production code, register and configure the IBrandFolderApi abstraction with the container, applying what ever 3rd party dependencies are required
Startup.ConfigureServices
//...
ApiOptions apiOptions = Configuration.GetSection("ApiSettings").Get<ApiOptions>();
services.AddSingleton(apiOptions);
services.AddScoped<IBrandFolderApi>(sp => {
ApiOptions options = sp.GetService<ApiOptions>();
string url = options.Url;
return RestClient.For<IBrandFolderApi>(url);
});
Where ApiOptions is used to store settings
public class ApiOptions {
public string Url {get; set;}
//... any other API specific settings
}
that can be defined in appsetting.json
{
....
"ApiSettings": {
"Url": "https://brandfolder.companyname.io"
}
}
so that they are not hard coded all over you code.
The HttpClient comes from System.Net.Http, which is not easy to mock.
You can, however, create a test HttpClient by passing a fake HttpMessageHandler. Here is an example:
public class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly bool _isSuccessResponse;
public FakeHttpMessageHandler(bool isSuccessResponse = true)
{
_isSuccessResponse = isSuccessResponse;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(
new HttpResponseMessage(_isSuccessResponse ? HttpStatusCode.OK : HttpStatusCode.InternalServerError));
}
}
You can create create a test instance of HttpClient as shown below:
var httpClient = new HttpClient(new FakeHttpMessageHandler(true))
{ BaseAddress = new Uri("baseUrl") };
Not sure how you are using verify on _httpClient, its not a mock. but what you are looking for is https://github.com/canton7/RestEase#custom-httpclient. Most people pass in factory for this
//constructor
public httpClientConstructor(string url, IHttpHandlerFactory httpHandler)
{
var httpClient = new HttpClient(httpHandler.GetHandler())
{
BaseAddress = new Uri(url),
};
_exampleApi = RestClient.For<IExampleApi>(url);
}
public interface IHttpHandlerFactory<T>
{
T GetHandler() where T: HttpMessageHandler
}
Thanks Ankit Vijay https://stackoverflow.com/a/68240316/5963888
public class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly bool _isSuccessResponse;
public FakeHttpMessageHandler(bool isSuccessResponse = true)
{
_isSuccessResponse = isSuccessResponse;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(
new HttpResponseMessage(_isSuccessResponse ? HttpStatusCode.OK : HttpStatusCode.InternalServerError));
}
}
[SetUp]
public void Setup()
{
var fakeHandler = new Mock<IHttpHandlerFactory>();
fakeHandler.Setup(e => e.GetHandler() ).Returns( new FakeHttpHandler() );
_httpClient = new HttpClient(fakeHandler.Object);
_exampleApi = new Mock<IExampleApi>();
}
I have a .Net Core(2.1) Web API that has to adapt to an existed .Net framework(4.6.2) system, and the existed system send a request that the Api accepts.
Here is the problem. In the .Net framework system, it calls the api like this:
var request = (HttpWebRequest)WebRequest.Create("http://xxx.xxx/CloudApi/RegionsList");
request.KeepAlive = true;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "*/*";
var data = new Person()
{
Name = "Alex",
Age = 40
};
byte[] dataBuffer;
using (MemoryStream ms = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, data);
dataBuffer = ms.GetBuffer();
}
request.ContentLength = dataBuffer.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(dataBuffer, 0, dataBuffer.Length);
requestStream.Close();
try
{
var response = (HttpWebResponse)request.GetResponse();
Console.WriteLine("OK");
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
}
Here is the api controller code:
[Route("cloudapi")]
public class LegacyController : ControllerBase
{
[HttpPost]
[Route("regionslist")]
public dynamic RegionsList([FromBody]byte[] value)
{
return value.Length;
}
}
Person class:
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
According to this article: Accepting Raw Request Body Content in ASP.NET Core API Controllers
I have made a custom InputFormatter to deal with this case:
public class RawRequestBodyFormatter : IInputFormatter
{
public RawRequestBodyFormatter()
{
}
public bool CanRead(InputFormatterContext context)
{
if (context == null) throw new ArgumentNullException("argument is Null");
var contentType = context.HttpContext.Request.ContentType;
if (contentType == "application/x-www-form-urlencoded")
return true;
return false;
}
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
var contentType = context.HttpContext.Request.ContentType;
if (contentType == "application/x-www-form-urlencoded")
{
using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
var content = ms.ToArray();
return await InputFormatterResult.SuccessAsync(content);
}
}
}
return await InputFormatterResult.FailureAsync();
}
}
But I found that the data I send(the Person class instance) was not in request.Body but in request.Form, and I can't deserialize it Form.
Any help greatly appreciated.
Since you need to read the raw Request.Body, it's better to enable rewind feature.
InputFormatter is overkill for this scenario. InputFormatter cares about content negotiation. Typically, we use it in this way : if the client sends a payload of application/json, we shoud do A ; if the client sends a payload of application/xml, we should do B . But your client (legacy system) only sends x-www-form-urlencoded. Rather than creating InputFormatter, you could create a dead simple ModelBinder to deserialize the payload.
Hack: Your legacy .Net framework(4.6.2) system use BinaryFormatter to serialize the Person class, and your .NET Core website needs to deserialize it to an object of Person. Typically, this requires your .NET Core app and the Legacy .NET Framework system share the same Person assembly. But obviously the original Person targets .NET Framewrok 4.6.2, in other words, this assembly cannot be referenced by .NET Core. A walkaround is to create a type that shares the same name of Person, and create a SerializationBinder to bind a new type.
Suppose in your Person class of the Legacy system is :
namespace App.Xyz{
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
You should create a same class in your .NET Core WebSite:
namespace App.Xyz{
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
Note the namespace should also keep the same.
How to in details.
Create a Filter that enables Rewind for Request.Body
public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context) { }
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.EnableRewind();
}
}
Now you can create a ModelBinder:
public class BinaryBytesModelBinder: IModelBinder
{
internal class LegacyAssemblySerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName) {
var typeToDeserialize = Assembly.GetEntryAssembly()
.GetType(typeName); // we use the same typename by convention
return typeToDeserialize;
}
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var modelName = bindingContext.BinderModelName?? "LegacyBinaryData";
var req = bindingContext.HttpContext.Request;
var raw= req.Body;
if(raw == null){
bindingContext.ModelState.AddModelError(modelName,"invalid request body stream");
return Task.CompletedTask;
}
var formatter= new BinaryFormatter();
formatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
formatter.Binder = new LegacyAssemblySerializationBinder();
var o = formatter.Deserialize(raw);
bindingContext.Result = ModelBindingResult.Success(o);
return Task.CompletedTask;
}
}
Finally, decorate your action method with the Filter, and use the model binder to retrieve the instance :
[Route("cloudapi")]
public class LegacyController : ControllerBase
{
[EnableRewindResourceFilter]
[HttpPost]
[Route("regionslist")]
public dynamic RegionsList([ModelBinder(typeof(BinaryBytesModelBinder))] Person person )
{
// now we gets the person here
}
}
a demo :
Alternative Approach : Use InputFormatter (not suggested)
Or if you do want to use InputFormatter, you should also enable rewind:
[Route("cloudapi")]
public class LegacyController : ControllerBase
{
[HttpPost]
[EnableRewindResourceFilter]
[Route("regionslist")]
public dynamic RegionsList([FromBody] byte[] bytes )
{
return new JsonResult(bytes);
}
}
and configure the services :
services.AddMvc(o => {
o.InputFormatters.Insert(0, new RawRequestBodyFormatter());
});
And also, you should deserialize the person object in the same way as we do in Model Binder.
But be careful the performance!
I know there is an already accepted response, but I came up with a way of parsing the request.Form data and rebuilding the content into an original request.Body format:
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
var contentType = request.ContentType;
if (contentType.StartsWith("application/x-www-form-urlencoded")) // in case it ends with ";charset=UTF-8"
{
var content = string.Empty;
foreach (var key in request.Form.Keys)
{
if (request.Form.TryGetValue(key, out var value))
{
content += $"{key}={value}&";
}
}
content = content.TrimEnd('&');
return await InputFormatterResult.SuccessAsync(content);
}
return await InputFormatterResult.FailureAsync();
}
Hi I am creating a web api controller which reads a path of the file from the web.config and then uses open xml sdk to read and load the excel document. I need to write a Nunit test to test this controller for the response.
I am basically having two problems and both are related
Problem 1
Since my Nunit is in the class librabry project it cant read the value from the config settings and errors out. How do I handle this in my test for the controller.
It errors out at this line of the code in the Nunit test method
_response = customerController.GetCustomer();
Problem 2
The same line of code i.e _response = customerController.GetCustomer(); also errors out because it returns type viewmodel and not response. How do I test the response object. Or do I need to test the view model object. Any insights would be helpful
WebApi Controller method
public IEnumerable<CustomerViewModel> GetCustomer()
{
string relativePath = ConfigurationManager.AppSettings["filePath"];
return (OpenSpreadsheetDocument(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath)));
}
Nunit test method
[Test]
public void GetCustomerTest()
{
var customerController = new CustomerController()
{
Request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(ServiceBaseURL + "api/getcustomer")
}
};
customerController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
_response = customerController.GetCustomer();
var responseResult = JsonConvert.DeserializeObject<List<CustomerViewModel>>(_response.Content.ReadAsStringAsync().Result);
Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK);
Assert.AreEqual(responseResult.Any(), true);
}
Based on suggestion
Updated WebAPI method
[HttpGet]
public HttpResponseMessage GetCustomer()
{
string relativePath = ConfigurationManager.AppSettings["filePath"];
IEnumerable <CustomerViewModel> customerViewModel = (OpenSpreadsheetDocument(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath)));
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, customerViewModel);
return response;
}
1) update app settings in app.config file of test to match web.config of the web project.
<appSettings>
<add key="filePath" value="...." />
</appSettings>
2) have action return IHttpActionResult abstraction which would allow for more flexibility when testing.
public IHttpActionResult GetCustomer() {
string relativePath = ConfigurationManager.AppSettings["filePath"];
IEnumerable<CustomerViewModel> customer = (OpenSpreadsheetDocument(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath)));
return Ok(customer);
}
you should also abstract the openxml to allow for mocking file access during unit testing
public class CustomerController : ApiController {
private readonly IExcelService service;
public CustomerController(IExcelService service) {
this.service = service;
}
[HttpGet]
public IHttpActionResult GetCustomer() {
IEnumerable<CustomerViewModel> customer = service.GetSpreadsheet();
return Ok(customer);
}
}
Service contract could look like this
public interface IExcelService {
IEnumerable<CustomerViewModel> GetSpreadsheet();
}
with an implementation that has what originally had in your controller.
public class ExcelService : IExcelService {
public IEnumerable<CustomerViewModel> GetSpreadsheet() {
string relativePath = ConfigurationManager.AppSettings["filePath"];
IEnumerable<CustomerViewModel> customer = (OpenSpreadsheetDocument(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath)));
return customer;
}
}
To Test lets make a fake service that is not dependent on path. (note: this could also be easily done with a mocking framework but for example purposes we'll use a fake)
public class FakeService : IExcelService {
public IEnumerable<CustomerViewModel> GetSpreadsheet() {
return new List<CustomerViewModel>() { new CustomerViewModel() };
}
}
And now the test
[Test]
public void GetCustomerTest() {
//Arrange
var fakeService = new FakeService();
var customerController = new CustomerController(fakeService) {
Request = new HttpRequestMessage {
Method = HttpMethod.Get,
RequestUri = new Uri(ServiceBaseURL + "api/getcustomer")
}
};
customerController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
//Act
var _response = customerController.GetCustomer() as OkNegotiatedContentResult<IEnumerable<CustomerViewModel>>;
//Assert
Assert.IsNotNull(_response);
var responseResult = _response.Content;
Assert.IsNotNull(responseResult);
Assert.AreEqual(responseResult.Any(), true);
}
This is my Web API and it works fine, I mean when i enter this URL on my browser:
http://localhost:18207/api/values/GetMyClass
I retrieve this result:
<MyClass>
<A>a</A>
<b>b</b>
</MyClass>
My codes:
public class MyClass
{
public MyClass()
{
this.A = "a";
this.b = "b";
}
public string A { get; set; }
public string b { get; set; }
}
public class ValuesController : ApiController
{
public MyClass GetMyClass()
{
return new MyClass();
}
}
I have another console application to use my Web API and want to know,
How can i have a complex or object type of MyClass?
Codes on my Console are below but it returns string type
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
MainAsync(args, cts.Token).Wait();
}
static async Task MainAsync(string[] args, CancellationToken token)
{
string baseAddress = "http://localhost:18207/api/values/GetMyClass";
using (var httpClient = new HttpClient())
{
string response = await httpClient.GetStringAsync(baseAddress);
}
}
Your response is probably coming to your console application as JSON (the reason your browser receives it as XML is because of different Accept headers, you can learn about that if you look at Content Negotiation). So what you need to do is parse the JSON and have it deserialize it into your object. There's quite a few libraries that can do that for you.
First make sure that your MyClass is defined in a Class Library project that both your Web API project and your Console project are referencing. This allows us to reuse the class definition without needing to have a separate copy in both projects.
Next, you need a JSON parsing library. There's one built into .NET, but there's a 3rd party one called Json.NET that is the gold standard. My answer will use that one since I'm more familiar with it. Install the Newtonsoft.Json package into your console app.
Then, change your console app as follows:
using Newtonsoft.Json; // at the top of your file
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
MainAsync(args, cts.Token).Wait();
}
static async Task MainAsync(string[] args, CancellationToken token)
{
string baseAddress = "http://localhost:18207/api/values/GetMyClass";
using (var httpClient = new HttpClient())
{
string json = await httpClient.GetStringAsync(baseAddress);
MyClass instance = JsonConvert.DeserializeObject<MyClass>(json);
}
}
The JsonConvert class handles serializing and deserializing the JSON. When deserializing, we just tell is which class to deserialize to and it will attempt to convert the JSON to an instance of that class and return it.
You can use method "GetAsync" which will return object of class "HttpResponseMessage" and then you can call "ReadAsAsync" on Content property.
Please see below code:
public class MyClass
{
public MyClass()
{
this.A = "a";
this.b = "b";
}
public string A { get; set; }
public string b { get; set; }
}
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
MainAsync(args, cts.Token).Wait();
}
static async Task MainAsync(string[] args, CancellationToken token)
{
string baseAddress = "http://localhost:18207/api/values/GetMyClass";
using (var httpClient = new HttpClient())
{
HttpResponseMessage response = await httpClient.GetAsync(baseAddress);
response.EnsureSuccessStatusCode();
MyClass result = await response.Content.ReadAsAsync< MyClass>();
}
}
Here is the full solution end-to-end. We are hosting a Web Api that returns MyClass and then we are calling the API and getting data formatted as XML through a console application.
First, we have MyClass annotated as a DataContract:
[DataContract]
public class MyClass
{
public MyClass()
{
this.A = "a";
this.b = "b";
}
[DataMember]
public string A { get; set; }
[DataMember]
public string b { get; set; }
}
The MyClass Web API:
[AllowAnonymous]
public class MyClassController : ApiController
{
public MyClass Get()
{
return new MyClass();
}
}
and a Console app that uses HttpWebRequest to call the Web Api.
Here's that code (the bottom half is from my original post):
static void Main(string[] args)
{
// this is my Web API Endpoint
var req = (HttpWebRequest)WebRequest.Create("http://localhost:17512/api/MyClass");
// default is JSON, but you can request XML
req.Accept = "application/xml";
req.ContentType = "application/xml";
var resp = req.GetResponse();
var sr = new StreamReader(resp.GetResponseStream());
// read the response stream as Text.
var xml = sr.ReadToEnd();
var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml));
// Deserialize
var ser = new XmlSerializer(typeof(MyClass));
var instance = (MyClass)ser.Deserialize(ms);
Console.WriteLine(instance.A);
Console.WriteLine(instance.b);
var final = Console.ReadLine();
}
NOTE: You'll need to figure out if you want to share a reference to MyClass between the two assemblies or if you just want to have a copy of the code file in each project.
You could just remove XML Formatter inside your WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Removing XML
config.Formatters.Remove(config.Formatters.XmlFormatter);
// Allows us to map routes using [Route()] and [RoutePrefix()]
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Then in your controller you would return just like in your example:
public class ValuesController : ApiController
{
public MyClass GetMyClass()
{
return new MyClass();
}
}
UPDATE 1: I did my answer become more consistent with the question
When making a Request from a Console Application, you could use RestSharp.
var client = new RestClient("http://localhost:18207/");
var request = new RestRequest("api/values/GetMyClass", Method.GET);
var response = client.Execute<MyClass>(request);
if(response.StatusCode == HttpStatusCode.OK)
var responseData = response.Data;
When you execute client.Execute<MyClass>(request) it will deserialize the response into an object of that class. If field names match it should work.