I am trying to map the response of an api call. What I've done is deserialize the response of the API call (the response is an array) into a model, and then return a new dto using the rootobject's data.
public class RoadStatusService : IRoadStatusService
{
string baseURL = "blah";
private readonly IMapToNew<Road, RoadDto> _mapper;
public RoadStatusService()
{
}
public RoadStatusService(IMapToNew<Road, RoadDto> mapper)
{
_mapper = mapper;
}
public RoadDto GetRoadStatusDetail()
{
var road = CallApi();
return new RoadDto
{
DisplayName = road.Result.DisplayName,
StatusSeverityDescription = road.Result.DisplayName,
StatusSeverity = road.Result.DisplayName
};
}
private async Task<Road> CallApi()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseURL);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage Res = await client.GetAsync(baseURL);
if (Res.IsSuccessStatusCode)
{
var roadResponse = Res.Content.ReadAsStringAsync().Result;
List<Road> road = JsonConvert.DeserializeObject<List<Road>>(roadResponse);
foreach (var item in road)
{
return new Road
{
DisplayName = item.DisplayName,
StatusSeverity = item.StatusSeverity,
StatusSeverityDescription = item.StatusSeverityDescription
};
}
}
return null;
}
}
My question is, how can I use a mapper class to map my model to my dto object without having to do this:
public RoadDto GetRoadStatusDetail()
{
var road = CallApi();
return new RoadDto
{
DisplayName = road.Result.DisplayName,
StatusSeverityDescription = road.Result.DisplayName,
StatusSeverity = road.Result.DisplayName
};
}
I've written a mapperclass and an interface to do this, but I just can't get it to work :
public class RoadToRoadDtoMapper : IMapToNew<Road, RoadDto>
{
public RoadDto Map(Road model)
{
return new RoadDto
{
DisplayName = model?.DisplayName,
StatusSeverity = model?.StatusSeverity,
StatusSeverityDescription = model?.StatusSeverityDescription
};
}
}
and:
public interface IMapToNew<in TIn, out TOut>
{
TOut Map(TIn model);
}
I think the problem I am having is that the api call responds with an array? I am wondering if I should somehow transform my object to a list and then call .Select and use the mapper.map function I have written. I can't get it to work though.
If I understand what you are trying to do, you need a mapper for the lists too and then call the mapper for the objects inside that class:
public class RoadListToRoadListDtoMapper : IMapToNew<List<Road>, List<RoadDto>>
{
private RoadToRoadDtoMapper roadToRoadDtoMapper = new RoadToRoadDtoMapper();
public List<RoadDto> Map(List<Road> models)
{
var roadDtos = new List<RoadDto>();
foreach(var road in models){
roadDtos.Add(roadToRoadDtoMapper.Map(road));
}
return roadDtos;
}
}
Related
my deserialize Dictionary's key results in "brand[0]" when I send in "brand" to the api.
I have a class like this:
public class SearchRequest
{
public bool Html { get; set; } = false;
public Dictionary<string, HashSet<string>> Tags { get; set; }
}
// MVC Controller
[HttpPost]
public ActionResult Index(SearchRequest searchRequest)
{
...
}
And a json request like this that I post to the controller:
{
"html": true,
"tags": {
"brand": [
"bareminerals"
]
}
}
The binding seams to work and the searchRequest object is created but the resulting dictionary dose not have the key "brand" in it but insted the key "brand[0]" how can I preserve the real values I send in?
Edit: I need tags to be able to contain multiple tags, with multiple options, this was a simpel example.
One soulution to my problem is to create a custom model bind, so this is what am using now, but I dont understand why I need to, and I feel like there should be a easyer way? But am gonna leve It here anyhow.
public class FromJsonBodyAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stream = controllerContext.HttpContext.Request.InputStream;
stream.Position = 0;
using (var reader = new StreamReader(stream))
{
var checkoutOrderDataStr = reader.ReadToEnd();
return JsonConvert.DeserializeObject(checkoutOrderDataStr, bindingContext.ModelType);
}
}
}
}
I'm not sure what is going on with your setup. You should not need a custom binder. I still think the problem is most likely with your calling code - whatever you're using as a client.
I'm using Asp.net Core 3.1. Here's what I threw together as a quick test.
Created Asp.net Core web application template with MVC. I declared two classes - a request POCO and a result POCO. The request was your class:
public class SearchRequest
{
public bool Html { get; set; } = false;
public Dictionary<string, HashSet<string>> Tags { get; set; }
}
The result was the same thing with a datetime field added just for the heck of it:
public class SearchResult : SearchRequest
{
public SearchResult(SearchRequest r)
{
this.Html = r.Html;
this.Tags = r.Tags;
}
public DateTime RequestedAt { get; set; } = DateTime.Now;
}
I Added a simple post method on the default HomeController.
[HttpPost]
public IActionResult Index([FromBody] SearchRequest searchRequest)
{
return new ObjectResult(new SearchResult(searchRequest));
}
I added a console Application to the solution to act as a client. I copied the two class definitions into that project.
I added this as the main method. Note you can either have the camel casing options on the request or not - asp.net accepted either.
static async Task Main(string[] _)
{
var tags = new[] { new { k = "brand", tags = new string[] { "bareminerals" } } }
.ToDictionary(x => x.k, v => new HashSet<string>(v.tags));
var request = new SearchRequest() { Html = true, Tags = tags };
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
var json = JsonSerializer.Serialize(request, options);
Console.WriteLine(json);
using (var client = new HttpClient())
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://localhost:59276", content);
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<SearchResult>(data, options);
Console.WriteLine(data);
var keysSame = Enumerable.SequenceEqual(request.Tags.Keys, result.Tags.Keys);
var valuesSame = Enumerable.SequenceEqual(request.Tags.Values.SelectMany(x => x),
result.Tags.Values.SelectMany(x=>x));
Console.WriteLine($"Keys: {keysSame} Values: {valuesSame}");
}
}
This outputs:
{"html":true,"tags":{"brand":["bareminerals"]}}
{"requestedAt":"2020-10-30T19:22:17.8525982-04:00","html":true,"tags":{"brand":["bareminerals"]}}
Keys: True Values: True
I have created a class and I am adding to this class values (when I debug they are there) however once the method below is called the client is empty. Can you please advise what am I doing wrong? I understand that the class is a reference type, however how can I make use of this? I am just a bit confused.
According to this I don't see where I am going wrong https://softwareengineering.stackexchange.com/questions/264525/best-oop-practice-in-c-passing-the-object-as-parameter-vs-creating-a-new-insta
Please explain
public class SesEmailSender : IEmailSender
{
ClientAmazon clientAmazon;
public SesEmailSender()
{
var clientAmazon = new ClientAmazon();
}
public async Task<SendResponse> SendAsync(EmailMessage email)
{
var response = new SendResponse();
var mailMessage = CreateEmailMessage(email);
using (var client = SesSenderExtension.GetClient(clientAmazon))
{
await client.SendEmailAsync(mailMessage).ConfigureAwait(false);
}
return response;
}
}
public static class SesSenderExtension
{
public static ClientAmazon JsonParse()
{
var myJsonString = File.ReadAllText("msgSettings.json");
var myJObject = JObject.Parse(myJsonString);
var client = new ClientAmazon()
{
AccessKeyID = myJObject.SelectToken("AccessKeyId").Value<string>(),
SecretAccessKey = myJObject.SelectToken("SecretAccessKey").Value<string>()
};
return client;
}
public static AmazonSimpleEmailServiceClient GetClient(ClientAmazon client)
{
return new AmazonSimpleEmailServiceClient(client.AccessKeyID, client.SecretAccessKey, Amazon.RegionEndpoint.EUCentral1);
}
}
callin in the console
static void Main(string[] args)
{
GetAuthentificationDetails();
SesSenderExtension.JsonParse();
var sesEmailSender = new SesEmailSender();
var email = new EmailMessage()
{
Sender = "ccc#gmail.com",
Reciever = "xxx#gmail.com",
Subject = "test",
Body = "test",
};
var response = sesEmailSender.Send(email);
}
You need to change your constructor to this. Right now you are assigning to a local variable instead of your class property.
So change the constructor from
public SesEmailSender()
{
var clientAmazon = new ClientAmazon();
}
To this:
public class SesEmailSender : IEmailSender
{
ClientAmazon clientAmazon;
public SesEmailSender()
{
// remove var here
clientAmazon = new ClientAmazon();
}
public async Task<SendResponse> SendAsync(EmailMessage email)
{
var response = new SendResponse();
var mailMessage = CreateEmailMessage(email);
using (var client = SesSenderExtension.GetClient(clientAmazon))
{
await client.SendEmailAsync(mailMessage).ConfigureAwait(false);
}
return response;
}
}
Update based on your comments:
In your Program.cs you are doing this:
SesSenderExtension.JsonParse();
This will set the credentials on a different ClientAmazon.
Then later you create a NEW ClientAmazon in your constructor. That instance will not have any credentials set on it.
To get around this you could do something like this:
Get the configured client from SesSenderExtension.JsonParse()
Refactor SesEmailSender to take a ClientAmazon in it's constructor
Pass the ClientAmazon to the SesEmailSender constructor.
Use the ClientAmazon that you passed into the constructor.
Profit
static void Main(string[] args)
{
GetAuthentificationDetails();
var client = SesSenderExtension.JsonParse();
var sesEmailSender = new SesEmailSender(client);
var email = new EmailMessage()
{
Sender = "ccc#gmail.com",
Reciever = "xxx#gmail.com",
Subject = "test",
Body = "test",
};
var response = sesEmailSender.Send(email);
}
public class SesEmailSender : IEmailSender
{
ClientAmazon _clientAmazon;
public SesEmailSender(ClientAmazon clientAmazon)
{
_clientAmazon = clientAmazon;
}
public async Task<SendResponse> SendAsync(EmailMessage email)
{
var response = new SendResponse();
var mailMessage = CreateEmailMessage(email);
using (var client = SesSenderExtension.GetClient(_clientAmazon))
{
await client.SendEmailAsync(mailMessage).ConfigureAwait(false);
}
return response;
}
}
I am trying to write an unit test at the moment and i am having difficult with the mocking the RestSharp. The test i am trying to write is for the GetAll method.
This is the code that i am trying to test.
public class Client: IClient
{
public IRestClient RestClient { get; set; }
public IOptions<ClientSettings>Settings { get; set; }
public Client(IOptions<ClientSettings>options)
{
Settings = options;
RestClient = new RestClient(options.Value.BaseUrl);
}
public async Task<List<EventDTO>> GetAll()
{
var allEvents = await RetrieveAllEvents();
var data = TransformData(allEvents);
return data;
}
private static List<EventDTO> TransformData(IEnumerable<Event> allEvents)
{
var data = allEvents.SelectMany(con =>
con.Geometries.Select(geo =>
new EventDTO
{
Title = con.Title,
Id = con.Sources.FirstOrDefault()?.Id,
CategoriesTitle = con.Categories.FirstOrDefault()?.Title,
Closed = con.Closed,
DateTime = geo.Date
})
).ToList();
return data;
}
private async Task<IEnumerable<Event>> RetrieveAllEvents()
{
var openEvents = await RetrieveEvent(Settings.Value.GetAllOpen);
var closedEvents = await RetrieveEvent(Settings.Value.GetAllClosed);
var allEvents = openEvents.Events.Concat(closedEvents.Events);
return allEvents;
}
private async Task<RootObject> RetrieveEvent(string request)
{
var responseData = new RestRequest(request, Method.GET);
var content = await RestClient.GetAsync<RootObject>(responseData);
return content;
}
}
When the code gets to this line, it just stops working. I tried putting in a try and catch around it see what the error is but it just blows the stack.
var data = await RestClient.GetAsync<RootObject>(responseData);
I saw an example online and i tried mocking the RestSharp
restClient.Setup(c => c.ExecuteAsync<EventDTO>(
Moq.It.IsAny<IRestRequest>(),
Moq.It.IsAny<Action<IRestResponse<EventDTO>, RestRequestAsyncHandle>>()))
.Callback<IRestRequest, Action<IRestResponse<EventDTO>, RestRequestAsyncHandle>>((request, callback) =>
{
var responseMock = new Mock<IRestResponse<EventDTO>>();
responseMock.Setup(r => r.Data).Returns(new EventDTO() { });
callback(responseMock.Object, null);
});
I am writing unit test for a controller with ODataQueryOptions and unable to mock it.
[EnableQuery]
public IQueryable<WeatherForecast> Get(ODataQueryOptions<WeatherForecast> options)
{
logger.LogInformation($"Call to end point [{options.Request.Method}]: Host :'{options.Request.Host}' Endpoint : '{options.Request.Path}'");
logger.LogInformation($"Query string: '{options.Request.QueryString}'");
return weatherForecastService.Get().Entity;
}
Trying something like
public class WeatherServiceTests
{
private readonly Mock<IWeatherService> weatherServiceMock;
private readonly Mock<ILogger<WeatherController>> loggerMock;
private readonly Mock<ODataQueryOptions<Weather>> optionsMock;
private readonly WeatherController weatherController;
public WeatherServiceTests()
{
weatherServiceMock = new Mock<IWeatherService>();
loggerMock = new Mock<ILogger<WeatherController>>();
optionsMock = new Mock<ODataQueryOptions<Weather>>();
weatherController = new WeatherController(loggerMock.Object, weatherServiceMock.Object);
}
private ApiResponse<IQueryable<Weather>> GetWeatherMockup()
{
var weather = new List<Weather>
{
new Weather {Content = "Content", Header = "Header", Source = "Source"}
}.AsQueryable();
return new ApiResponse<IQueryable<Weather>>(weather);
}
[Fact]
public void WeatherData_Get()
{
weatherServiceMock.Setup(x => x.Get()).Returns(GetWeatherMockup());
var response = weatherController.Get(optionsMock.Object).ToList();
Assert.NotNull(response);
Assert.True(response.Count == 1);
}
}
How can I mock ODataQueryOptions? I have referred to few answered question regarding the same but they are or little use.
I am having a simple controller which needs to be unit tested not integration tested. I just need a way to mock so that I can verify if receive method is called. We already have test against Receive(), so no need to verify what is going inside that method.
My code looks like
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
private IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
Refactor the CreateConnectionAndChannel function out into its own service
public interface IChannelProvider {
IChannel CreateConnectionAndChannel();
}
and have controller explicitly depend on that service
public class MessageController : Controller {
private readonly IChannelProvider channelProvider;
public MessageController(IChannelProvider channelProvider) {
this.channelProvider = channelProvider;
}
[HttpGet()]
public IActionResult Get() {
try {
var channel = channelProvider.CreateConnectionAndChannel();
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
} catch {
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
So now only the IChannelProvider needs to be mocked to test the controller in isolation.
I just need a way to mock so that I can verify if receive method is called.
public void Verify_Received_Called() {
//Arrange
var channel = new Mock<IChannel>();
channel
.Setup(_ => _.Receive())
.Returns("My mock value here");
var mockProvider = new Mock<IChannelProvider>();
mockProvider.Setup(_ => _.CreateConnectionAndChannel())
.Returns(channel.Object);
var controller = new MessageController(mockProvider.Object);
//Act
var result = controller.Get();
//Assert
channel.Verify(_ => _.Receive(), Times.AtLeastOnce);
}
The provider implementation could look like...
public class ChannelProvider : IChannelProvider {
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public ChannelProvider(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail) {
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
public IChannel CreateConnectionAndChannel() {
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
In order to do this, you need to move your CreateConnectionAndChannel method to a separate dependency, for instance, ChannelFactory which implements IChannelFactory interface.
public interface IChannelFactory {
IChannel CreateConnectionAndChannel(QueueDetail queueDetail);
}
public class ChannelFactory : IChannelFactory {
public IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
private readonly IChannelFactory channelFactory;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail, IChannelFactory channelFactory)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
this.channelFactory = channelFactory;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = channelFactory.CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
After that you can mock your controller in test (using Moq for example):
[TestFixture]
public class TestMessageController
{
[Test]
public void TestGet()
{
var channelMock = new Mock<IChannel>(MockBehavior.Strict);
channelMock
.Setup(c => c.Receive())
.Returns(null);
var channelFactoryMock = new Mock<IChannelFactory>(MockBehavior.Strict);
channelFactory
.Setup(cf => cf.CreateConnectionAndChannel(It.IsAny<IOptions<QueueDetail>>()))
.Returns();
var controller = new MessageController(null, null, channelFactoryMock.Object);
controller.Get();
}
}