Asynchronously calling an external REST API from controller or view? - c#

I've been trying to wrap my head around this for too long. This is my first time with C# and .NET and apart from dabbling with C++ almost 15 years ago I have no programming experience. REST APIs and asynchronicity are new concepts to me.
I'm trying to make a scoreboard page in .NET MVC 5 consisting of a simple table with rows containing values retrieved from a MySQL database.
Each row in the database table represents a unique player along with his various stat values. I've created a model to represent each player, PlayerRow, and am passing a List<PlayerRow> to my view where the table gets generated via #foreach (PlayerRow playerrow in Model).
This is my current ActionResult PlayerList():
public class HomeController : Controller
{
public ActionResult PlayerList()
{
List<PlayerRow> PlayerRows = new List<PlayerRow>();
string constr = ConfigurationManager.AppSettings["MySQLConnStr"];
using (MySqlConnection con = new MySqlConnection(constr))
{
string query = "SELECT * FROM `rankme`";
using (MySqlCommand cmd = new MySqlCommand(query))
{
cmd.Connection = con;
con.Open();
using (MySqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
PlayerRows.Add(new PlayerRow
{
TableID = rdr["id"].ToString(),
LegacySteamID = rdr["steam"].ToString(),
Steam64 = ConvertToSteam64(rdr["steam"].ToString()),
Name = rdr["name"].ToString(),
IP = rdr["lastip"].ToString(),
Score = Convert.ToInt32(rdr["score"]),
etc etc...
});
}
}
con.Close();
}
}
return View(PlayerRows);
}
}
Relevant content from my PlayerList.cshtml:
#foreach (PlayerRow playerrow in Model)
{
<tr>
<th scope="row" class="avatar" id="#playerrow.Steam64"><img src="~/Content/placeholder.png"</th>
<td>#playerrow.Name</td>
<td>#playerrow.Score</td>
<td>#playerrow.Kills</td>
<td>#playerrow.Deaths</td>
<td>#playerrow.Assists</td>
etc etc...
</tr>
}
The MySQL connection works just fine and the table gets displayed just how I want it to. (albeit not quite polished, yet)
Picture of my current table
What I would like to do next is replace the placeholder avatar in each table row with the players avatar retreived via the Steam API. Each players avatar url can be retreived from the Steam API by calling https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=_API_KEY_&steamids=_STEAMID64_ which returns a json response:
{
"response":{
"players":[
{
"steamid":"EXAMPLE_STEAMID",
"communityvisibilitystate":3,
"profilestate":1,
"personaname":"EXAMPLE_NAME",
"commentpermission":1,
"profileurl":"EXAMPLE_STEAM_PROFILE_URL",
"avatar":"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/EXAMPLE_AVATAR.jpg",
"avatarmedium":"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/81/EXAMPLE_AVATAR_medium.jpg",
"avatarfull":"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/EXAMPLE_AVATAR_full.jpg",
"lastlogoff":1586222967,
"personastate":0,
"realname":"EXAMPLE_REALNAME",
"primaryclanid":"103582",
"timecreated":1070200624,
"personastateflags":0,
"loccountrycode":"US"
}
]
So I guess that requires me to make the request using HttpClient() and awaiting the response. That's where I get lost because I can't seem to await the response outside of an async method. Maybe using AJAX (which I've never used) would be more appropriate?
Could I create an async method to consume the Steam API? When and from where should that method be called?

To call an async method from a controller you can simply change your signature to
public async Task<IActionResult> PlayerList(CancellationToken cancellationToken)
From here, you'll be able to call any async method you like.
I don't really know the Steam API so if you're going to be making lots of network calls it might be better to do this on the Browser/Javascript side (lazy-loading the avatars as the browser scrolls into view).

Related

Populate table column from code foreach row

I recently started the transition from asp.net webforms to blazor server side.
I am still reading and learning about it, and as a side project in order to learn, i started to rewrite an application i made in webforms.
So, in the webforms, when i had to present very big amounts of data coming from ms sql server, i used asp:repeater to populate the main values, and then with nested update panels i managed to have the basic results almost instantly, and after 1-2 seconds, the other columns were populated.
I try to do something similar with Blazor, but with no luck at all.
My code for the main values is
<tbody>
#if(antists==null)
{
}
else
{
#foreach (var antist in antists)
{
string? trdr = "405";
string? mtrl = antist.Mtrl;
string? trdbusiness = "2001";
string? mtrpcategory = antist.Mtrpcategory;
string? mtrmanfctr = antist.Mtrmanfctr;
string image = "https://zerog01.b-cdn.net/" + antist.Image_guid + ".jpg";
//getfldasync(trdr, mtrl, trdbusiness, mtrpcategory, mtrmanfctr);
<tr>
<td style="text-align: center">
<a data-fancybox href=#image>
<img id="img_eikona_in" runat="server" class="img-fluid" style="max-height:100px;"
src=#image onerror="this.onerror=null; this.src='/webimages/no-image.jpg'" /></a>
</td>
<td>#antist.Code.ToString() <br>
#antist.Name</td>
<td>#fld</td>
</tr>
}
}
</tbody>
My goal is to populate #fld from a stored procedure. If i use code to do this on the fly as the rows are being created, it is very slow. (same problem as webforms)
I tried to use async method, but no luck. The closer i got was to have System.Threading.Tasks.Task`1[System.String] shown in the column of #fld. I didn't keep the exact code, but it was something like
public async Task<string> getfldasync(string trdr, string mtrl, string trdbusiness, string mtrpcategory, string mtrmanfctr )
SqlCommand cmd = new SqlCommand(str, con_digi);
cmd.Parameters.AddWithValue("#trdr", trdr);
cmd.Parameters.AddWithValue("#mtrl", mtrl);
cmd.Parameters.AddWithValue("#trdbusiness", trdbusiness);
cmd.Parameters.AddWithValue("#mtrpcategory", mtrpcategory);
cmd.Parameters.AddWithValue("#mtrmanfctr", mtrmanfctr);
await con_digi.OpenAsync();
var scalar = cmd.ExecuteScalarAsync();
fld = Convert.ToString(scalar);
await con_digi.CloseAsync();
return fld;
>not a sp, but i wanted to make changes in order to test the code.
Is there another method i miss for presenting that kind of data? Or should i keep on trying to make async call?
Edit: I uploaded the code where i used datatable instead of list for the initial databind of the table.

POST Object to VB.NET web service via ASP.NET MVC controller

I'm trying to pass a object from .NET MVC to Web Service (VB.NET) suing SOAP.
Passing individual fields works but when I try to pass an object, it throws an error
Cannot convert from Project.Models.Table to Project.WebService.Table
REQUIREMENTS
To pass an Object from MVC to Web Service.
Below is my Web Service code.
Questions: Do I need to serialize once I get the object.
<WebMethod()>
Public Function FormData(ByVal obj As Table)
Dim sqlconn As New SqlConnection
Dim sqlcmd As New SqlCommand
Try
Dim formSerializer As New XmlSerializer(GetType(Table))
Using reader As TextReader = New StringReader(obj)
data = formSerializer.Deserialize(reader)
End Using
Below is my MVC Controller, I have added the Service Reference.
public ActionResult Submission(Table data)
{
Table obj = new Table();
FormService.WebServiceSoapClient client = new FormService.WebServiceSoapClient();
obj = client.FormData(data);
return obj;
}
The error is while passing the data object from Controller to Web Service.
The Table model class with all the fields are added in both the projects.
Your help is appreciated.
Thanks.
UPDATE
As per the suggestions of #Panagiotis Kanavos, I tried using AutoMapper. So below is the working code. I am able to store values to database. Please do let me know if its correct and secured way. Thanks.
CLIENT SIDE
public JsonResult PostMethod(Table data)
{
FormService.WebServiceSoapClient client = new FormService.WebServiceSoapClient();
var config = new MapperConfiguration(cfg => { cfg.CreateMap<Table, Table_WS>(); });
IMapper iMapper = config.CreateMapper();
var destination = iMapper.Map<Table, Table_WS>(data);
var result = client.FormData(destination);
WEB SERVICE
<WebMethod()>
Public Function FormData(ByVal Obj As Table_WS) As Table_WS
sqlconn.ConnectionString = CONNECTION_STRING
sqlcmd.Connection = sqlconn
sqlconn.Open()
sqlcmd.CommandType = Data.CommandType.StoredProcedure
sqlcmd.CommandText = "SPR_INSERT"
sqlcmd.Parameters.AddWithValue("#NAME", IIf(IsNothing(Obj.NAME), DBNull.Value, Obj.NAME))
Thank you for helping.
First of all, if you're passing that much data, I think you should POST it, not GET it cause you clearly attempt to send something, not retrieve.
Secondly - I don't know VB.NET, but I assume '' stands for comment so your code will always try to deserialize data as XML. You could try passing parameter to inform your web method which serialization method is used. Again - I'm not sure how it's done in VB.NET.
You cannot convert between the 2 tables.
Public Function FormData(ByVal obj As Table) is expecting Project.WebService.Table and you are passing in Project.Models.Table. I think that is the correct order.
You need to pass into client.FormData a Project.WebService.Table.
You will have to convert your Project.Models.Table to a Project.WebService.Table
Something like this, should work.
public ActionResult Submission(Table data)
{
var client = new FormService.WebService1();
var table = new FormService.Table();
table.FirstName = data.FirstName;
table.LastName = data.LastName;
var obj = client.FormData(table);
data.FirstName = obj.FirstName;
data.LastName = obj.LastName;
...
}
The only way to do this with serialization would to use JSON and pass it as a string to the web method, at that point you could convert into any class you want.

Retrieve all contents of Zoho module via REST API c#

I am trying to get the full contents of my modules From Zoho to our local Server. The deluge code does work as it returns to me the data which is being sent via the API. However, once it reaches the API, it is null. Any idea?
Below is the deluge code:
// Create a map that holds the values of the new contact that needs to be created
evaluation_info = Map();
evaluation_info.put("BulkData",zoho.crm.getRecords("Publishers"));
data = Map();
data.put(evaluation_info);
response = invokeurl
[
url :"https://zohoapi.xxxxx.com/publisher/publish"
type :POST
parameters:data
connection:"zohowebapi"
];
info data; (data returns all the data from publishers)
Here is my ASP.NET core restful API. It does ping it and create the file but the content of the file is null.
Route("[controller]")]
[ApiController]
public class PublisherController : ControllerBase
{
[HttpGet("[action]"), HttpPost("[action]")]
public void Publish(string data)
{
(it's already null when it comes here. why?)
string JSONresult = JsonConvert.SerializeObject(data);
string path = #"C:\storage\journalytics_evaluationsv2.json";
using (var file = new StreamWriter(path, true))
{
file.WriteLine(JSONresult.ToString());
file.Close();
}
}
}
}
What am I missing? Thank you
After contacting Zoho support, the solution he offered was to loop through the data in order to get all the contents from a module (if they are more than 200 records. With the solution provided, one doesn't really need the deluge code anymore as long as you have the ZOHO api set to your account in code. This was my final solution. This solution is not scalable at all. It's best to work with the BULK CSV.
// Our own ZohoAPI which lets us connect and authenticate etc. Yours may look slightly different
ZohoApi zohoApi = new ZohoApi();
zohoApi.Initialize();
ZCRMRestClient restClient = ZCRMRestClient.GetInstance();
var allMedicalJournals = new List<ZCRMRecord>();
for (int i = 1; i <= 30; i++)
{
List<ZCRMRecord> accountAccessRecords2 =
restClient.GetModuleInstance("Journals").SearchByCriteria("Tag:equals:MedicalSet", i, 200).BulkData.ToList();
foreach (var newData in accountAccessRecords2)
allMedicalJournals.Add(newData);
}

Expanding a Source object in a Stripe API call to StripeBalenceService doesn't return any customer info

I'm making a c# call to the Stripe.net API to fetch a balance history for a connected account. I'm trying to expand on the balance transaction object to see where the charge is coming from (ex. the customer who made the charge) as all the charges to connected accounts on my platform are from charge objects with a destination property to the connected account.
Here is my code and a screenshot of what the expanded source looks like, but think I should see a charge id or a customer or something refering me to the initial customer somewhere, but I don't...
var balanceService = new StripeBalanceService();
balanceService.ExpandSource = true;
var list = new List <string> () {
"data.source.source_transfer"
};
StripeList <StripeBalanceTransaction> balanceTransactions
= balanceService.List(
new StripeBalanceTransactionListOptions() {
Limit = 20,
Type = "payment",
Expand = list
},
new StripeRequestOptions() {
StripeConnectAccountId = accountId
}
);
foreach(var transaction in balanceTransactions) {
var test = transaction;
}
I feel like I should see a charge id (ex. ch_xxx) or a Customer value (which is null) all I see of any relevance is a payment id (ex. py_xxx)
It is possible to get the charge object(ch_xxx), it is just a little involved!
As you are using destination charges, the charge(ch_xxx) takes place on the platform account, and then a transfer(tr_xxx) is made to the connected account. That transfer creates a payment(py_xxx) on the connected account, which results in a balance transaction(txn_xxx).
As your code expands the source of those balance transactions, you get the payment(py_xxx). The payment is equivalent to a charge, so it has a source_transfer field. You can expand this field also! This will give you the transfer object(tr_xxx). Finally, the transfer has a source_transaction field, and this can be exapanded to give the original charge(ch_xxx)!
Putting that all together, you will want to expand on "data.source.source_transfer.source_transaction".
If you use a Stripe library in a dynamic language you can see this in action ... unfortunately, stripe-dotnet has an open issue right now which means that you can not do this directly. Instead, you will need to make the API calls manually by calling the various Retrieve functions on the IDs, instead of doing a single expansion. It would look something like this:
var paymentId = transaction.Source.Id;
var chargeService = new StripeChargeService();
var payment = chargeService.Get(
paymentId,
new StripeRequestOptions()
{
StripeConnectAccountId = accountId
}
);
var transferService = new StripeTransferService();
transferService.ExpandSourceTransaction = true;
var transfer = transferService.Get(payment.SourceTransferId);
var charge = transfer.SourceTransaction;
Console.WriteLine(charge.Id);

asp.net MVC3 C#: Passing query as a parameter and displaying result

I am new to asp.net , C# and building an MVC application based on the popular Music Store application.
I have my basic navigation ready and I have reached a point where I am drawing a complete blank. Basically, my asp page displays a SQL query (which is saved in SQL DB on same machine)
Need:
I need to have a button next to this query which when clicked, connects to another DB through OLEDB, and runs the query and shows result in a pop up window.
Questions:
How do I pass the query (which is being fetched from DB) as a parameter to code below and How do I make the results pop up in a window.
Can you please point me in correct direction. The code below is from a stand alson asp page which i used for testing connections etc. basically i need to pass the query as a parameter (replacing query seen below) and have the result in a pop window.
<%# Import Namespace="System.Data.OleDb" %>
<%# Import Namespace="System.Data.Odbc" %>
<script runat="server">
sub Page_Load
Dim dbconn, sql, dbcomm, dbread
dbconn = New OleDbConnection("Provider=xxxx;Password=xxxx;User ID=xxxx;Data Source=xxxx;Initial Catalog=xxxx;Port=xxxx;")
dbconn.Open()
sql = "Select ID from TABLE1"
dbcomm = New OleDbCommand(sql, dbconn)
dbread = dbcomm.ExecuteReader() <%-- Call this method within oledbcommand--%>
If dbread.Read = False Then
MsgBox("No Data Check")
Else
Response.Write("<table>")
Do While dbread.Read()
Response.Write("<tr>")
Response.Write("<td>")
Response.Write(dbread(0))
Response.Write("</td>")
Response.Write("</tr>")
Loop
Response.Write("</table>")
End If
dbconn.Close()
end sub
</script>
ADDITIONAL DETAILS
CONTROLLER CLASS
.
.
public ActionResult DisplayResult(String Qry)
{
List<QuerySet> QueryToExecute = new List<QuerySet>();
return View(QueryToExecute);
VIEW that provides this contoller with DATA, this is query that is fetched from my SQL DB and should be executed to a separate DB on a separate server.
<ul>
#foreach (var ShowQueries in Model.Queriess)
{
<li>
#Html.ActionLink(ShowQueries.Query, "DisplayResult", new { Qry = ShowQueries.Query })
</li>
}
ISSUE:
How should I use a view named 'DisplayResult' which handles the query fetched by view above and executes it agaisnt another DB.
I was hoping I can use a Webform view rather than a razor view but either way i am not able to pass the parameter
Any ideas are appreciated
The point of MVC is to move data connections out of the View (aspx page) and into a Controller.
Read some more MVC tutorials, and buy a book or two. You should actually populate the data into a viewmodel on the controller, and then pass that viewmodel to the view. This way, the view knows nothing about how to get the data -- it already got it from the controller.
Views should have the responsibility of displaying data to users over the web, not getting the data or manipulating it directly.
With that aside, here is how you would do it:
Pass the query as a string to an Action Method on a Controller (using HTTP POST or GET) using AJAX (i.e. jQuery $.ajax() method).
Have the action method return the HTML for your popup window, using a Partial View. You could also return Json, but I think HTML / partial view would be easier in this case. This is the method that will do your OLE DB connection, and execute the query.
In your $.ajax() success function callback, write javascript that will popup a new dialog with the partial view HTML that was returned by the controller action method.
You could create a class to hold the data you want to display:
namespace sample {
class viewList
{
public string field1 {get;set;}
...
}
}
and create a list to hold your results in your controller:
List<viewList> theList = new List<viewList>();
//Populate dbread here...
while (dbread.Read())
{
viewList listData = new viewList();
listData.field1 = (dataType)dbread[0]; //Convert to your data type
theList.Add(listData);
}
and pass this to the view:
return view(theList);
Then in your model (of model type viewList) display your results in a table:
#model sample.viewList
<table>
#foreach (var item in Model)
{
<tr>
<td>#item.field1</td>
</tr>
}
</table>
ALTERNATIVE
To display in popup, put the list into the ViewBag like this:
List<viewList> theList = new List<viewList>();
//Populate dbread here...
while (dbread.Read())
{
viewList listData = new viewList();
listData.field1 = (dataType)dbread[0];
theList.Add(listData);
}
ViewBag.Items = theList;
Then in your view:
<script type="text/javascript">
$(function() {
var array = #Html.Raw(Json.Encode(ViewBag.Items));
//Construct your table using the array here...
alert(theConstructedTable);
});
</script>

Categories