How to consume ASP Rest web Api in Xamarin forms? - c#

I have created a REST Api in ASP.Net web form. I can read and write data from MySql database using the API (I tested the Api using chrome's REST Client extension). Now I am trying to consume the Rest Api in my cross platform xamarin forms. I just added a button in my app and see whether it can retrieve data if I click the button. I inserted a breakpoint at the var Json to see whether I'm getting any data. But I am not able to retrieve it. I am running the Web Api in localhost. I run the app in VS Android emulator. Please guide me on how to properly consume the REST web service. Thank you.
Web Api
namespace WorkAppApi.Controllers
{
public class MachinesController : ApiController
{
// GET: api/Machines
public List<machines> Get()
{
DBConn db = new DBConn();
return db.getMachineList();
}
// GET: api/Machines/5
public machines Get(long id)
{
DBConn db = new DBConn();
machines m = db.getMachine(id);
return m;
}
// POST: api/Machines
public HttpResponseMessage Post([FromBody]machines value)
{
DBConn db = new DBConn();
long id;
id = db.addMachine(value);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created);
response.Headers.Location = new Uri(Request.RequestUri, String.Format("machines/{0}", id));
return response;
}
// PUT: api/Machines/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE: api/Machines/5
public void Delete(int id)
{
}
}
}
Function submit button.
namespace WorkApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage : ContentPage
{
private string Uri = "http://192.168.0.124:59547/api/Machines/";
public TestPage ()
{
InitializeComponent ();
check();
}
private async Task submit(object sender, EventArgs e)
{
var httpClient = new HttpClient();
var json = await httpClient.GetAsync(Uri);
}
}
}

this would be my answer:
namespace WorkApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage : ContentPage
{
private string Uri = "http://192.168.0.124:59547/api/";
List<Machine> machines;
public TestPage ()
{
InitializeComponent ();
check();
}
private async Task submit(object sender, EventArgs e)
{
var httpClient = new HttpClient();
httpClient.BaseAddress = Uri // I have changed the Uri variabele, you should extend this class and give it the same base address in the constructor.
var resp= await httpClient.GetAsync("Machines");
if (resp.Result.IsSuccessStatusCode)
{
var repStr = resp.Result.Content.ReadAsStringAsync();
machines= JsonConvert.DeserializeObject<List<Machine>>(repStr.Result.ToString());
}
}
}
}

Related

.NET MAUI, how to get data from SecureStorage in constructor

I am struggling to get JWT from SecureStorage, as it's Get method is async, and we all know that constructor doesn't support async calls.
UPDATE:
What I want to do is check if I have a token and at the app start show the LoginPage or the MainPage.
I tried something like this:
public AppShell()
{
JWTokenModel jwt = null;
Task.Run(async () =>
{
jwt = await StorageService.Secure.GetAsync<JWTokenModel>(StorageKeys.Secure.JWT);
});
InitializeComponent();
RegisterRoutes();
shellContent.Title = "Amazons of Volleyball";
if (jwt is null || jwt?.Expiration < DateTime.Now)
{
shellContent.Route = PageRoutes.LoginPage;
shellContent.ContentTemplate = new DataTemplate(typeof(LoginPage));
}
else
{
shellContent.Route = PageRoutes.HomePage;
shellContent.ContentTemplate = new DataTemplate(typeof(MainPage));
}
}
private void RegisterRoutes()
{
Routing.RegisterRoute(PageRoutes.LoginPage, typeof(LoginPage));
Routing.RegisterRoute(PageRoutes.HomePage, typeof(MainPage));
Routing.RegisterRoute(PageRoutes.DetailsPage, typeof(PlayerDetailsPage));
Routing.RegisterRoute(PageRoutes.AddOrUpdatePage, typeof(AddOrUpdatePlayer));
}
When it hits the StorageService.Secure.GetAsync method's line, where I wan't to get the data like
public static async Task<T> GetAsync<T>(string key)
{
try
{
var value = await SecureStorage.Default.GetAsync(key);
if (string.IsNullOrWhiteSpace(value))
return (T)default;
var data = JsonSerializer.Deserialize<T>(value);
return data;
}
catch(Exception ex)
{
return (T)default;
}
}
it simple jumps out of the method.
UPDATE: I update the code suggested by ewerspej. The error still stands, when the SecureStore tries to get the value it jumps out from the method, no exception and I got the following error:
System.InvalidOperationException: 'No Content found for ShellContent, Title:, Route D_FAULT_ShellContent2'
The updated code:
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
RegisterRoutes();
SetShellContentTemplate();
}
private async void SetShellContentTemplate()
{
var hasValidJWT = await LoadTokenAsync();
if (hasValidJWT)
{
shellContent.ContentTemplate = new DataTemplate(typeof(MainPage));
shellContent.Route = PageRoutes.HomePage;
shellContent.Title = "Amazons of Volleyball";
}
else
{
shellContent.ContentTemplate = new DataTemplate(typeof(LoginPage));
shellContent.Route = PageRoutes.LoginPage;
shellContent.Title = "Amazons of Volleyball";
}
}
private async Task<bool> LoadTokenAsync()
{
var jwt = await StorageService.Secure.GetAsync<JWTokenModel>(StorageKeys.Secure.JWT);
return !(jwt is null || jwt?.Expiration < DateTime.Now);
}
private void RegisterRoutes()
{
Routing.RegisterRoute(PageRoutes.LoginPage, typeof(LoginPage));
Routing.RegisterRoute(PageRoutes.HomePage, typeof(MainPage));
Routing.RegisterRoute(PageRoutes.DetailsPage, typeof(PlayerDetailsPage));
Routing.RegisterRoute(PageRoutes.AddOrUpdatePage, typeof(AddOrUpdatePlayer));
}
}
UPDATE 2:
Moved the logic to App class:
public static class PageRoutes
{
public static string LoginPage = "login";
public static string HomePage = "home";
public static string AddOrUpdatePage = "add-or-update";
public static string DetailsPage = "/details";
}
public partial class App : Application
{
private readonly ISecurityClient securityClient;
public App(ISecurityClient securityClient)
{
this.securityClient = securityClient;
InitializeComponent();
SetStartPage();
}
private async void SetStartPage()
{
var hasValidJWT = await ReatJwtAsync();
MainPage = hasValidJWT ?
new AppShell() :
new LoginPage(securityClient);
}
private async Task<bool> ReatJwtAsync()
{
var jwt = await StorageService.Secure.GetAsync<JWTokenModel>(StorageKeys.Secure.JWT);
return !(jwt is null || jwt?.Expiration < DateTime.Now);
}
}
public partial class AppShell : Shell
{
public AppShell()
{
RegisterRoutes();
InitializeComponent();
}
private void RegisterRoutes()
{
Routing.RegisterRoute(PageRoutes.LoginPage, typeof(LoginPage));
Routing.RegisterRoute(PageRoutes.HomePage, typeof(MainPage));
Routing.RegisterRoute(PageRoutes.DetailsPage, typeof(PlayerDetailsPage));
Routing.RegisterRoute(PageRoutes.AddOrUpdatePage, typeof(AddOrUpdatePlayer));
}
}
AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="MauiUI.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiUI"
xmlns:pages="clr-namespace:MauiUI.Pages"
Shell.FlyoutBehavior="Disabled">
<ShellContent
Title="Amazons of Volleyball"
ContentTemplate="{DataTemplate local:MainPage}"
Route="HomePage" />
</Shell>
Still not reading a token successfully. But looking at the output I can see that Thread #8 started (might have some impact).
Now I am getting a new error:
System.NotImplementedException: 'Either set MainPage or override CreateWindow.'
any idea?
thnx
Instead of loading the token directly inside the constructor, you should defer it to an asynchronous method which you can still call from within the constructor, but you cannot await it and you shouldn't be using a blocking call, either.
You could do this in the App.xaml.cs, which is a common place for loading data when an app starts. While the data is loading, you can set the MainPage object to some type of loading page and once the token was loaded, you can set the MainPage to the AppShell or a LoginPage instance, e.g. like this:
public App()
{
InitializeComponent();
MainPage = new LoadingPage();
Load();
}
private async void Load()
{
if (await LoadTokenAsync())
{
MainPage = new AppShell();
}
else
{
MainPage = new LoginPage();
}
}
private async Task<bool> LoadTokenAsync()
{
var jwt = await StorageService.Secure.GetAsync<JWTokenModel>(StorageKeys.Secure.JWT);
return !(jwt is null || jwt?.Expiration < DateTime.Now);
}
This is just to demonstrate how you could solve the problem. You shouldn't do this in AppShell.xaml.cs but rather in the App.xaml.cs. Obviously, you'll need to store the token in memory somewhere.

How to access posted server json data from client

I am writing my first app using net 2.1 and angular 6.  I am able to post the json data from net 2.1 on an iis express webpage from visual studio using C#.
I am able to post data from the front end, angular/typscript, to the server. I perform calculations on the data and post the json results on the iis express server webpage. How do I get that data using angular?
Here is my angular api service file that allow me to post to the server
export class ApiService {
private handleError: HandleError;
constructor(
private http: HttpClient,
httpErrorHandler: HttpErrorHandler) {
this.handleError = httpErrorHandler.createHandleError('HeroesService');
}
stock: StockComponent ;
stockURL = 'https://localhost:44310/api/stock'
/** POST: */
postStock (stock) {
console.log('stock is ', stock);
this.http.post(this.stockURL, stock).subscribe(res => {
console.log(res)
})
}
getBuySellData(){
return this.http.get('https://localhost:44310/api/stock');
}
}
Here is the component file:
export class StockComponent {
stock = {}
constructor(private api: ApiService){
}
post(stock){
console.log('this is the stock ', stock)
this.api.postStock(stock)
}
Here is part of the controller in visual studio
[HttpPost]
public async Task<IActionResult> Post([FromBody]Models.Stock stock)
{
_context.Stocks.Add(stock);
await _context.SaveChangesAsync();
return Ok(stock);
}
[HttpGet]
public IEnumerable<DateCloseBuySell> GetQuote()
{
string responseString = string.Empty;
I had a similar trouble in Xamarin.Forms.
To solve it I did something like:
private const string Url = "https://localhost:44310/api/stock";
private HttpClient _client = new HttpClient();
protected async void OnGetList()
{
if (CrossConnectivity.Current.IsConnected)
{
try
{
var content = await _client.GetStringAsync(Url);
var list = JsonConvert.DeserializeObject<List<Model>>(content);
}
catch (Exception e)
{
Debug.WriteLine("", e);
}
}
}
I modified api.service.ts to
stock: StockComponent ;
stockURL = 'https://localhost:44310/api/stock'
/** POST: */
postStock (stock) {
this.http.post(this.stockURL, stock).subscribe(res => {
console.log(res)
})
}
getBuySellData(){
this.http.get(this.stockURL).subscribe(res => {
console.log(res)
})
and the component.ts to
constructor(private api: ApiService){
}
ngOnInit() {
this.api.getBuySellData()
}
post(stock){
this.api.postStock(stock)
}
I see the json data in my client console window. Thanks Chris.

Add multiple tokens into DefaultRequestHeaders.Authorization in HttpClient

The API I'm calling from my ASP.NET Web API app requires two tokens i.e. accessToken and userToken.
The following code is not working because it takes only the second token, not both. Looks like the second line is over-writing the first one.
How do I add multiple tokens to my request header?
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("APIAccessToken", "token1");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("UserToken", "token2");
UPDATE:
Here's the way I set this up and it's not working. Basically, my API calls seem to go nowhere. I get no errors. Just no response.
First, I have the HttpClientAccessor that looks like this:
public static class HttpClientAccessor
{
private static Lazy<HttpClient> client = new Lazy<HttpClient>(() => new HttpClient());
public static HttpClient HttpClient
{
get
{
client.Value.BaseAddress = new Uri("https://api.someurl.com");
client.Value.DefaultRequestHeaders.Accept.Clear();
client.Value.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client.Value;
}
}
}
I then have my ApiClient that will perform my API calls which looks like this:
public class MyApiClient
{
HttpClient _client;
public MyApiClient()
{
_client = HttpClientAccessor.HttpClient;
}
public async Task Get()
{
try
{
HttpResponseMessage response = await _client.GetAsync("/myendpoint"); // This is where it gets lost
var data = await response.Content.ReadAsStringAsync();
}
catch(Exception e)
{
var error = e.Message;
}
}
}
This is my controller action:
public class MyController : Controller
{
private readonly MyApiClient _client;
public MyController()
{
_client = new MyApiClient();
}
public IActionResult SomeAction()
{
_client.Get().Wait();
}
}
You are confusing the standard authorization header with custom headers
According to the linked documentation
Request Header
Add the generated tokens to the request headers "APIAccessToken" and "UserToken"
Example Request
APIAccessToken: zjhVgRIvcZItU8sCNjLn+0V56bJR8UOKOTDYeLTa43eQX9eynX90QntWtINDjLaRjAyOPgrWdrGK12xPaOdDZQ==
UserToken: 5sb8Wf94B0g3n4RGOqkBdPfX+wr2pmBTegIK73S3h7uL8EzU6cjsnJ0+B6vt5iqn0q+jkZgN+gMRU4Y5+2AaXw==
To get headers like above, add them to the client like below
_client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
_client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
Based on shown update, the client is adding the headers every time the client is called. This should be in the value factory of the lazy client.
public static class HttpClientAccessor {
public static Func<HttpClient> ValueFactory = () => {
var client = new HttpClient();
client.BaseAddress = new Uri("https://someApiUrl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient {
get {
return client.Value;
}
}
}
The controller action also needs to be refactored to avoid deadlocks because of the mixing of async and blocking calls like .Wait() or .Result.
public class MyController : Controller {
private readonly MyApiClient _client;
public MyController() {
_client = new MyApiClient();
}
public async Task<IActionResult> SomeAction() {
await _client.Get();
//... code removed for brevity
}
}

In webapi asp.net I am getting following error.,response status code does not indicate success 404 (not found)

I am getting following error when calling get method from web API.
This is my controller class.
[RoutePrefix("api/AnnouncementController")]
public class AnnouncementController : ApiController
{
// GET api/<controller>
[Route("{code}")]
[HttpGet]
public IEnumerable<EAnnouncement> Get(string code)
{
return AnnouncementC.getdata(code);
}
...
}
This is my configuartion:
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
}
}
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
Here I am calling get method.But do not getting error. Please help me
protected void Page_Load(object sender, EventArgs e)
{
string Jcode = (string)(Session["JCode"]);
GridView2.DataSource = addressBookGrid_GetData(Jcode);
GridView2.DataBind();
}
public IQueryable<EAnnouncement> addressBookGrid_GetData(string code)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:1146");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("api/AnnouncementController").Result;
response.EnsureSuccessStatusCode();
List<EAnnouncement> addressBooks = response.Content.ReadAsAsync<List<EAnnouncement>>().Result;
return addressBooks.AsQueryable();
}
Remove controller name while calling the service
[RoutePrefix("api/Announcement")]
HttpResponseMessage response = client.GetAsync("api/Announcement").Result;
your api methord is expecting one parameter pass the parameter like
client.GetAsync("api/Announcement/1").Result;
[Route("api/Announcement/{code}")] decorate with this attribute on the top of your action methord

Call web API from client app

I used to use ASMX web services, however have since read (and been told) that a better way to request data from a client etc is to use web API's with MVC.
I have created an MVC 4 web api application and getting to grips with how it works.
Currently I have a single public string in my valuesControllers -
public class ValuesController : ApiController
{
// GET api/values/5
public string Get(int id)
{
return "value";
}
}
And I am currently trying to call this in my client like this -
class Product
{
public string value { get; set; }
}
protected void Button2_Click(object sender, EventArgs e)
{
RunAsync().Wait();
}
static async Task RunAsync()
{
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://localhost:12345/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP GET
HttpResponseMessage response = await client.GetAsync("api/values/5");
if (response.IsSuccessStatusCode)
{
Product product = await response.Content.ReadAsAsync<Product>();
Console.WriteLine("{0}", product.value);
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message.ToString());
}
}
}
On debugging I can step through the request and enter the web API code successfully however on the line -
Product product = await response.Content.ReadAsAsync<Product>();
This fails and enters my catch with the exception -
Error converting value "value" to type 'myDemo.Home+Product'. Path '', line 1, position 7.
Why is this?
Why is this?
Because from your controller action you are returning a string, not a Product which are 2 quite different types:
public string Get(int id)
{
return "value";
}
so make sure that you are consistently reading the value on the client:
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsAsync<string>();
Console.WriteLine("{0}", result);
}
Of course if you modified your API controller action to return a Product:
public Product Get(int id)
{
Product product = ... go fetch the product from the identifier
return product;
}
your client code would work as expected.

Categories

Resources