BackgroundService with asp.net Core MVC 3.1 - asp.net-core-mvc

Here is what i've done:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHostedService<MyBatch>();
}
public class MyBatch : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
Console.Writeline("test");
await Task.Delay(TimeSpan.FromSeconds(10));
}
}
}
I have 2 problems:
First: The web application does not respond on 5000 port when i launch "dotnet run". If i remove my batch, the 5000 port works again. (test messages are displayed on console)
Second: I want to access to my database with Entity Framework. How can i access to my database context from this batch file ?
Thanks

I want to access to my database with Entity Framework. How can i access to my database context from this batch file ?
To consume a scoped service (such as DbContext etc) from singleton hosted service, you can create a scope then use it to resolve scoped service(s) you need from this scope, like below.
public class MyBatch : BackgroundService
{
public IServiceProvider Services { get; }
public MyBatch(IServiceProvider services)
{
Services = services;
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
Console.WriteLine("test");
await Task.Delay(TimeSpan.FromSeconds(10));
await DoWithDb(stopToken);
}
}
private async Task DoWithDb(CancellationToken stoppingToken)
{
//...
using (var scope = Services.CreateScope())
{
var _myDbContext =
scope.ServiceProvider
.GetRequiredService<MyDbContext>();
//code logic here
//...
}
}
}
You can also get detailed information about "consuming a scoped service in a background task" from here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#consuming-a-scoped-service-in-a-background-task

Related

self hosted ASP.NET Core Web Api capable of starting and stopping

I'm attempting to convert an old WCF service to an ASP.NET Core Web API, making use of the CoreWCF package. A key feature of this existing service is that it's being self hosted by an other application and is able to gracefully start & stop, without creating memory leaks.
I have been able to figure out how to start and stop a prototype service. However, after performing some stress testing, it does seem like I've left a memory leak somewhere and I'm sadly out of ideas or available documentation at this point. I'm also considering that an ASP.NET Core Web API just isn't supposed to be used like this and I misunderstood this, if so, be sure to let me know. Also my apologies for the truckload of code, but I'm not sure what's relevant or not to the question.
The code for my prototype service looks like this:
Configuring the webhost:
private void CreateWebHostBuilder(){
host = WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
{
options.AllowSynchronousIO = true;
options.ListenLocalhost(Startup.PORT_NR);
options.ConfigureHttpsDefaults(
options => options.ClientCertificateMode = ClientCertificateMode.RequireCertificate
);
})
.ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Warning); })
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
.UseShutdownTimeout(TimeSpan.FromSeconds(1))
.UseStartup<Startup>()
.Build();
}
Inside the Startup class:
Configuring the IApplicationBuilder:
public void Configure(IApplicationBuilder app){
app.UseServiceModel(builder =>
{
// Add the Echo Service
builder.AddService<EchoService>()
// Add service web endpoint
.AddServiceWebEndpoint<EchoService, IEchoService>(
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
});
app.UseMiddleware<SwaggerMiddleware>();
app.UseSwaggerUI();
app.UseAuthentication();
}
Configuring the services:
public void ConfigureServices(IServiceCollection services){
services.AddServiceModelWebServices()
.AddHostedService<EchoService>()
.AddSingleton(new SwaggerOptions())
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
}
The service interface:
[ServiceContract]
[OpenApiBasePath($"/{Startup.WEB_API_PATH}")]
public interface IEchoService : IHostedService {
[OperationContract]
[WebGet(UriTemplate = "/hello")]
[OpenApiOperation(Description = "Method used to receive a friendly \"Hello world\"",
Summary = "Hello world")]
[OpenApiResponse(Description = "OK Response", StatusCode = HttpStatusCode.OK)]
string HelloWorld();
}
The implemented service:
public class EchoService : IEchoService {
public EchoService() { }
public string HelloWorld() {
return "Hello world!";
}
public Task StartAsync(CancellationToken cancellationToken) {
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Creating and starting the host + services:
public void StartWebService(object obj){
CreateWebHostBuilder();
host.StartAsync();
}
Stopping and disposing the services and host:
public void StopWebService(object obj) {
host.StopAsync().Wait();
host.Dispose();
}
So if anyone has any suggestions or tutorial reference, be sure to let me know, any help is welcome.

asp.net core web api work on iis express but not working in iis on windows

i am new in asp.net core. i use asp.net core 2.1. i have two controller. a default valuecontroller that doesn't have db connection and customerController that have sqlserver db connection. when i run my project on iis express everything is good but when i publish my project and use windows iis value api work nice but my customer api that have a sqlserver connection doesn't work.
appsettings calss:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Data Source=AHAD;Initial Catalog=mydb;Integrated Security=True"
}
}
Startup class:
namespace SalesApp
{
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<MyDbContext>(Options =>
{
Options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
//,
//ILoggerFactory loggerFactory,
//MyDbContext db)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
//loggerFactory.AddConsole(Configuration.GetSection("Logging"));
//loggerFactory.AddDebug();
//db.Database.Migrate();
//app.UseMvc();
}
}
}
MyDbContext class:
public class MyDbContext : DbContext
{
public MyDbContext()
{
}
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
//public DbSet<CUSTOMER> customers { get; set; }
public DbSet<CUSTOMER> Customer { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CUSTOMER>(entity =>
{
entity.Property(e => e.C_Code).HasMaxLength(5);
entity.Property(e => e.C_Name).HasMaxLength(60);
entity.Property(e => e.C_Code_C).HasMaxLength(12);
});
}
}
CustomerController Class:
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
MyDbContext _context;
public CustomerController(MyDbContext context)
{
_context = context;
}
[HttpGet]
public IActionResult GetCustomers()
{
return new ObjectResult(_context.Customer);
}
}
values api:
enter image description here
customer api:
enter image description here
it confused me 2 weeks.
You will need to have IIS set up in order to get it to work correctly.
You also need to ensure you are using the .NET Core Windows Server Hosting Bundle
Then:
Restart the system or execute net stop was /y, followed by net start w3svc from a command shell. Restarting IIS picks up a change to the system PATH, which is an environment variable, made by the installer.
After that open the command prompt as Administrator and type:
C:\Windows\System32> iisreset
Then publish the app to a folder and open the command prompt there. Run the application by typing
C:\Temp\publish> dotnet YourApplicationName.dll
You can now go to the browser and type in http://localhost:port/ and it will display your .Net Core app.
Using sql server auth worked for me.
I use connection string like this:
Server=.;
Database=myDB;
User Id=sa2;
Password=myPass;

How to unit test an action filter attribute for web api in asp.net core?

I have written an action filter for a web api. If a method in the api controller throws an unhandled exception, then the filter creates an internal error 500 response.
I need to know how to test the filter?
I have researched extensively but could not create a suitable test. I tried context mocking, a service locator implementation and even an integration test using a test server.
The web api controller looks like this:
namespace Plod.Api.ApiControllers
{
[TypeFilter(typeof(UnhandledErrorFilterAttribute))]
[Route("api/[controller]")]
public class GamesController : BaseApiController
{
public GamesController(IGameService repository,
ILogger<GamesController> logger,
IGameFactory gameFactory
) : base(
repository,
logger,
gameFactory
)
{ }
// ..... controller methods are here
}
}
The complete controller is found here.
The filter is this:
namespace Plod.Api.Filters
{
public class UnhandledErrorFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Exception != null)
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.ExceptionHandled = true;
}
}
}
}
I even welcome changes to the filter implementation as a possible work around. Any help or ideas would be much appreciated. Thanks.
You probably can't. However, what you can do is spin up a TestServer and then hit it with a HttpClient. This really is an integration test and not a unit test. However, it's the good kind of integration test because it can be run safely in pipelines.
This document explains how to do this:
https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1
The issue you are going to face is that you will need to mock the underlying services inside your app. If you don't do that, your whole server will spin up and attempt to hit the database etc. Here is an example. This is using Moq. Incidentally I am sharing the ConfigureServices method with unit tests so they use the same object mesh of mocked services. You can still use the full functionality of Moq or NSubstitute to test the back-end (or even front -end).
I can hit my attributes in the test with breakpoint.
private void ConfigureServices(IServiceCollection services)
{
var hostBuilder = new WebHostBuilder();
hostBuilder.UseStartup<TestStartup>();
hostBuilder.ConfigureServices(services =>
{
ConfigureServices(services);
});
_testServer = new TestServer(hostBuilder);
_httpClient = _testServer.CreateClient();
}
private void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_storageManagerFactory.Object);
services.AddSingleton(_blobReferenceManagerMock.Object);
services.AddSingleton(_ipActivitiesLoggerMocker.Object);
services.AddSingleton(_loggerFactoryMock.Object);
services.AddSingleton(_hashingService);
services.AddSingleton(_settingsServiceMock.Object);
services.AddSingleton(_ipActivitiesManager.Object);
services.AddSingleton(_restClientMock.Object);
_serviceProvider = services.BuildServiceProvider();
}
public class TestStartup
{
public void Configure(
IApplicationBuilder app,
ISettingsService settingsService)
{
app.Configure(settingsService.GetSettings());
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var mvc = services.AddMvc(option => option.EnableEndpointRouting = false);
mvc.AddApplicationPart(typeof(BlobController).Assembly);
services.AddSingleton(new Mock<IHttpContextAccessor>().Object);
return services.BuildServiceProvider();
}
}

Using FluentScheduler - ASP.NET Core MVC

I currently have a simple website setup with ASP.NET Core MVC (.NET 4.6.1), and I would like to periodically do some processes like automatically send emails at the end of every day to the registered members.
After doing some searching, I came across two common solutions - Quartz.NET and FluentScheduler.
Based on this SO thread, I found the approach of using FluentScheduler more easier to digest and use for my simple task. After quickly implementing the following lines of code into my Program.cs class, I had the emails going out successfully every minute (for testing purposes).
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
var registry = new Registry();
JobManager.Initialize(registry);
JobManager.AddJob(() => MyEmailService.SendEmail(), s => s
.ToRunEvery(1)
.Minutes());
host.Run();
}
}
However, now apart from sending emails I also need to do some back-end processing for e.g. updating the user records in the DB when mails are being sent out. For this, I normally inject my Entity Framework Context into the constructor of my controllers and use it to get/update SQL records.
My question is, since I cannot really inject these services into the main method, where would be the appropriate place to initialize the registry and add jobs for scheduling?
Thanks for the help, I am a little new to this so a little guidance would be much appreciated!
Instead of Program's Main function, I initialized the same in Startup.cs before app.UseMvc..
public void Configure(...., IDependencyObject dependencyObject)
{
....
JobManager.Initialize(new MyRegistry(dependencyObject));
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/{controller}/{action}/{id?}");
});
}
My registry class looks like this:
public class MyRegistry: Registry
{
public MyRegistry(IDependencyObject dependencyObject)
{
Schedule(() => new SyncUpJob(dependencyObject)).ToRunNow().AndEvery(10).Seconds();
}
}
My Job class looks like this:
public class SyncUpJob: IJob
{
public SyncUpJob(IDependencyObject dependencyObject)
{
DependencyObject= dependencyObject;
}
public IDependencyObject DependencyObject{ get; set; }
public void Execute()
{
// call the method to run weekly here
}
}
You can define all your jobs and their schedules, by subclassing from FluentScheduler Registry class. something like:
public class JobRegistry : Registry {
public JobRegistry() {
Schedule<EmailJob>().ToRunEvery(1).Days();
Schedule<SomeOtherJob>().ToRunEvery(1).Seconds();
}
}
public class EmailJob : IJob {
public DbContext Context { get; } // we need this dependency, right?!
public EmailJob(DbContext context) //constructor injection
{
Context = context;
}
public void Execute()
{
//Job implementation code: send emails to users and update database
}
}
For injecting dependencies into jobs, you need to implement FluentScheduler IJobFactory interface. GetJobIntance method is called by FluentScheduler for creating job instances. Here you can use any DI library you want; In this sample implementation, I'm going to assume that you use Ninject:
public class MyNinjectModule : NinjectModule {
public override void Load()
{
Bind<DbContext>().To<MyDbContextImplemenation>();
}
}
public class JobFactory : IJobFactory {
private IKernel Kernel { get; }
public JobFactory(IKernel kernel)
{
Kernel = kernel;
}
public IJob GetJobInstance<T>() where T : IJob
{
return Kernel.Get<T>();
}
}
Now you can start your jobs in main method by calling:
JobManager.JobFactory = new JobFactory(new StandardKernel(new MyNinjectModule()));
JobManager.Initialize(new JobRegistry());

How to persist policy authorization results for users in ASP.NET Core, MVC 6?

Currently I have a simple custom policy handler that looks like so:
protected override void Handle(AuthorizationContext context, UserPolicyRequirement requirement)
{
// authorize user against policy requirements
if (_authorizationTask.AuthorizeUserAgainstPolicy(context.User, requirement))
{
// User passed policy req's
context.Succeed(requirement);
}
}
Problem is, this authorization step takes a long time to execute, but this is required in many different areas of the website. Is there any readily available mechanisms to save/cache the results of this policy authorization so that I only need to do this once per session?
I am currently using Windows Authentication, if that helps.
If per session way does not cause any problem, you can use Session to store user data. Simple implementation is something like below:
First you need a service to get user data from any store
public interface IGetUserDataService
{
<type> GetUserData();
}
I assume that there is Session configuration(see) and IGetUserDataService implementation.
Then you need to create a middleware to handle Session
public class SessionMiddleware
{
private readonly RequestDelegate _next;
private readonly IGetUserDataService _getUserDataService;
public SessionMiddleware(RequestDelegate next, IGetUserDataService getUserDataService)
{
_next = next;
_getUserDataService = getUserDataService;
}
public async Task Invoke(HttpContext context)
{
//user data is obtained only once then is stored in Session
if (context.Session.Get("UserData") == null)
{
context.Session.Set("UserData", getUserDataService.GetData());
}
await _next.Invoke(context);
}
}
//In Startup.cs
app.UseMiddleware<SessionMiddleware>();
Finally get and use session data in handler
public class YourHandler : AuthorizationHandler<YourRequirement>
{
private readonly IHttpContextAccessor _accessor;
public YourHandler(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
var userData =(<type>)_accessor.HttpContext.Session.Get("UserData");
// check
}
}

Categories

Resources