I want to be able to switch between API providers and achieve the same result.
I have an interface called IApi that I am using for both APIs.
public interface IApi
{
T GetData<T>();
}
I then have my two API classes implementing this interface
public class ApiOne: IApi
{
private IWebClient _client;
public ApiOne(IWebClient client)
{
_client = client;
}
public T GetData<T>()
{
return _client.Get<T>($"{some specific url for this api");
}
}
public class ApiTwo: IApi
{
private IWebClient _client;
public ApiTwo(IWebClient client)
{
_client = client;
}
public T GetData<T>()
{
return _client.Get<T>($"{some specific url for this api");
}
}
Both of these calls obviously return different JSON responses depending on the API. I am using Newtonsoft to deserialze the responses into strong typed classes.
Which means I have 3 data models. 1 for each API response and a 3rd which is what I would like to transform the API responses into so that I can use only one data model as the Generic type.
public class ApiOneResponse
{
public string FieldOne { get; set; }
public string FieldTwo { get; set; }
}
public class ApiTwoResponse
{
public string SomeOtherFieldOne { get; set; }
public string SomeOtherFieldTwo { get; set; }
}
How can I achieve this so that both my API calls can be deserialized down to the same class and I can call it with a simple one liner?
public class CommonResponse
{
public string CommonFieldOne { get; set; }
public string CommonFieldTwo { get; set; }
}
I want to be able to simply call it like the below
static void Main(string[] args)
{
//some additional logic
//call the API
var response = _api.GetData<CommonResponse>();
}
EDIT
The issue is that _webClient.Get will try and deserialise the JSON properties into CommonResonse and each JSON reponse cannot be directly mapped into CommonResponse as the JSON keys would be different on each response.
Below is the WebClient code
public class WebClient : IWebClient
{
public T Get<T>(string endpoint)
{
using (var client = new HttpClient())
{
HttpResponseMessage response = client.GetAsync(endpoint).Result;
response.EnsureSuccessStatusCode();
string result = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<T>(result);
}
}
}
GetData doesn't need to be generic if you're always returning a CommonResponse:
public interface IApi
{
CommonResponse GetData();
}
Then in each implementation, project the response to your CommonResponse:
public class ApiOne: IApi
{
private IWebClient _client;
public ApiOne(IWebClient client)
{
_client = client;
}
public CommonResponse GetData()
{
var response = _client.Get<ApiOneResponse>($"{some specific url for this api");
return new CommonResponse
{
CommonFieldOne = response.FieldOne,
CommonFieldTwo = response.FieldTwo
}
}
}
Related
I have the following bit of code whihc sends a Http POST request to the server. The server reurns a 400 Bad request response along with a error object in the form of Json:
namespace MyApp.Shared.Dtos.Response
{
public class ErrorItem
{
public string Message { get; set; }
public string Tag { get; set; }
}
public class ErrorDto
{
public string Title { get; set; }
public List<ErrorItem> Errors { get; set; } = new();
}
}
namespace Accounting.Web.Services
{
public interface IHttpService
{
Task<T> Get<T>(string uri);
Task<T> Post<T>(string uri, object value, bool addBearerToken = false);
public ErrorDto Error { get; set; }
}
public class HttpService: IHttpService
{
private HttpClient _httpClient;
public ErrorDto Error { get; set; }
public HttpService(HttpClient httpClient)
{
_httpClient = httpClient;
_stateService = stateService;
}
public async Task<T> Post<T>(string uri, object value)
{
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = new StringContent(JsonSerializer.Serialize(value), Encoding.UTF8, "application/json");
return await sendRequest<T>(request, addBearerToken);
}
private async Task<T> sendRequest<T>(HttpRequestMessage request)
{
using var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
var result = await response.Content.ReadAsStringAsync();
Error = JsonSerializer.Deserialize<ErrorDto>(result);
//..
}
else
{
//..
}
}
}
}
The result correctly recieves the following response from the server as a JSON string:
{"title":"Username or password is incorrect","errors":[]}
And I can confirm by inspecting var result, it has the above value.
However, It doesn't seem deserialize into the ErrorDto class as one would expect it to:
Error = JsonSerializer.Deserialize(result);
But I simply cannot see any problems with the code, it looks like it should be working.
*** UPDATE ***
My server API code returrns the JSOn using the same DTO class (It's a shared class) using the following code:
[HttpPost("authenticate")]
public ActionResult Authenticate(AuthenticateRequest loginRequest)
{
var auth = _userService.Authenticate(loginRequest);
ErrorDto error = new()
{
Title = "Username or password is incorrect"
};
if (auth.user == null || auth.token == null)
{
return BadRequest(error);
}
return Ok(auth.user.ConvertToDto(auth.token));
}
By default System.Text.Json is case-sensitive. There are multiple options to handle this, for example by providing corresponding JsonSerializerOptions:
var json = #"{""title"":""Username or password is incorrect"",""errors"":[]}";
var errorDto = JsonSerializer.Deserialize<ErrorDto>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Or marking properties with corresponding JsonPropertyNameAttribute:
public class ErrorItem
{
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("tag")]
public string Tag { get; set; }
}
public class ErrorDto
{
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("errors")]
public List<ErrorItem> Errors { get; set; } = new();
}
UPD
From How to customize property names and values with System.Text.Json doc:
Note
The web default is camel case.
If you want to switch from camel case to the naming policy used for DTOs you can do the following:
builder.Services.AddControllers()
.AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
I have implement MediatR in .net 5 application and want to resolve require dependencies using Handler interfaces. Currently I using class name to resolve as following
_mediator.Send(new GetDeviceByIMEI(imei)); // want to use interface ??
//var result = await _mediator.Send(IGetHandHeldByIMEI????);
full code reference as following;
Handler Interface
public interface IGetDeviceByIMEIEventHandler
{
Task<DeviceWrapperDataView> Handle(GetDeviceByIMEI request, CancellationToken cancellationToken);
}
Query Interface
public interface IGetDeviceByIMEI
{
string IMEI { get; set; }
}
Query
public class GetDeviceByIMEI: IRequest<DeviceWrapperDataView>
{
public string IMEI { get; set; }
public GetDeviceByIMEI(string imei)
{
this.IMEI = imei;
}
}
Handler
public class GetDeviceByIMEIEventHandler : IRequestHandler<GetDeviceByIMEI, DeviceWrapperDataView>, IGetDeviceByIMEIEventHandler
{
private readonly IDeviceEntity _DeviceEntity;
public GetDeviceByIMEIEventHandler(IDeviceEntity DeviceEntity)
{
_DeviceEntity = DeviceEntity;
}
public async Task<DeviceWrapperDataView> Handle(GetDeviceByIMEI request, CancellationToken cancellationToken)
{
// code to get data
return DeviceOutput;
}
}
API controller
private readonly IMediator _mediator;
public DeviceController(
IMediator mediator)
{
_mediator = mediator;
}
[HttpGet()]
public async Task<IActionResult> GetDeviceByIMEI(string imei)
{
Var result = await _mediator.Send(new GetDeviceByIMEI(imei));
// want to use
}
To do that you have to register your handler in the container with each of the class that inherit your query interface.
For instance with the code you provided.
public interface IGetDeviceByIMEI : IRequest<DeviceWrapperDataView>
{
string IMEI { get; set; }
}
public class GetDeviceByIMEI: IGetDeviceByIMEI
{
public string IMEI { get; set; }
public GetDeviceByIMEI(string imei)
{
this.IMEI = imei;
}
}
public class AnotherGetDeviceByIMEI: IGetDeviceByIMEI
{
public string IMEI { get; set; }
public GetDeviceByIMEI(string imei)
{
this.IMEI = imei;
}
}
public class GetDeviceByIMEIEventHandler : IRequestHandler<IGetDeviceByIMEI, DeviceWrapperDataView>, IGetDeviceByIMEIEventHandler
{
private readonly IDeviceEntity _DeviceEntity;
public GetDeviceByIMEIEventHandler(IDeviceEntity DeviceEntity)
{
_DeviceEntity = DeviceEntity;
}
public async Task<DeviceWrapperDataView> Handle(IGetDeviceByIMEI request, CancellationToken cancellationToken)
{
// code to get data
return DeviceOutput;
}
}
Once you have done that, you have to register the handler in your container with each use case.
For instance in .Net Core, you can do it with the serviceCollection in the StartUp class.
serviceCollection.AddTransient<IRequestHandler<GetDeviceByIMEI, DeviceWrapperDataView>, GetDeviceByIMEIEventHandler >();
serviceCollection.AddTransient<IRequestHandler<AnotherGetDeviceByIMEI, DeviceWrapperDataView>, GetDeviceByIMEIEventHandler >();
Regards.
I am new to generics and just wondering if it's possible to avoid the casting in the following code using better OO approach.
public class CollectorFactory
{
public static MyCollector Create(ICredential credential)
{
return new MyCollector(credential);
}
}
public class MyCollector {
public MyCredential Credential { get; set; }
public MyCollector(ICredential credential)
{
this.Credential = (MyCredential)credential;
}
public void Show()
{
Console.WriteLine(this.Credential.Username);
Console.WriteLine(this.Credential.AuthToken);
}
}
public class MyCredential : ICredential
{
public string Username{ get; set; }
public string AuthToken { get; set; }
}
public interface ICredential
{
}
Is there a way to save the casting of ICredential to MyCredential in MyCollector's Constructor? I don't have option to put Username and AuthToken in ICredential as it's implemented by two different Credentials that both have different set of properties. CollectorFactory will be returning different MyCollector instances in the future and both need to have different credentials.
Any help would be really appreciated.
I don't think it's possible given that you're implementing different credentials and trying to use them for ICredential as well.
Here is a way of doing this using generics. Please read my comments in the code.
public class CollectorFactory<T>
{
public T Create(ICredential credential)
{
return (T)Activator.CreateInstance(typeof(T), credential);
}
}
public class MyCollector : BaseCollector
{
public dynamic Credential { get; private set; }
public MyCollector(ICredential credential)
: base(credential)
{
this.Credential = credential;
}
// Having this method here limits your ability to make it more generic.
// Consider moving this to MyCredential since it refers to specific properties in MyCredential.
// If that is not what you want, then you must do a type check before calling methods/ accessing props in Credentials.
public void Show()
{
Console.WriteLine(this.Credential.Username);
Console.WriteLine(this.Credential.AuthToken);
}
}
public class MyCredential : ICredential
{
public string Username { get; set; }
public string AuthToken { get; set; }
}
public abstract class BaseCollector : ICredentialCollector
{
protected BaseCollector(ICredential credential)
{
if (credential == null)
{
throw new ArgumentNullException(nameof(credential));
}
}
}
public interface ICredentialCollector
{
}
public interface ICredential
{
}
// test implementation
public class TestClass
{
public void AuthFactoryTest()
{
// test auth instance
MyCredential auth = new MyCredential() {AuthToken = "asfgasdgdfg", Username = "xuser"};
// Create test factory
var fact = new CollectorFactory<MyCollector>();
var myCollector = fact.Create(auth);
// Do what you need to do to collector object
myCollector.Show();
}
}
Generics isn't the solution in this case. The issue here is that your factory is returning a specific type (MyCollector). A solution around this would be the following:
public class CollectorFactory
{
public static ICollector Create(MyCredential credential)
{
return new MyCollector(credential);
}
public static ICollector Create(OtherCredential credential)
{
return new OtherCollector(credential);
}
}
public interface ICollector
{
void Show();
}
public class MyCollector : ICollector
{
public MyCredential Credential { get; set; }
public MyCollector(MyCredential credential)
{
this.Credential = credential;
}
public void Show()
{
Console.WriteLine(this.Credential.Username);
Console.WriteLine(this.Credential.AuthToken);
}
}
public class MyCredential : ICredential
{
public string Username{ get; set; }
public string AuthToken { get; set; }
}
public interface ICredential
{
}
The above is pretty much the canonical example of the Factory design pattern.
Instead of overloads you could also do typechecking in the factory:
public class CollectorFactory
{
public static ICollector Create(ICredential credential)
{
if(credential.GetType() == typeof(MyCredential))
return new MyCollector((MyCredential) credential);
if(credential.GetType() == typeof(OtherCredential ))
return new OtherCollector((OtherCredential ) credential);
}
}
I am developing Web API for my client. They have suggestion that all response should be a common JSON structure.
{ Data:"", Status:true, Message:"" }
If error means
{ Error:"", Status:false, Message:"" }
Which is the best method to create a common JSON structure as returns.
Now I created a class having these properties. And created 2 classes from IHttpActionResult,Error.cs and Success.cs, From that the response is created and returned from the controller.
The thing is in my controller,
public IHttpActionResult GetNewsAndAnnouncements()
{
var data = newsAndAnnouncementsDataServices.NewsAndAnnouncements();
if (data != null && data.Count() > 0)
{
return new Success(Request, "News and Announcements Retrieved Successfully", data);
}
return new Error(Request, "No News and Announcements Found");
}
Error.cs
public class Error : IHttpActionResult
{
private readonly string _message;
private readonly HttpRequestMessage _request;
private IErrorResponseModel errorResponse;
public Error(HttpRequestMessage request, string message)
{
_message = message;
_request = request;
errorResponse = new ErrorResponseModel();
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
errorResponse.Message = _message;
errorResponse.Status = false;
errorResponse.Error = _message;
var response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent<object>(errorResponse, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
Success.cs
public class Success : IHttpActionResult
{
private readonly string _message;
private readonly object _data;
private readonly HttpRequestMessage _request;
private IDataResponseModel dataResponse = new DataResponseModel();
public Success(HttpRequestMessage request, string message, object data)
{
_message = message;
_request = request;
_data = data;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
dataResponse.Message = _message;
dataResponse.Status = true;
dataResponse.Data = _data;
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(dataResponse, new JsonMediaTypeFormatter()),
RequestMessage = _request
};
return Task.FromResult(response);
}
}
DataResponseModel.cs
public class DataResponseModel : Mobility.Common.IDataResponseModel
{
public object Data { get; set; }
public string Message { get; set; }
public bool Status { get; set; }
}
ErrorResponseModel.cs
public class ErrorResponseModel : Mobility.Common.IErrorResponseModel
{
public object Error { get; set; }
public string Message { get; set; }
public bool Status { get; set; }
}
Is this a right method. I need suggestion. Is there any other way to achieve this. I heard about delegating handler something. But I don't have much idea on these.
Please help me.
Another solution to this problem is to hook into the ASP.NET pipeline using custom handlers to build a common response object.
For instance:
[DataContract]
public class ApiResponse
{
[DataMember]
public string Version { get { return "1.2.3"; } }
[DataMember]
public int StatusCode { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ErrorMessage { get; set; }
[DataMember(EmitDefaultValue = false)]
public object Result { get; set; }
public ApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
StatusCode = (int)statusCode;
Result = result;
ErrorMessage = errorMessage;
}
}
Have a look at this post for a reference implementation http://www.devtrends.co.uk/blog/wrapping-asp.net-web-api-responses-for-consistency-and-to-provide-additional-information
I'm trying to use the new API approach for ServiceStack and I'm building a test console application to host it. So far I have the route instantiating the request DTO, but before the request reaches my service's Any method, I get this exception:
Error Code NullReferenceException
Message Object reference not set to an instance of an object.
Stack Trace at ServiceStack.WebHost.Endpoints.Utils.FilterAttributeCache.GetRequestFilterAttributes(Type requestDtoType) at
ServiceStack.WebHost.Endpoints.EndpointHost.ApplyRequestFilters(IHttpRequest httpReq, IHttpResponse httpRes, Object requestDto) at
ServiceStack.WebHost.Endpoints.RestHandler.ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, String operationName)
Below is my test Service using IReturn and Service (at this point I'm only trying to return hard-coded results to see it working)
[DataContract]
public class AllAccounts : IReturn<List<Account>>
{
public AllAccounts()
{
}
}
[DataContract]
public class AccountTest : IReturn<string>
{
public AccountTest()
{
this.Id = 4;
}
[DataMember]
public int Id { get; set; }
}
public class AccountService : Service
{
public AccountService()
{
}
public object Any(AccountTest test)
{
return "hello";
}
public object Any(AllAccounts request)
{
var ret = new List<Account> {new Account() {Id = 3}};
return ret;
}
}
All ServiceStack references come from NuGet. I get the same error with either route. Any suggestions?
It might help to see your AppHost code and the code in your Configure() method. Nothing you provided in the code above stands out. Below is how I would set up a simple Console app using the code/classes you have provided.
Initialize and Start the ServiceStack AppHost
class Program
{
static void Main(string[] args)
{
var appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:1337/");
System.Console.WriteLine("Listening on http://localhost:1337/ ...");
System.Console.ReadLine();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
}
}
Inherit from AppHostHttpListenerBase and Configure (not configuring anything for this example)
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Test Console", typeof(AppHost).Assembly) { }
public override void Configure(Funq.Container container)
{
}
}
Dto/Request classes
public class Account
{
public int Id { get; set; }
}
[Route("/AllAccounts")]
[DataContract]
public class AllAccounts : IReturn<List<Account>>
{
public AllAccounts()
{
}
}
[Route("/AccountTest")]
[DataContract]
public class AccountTest : IReturn<string>
{
public AccountTest()
{
this.Id = 4;
}
[DataMember]
public int Id { get; set; }
}
Service code to handle your requests - URLS: localhost:1337/AllAccounts & localhost:1337/AccountTest
public class AccountService : Service
{
public AccountService()
{
}
public object Any(AccountTest test)
{
return "hello";
}
public object Any(AllAccounts request)
{
var ret = new List<Account> { new Account() { Id = 3 } };
return ret;
}
}