Call an api as part of startup of an application? - rest

I am currently in a situation where I need to call a controller as part of the startup of an application?
The controller is being hosted by the application itself..
Is that somehow possible? It just needs to be triggered every time the application starts.

I ended implementing an interface and implement the required functionality within this interface.
IControllerService.cs
public interface IControllerService
{
void InsertIntoDB(string name);
}
Controller.cs
public InsertIntoDB(string name)
{
....
}
so I in my Startup.Configure could call
startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, SchemaContext schemaContext, IControllerService controllerService)
{
....
controllerService.InsertIntoDB("InitData")
}
My API endpoint uses the same interface to call out

You could inject IHostApplicationLifetime on Startup.Configure() method , then write the callback for ApplicationStarted that would be triggered when the application host has fully started, and call your controller action within callback method.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
//register other services
}
private async Task<Action> OnApplicationStartedAsync(IHttpClientFactory httpClientFactory)
{
var client = httpClientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44326/api/values");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
//deal with the response
var result = await response.Content.ReadAsStringAsync();
}
return null;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
{
IHttpClientFactory _clientFactory = app.ApplicationServices.GetService(typeof(IHttpClientFactory)) as IHttpClientFactory;
lifetime.ApplicationStarted.Register(OnApplicationStartedAsync(_clientFactory).Wait);
//other middlewares
}

In your Startup, you could call:
public void ConfigureServices(IServiceCollection services)
{
...
...
services.AddTransient<Interfaces.IService, Service.ServiceImplementator>();
...
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
...
Task.Run(() => {
app.ApplicationServices.GetRequiredService<Interfaces.IService>().DoWorkOnStartup();
});
...
...
}
Don't call a controller action, I think your controller should be invoking a service to do the work.

Related

.net core: run big tasks in the background

I created a .net core web api project. It has gotten kinda big and I want to program a "delete" operation which deletes a lot of stuff from the database. Since there are a lot of things to delete, this will be a long running process. So I thought maybe I can run this in the background and just write status updates somewhere for the user to see whats happening.
I googled this and I found BackgroundWorkerQueue and thought this might be my solution.
So I registered the service and everything and here is my method that calls it:
public class DeleteController : ControllerBase {
private readonly BackgroundWorkerQueue _backgroundWorkerQueue;
public AdminController(BackgroundWorkerQueue backgroundWorkerQueue){
_backgroundWorkerQueue = backgroundWorkerQueue;
}
public async Task<ActionResult> HugeDeleteMethod(int id)
{
// some prechecks here...
// and here I thought I'd start the background task
_backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>
{
var a = _context.StatusTable.Find(id);
a.Status += "Blablablabla\n";
_context.StatusTable.Update(a);
await _context.SaveChangesAsync();
//now start doing delete operations
});
}
}
And that class looks like this:
public class BackgroundWorkerQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
}
There is also a DeleteService, which is also called in my startup, but I am not sure what it does:
public class DeleteService : BackgroundService
{
private readonly BackgroundWorkerQueue queue;
public NukeService(BackgroundWorkerQueue queue)
{
this.queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await queue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}
Both are added in my startup.cs:
services.AddHostedService<DeleteService>();
services.AddSingleton<BackgroundWorkerQueue>();
Well, maybe I'm going about this all wrong. This is never called it seems, the StatusTable field "Status" is always empty. So how do I do this?
You just need to subclass BackgroundService class or implement IHostedService and than register your service as hosted service.
This will run a service in the background. Than in your service you can leverage the BlockingQueue that will perform tasks only when they are added, e.g. like this:
public class MyService : BackgroundService {
private readonly BlockingCollection<long> queue;
public MyService(){
this.queue = new BlockingCollection<long>();
Task.Run(async () => await this.Execute());
}
public void AddId(long id) {
this.queue.Add(id);
}
private async Task Execute()
{
foreach (var id in this.queue.GetConsumingEnumerable())
{
... do your stuff ...
}
}
}
services.AddHostedService<MyService>();
Here is the docu: Background services in .net core

ASP.NET Core 2: Entity Framework Context is disposed too early in asynchronous PayPal IPN. How do I get it back in a later thread?

I have an endpoint which is receiving IPN activity from PayPal. Here is the POST Action that is taken straight from their docs with manual modifications:
[HttpPost]
public IActionResult Receive()
{
IPNContext ipnContext = new IPNContext()
{
IPNRequest = Request
};
using (StreamReader reader = new StreamReader(ipnContext.IPNRequest.Body, Encoding.ASCII))
{
ipnContext.RequestBody = reader.ReadToEnd();
}
List<KeyValuePair<string, string>> ipnVarsWithCmd = ipnContext.RequestBody.Split('&')
.Select(x => new KeyValuePair<string, string>(x.Split('=')[0], x.Split('=')[1])).ToList();
//Fire and forget verification task -- ** THIS **
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
//Reply back a 200 code
return Ok();
}
The issue is the indicated line. This is a "fire and forget" route, and is executed asynchronously. When the Action is complete, and returns Ok, I am assuming that the injected Entity Framework context from the controller:
public class IPNController : Controller
{
private readonly EFContext _context;
public IPNController(EFContext context)
{
_context = context;
}
}
... gets Disposed? According to my logs, it looks like it.
Meanwhile, I have that second thread doing the actual legwork of the IPN request which needs that EFContext to be around.
Is there a pattern I am missing here? (Bearing in mind whilst I'm not new to .NET I am to .NET Core)
Or is there a way I can "get it back" so I can use it?
Update:
You might find my initialisation of the context useful:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<EFContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
}
Change:
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
to
await Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
and method declaration to:
public async Task<IActionResult> Receive()
Also wrap IPNContext to using block to let it dispose when it is not needed.

Why am I getting error: "Cannot access disposed object" in .net core 2 with EF and AutoFac?

First the error:
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context instances.
Object name: 'MemberContext'.
I have 3 projects, Domain, API and WebSPA app.
Domain has 2 modules, DomainModule and MediatorModule
public class DomainModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(MemberContext).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf()
.InstancePerLifetimeScope(); // or individually
}
}
public class MediatorModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
// enables contravariant Resolve() for interfaces with single contravariant ("in") arg
builder
.RegisterSource(new ContravariantRegistrationSource());
// mediator itself
builder
.RegisterType<Mediator>()
.As<IMediator>()
.InstancePerLifetimeScope();
// request handlers
builder
.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t =>
{
object o;
return c.TryResolve(t, out o) ? o : null;
};
})
.InstancePerLifetimeScope();
// notification handlers
builder
.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>) c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
})
.InstancePerLifetimeScope();
}
}
In API project I have also 2 modules, ApplicationModule and again MediatorModule same as the one above.
public class ApplicationModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(Startup).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf().InstancePerLifetimeScope(); // or individually
}
}
No, when I debug I can see that member context gets newed up on each request, yet on second request, it throws above error. To make sure I am not going crazy, I modified constructor of dbcontext to create an id for context so i can verify they are different. What am I doing wrong?
public MemberContext(DbContextOptions<MemberContext> options) : base(options)
{
MemberContextId = Guid.NewGuid();
Console.WriteLine("member context created: " + MemberContextId);
}
Here is the startup in API
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
// .AllowCredentials()
);
});
services.AddMvc()
.AddControllersAsServices();//Injecting Controllers themselves thru DI
//For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
AddSwaggerGen(services);
//var connection = Configuration["ConnectionString"];
//services.AddDbContext<MemberContext>(options => options.UseSqlServer(connection),ServiceLifetime.Scoped);
services.AddEntityFrameworkSqlServer()
.AddDbContext<MemberContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"]
//,sqlServerOptionsAction: sqlOptions =>
//{
// sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
// sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
//}
);
},
ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
);
var container = new ContainerBuilder();
container.Populate(services);
container.RegisterAssemblyModules(typeof(VIN.Members.Domain.Entities.Member).Assembly,
typeof(Startup).Assembly);
return new AutofacServiceProvider(container.Build());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//NOTE: must be before UseMVC !!!
app.UseCors("CorsPolicy");
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
private void AddSwaggerGen(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "VIN Members HTTP API",
Version = "v1",
Description = "Members Service HTTP API",
TermsOfService = "Terms Of Service"
});
});
}
}
UPDATE:
What I am trying to do is delete a record. On client side code looks like this
onDelete(item: IMember) {
//TODO: replace this with dialog service component
if (window.confirm('Are sure you want to delete this member?')) {
//put your delete method logic here
this.service.deleteMember(item).subscribe(x => {
this.getMembers();
});
}
}
this delete request gets mapped to a controller that passes it to mediator
Controller
// DELETE api/members/5
[HttpDelete("{id}")]
public void Delete(Guid id)
{
var command = new DeleteMember.Command(id);
_mediator.Send(command).ConfigureAwait(false);
}
and finally handler
public class DeleteMember
{
public class Command : IRequest
{
public Command(Guid memberId)
{
Guard.NotNull(memberId, nameof(memberId));
MemberId = memberId;
}
public Guid MemberId { get; }
}
public class Handler : AsyncRequestHandler<Command>
{
private MemberContext _context;
public Handler(MemberContext context)
{
_context = context;
Console.WriteLine("Delete member context: " + context.MemberContextId);
}
protected override async Task HandleCore(Command cmd)
{
try
{
var member = await _context.FindAsync<Member>(cmd.MemberId);//.ConfigureAwait(false);
// if (member != null)
//// {
_context.Remove(member);
await _context.SaveChangesAsync().ConfigureAwait(false);
// }
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
}
As you can see there is no code that disposes that context. Scratching my head.
See this commented out check for member if null. That was throwing error as well, I commented it out just to see what will happen, and now it throws as SaveChangesAsync.
As request completes, context gets disposed. Since command handler uses SaveChangesAsync(), context is disposed before save completes. Culprit is controller method :). It should be async as well.
[HttpDelete("{id}")]
public async Task Delete(Guid id)
{
var command = new DeleteMember.Command(id);
await _mediator.Send(command).ConfigureAwait(false);
}
Your DbContext is scoped, meaning that Dependency Injection will return the same DbContext object every time one is asked for within the same HTTP request (in the case of ASP.NET).
That means that you should not be calling Dispose on your DbContext (otherwise that same object can't be used a second time). That seems to be what is happening to you, intentionally or not.
That does mean you should not be using using with it. Are you using using anywhere in your code against your DbContext?
I don't think you showed the line where the Exception is being thrown.
Update:
Try overriding Dispose in your MemberContext class. Something like this:
public override void Dispose() {
base.Dispose();
}
But just set a breakpoint there. When it breaks (if it does) check the stack trace and see what called it.
This can also be caused by having async void instead of async Task within WebAPI in my experience.

Xamarin.Auth: Using Facebook oauth, how to redirect to my app?

I've just started using Xamarin.Auth and I want to enable Facebook login via oauth.
Here is my config:
public static string ClientId = "client id";
public static string ClientSecret = "client secret";
public static string Scope = "email";
public static string AuthorizeUrl = "https://m.facebook.com/dialog/oauth";
public static string RedirectUrl = "https://www.facebook.com/connect/login_success.html";
public static string AccessTokenUrl = "https://m.facebook.com/dialog/oauth/token";
Code for initiating the authentication:
public class AuthenticationPageRenderer : PageRenderer
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear (animated);
var auth = new OAuth2Authenticator (
Constants.ClientId,
Constants.ClientSecret,
Constants.Scope,
new Uri (Constants.AuthorizeUrl),
new Uri (Constants.RedirectUrl),
new Uri (Constants.AccessTokenUrl)
);
auth.Completed += OnAuthenticationCompleted;
PresentViewController (auth.GetUI (), true, null);
}
async void OnAuthenticationCompleted (object sender, AuthenticatorCompletedEventArgs e)
{
Debug.WriteLine ("AUTH Completed!");
if (e.IsAuthenticated) {
}
}
}
Seems to work fine, but instead of redirecting the user to https://www.facebook.com/connect/login_success.html, I want to redirect him back to my app again. Any help much appreciated!
Best,
Sascha
You can "redirect back" to your app again by simply invoking your own method to display the app's page you want to show to your user like this.
async void OnAuthenticationCompleted (object sender, AuthenticatorCompletedEventArgs e)
{
Debug.WriteLine ("AUTH Completed!");
if (e.IsAuthenticated) {
//invoke the method that display the app's page
//that you want to present to user
App.SuccessfulLoginAction.Invoke();
}
}
In your App.cs
public static Action SuccessfulLoginAction
{
get
{
return new Action(() =>
{
//show your app page
var masterDetailPage = Application.Current.MainPage as MasterDetailPage;
masterDetailPage.Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(MainPage)));
masterDetailPage.IsPresented = false;
});
}
}
Assuming that MainPage is the page you wanted to show after successful login. I am using Xamarin.Forms with MasterDetailPage to display pages in my example which maybe different from your app but the concept is the same.
Just call DismissViewController (true, null) in your OnAuthenticationCompleted method. Or use the async equivalent:
async void OnAuthenticationCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
Debug.WriteLine("AUTH Completed!");
await DismissViewControllerAsync(true);
if (e.IsAuthenticated)
{
}
}

Building unit tests for MVC2 AsyncControllers

I'm considering re-rewriting some of my MVC controllers to be async controllers. I have working unit tests for these controllers, but I'm trying to understand how to maintain them in an async controller environment.
For example, currently I have an action like this:
public ContentResult Transaction()
{
do stuff...
return Content("result");
}
and my unit test basically looks like:
var result = controller.Transaction();
Assert.AreEqual("result", result.Content);
Ok, that's easy enough.
But when your controller changes to look like this:
public void TransactionAsync()
{
do stuff...
AsyncManager.Parameters["result"] = "result";
}
public ContentResult TransactionCompleted(string result)
{
return Content(result);
}
How do you suppose your unit tests should be built? You can of course invoke the async initiator method in your test method, but how do you get at the return value?
I haven't seen anything about this on Google...
Thanks for any ideas.
As with any async code, unit testing needs to be aware of thread signalling. .NET includes a type called AutoResetEvent which can block the test thread until an async operation has been completed:
public class MyAsyncController : Controller
{
public void TransactionAsync()
{
AsyncManager.Parameters["result"] = "result";
}
public ContentResult TransactionCompleted(string result)
{
return Content(result);
}
}
[TestFixture]
public class MyAsyncControllerTests
{
#region Fields
private AutoResetEvent trigger;
private MyAsyncController controller;
#endregion
#region Tests
[Test]
public void TestTransactionAsync()
{
controller = new MyAsyncController();
trigger = new AutoResetEvent(false);
// When the async manager has finished processing an async operation, trigger our AutoResetEvent to proceed.
controller.AsyncManager.Finished += (sender, ev) => trigger.Set();
controller.TransactionAsync();
trigger.WaitOne()
// Continue with asserts
}
#endregion
}
Hope that helps :)
I've written short AsyncController extension method that simplifies unit testing a bit.
static class AsyncControllerExtensions
{
public static void ExecuteAsync(this AsyncController asyncController, Action actionAsync, Action actionCompleted)
{
var trigger = new AutoResetEvent(false);
asyncController.AsyncManager.Finished += (sender, ev) =>
{
actionCompleted();
trigger.Set();
};
actionAsync();
trigger.WaitOne();
}
}
That way we can simply hide threading 'noise':
public class SampleAsyncController : AsyncController
{
public void SquareOfAsync(int number)
{
AsyncManager.OutstandingOperations.Increment();
// here goes asynchronous operation
new Thread(() =>
{
Thread.Sleep(100);
// do some async long operation like ...
// calculate square number
AsyncManager.Parameters["result"] = number * number;
// decrementing OutstandingOperations to value 0
// will execute Finished EventHandler on AsyncManager
AsyncManager.OutstandingOperations.Decrement();
}).Start();
}
public JsonResult SquareOfCompleted(int result)
{
return Json(result);
}
}
[TestFixture]
public class SampleAsyncControllerTests
{
[Test]
public void When_calling_square_of_it_should_return_square_number_of_input()
{
var controller = new SampleAsyncController();
var result = new JsonResult();
const int number = 5;
controller.ExecuteAsync(() => controller.SquareOfAsync(number),
() => result = controller.SquareOfCompleted((int)controller.AsyncManager.Parameters["result"]));
Assert.AreEqual((int)(result.Data), number * number);
}
}
If you want to know more I've written a blog post about how to Unit test ASP.NET MVC 3 asynchronous controllers using Machine.Specifications
Or if you want to check this code it's on a github