Get data from external API with a asp.net core API - asp.net

I'm learning to create APIs using ASP.NET core, in this I came up with a problem, I'm trying to execute a request for an external API using my API, but I do not know how to execute the request and return the JSON of the request, any help?
The flow of the application looks something like this:
SPA -> AspNet Core WEB API -> External API
What I've done so far:
[Route("api/[Controller]")]
public class RankingsController : Controller
{
private readonly IRankingRepository _rankingRepository;
public RankingsController(IRankingRepository rankingRepo)
{
_rankingRepository = rankingRepo;
}
[HttpGet("{id}", Name = "GetRanking")]
public IActionResult GetById(long id)
//Here is where I want to make the requisition
}
}
I need to make the request for this API:
http://api.football-data.org/v1/competitions/{id}/leagueTable
In the ID location, I need to pass a parameter that comes from the request made in my API;
Any help for this problem?
Sorry for not being such a complex question.
Thanks!!

You could use an HttpClient instance to achieve what you want. However, I always find easier to use RestSharp though.
That, of course will depend on your constraints but assuming you have none for this case, you could use RestSharp to make the call to the external API:
Install it:
Install-Package RestSharp
Usage:
using RestSharp;
[HttpGet("{id}", Name = "GetRanking")]
public async Task<IActionResult> GetByIdAync(long id)
{
var client = new RestClient($"http://api.football-data.org/v1/competitions/{id}/leagueTable");
var request = new RestRequest(Method.GET);
IRestResponse response = await client.ExecuteAsync(request);
//TODO: transform the response here to suit your needs
return Ok(data);
}
To consume the rest response from RestSharp you must use the response.Content property.
You could, for example, deserialize it to Json, manipulate it to fit your needs and return the required data to your API caller.
Example:
Let's say I'd like to get the rankings for the Premier League 2017/18 (Id = 445):
I'll get a lot of help below from the legendary Newtonsoft.Json package and a little of jpath syntax but I'll assume you've already used both :)
Create a couple models to hold the values to be returned to the API caller:
public class LeagueTableModel
{
public string LeagueCaption { get; set; }
public IEnumerable<StandingModel> Standings { get; set; }
}
public class StandingModel
{
public string TeamName { get; set; }
public int Position { get; set; }
}
Implement the below method in the service class, injected to your controller through DI/IoC to avoid coupling and increase testability (as we all know we should do right?). I'm assuming this class is RankingRepository in your sample:
public RankingRepository: IRankingRepository
{
public Task<LeagueTableModel> GetRankingsAsync(long id)
{
var client = new RestClient($"http://api.football-data.org/v1/competitions/{id}/leagueTable");
var request = new RestRequest(Method.GET);
IRestResponse response = await client.ExecuteAsync(request);
if (response.IsSuccessful)
{
var content = JsonConvert.DeserializeObject<JToken>(response.Content);
//Get the league caption
var leagueCaption = content["leagueCaption"].Value<string>();
//Get the standings for the league.
var rankings = content.SelectTokens("standing[*]")
.Select(team => new StandingModel
{
TeamName = (string)team["teamName"],
Position = (int)team["position"]
})
.ToList();
//return the model to my caller.
return new LeagueTableModel
{
LeagueCaption = leagueCaption,
Standings = rankings
};
}
//TODO: log error, throw exception or do other stuffs for failed requests here.
Console.WriteLine(response.Content);
return null;
}
}
Use it from the controller:
[Route("api/[Controller]")]
public class RankingsController : Controller
{
private readonly IRankingRepository _rankingRepository;
public RankingsController(IRankingRepository rankingRepo)
{
_rankingRepository = rankingRepo;
}
[HttpGet("{id}", Name = "GetRanking")]
public Task<IActionResult> GetByIdAsync(long id)
//Here is where I want to make the requisition
var model = await _rankingRepository.GetRankingsAsync(id);
//Validate if null
if (model == null)
return NotFound(); //or any other error code accordingly. Bad request is a strong candidate also.
return Ok(model);
}
}
Hope this helps!

Related

.NET Core SignalR: How to accomplish resource-based authorization?

All my SignalR clients connect using a JWT bearer token. I utilize the [Authorize] attribute in my SignalR Hub.
This token contains a userId which can be used to check if a user has read access on the resource through the resource's users property which contains a List<PuppyUserPermission> that look like this:
public class PuppyUserPermission
{
public string userId { get; set; }
public bool read { get; set; }
public bool write { get; set; }
}
The question is: how do I connect the dots here? Ideally, instead of something like
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
await Clients.All.SendAsync(pup);
}
}
I would so something like the following (this is more pseudo code than anything else, as I don't use valid methods):
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
var clients = Puppy.users.Where(u => u.read == true);
await clients.SendAsync(pup);
}
}
Basically, I'd like to ensure that the clients recieving the Puppy object via SignalR would be authorized users on the resource. Problem is, Clients is just a list of string client IDs, and I'm not sure how to go about tying them to actual users on my Puppy resource.
How do I go about achieving this?
From the beginning, I had the feeling that the answer lay in IUserIdProvider, but I didn't see how that would work for multiple users.
I finally found the answer, but it'll definitely need some cleanup.
First, create your own implementation of IUserIdProvider as follows:
public class MyUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var username = connection.User.Claims.Where(x => x.Type == "THE_CLAIM_YOU_WANT_TO_USE_TO_IDENTIFY_USERS").First().Value;
return username;
}
}
Next, register it using DI:
services.AddSingleton<IUserIdProvider, MyUserIdProvider >();
Now, when you want to send events from the server, use DI in your constructor to pull down an instance of your SignalR Hub as per usual:
private IHubContext<PuppyHub> puppyHub { get; }
public UsersController(IHubContext<PuppyHub> _puppyHub)
{
puppyHub = _puppyHub;
}
Then, where when you want to tell your clients about the new Puppy:
// ... typical controller code
// assume we have a var, puppy, with a list of authorized users
// use System.Linq to get a list of userIds where the user is authorized to read the puppy
var authorizedUsers = (IReadOnlyList<string>)puppy.users.Where(x => x.permissions.read == true).Select(i => i._id).ToList();
// send the new puppy to the authorized users
await puppyHub.Clients.Users(authorizedUsers).SendAsync("SendPuppy", puppy);
And viola! You have now done resource-based authorization with SignalR.

How to create an object once and use it several times in Controller

I want to populate an object using methods in a Controller class. So I've created the object in the Controller class and then tried to populate it using methods in the class. This does not work though, because everytime a method in a controller is called, the entire Controller class is reinitiated. So I get a brand new object everytime I try to populate the object. However, I don't know how else I can create an object and populate it. I've added the code below. The object I'm talking about is ProcessModel. Ignore the other objects I created at the top of the class.
Controller:
public class HomeController : Controller
{
ProcessModel pm = new ProcessModel();
RetrievePatterns pt = new RetrievePatterns();
RetrieveModel rm = new RetrieveModel();
public IActionResult Index()
{
FindPatterns fp = new FindPatterns(rm.pm, pt.KpiPatterns);
ViewData["KPIs"] = fp.passdata;
return View();
}
[HttpPost]
public IActionResult AddEvent([FromBody] Event data)
{
data.ID = pm.EventObjects.Count + 1;
pm.EventObjects.Add(data);
return Json(pm.EventObjects.Count);
}
[HttpPost]
public IActionResult AddProcessName(string data)
{
pm.ID = 1;
pm.Name = data;
return Json(new { title = pm.Name });
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public ActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Object:
public class ProcessModel
{
public List<Event> EventObjects = new List<Event>();
public List<Entity> EntityObjects = new List<Entity>();
public int ID { get; set; }
public string Name { get; set; }
}
You could create a Singleton class which is responsible to create only one instance from Process model;
public class ProcessModelSingleton
{
private static ProcessModel _processModel = new ProcessModel();
private ProcessModelSingleton()
{
}
public static ProcessModel Instance
{
get { return _processModel; }
}
}
Then assign it a global variable in the controller;
ProcessModel pm = ProcessModelSingleton.Instance;
EDIT
Created instance by singleton class shared by all users. So, if you want to make it user specific, using Session is the best option.
public ProcessModel pm {
get
{
if (Session["ProcessModel"] == null)
{
Session["ProcessModel"] = new ProcessModel();
}
return (ProcessModel)Session["ProcessModel"];
}
}
Keep in mind static variables are shared across all users for that web server. So data populated into a singleton class by one user's request can be read/overwritten by a different user's request. This may be what you are after, but if not you should look into Session State.
ASP.NET Session State Overview
As HTTP is stateless by nature, usage of sessions are discouraged my Microsoft for Asp.NET. By default they are only visible to the web server that creates them so if you are using a farm, you either need to look at server affinity (also called sticky sessions) with a load balancer or out of proc state (such as SQL Server or NCache).
Ideally your application design should avoid the need for sessions and carry only the necessary data between requests via cookies and/or query strings. For more complex applications however this is usually not possible and so Session State is used.

OData paging with expand issue

I'm using OData v5/Web API 2.2 to create an endpoint that will return a list of employees from each company.
My problem occurs when I try to implement server-side paging while also using the OData $expand property. When I try to make a call to
http://localhost:60067/Companies?$expand=Employees
I get an error that says "Could not find a property named 'Employees' on type 'System.Web.OData.Query.Expressions.SelectAllAndExpand_1OfCompanyApiModel'"
However, when I removed the EnableQuery attribute the call to the endpoint or when I didn't expand it works as expected. Does anyone have an idea of what I am doing wrong? I've been googling this for a while but haven't found anything.
Here are some code snippets -
Data Models:
public class CompanyApiModel
{
[Key]
public Guid CompanyGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
public List<EmployeeApiModel> Employees { get; set; }
}
public class EmployeeApiModel
{
[Key]
public Guid EmployeeGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
}
CompaniesController.cs:
[EnableQuery(PageSize = 10)] // If I comment this out everything works
//[EnableQuery] // This fails as well
public IHttpActionResult Get(ODataQueryOptions<CompanyApiModel> queryOptions)
{
var companies = GetCompanies(queryOptions);
return Ok(companies);
// return Ok(companies.AsQueryable()); // This doesn't work either
}
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new OptionsRoutingConvention());
config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataPathHandler(), routingConventions);
// below code allows endpoints to respond with either XML or JSON, depending on accept header preferences sent from client
// (default in absence of accept header is JSON)
var odataFormatters = ODataMediaTypeFormatters.Create();
config.Formatters.InsertRange(0, odataFormatters);
config.EnsureInitialized();
}
public static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "Demos";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<CompanyApiModel>("Companies");
builder.EntitySet<EmployeeApiModel>("Employees");
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
Figured out the problem. We were overriding the EnableQuery attribute somewhere in our code and calling it EnableMappedQuery and applying it to the controller. Thus instead of having [EnableQuery(PageSize = 10)] I should have had [EnableMappedQuery(PageSize = 10)].
EnableQuery Attribute do many works,
1. it will validate the queryoption for you.
2. it will apply the queryoption for you.
3. it can add some querysettings like PageSize.
Your scenario not working is because your GetCompanies is already applied the queryoption, so when EnableQuery get the result and apply the queryoption again, it fails, it can't find the expand property, my suggestion is just return original Company and let EnableQuery do the reset of work for you, ODataQueryOption in parameter is also not needed.
If you realy do some custom work in GetCompanies and don't need EnableQuery to apply for you, you can add PageSize in ODataQuerySettings when you call method ODataQueryOptions.ApplyTo(IQueryable, ODataQuerySettings).

Json.Net Deserialization - Web API and Nullable Date

Say you've got a model that looks like
public class UserModel
{
public string UserName { get; set; }
public DateTime? DateOfBirth { get; set; }
}
The DateOfBirth field isn't required, but could be specified. You have a Web API POST endpoint that looks like
[Route("")]
[AcceptVerbs("POST")]
public async Task<IHttpActionResult> Create(UserModel user)
{
}
And we've set the JSON serializer in start up like so,
public static void Register(HttpConfiguration config)
{
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
var settings = jsonFormatter.SerializerSettings;
settings.Converters.Add(new IsoDateTimeConverter());
settings.Error += (sender, args) => Console.WriteLine("This event is fired ok");
}
If we send some JSON to the endpoint that looks like this
{
"userName": "User1",
"dateOfBirth": "jhdgjhjfg"
}
...the error event is fired in the Serializer settings and the endpoint is called. At this point, the DateOfBirth field is null and I don't have any context that a deserialization error has occurred
Reading the JSON.Net documentation, because Handled == false in the Error event arguments of the Settings object, an exception should be raised into the application code - this doesn't happen? Is there a setting I haven't configured correctly for this?
How can I get context within the action so that I know a value was specified for a field and couldn't be deserialized? Even global behaviour would be fine, as long as I know this has happened and can return a 400.
UPDATE:
We can use a filter to check the Model state, then check the Model State errors for exceptions of type JsonReaderException. This lets you return a 400 with a list of violating fields
public class CheckJsonExceptionModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid)
{
return;
}
var fieldsInError = new List<string>();
foreach (var jsonException in
actionContext.ModelState.Keys.SelectMany(key => actionContext.ModelState[key].Errors)
.Select(error => error.Exception).OfType<JsonReaderException>())
{
Trace.TraceError(jsonException.Message);
fieldsInError.Add(jsonException.Path);
}
var apiError = new { ErrorMessages.BadRequestModel.Message, FieldsInError = new List<string>() };
foreach (var fieldError in fieldsInError)
{
apiError.FieldsInError.Add(fieldError);
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, apiError);
}
}
You have multiple options. But first, you are getting no exception because the WebApi handles this exception. Bad news.
Good news, you can handle it in at least two ways; use the ModelState.IsValid property - in your case it will be false. You can access them in your post-method. When you remove the invalid dateOfBirth it is true ;-)
Or you can use an ActionFilterAttribute to put it on your methods for re-use purposes.
For example:
public async Task<IHttpActionResult> Create(UserModel user) {
if (!ModelState.IsValid) {
// ModelState.Keys // Get all error-keys
}
}

Asynchronous call to webservice in MVC 4 web application

I am building my first real MVC4 application and I have run into following issue.
I have a model for "User" class. Data for it are obtained through asynchronous call to webservice:
public sealed class AdminDMSEntities
{
public List<User> UserList { get; private set; }
public AdminDMSEntities()
{
this.UserList = new List<User>(0);
ServiceClient client = new ServiceClient();
client.GetUsersCompleted += (s, e) =>
{
if (e.Result == null)
throw new ArgumentNullException("No users were retrieved");
UserList = new List<User>(0);
e.Result.ForEach(w => this.UserList.Add(new User(w.Guid, w.TrusteeType, w.Username, w.Email, w.LastLogin, w.PasswordChanged, w.IsUsingTempPassword)));
};
client.GetUsersAsync();
}
}
I intend to use this class as I would use class derived from DbContext (if I could use Entity Framework which I cant). So far I have only users in the class.
I am using tis class in UsersController like this:
public class UsersController : Controller
{
private AdminDMSEntities adminEntities = new AdminDMSEntities();
//
// GET: /User/
public ActionResult Index()
{
return View(adminEntities.UserList);
}
}
The problem is that I will end up with InvalidOperationException, because controller is not waiting for async call completion and passes UserList to the view before it is properly filled with users.
I can have the call synchronous for the time being, but it is very likely I will be forced to use asynchronous calls later, so I would like to know how to ensure, that controller will wait for async call to be completed before UserList is passed to view...
Thanks in advance
EDIT: I have tried the approach with AsyncController as listed below, currently I have added this to AdminDMS entities class:
public static async Task<AdminDMSEntities> AdminDMSEntitiesAsync()
{
AdminDMSEntities result = null;
Task<AdminDMSEntities> getUsersAsyncTask = Task.Factory.StartNew(() => new AdminDMSEntities());
await getUsersAsyncTask;
return result;
}
and this is the change to the controller:
public class UsersController : AsyncController
{
private AdminDMSEntities adminEntities = null;
//
// GET: /User/
public async Task<ActionResult> Index()
{
if (adminEntities == null)
{
adminEntities = await AdminDMSEntities.AdminDMSEntitiesAsync();
}
return View(adminEntities.UserList);
}
}
The result is that adminEntities are containing an instance of the class, but there are no users in the list (there should be 11).
EDIT2: Since i was told that creating new task is not the right thing to do, I went with the first suggested approach removin AdminDMSEntities class from the code. My thanks to Darin for helping me out :)
You could use an asynchronous controller. The idea is to have your controller derive from the AsyncController class instead of the Controller class. This class provides methods that allow you to perform asynchronous operations.
For example:
public class MyController: AsyncController
{
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var client = new SomeClient();
client.GetUsersCompleted += (s, e) =>
{
UserList = new List<User>();
AsyncManager.Parameters["users"] = e.Result.Select(
w => new User(
w.Guid,
w.TrusteeType,
w.Username,
w.Email,
w.LastLogin,
w.PasswordChanged,
w.IsUsingTempPassword
)
)
.ToList();
AsyncManager.OutstandingOperations.Decrement();
};
client.GetUsersAsync();
}
public ActionResult IndexCompleted(IEnumerable<User> users)
{
return View(users);
}
}
and if you are using .NET 4.5 you could even take advantage of the new async keyword simplifying the asynchronous code even further. This is possible if you refactor your data access layer to the new pattern (i.e. return Tasks):
public class MyController: AsyncController
{
public async Task<ActionResult> Index()
{
var client = new SomeClient();
var users = await client.GetUsersAsync().Select(
w => new User(
w.Guid,
w.TrusteeType,
w.Username,
w.Email,
w.LastLogin,
w.PasswordChanged,
w.IsUsingTempPassword
)
)
.ToList();
return View(users);
}
}

Resources