In my xamarin.forms app, I am trying to create background task and service in ios and android . I created service for android and task for ios. According to the service and task I am trying to call a web API in my shared code. I am communicating to my shared code using Messaging center. In android the communication works fine. But in ios the messaging center not subscribing in the shared code.
My ios background Task
public class iOSLongRunningTaskExample
{
nint _taskId;
CancellationTokenSource _cts;
public async Task Start()
{
_cts = new CancellationTokenSource();
_taskId = UIApplication.SharedApplication.BeginBackgroundTask("LongRunningTask", OnExpiration);
try
{
// Here I am calling the shared code
Device.BeginInvokeOnMainThread(() =>
MessagingCenter.Send<Object>(new Object(),
"CheckNotificationAPI")
);
}
catch (OperationCanceledException)
{
}
finally
{
//if (_cts.IsCancellationRequested)
//{
// var message = new CancelledMessage();
// Device.BeginInvokeOnMainThread(
// () => MessagingCenter.Send(message, "CancelledMessage")
// );
//}
}
UIApplication.SharedApplication.EndBackgroundTask(_taskId);
}
public void Stop()
{
_cts.Cancel();
}
void OnExpiration()
{
_cts.Cancel();
}
}
My Shared code
MessagingCenter.Subscribe<Object>(this, "CheckNotificationAPI", async (sender) =>
{
Device.BeginInvokeOnMainThread(async () =>
{
await DisplayAlert("Message", "Recieved", "OK");
});
});
My android service which works fine
[Service]
public class LongRunningTaskService : Service
{
CancellationTokenSource _cts;
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
_cts = new CancellationTokenSource();
Task.Run(() => {
try
{
//INVOKE THE SHARED CODE
Device.BeginInvokeOnMainThread(()=>
MessagingCenter.Send<Object>(new Object(), "CheckNotificationAPI")
);
}
catch (System.OperationCanceledException)
{
}
finally
{
//if (_cts.IsCancellationRequested)
//{
// var message = new CancelledMessage();
// Device.BeginInvokeOnMainThread(
// () => MessagingCenter.Send(message, "CancelledMessage")
// );
//}
}
}, _cts.Token);
return StartCommandResult.Sticky;
}
public override void OnDestroy()
{
if (_cts != null)
{
_cts.Token.ThrowIfCancellationRequested();
_cts.Cancel();
}
base.OnDestroy();
}
}
Any help is appreciated.
Related
I've set up signalr in my blazor server side application and for some reason this hubconnection is not being triggered, when the hubconnection is on, it completely ignores the BroadcastData method and doesnt even fire it:
private HubConnection hubConnection;
private string _hubUrl;
protected override async Task OnInitializedAsync()
{
string baseUrl = NavigationManager.BaseUri;
_hubUrl = baseUrl.TrimEnd('/') + SignalRHub.HubUrl;
_hubConnection = new HubConnectionBuilder()
.WithUrl(_hubUrl)
.Build();
hubConnection.On<ClientDTO>("BroadcastData", BroadcastData);
await hubConnection.StartAsync();
}
private void BroadcastData(ClientDTO payload)
{
dashboardData = payload;
StateHasChanged();
}
I have everything setup for this to be "working" but clearly it isn't working and I'm completely lost at what could be the problem... Please take a look at what I have so far and see if you can see what's going on:
Startup:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
StartTimer();
}
private void StartTimer()
{
_timer = new System.Timers.Timer();
_timer.Interval = 5000;
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(Object source, ElapsedEventArgs e)
{
Trigger();
}
public void Trigger()
{
try
{
using (HttpClient client = new HttpClient())
{
//Trigger on elapsed
var response = client.GetAsync(Configuration.GetConnectionString("ApiTriggerURL")).Result;
}
}
catch
{
Console.WriteLine("something terrible has happened...");
}
}
services.AddScoped(typeof(SignalRHub));
services.AddScoped<IHub, SignalRHub>();
services.AddScoped<HttpClient>();
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
public void Configure(IApplicationBuilde app, IWebHostEnvironment env)
{
app.UseResponseCompression();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
endpoints.MapHub<SignalRHub>(SignalRHub.HubUrl);
});
}
appsettings.json: (fyi, the trigger is working, the api endpoint is being hit as it returns a status 200 ok result)
"ConnectionStrings":
{
"ApiTriggerURL": "http://localhost:5000/api/SignalRHub/GetMyData"
}
Then we have my api controller: (here you can see the status 200 ok)
private readonly SignalRHub _signalRHub;
public SignalRHubController(SignalRHub signalRHub)
{
_signalRHub = signalRHub;
}
[HttpGet]
public ObjectResult GetMyData()
{
try
{
Task.WhenAll(_signalRHub.BroadcastData()); // Call hub broadcast method
return this.StatusCode((int)HttpStatusCode.OK, "trigger has been triggered");
}
catch
{
return this.StatusCode((int)HttpStatusCode.InternalServerError, "christ, the ting is broken fam");
}
}
When we look into the _signalRHub.BroadcastData(), we see this:
public class SignalRHub : Hub, IHub
{
private readonly ClientService _clientService;
readonly IHubContext<SignalRHub> _hubContext;
public const string HubUrl = "/chathub"; //this is being read in the startup in the endpoints
public SignalRHub(ClientService clientService, IHubContext<SignalRHub> hubContext)
{
_clientService = clientService;
_hubContext = hubContext;
}
public async Task BroadcastData()
{
var data = _clientService .GetDataAsync().Result;
await _hubContext.Clients.All.SendAsync("BroadcastData", data); //send data to all clients
}
}
And this in turn should basically do this signalrhub every x seconds (depending on timer)
I know my code is a whole load of madness, but please look pass this and help me to understand why this isn't working! Thank you in advance!
Try following:
hubConnection.On<ClientDTO>("BroadcastData", (payload)=>
BroadcastData(payload);
);
Instead of
hubConnection.On<ClientDTO>("BroadcastData", BroadcastData);
I create Event Maped Seat reservation and using SignalR creates a realtime seat update status view
My BroadcastHub
public class BroadcastHub : Hub
{
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public async Task RemoveFromGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
public async Task SeatUpdate(long SectinId, string groupName, long SeatId, SeatStatus seatStatus)
{
await Clients.OthersInGroup(groupName).SendAsync("ReceiveSeatUpdate", SectinId, SeatId, seatStatus);
}
}
Component
<div class="col-md-12 mb-3">
#((MarkupString)#SectionData.Salon.SalonMap)
</div>
...Seat Selection Murkup....
#code {
private HubConnection hubConnection;
public bool IsConnected => hubConnection.State == HubConnectionState.Connected;
Task SeatUpdate(long SectinId, string EventId, long SeatId, SeatStatus seatStatus) => hubConnection.SendAsync("SeatUpdate", SectinId, EventId, SeatId, seatStatus);
protected override async Task OnInitializedAsync()
{
SectionData.OnChange += StateHasChanged;
SectionData.Salon = await DataService.GetSalon();
action = GetSection;
foreach (var item in salon.Sections)
{
SectionData.Salon.SalonMap =salon.SalonMap.Replace(item.Action,$"onclick='app.GetSectionCallerJS({item.Id})'");
}
#region Hub
hubConnection = new HubConnectionBuilder().WithUrl(NavigationManager.ToAbsoluteUri("/broadcastHub")).Build();
hubConnection.On("ReceiveSeatUpdate", async (long SectinId, long SeatId, SeatStatus seatStatus) =>
{
if (SectionData.Section.Id == SectinId)
{
var Seat = SectionData.Section.Seats.Values.Where(x => x.Id == SeatId).FirstOrDefault();
Seat.SeatStatus = seatStatus;
}
StateHasChanged();
});
await hubConnection.StartAsync();
await hubConnection.SendAsync("AddToGroup", EventSansUniqueId);
#endregion Hub
}
#region GetSection
private static Action<long> action;
private void GetSection(long SectionId)
{
var section= salon.Sections.Where(x => x.Id == SectionId).FirstOrDefault();
SectionData.SetSection(section);
SectionData.Section.Seats = DataService.GetSection(SectionId);
StateHasChanged();
}
[JSInvokable]
public static void GetSectionCaller(long SectionId)
{
action.Invoke(SectionId);
}
#endregion GetSection
public void Dispose()
{
SectionData.OnChange -= StateHasChanged;
if (IsConnected) hubConnection.SendAsync("RemoveFromGroup", EventSansUniqueId);
}
}
JavaScript Is
window.app = {
GetSectionCallerJS: (id) => {
DotNet.invokeMethodAsync('KishApp.TRMS.Salon', 'GetSectionCaller', id);
}
};
The problem is when the hub registers for the second, third, and... time DotNet.invokeMethodAsync Call Last registered page, not the one actually calling the method and causing the wrong page update
tanks to #MisterMango I found that problem GetSectionCaller is a static method and I must have created new DotNetObjectReference every time page initial so
DotNetObjectReference<Salon> ObjectReference;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
ObjectReference = DotNetObjectReference.Create<Salon>(this);
await JSRuntime.InvokeVoidAsync("app.setObjectReference", ObjectReference);
}
}
[JSInvokable("GetSectionCaller")]
public void GetSectionCaller(long SectionId)
{
GetSection(SectionId);
}
JavaScript
window.app = {
GetSectionCallerJS: (id) => {
dotNetObject.invokeMethodAsync('GetSectionCaller', id);
},
setObjectReference: (ObjectReference) => {
this.dotNetObject = ObjectReference;
}};
I have implemented the polly retry and Circuit breaker policy (wrapped). when the call fails and the circuit is open for the previous call the next call again goes to the retry and hit the circuit breaker again instead of just throwing the circuitbreakexception. I think somehow the HTTP client is getting recreated again even though am using the typed client. I am not able to figure the issue. Here is the code
Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddHttpClient<IIntCall, IntCall>().WrapResilientPolicies();
}
Interface
public interface IIntCall
{
Task<bool> DoSomething();
}
Implementation:
public class IntCall : IIntCall
{
private readonly HttpClient client;
public IntCall(HttpClient httpClient)
{
this.client = httpClient;
}
public async Task<bool> DoSomething()
{
var response = await client.GetAsync("http://www.onegoogle.com");
var content = await response.Content.ReadAsStringAsync();
return false;
}
}
Polly Implementation
public static class CBExtensions
{
public static void WrapResilientPolicies(this IHttpClientBuilder builder)
{
builder.AddPolicyHandler((service, request) =>
GetRetryPolicy().WrapAsync(GetCircuitBreakerPolicy()));
}
private static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions.HandleTransientHttpError()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(30), (result, retryAttempt) =>
{
Debug.WriteLine("circuit broken");
},
() =>
{
Debug.WriteLine("circuit closed");
});
}
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions.HandleTransientHttpError()
.Or<Exception>(e => !(e is BrokenCircuitException))
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromMilliseconds(500),
onRetry: (context, attempt) =>
{
Debug.WriteLine("error");
}
);
}
}
I figured the issue. because I am fetching the request details the policy is injected every call and hence the state is renewed. I moved my code from
public static void WrapResilientPolicies(this IHttpClientBuilder builder)
{
builder.AddPolicyHandler((service, request) =>
GetRetryPolicy().WrapAsync(GetCircuitBreakerPolicy()));
}
to this
public static void WrapResilientPolicies(this IHttpClientBuilder builder)
{
builder.AddPolicyHandler(
GetRetryPolicy().WrapAsync(GetCircuitBreakerPolicy()));
}
I tried to implement and firebase listener in my Xamarin iOS App.
But, if my app is in foreground and an firebase cloud message receives, CrossPushNotification.Current.OnNotificationReceived is not be called.
What's problem?
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IUNUserNotificationCenterDelegate, IMessagingDelegate
{
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
{
global::Xamarin.Forms.Forms.Init();
App.Configure();
this.RegisterForRemoteNotifications(launchOptions);
this.LoadApplication(new MyApp());
return base.FinishedLaunching(uiApplication, launchOptions);
}
private void RegisterForRemoteNotifications(NSDictionary launchOptions)
{
PushNotificationManager.Initialize(launchOptions, true);
CrossPushNotification.Current.RegisterForPushNotifications();
Messaging.SharedInstance.ShouldEstablishDirectChannel = true;
CrossPushNotification.Current.OnTokenRefresh += (s, p) =>
{
System.Diagnostics.Debug.WriteLine($"TOKEN : {p.Token}");
Messaging.SharedInstance.ApnsToken = p.Token;
};
CrossPushNotification.Current.OnNotificationReceived += (s, p) =>
{
System.Diagnostics.Debug.WriteLine("Received");
};
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
PushNotificationManager.DidRegisterRemoteNotifications(deviceToken);
Messaging.SharedInstance.ApnsToken = deviceToken;
}
// To receive notifications in background in any iOS version
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
PushNotificationManager.DidReceiveMessage(userInfo);
}
public override void DidEnterBackground(UIApplication uiApplication)
{
Messaging.SharedInstance.ShouldEstablishDirectChannel = false;
}
}
If you use the Firebase.You should use the package Plugin.FirebasePushNotification from NuGetnot Plugin.PushNotification.
Change the method like following
CrossFirebasePushNotification.Current.OnTokenRefresh += (s,p) =>
{
System.Diagnostics.Debug.WriteLine($"TOKEN : {p.Token}");
};
CrossFirebasePushNotification.Current.OnNotificationReceived += (s,p) =>
{
System.Diagnostics.Debug.WriteLine("Received");
};
For more detail you can refer here
Is there any way to have something like this?
options.AddPolicy("IsEducationOwner", policy =>
{
// Eather first OR second policy requirement needs to be true
policy.Requirements.Add(new EducationOwnerRequirement()); // My custom requirement that has one handler
policy.RequireRole("CatalogAdmin"); // Role based requirement
});
I found that this is working. Requirements needs to have additional handler that checks for role in user claim so the code looks like this.
Additional information can be found on this MSDN page or in this article
My example:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options => {
options.AddPolicy("IsEducationOwner", policy =>
{
policy.Requirements.Add(new EducationOwnerRequirement());
});
});
services.AddTransient<IAuthorizationHandler, IsEducationOwnerHandler>();
services.AddTransient<IAuthorizationHandler, HasCatalogAdminRoleHandler>();
}
}
public class EducationOwnerRequirement : IAuthorizationRequirement
{
}
public class HasCatalogAdminRoleHandler : AuthorizationHandler<EducationOwnerRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EducationOwnerRequirement requirement)
{
if (context.User.IsInRole("CatalogAdmin"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class IsEducationOwnerHandler : AuthorizationHandler<EducationOwnerRequirement>
{
private PerformaContext _db;
public IsEducationOwnerHandler(PerformaContext db)
{
_db = db;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EducationOwnerRequirement requirement)
{
var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext == null || !context.User.HasClaim(c => c.Type == ClaimTypeNaming.oid))
{
return Task.CompletedTask;
}
var path = mvcContext.HttpContext.Request.Path.Value;
var educationId = path.Substring(path.IndexOf("/api/educations/") + 16, path.Length - path.IndexOf("/api/educations/") - 16);
var userExternalId = context.User.FindFirst(ClaimTypeNaming.oid).Value;
var userId = _db.GetUserByExternalId(userExternalId).Select(x => x.Id).FirstOrDefault();
if(userId == Guid.Empty)
{
return Task.CompletedTask;
}
var educationOwners = _db.GetOwnersForEducation(Guid.Parse(educationId)).Select(x => x.UserId).ToList();
if (educationOwners.Contains(userId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}