Issue getting NullReferenceException using Http with jwt Xamarin Forms Android - http

So my code works in PostMan querying the api to populate a listview locally in my Android app. But when I run it from within the app, I get NullReferenceException on the line "Items.Clear() in ShipViewModel.cs
I tried hardcoding the address rather than using my APIQueriable path, I tried generating new JWT, and I tried manually cleaning my /bin /obj folders to rule out code not compiling correctly.
ShipsViewModel.cs Xamarin.Forms/ViewModels
{
private GallogClient _gallogClient;
public ObservableCollection<ShipCatalog> Items { get; set; }
public Command LoadItemsCommand { get; }
public ShipsViewModel()
{
Title = "Ships";
_gallogClient = new GallogClient("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9hcGkuZ2FsbG9nLmNvIiwiYXVkIjoiaHR0cDpcL1wvYXBpLmdhbGxvZy5jbyIsImlhdCI6MTM1Njk5OTUyNCwibmJmIjoxMzU3MDAwMDAwLCJkYXRhIjp7ImlkIjo1NywidXNlcm5hbWUiOiJQYXJhIiwiaGFuZGxlIjoiUGFyYSIsImVtYWlsIjoicGFyYWJvbGE5NDlAZ21haWwuY29tIn19.bRpI9hVy-Spky5pbZhJCkyN-MT9RA6ap_yD9ezRxCxo");
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand(), () => !IsBusy);
}
async Task ExecuteLoadItemsCommand()
{
if (IsBusy)
return;
IsBusy = true;
try
{
LoadItemsCommand.ChangeCanExecute();
Items.Clear();
var items = await _gallogClient.GetItemsAsync<ShipList>();
foreach (var item in items.ships.ToList())
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
}
GallogClient.cs Gallog.API
{
internal readonly HttpClient Client = new HttpClient { BaseAddress = new Uri("https://api.gallog.co/api/") };
public string Jwt { get; set; }
public GallogClient()
{
}
public GallogClient(string jwt)
{
Jwt = jwt;
}
private StringContent JwtContent
{
get {
return new StringContent(JsonConvert.SerializeObject(new
{
jwt = Jwt
}), Encoding.UTF8, "application/json");
}
}
//...
public async Task<T> GetItemAsync<T>(string name) where T : ApiQueryable
{
return await PostAsync<T>($"{GetPath<T>()}/{name}");
}
public async Task<T> GetItemsAsync<T>() where T : ApiQueryable
{
return await PostAsync<T>($"{GetPath<T>()}");
}
internal string GetPath<T>()
{
if (typeof(T).GetCustomAttributes(
typeof(ApiPathAttribute), true
).FirstOrDefault() is ApiPathAttribute at)
{
return at.Path;
}
return null;
}
public async Task<T> PostAsync<T>(string path) where T : ApiQueryable
{
var response = await Client.PostAsync(path, JwtContent);
return JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
}
public async Task<T> PostAsync<T>(object body, string path) where T : ApiQueryable
{
var response = await Client.PostAsync(path,
new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"));
return JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
}
}
}
ShipList.cs Xamarin.Forms/Models
{
[ApiPath("ships")]
public class ShipList : ApiQueryable
{
public ShipCatalog[] ships { get; set; }
}
public class ShipCatalog
{
public int id { get; set; }
public string name { get; set; }
// ...etc etc
}
}

Items is null because you declared it but have never initialized it
public ShipsViewModel()
{
...
Items = new ObservableCollection<ShipCatalog>();
...
}

Related

How to create a custom error message for Authorize attribute?

I want only admins to have access to this controller and its actions, so I've written this code:
[Authorize(Roles = Helper.AdminRole)]
public class AdminController : Controller
{
public IActionResult AdminPanel()
{
return View();
}
//other actions only available to admins
}
If the user is not logged in and he's not in the specified role I get a 404 Not Found page and this in the URL:
..../AccessDenied?ReturnUrl=%2FAdmin%2FAdminPanel
How can I make a custom error page for this scenario where the user is asked to log in so he can confirm his role, and when he does log in successfully AND he is in the right role to be redirected to where he wanted to go, but if his role is invalid to be redirected elsewhere/ shown a custom error page?
Your error was caused due to lack of Loginpath settings,not wrong role or password.(So the error code was 404 not 401)
You could see the test Result:
If you want to custom error page,you could read the official document:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-5.0
I tried with the codes below:
ErrorResult class:
public class ErrorResult
{
public bool Success { get; set; } = true;
public string Msg { get; set; } = "";
public string Type { get; set; } = "";
public object Data { get; set; } = "";
public object DataExt { get; set; } = "";
}
ErrorHandlingMiddleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
var statusCode = context.Response.StatusCode;
if (ex is ArgumentException)
{
statusCode = 200;
}
await HandleExceptionAsync(context, statusCode, ex.Message);
}
finally
{
var statusCode = context.Response.StatusCode;
var msg = "";
if (statusCode == 401)
{
msg = "unauthorize";
}
else if (statusCode == 404)
{
msg = "NotFound";
}
else if (statusCode == 400)
{
msg = "BadRequest";
}
else if (statusCode != 200)
{
msg = "Unkonwn";
}
if (!string.IsNullOrWhiteSpace(msg))
{
await HandleExceptionAsync(context, statusCode, msg);
}
}
}
private static Task HandleExceptionAsync(HttpContext context, int statusCode, string msg)
{
var result = JsonConvert.SerializeObject(new ErrorResult() { Success = false, Msg = msg, Type = statusCode.ToString() });
context.Response.ContentType = "application/json;charset=utf-8";
return context.Response.WriteAsync(result);
}
}
public static class ErrorHandlingExtensions
{
public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
in startup class:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseErrorHandling();
....
}
The Result:
Take cookie authentication as an example, you just need to configure it like this in program.cs(.Net 6):
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x =>
{
//When user doesn't login and he access to an action with [Authorize],
//He will redirect to the loginPath
x.LoginPath = "/{controller}/{action}";
//When user has loged in but the role is not the specified role,
//He will redicet to the AccessDeniedPath,
//Then you can custom your own error page in this path.
x.AccessDeniedPath = "/{controller}/{action}";
});

sqlite-net-extensions -- Create Table Async

My problem is that I'm using the CreateTableAsync method and is always returning "0". I researched and saw that this return is a mistake.
I wanted to know what I'm doing wrong.
Class Service Table:
public class SqliteTable
{
private readonly SqliteWrapper _sqliteWrapper;
public SqliteTable()
{
_sqliteWrapper = new SqliteWrapper();
}
private async Task<bool> CheckIfExistTable<T>() where T : new()
{
var connection = _sqliteWrapper.OpenDatabase();
try
{
var result = await connection.Table<T>().CountAsync();
return result.Equals(0);
}
catch (Exception e)
{
Logs.Logs.Error($"Error get count table {typeof(T).Name}: {e.Message}");
return false;
}
}
public async void CreateTable<T>() where T : new()
{
var connection = _sqliteWrapper.OpenDatabase();
if (await CheckIfExistTable<T>())
{
Logs.Logs.Info($"This table {typeof(T).Name} was created");
return;
}
var createTableResult = await connection.CreateTableAsync<T>();
var value = createTableResult.Results.Values.FirstOrDefault();
if (value.Equals(1))
{
Logs.Logs.Info($"Create table {typeof(T).Name}");
}
else
{
throw new Exception($"Error create table {typeof(T).Name}");
}
}
}
I create a Class Model Login. which would be the object to create the database.
public class Login
{
public Login()
{
}
public Login(string user, string password)
{
User = user;
Password = password;
}
public Login(int id, string user, string password)
{
Id = id;
User = user;
Password = password;
}
[PrimaryKey, AutoIncrement, Column("login_id")]
public int Id { get; set; }
[Unique, NotNull, Column("login_user")]
public string User { get; set; }
[NotNull, Column("login_password")] public string Password { get; set; }
}
I create Class CreateTableAsync. Which would be to intanciar the SqliteTable, to call the method CreateTable sending the object to create the database:
protected override void OnStart()
{
try
{
var sqliteTable = new SqliteTable();
sqliteTable.CreateTable<Login>();
}
catch (Exception e)
{
Logs.Logs.Error($"Error init application: {e.Message}");
}
}
Can someone help me?

Error retrieving data from Api Controller

I'm working on an ASP.NET Core Api and Xamarin forms client using Visual Studio 2017.
I'm getting an error
System.Runtime.Serialization.SerializationException: Invalid JSON string
because response.Content is null, when retrieving data from API but when paste this Url in browser "https://localhost:44305/api/Agreement/GetAgreementText/1" it shows data in the browser. When I run using client it's not hit to api method debug point .
Here is my APi method
[HttpGet]
[Route("GetAgreementText/{id}")]
public DefaultApiResult GetAgreementText(long Id)
{
Company com = _companyRepository.Get(Id);
string st = com.AgreementText;
DefaultApiResult result = new DefaultApiResult
{
Data = st
};
return result;
}
Here is my client application Api invoking method
public string GetAgreementTextLoading(long idCompany)
{
string agreementText = "";
// var token = _tokenService.GetLastActivateToken().Hash;
var clientURL = "https://localhost:44305/";
var client = new RestClient(clientURL);
var request = new RestRequest("api/Agreement/GetAgreementText/{Id}", Method.GET);
request.AddUrlSegment("Id", idCompany.ToString());
IRestResponse response = client.Execute(request);
AppRestResponse apiResponse = SimpleJson.DeserializeObject<AppRestResponse>(response.Content);
var statusMessage = "";
if (apiResponse.Success)
{
statusMessage = "Success.";
if (!string.IsNullOrEmpty(response.Content))
{
agreementText = apiResponse.Data.ToString();
}
else
{
throw new Exception("Invalid response");
}
}
else
{
agreementText = "Error retrieving agreement text";
}
return agreementText;
}
public class AppRestResponse
{
public bool Success { get; set; }
public object Data { get; set; }
public IEnumerable<AppRestReponseError> ErrorMessages { get; set; }
}
public class DefaultApiResult
{
public bool Success
{
get
{
return ErrorMessages.Count == 0;
}
private set { }
}
public List<ErrorMessage> ErrorMessages { get; set; }
public object Data { get; set; }
public DefaultApiResult()
{
ErrorMessages = new List<ErrorMessage>();
}
public DefaultApiResult(string errorMessage)
:this()
{
ErrorMessages.Add(new ErrorMessage()
{
Message = errorMessage
});
}
public DefaultApiResult(string[] errorMessages)
:this()
{
foreach (var errorMessage in errorMessages)
{
ErrorMessages.Add(new ErrorMessage()
{
Message = errorMessage
});
}
}
}
I'm not sure about the SimpleJson and the rest client you are using .
However , assuming you're using the RestSharp , it seems that there's no need to use the SimpleJson to deserialize response here .
I just remove the following codes :
IRestResponse response = client.Execute(request);
AppRestResponse apiResponse = SimpleJson.DeserializeObject<AppRestResponse>(response.Content);
and add the following two lines:
IRestResponse<AppRestResponse> response = client.Execute<AppRestResponse>(request);
var apiResponse= response.Data;
It works as expected .

Error in Redis Connection in ASP.NET Core App Hosted on Azure

We are facing problems with Redis caching and it's causing crashes in our site.
The following is how we implemented it:
We used the following connection string:
"*******.redis.cache.windows.net:6380,password=*****=,ssl=True,abortConnect=False"
We created a service class:
using Microsoft.Extensions.Options;
using SarahahDataAccessLayer;
using StackExchange.Redis;
using System;
namespace Sarahah.Services
{
public class RedisService
{
private static Lazy<ConnectionMultiplexer> lazyConnection;
private readonly ApplicationSettings _settings;
public RedisService(IOptions<ApplicationSettings> settings)
{
_settings = settings.Value;
lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect(_settings.RedisConnection);
});
}
public ConnectionMultiplexer Connection
{
get
{
return lazyConnection.Value;
}
}
}
}
Then in Startup.cs I use the following:
services.AddSingleton<RedisService>();
Then in controllers we use dependency injection and we assign to a multiplexer:
connectionMultiplexer = redisService.Connection;
This is how we get from the cache:
private async Task<string> GetFromCache(string key)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
return await cache.StringGetAsync(key);
}
else
{
return null;
}
}
This is how we delete:
private async Task DeleteFromCache(string subdomain)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
await cache.KeyDeleteAsync(subdomain).ConfigureAwait(false);
}
}
This is how we add:
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
TimeSpan expiresIn;
// Search Cache
if (key.Contains("-"))
{
expiresIn = new TimeSpan(0, GetMessagesCacheExpiryMinutes, 0);
}
// User info cache
else
{
expiresIn = new TimeSpan(GetProfileCacheExpiryHours, 0, 0);
}
await cache.StringSetAsync(key, serializedData, expiresIn).ConfigureAwait(false);
}
However, we get the following error:
No connection is available to service this operation
Although we have a lot of users, we only see few connections in Azure portal:
Please note that we hosted the redis cache in the same region of the web app.
Your support is appreciated.
Each time your dependency injection calls instantiates the RedisService class, your code ends up assigning a new Lazy<ConnectionMultiplexer> to lazyConnection, thus resulting in a new connection as well as a connection leak as you are not calling Close() or Dispose() on the old lazyConnection.
Try changing your code like this:
In Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
.........<whatever you have here>
services.AddSingleton<RedisService>();
services.Configure<ApplicationSettings>(options => Configuration.GetSection("ApplicationSettings").Bind(options));
}
RedisService.cs
public class RedisService
{
private readonly ApplicationSettings _settings;
private static Lazy<ConnectionMultiplexer> lazyConnection;
static object connectLock = new object();
public RedisService(IOptions<ApplicationSettings> settings)
{
_settings = settings.Value;
if (lazyConnection == null)
{
lock (connectLock)
{
if (lazyConnection == null)
{
lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect(_settings.RedisConnection);
});
}
}
}
}
public static ConnectionMultiplexer Connection
{
get
{
return lazyConnection.Value;
}
}
}
ApplicationSettings.cs
public class ApplicationSettings
{
public string RedisConnection { get; set; }
}
appsettings.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ApplicationSettings": {
"RedisConnection": "yourcachename.redis.cache.windows.net:6380,password=yourpassword,ssl=True,abortConnect=False,syncTimeout=4000"
}
}
HomeController.cs
public class HomeController : Controller
{
private RedisService redisService;
private ConnectionMultiplexer connectionMultiplexer;
public HomeController(IOptions<ApplicationSettings> settings)
{
redisService = new RedisService(settings);
connectionMultiplexer = RedisService.Connection;
}
public IActionResult Index()
{
AddToCache("foo1", "bar").GetAwaiter().GetResult();
return View();
}
private async Task<string> GetFromCache(string key)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
return await cache.StringGetAsync(key);
}
else
{
return null;
}
}
private async Task DeleteFromCache(string subdomain)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
await cache.KeyDeleteAsync(subdomain).ConfigureAwait(false);
}
}
private async Task AddToCache(string key, string serializedData)
{
var GetMessagesCacheExpiryMinutes = 5;
var GetProfileCacheExpiryHours = 1;
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
TimeSpan expiresIn;
// Search Cache
if (key.Contains("-"))
{
expiresIn = new TimeSpan(0, GetMessagesCacheExpiryMinutes, 0);
}
// User info cache
else
{
expiresIn = new TimeSpan(GetProfileCacheExpiryHours, 0, 0);
}
await cache.StringSetAsync(key, serializedData, expiresIn).ConfigureAwait(false);
}
}

ServiceStack RSS serialisation issue

I'm trying to create an RSS feed for a ServiceStack Service. I've followed various examples as closely as I can. My problem is that I get no output and I am not sure how to troubleshoot the issue. I suspect I have done something wrong on the serialisation. Here is (a simplified version of) what I have
My DTO's are
using System.Collections.Generic;
using ServiceStack;
using Library;
[Route("/MyCollection/Tomorrow/{ID}", "GET, POST")]
[Api("MyCollections Delivery")]
public class MyCollectionTomorrow
: IReturn<MyCollectionTomorrowResponse>
{
public long ID { get; set; }
}
public class MyCollectionTomorrowResponse : IHasResponseStatus
{
public long ID { get; set; }
public List<MyCollection> Result { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
public class MyCollection
{
public string Description { get; set; }
public string MyCollectionDayOfWeek { get; set; }
public DateTime MyCollectionDate { get; set; }
public bool Assisted { get; set; }
public string RoundType { get; set; }
public string Description { get; set; }
}
My service is
using System;
using Library;
using ServiceStack;
using ServiceStack.Configuration;
using System;
using Library;
using ServiceStack;
using ServiceStack.Configuration;
using MyCollection.Tomorrow;
using MyCollections.Tomorrow;
public class MyCollectionTomorrowService : Service
{
public object Any(WasteCollectionTomorrow request)
{
int id;
var param = new CollectionTomorrow();
param.ID = ID;
var response = client.Get<CollectionTomorrowResponse>(param);
return response;
}
catch (Exception ex)
{
var response = new CollectionTomorrowResponse();
response.Result = null
var status = new ResponseStatus { Message = ex.Message, StackTrace = ex.StackTrace };
response.ResponseStatus = status;
return response;
}
}
}
and my media type is
namespace DataFeedServices
{
using System;
using System.IO;
using System.ServiceModel.Syndication;
using System.Text;
using System.Xml;
using ServiceStack;
using ServiceStack.Data;
using ServiceStack.Web;
using MyCollections.Tomorrow;
public class RssFormat
{
private const string RssContentType = "application/rss+xml";
public static void Register(IAppHost appHost)
{
appHost.ContentTypes.Register(RssContentType, SerializeToStream, DeserializeFromStream);
}
public static void SerializeToStream(IRequest req, object response, Stream stream)
{
StreamWriter sw = null;
try
{
var syndicationFeedResponse = response as MyCollectionResponse;
sw = new StreamWriter(stream);
if (response != null)
{
WriteRssCollectionFeed(sw, syndicationFeedResponse);
}
}
finally
{
if (sw != null)
{
sw.Dispose();
}
}
}
public static void WriteRssCollectionFeed(StreamWriter sw, MyCollectionResponse Mycollections)
{
const string Baseuri = "example.com";
try
{
var uri = new Uri(Baseuri);
var syndicationFeed = new SyndicationFeed(
"MyCollection Service",
"Mycollections " ,
uri);
syndicationFeed.Authors.Add(new SyndicationPerson("email#mysite.com"));
if (Mycollections.Result != null)
{
foreach (var cats in Mycollections.Result)
{
syndicationFeed.Categories.Add(new SyndicationCategory(cats.RoundID));
}
}
syndicationFeed.Generator = "MyApp";
syndicationFeed.Copyright = new TextSyndicationContent("Copyright 2015");
syndicationFeed.LastUpdatedTime = DateTime.Now;
if (Mycollections.Result != null)
{
// set items
foreach (var coll in Mycollections.Result)
{
var item = new SyndicationItem { Title = new TextSyndicationContent(coll.CollectionDate) };
item.Links.Add(new SyndicationLink(uri));
item.Authors.Add(new SyndicationPerson("email#mysite.com"));
var itemContent = new StringBuilder();
itemContent.Append("My Item content");
item.Content = new TextSyndicationContent(
itemContent.ToString(),
TextSyndicationContentKind.Plaintext);
}
}
Rss20FeedFormatter rssFeed = syndicationFeed.GetRss20Formatter();
var xwriter = XmlWriter.Create(sw);
rssFeed.WriteTo(xwriter);
}
catch (Exception)
{
throw new Exception("Something bad happened");
}
}
public static object DeserializeFromStream(Type type, Stream stream)
{
throw new NotImplementedException();
}
}
}
Since your ContentType is not reusable and coupled to a specific MyCollectionResponse, it's easier to just return a raw string with the RSS XML:
[AddHeader(ContentType = "application/rss+xml")]
public object Any(WasteCollectionTomorrow request)
{
//..
return rssXml;
}
You can also write it directly to the Response Output Stream with something like:
public object Any(WasteCollectionTomorrow request)
{
//..
base.Response.ContentType = "application/rss+xml";
RssFormat.SerializeToStream(response, Response.OutputStream);
base.Response.EndRequest();
return null;
}

Resources