Async or Task<T> confusion [duplicate] - c#

This question already has answers here:
Why use async and return await, when you can return Task<T> directly?
(9 answers)
How and when to use ‘async’ and ‘await’
(25 answers)
Closed 1 year ago.
I am trying to understand how to write async controllers in webapi. Below is an example, I fake a 5 sec work in DB.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public async Task<int> Get()
{
var r = new ServiceLayer();
var res = r.getDataAsync(_logger);
var res2 = r.getDataAsync(_logger);
var val = await res;
var val2 = await res2;
_logger.Log(LogLevel.Error, "UNDER");
return val+val2;
}
}
public class ServiceLayer
{
public Task<int> getDataAsync(ILogger<WeatherForecastController> _logger)
{
var a = new DAtaBaseLayer();
return a.queryAsync(_logger);
}
}
public class DAtaBaseLayer
{
public Task<int> queryAsync(ILogger<WeatherForecastController> _logger)
{
var t = new Task<int>(() =>
{
_logger.Log(LogLevel.Error, "BEFORE");
Thread.Sleep(5555);
_logger.Log(LogLevel.Error, "AFTER");
return 77;
});
t.Start();
return t;
}
}
I am a bit confused what would be the difference if I let the ServiceLayer be async too. What benefit would I get?
Is this idiomatic?
Should it then not be called getDataAsync but instead getData?
public class ServiceLayer
{
public async Task<int> getDataAsync(ILogger<WeatherForecastController> _logger)
{
var a = new DAtaBaseLayer();
return await a.queryAsync(_logger);
}
}

Related

Conflict with mocking service when seeding database for testing

I have a multi tenant web API where I seed a database with initial data.
I also have a transient IUserService which has a GetCustomerId function to retrieve the current customerId. This service is used in the databaseContext to store the CustomerId foreign key on the created domain entity "under the hood".
So when I seed the database I create a new scope and use a ICurrentUserInitializer to set the CustomerId in the IUserService for that scope, so the CustomerId is valid when the database context stores the entity.
This works just fine in development, but not for testing. Since I want to mock the IUserService when I test, this means that Moq overrides the GetCustomerId. But I only want to mock that service AFTER I've finished seeding the test database.
I've also tried not mocking the IUserService, and instead use a ICurrentUserInitializer for every test that runs, i.e. for every test, create a new scope, set the CustomerId with the ICurrentUserInitializer in that scope, and run the test in that scope, and then reset for the next test. This seems to work, but isn't as flexible when you want to run tests as different users and it doesn't seem as elegant, since I have to write more code to handle the scope correctly.
I Use xUnit, Moq, Respawn, and Microsoft.AspNetCore.Mvc.Testing
DbContext :
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
int? customerId = CurrentUser.GetCustomerId();
HandleAuditingBeforeSaveChanges(customerId);
int result = await base.SaveChangesAsync(cancellationToken);
return result;
}
private void HandleAuditingBeforeSaveChanges(int? customerId)
{
foreach (var entry in ChangeTracker.Entries<IMustHaveTenant>().ToList())
{
entry.Entity.CustomerId = entry.State switch
{
EntityState.Added => customerId.Value,
_ => entry.Entity.CustomerId
};
}
}
DatabaseInitializer :
public async Task InitializeApplicationDbForCustomerAsync(Customer Customer, CancellationToken cancellationToken)
{
// First create a new scope
using var scope = _serviceProvider.CreateScope();
// This service injects a CustomerId, so that ICurrentUser retrieves this value, but
// doesn't work, since Moq overrides the value
scope.ServiceProvider.GetRequiredService<ICurrentUserInitializer>()
.SetCurrentCustomerId(customer.Id);
// Then run the initialization in the new scope
await scope.ServiceProvider.GetRequiredService<ApplicationDbSeeder>()
.SeedDatabaseAsync(_dbContext, cancellationToken);
}
CustomWebApplicationFactory:
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(configurationBuilder =>
{
var integrationConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
configurationBuilder.AddConfiguration(integrationConfig);
});
builder.ConfigureServices((context, services) =>
{
services
.Remove<DbContextOptions<ApplicationDbContext>>()
.AddDbContext<ApplicationDbContext>((sp, options) =>
{
options.UseSqlServer(context.Configuration.GetConnectionString("DefaultConnection"),
builder => builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
});
});
builder.ConfigureTestServices(services =>
{
services
.Remove<ICurrentUser>()
.AddTransient(_ => Mock.Of<ICurrentUser>(s =>
s.GetCustomerId() == GetCurrentCustomerId()));
});
}
}
Testing / CollectionFixture :
public class DatabaseCollection : ICollectionFixture<Testing>
{
}
public partial class Testing : IAsyncLifetime
{
private static WebApplicationFactory<Program> _factory = null!;
private static IConfiguration _configuration = null!;
private static IServiceScopeFactory _scopeFactory = null!;
private static Checkpoint _checkpoint = null!;
private static int? _currentCustomerId = null;
public Task InitializeAsync()
{
_factory = new CustomWebApplicationFactory();
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
_checkpoint = new Checkpoint
{
TablesToIgnore = new[] { new Table("__EFMigrationsHistory") },
};
return Task.CompletedTask;
}
public static int? GetCurrentCustomerId()
{
return _currentCustomerId;
}
public static void RunAsDefaultUserAsync()
{
_currentCustomerId = DefaultValues.Customer.Id;
}
public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
{
using var scope = _scopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
return await mediator.Send(request);
}
public static async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
return await context.FindAsync<TEntity>(keyValues);
}
public static async Task AddAsync<TEntity>(TEntity entity)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Add(entity);
await context.SaveChangesAsync();
}
public static async Task ResetState()
{
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
await _factory.Services.InitializeDatabasesAsync();
_currentCustomerId = null;
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
}
DepartmentTest:
[Collection("Database collection")]
public class GetDepartmentsTest : BaseTestFixture
{
[Fact]
public async Task ShouldReturnDepartments()
{
RunAsDefaultUserAsync();
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
}
[Fact]
public async Task ShouldReturnAllDepartments()
{
RunAsDefaultUserAsync();
await AddAsync(new Department
{
Description = "Department 1",
});
await AddAsync(new Department
{
Description = "Department 2",
});
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
result.Count.ShouldBe(2);
}
}
BaseTestFixture:
public class BaseTestFixture : IAsyncLifetime
{
public async Task InitializeAsync()
{
await ResetState();
}
public async Task DisposeAsync()
{
await ResetState();
//return Task.CompletedTask;
}
}

Can't debug async method even if I am awaiting

I have the following scenario:
in my Test class:
public class ControllerTests
{
private readonly MyClass aux;
private readonly Mock<IOrchestrator> _orchestrator;
public ControllerTests()
{
orchestrator = new();
aux = new MyClass(_orchestrator.Object);
}
[Fact]
public async Task MyTest()
{
var result = await aux.Start(new MyParameters());
result.Should().BeOfType<AcceptedResult>();
}
}
MyClass:
public class MyClass : ControllerBase
{
private readonly Orchestrator _orchestrator;
[HttpPost]
public async Task<IActionResult> Start([FromBody] MyParametera parameters)
{
var result = await _orchestrator.StartProcessAsync(parameters);
return Accepted(result);
}
}
and at the end the Orcherstrator class:
public class Orchestrator : IOrchestrator{
public async Task<Guid> StartProcessAsync(MyParameters parameters)
{
var guid = await MyLogic(parameters, async () => {
var manager = _SomeLogic.Get<SomeClass>();
await manager.StartAsync(parameters);
});
}
}
while debugging the test I notice I am not able to hit a breakpoint in the Orchestrator class at the StartProcessAsync method. But I am awaiting everything! What am I doing wrong? Thank you!

How to use LoggingBehaviour in Clean Architecture with .NET Core?

I use CleanArchitecture for my .NET Core project with Angular and I am trying to use LoggingBehaviour class located on CleanArchitecture/src/Application/Common/Behaviours/ in that project template as shown below:
namespace CleanArchitecture.Application.Common.Behaviours
{
public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest>
{
private readonly ILogger _logger;
private readonly ICurrentUserService _currentUserService;
private readonly IIdentityService _identityService;
public LoggingBehaviour(ILogger<TRequest> logger, ICurrentUserService currentUserService,
IIdentityService identityService)
{
_logger = logger;
_currentUserService = currentUserService;
_identityService = identityService;
}
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
var userId = _currentUserService.UserId ?? string.Empty;
string userName = string.Empty;
if (!string.IsNullOrEmpty(userId))
{
userName = await _identityService.GetUserNameAsync(userId);
}
_logger.LogInformation("CleanArchitecture Request: {Name} {#UserId} {#UserName} {#Request}",
requestName, userId, userName, request);
}
}
}
However, I have no idea about how to use it properly as there is not an example usage in that solution template. Could you please clarify me on how to use it properly according to this template example?
You can use like below;
namespace CleanArchitecture.Application.UnitTests.Common.Behaviours
{
public class RequestLoggerTests
{
private readonly Mock<ILogger<CreateTodoItemCommand>> _logger;
private readonly Mock<ICurrentUserService> _currentUserService;
private readonly Mock<IIdentityService> _identityService;
public RequestLoggerTests()
{
_logger = new Mock<ILogger<CreateTodoItemCommand>>();
_currentUserService = new Mock<ICurrentUserService>();
_identityService = new Mock<IIdentityService>();
}
[Test]
public async Task ShouldCallGetUserNameAsyncOnceIfAuthenticated()
{
_currentUserService.Setup(x => x.UserId).Returns("Administrator");
var requestLogger = new LoggingBehaviour<CreateTodoItemCommand>(_logger.Object, _currentUserService.Object, _identityService.Object);
await requestLogger.Process(new CreateTodoItemCommand { ListId = 1, Title = "title" }, new CancellationToken());
_identityService.Verify(i => i.GetUserNameAsync(It.IsAny<string>()), Times.Once);
}
[Test]
public async Task ShouldNotCallGetUserNameAsyncOnceIfUnauthenticated()
{
var requestLogger = new LoggingBehaviour<CreateTodoItemCommand>(_logger.Object, _currentUserService.Object, _identityService.Object);
await requestLogger.Process(new CreateTodoItemCommand { ListId = 1, Title = "title" }, new CancellationToken());
_identityService.Verify(i => i.GetUserNameAsync(null), Times.Never);
}
}
}

cannot convert from 'method group' to 'TimerCallback' [duplicate]

This question already has answers here:
The best overloaded method match for System.Threading.Timer.Timer() has some invalid arguments
(3 answers)
Closed 3 years ago.
I'm trying to run function getOrg though hosted services but some how its not working I'm not sure what I'm doing wrong.
Error:
Argument 1: cannot convert from 'method group' to 'TimerCallback'
(CS1503)
public class TokenService : IHostedService
{
public IConfiguration _Configuration { get; }
protected IMemoryCache _cache;
private Timer _timer;
public IHttpClientFactory _clientFactory;
private readonly IServiceScopeFactory _scopeFactory;
public TokenService(IConfiguration configuration, IMemoryCache memoryCache, IHttpClientFactory clientFactory, IServiceScopeFactory scopeFactory)
{
_Configuration = configuration;
_cache = memoryCache;
_clientFactory = clientFactory;
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(getOrg, null, 0, 1000); // getting error here
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Timer does not have a stop.
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public async Task getOrg()
{
var request = new HttpRequestMessage(HttpMethod.Get, "organizations");
var response = await _client_NP.SendAsync(request);
var json = await response.Content.ReadAsStringAsync();
OrganizationsClass.OrgsRootObject model = JsonConvert.DeserializeObject<OrganizationsClass.OrgsRootObject>(json);
using (var scope = _scopeFactory.CreateScope())
{
var _DBcontext = scope.ServiceProvider.GetRequiredService<DBContext>();
foreach (var item in model.resources)
{
var g = Guid.Parse(item.guid);
var x = _DBcontext.Organizations.FirstOrDefault(o => o.OrgGuid == g);
if (x == null)
{
_DBcontext.Organizations.Add(new Organizations
{
OrgGuid = g,
Name = item.name,
CreatedAt = item.created_at,
UpdatedAt = item.updated_at,
Timestamp = DateTime.Now,
Foundation = 3
});
}
else if (x.UpdatedAt != item.updated_at)
{
x.CreatedAt = item.created_at;
x.UpdatedAt = item.updated_at;
x.Timestamp = DateTime.Now;
}
}
await getSpace();
await _DBcontext.SaveChangesAsync();
}
}
}
TimerCallback takes on object parameter for state. Try changing getOrg to:
public async void getOrg(object state)
You are providing wrong parameters to System.Threading.Timer constructor.
The first parameter should be a delegate type (instead of getOrg):
public delegate void TimerCallback(object state);
So add a delegate to your code:
private void TimerProc(object state)
{
}
Change the constructor:
_timer = new Timer(TimerProc, null, 0, 1000); // getting error here

Callback never comes

I have a WebApi controller which calls a third party API in asynchronous mode.
All works ok and now I want to sort the result in a separate action method.
Now, when I call the API, the callback with the result never happens after running "await client.GetAsycn(...)" in the DAL. What am I missing?
This is my API controller:
// GET api/lookup
[ResponseType(typeof(RestaurantModel))]
public async Task<IHttpActionResult> Get(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
var result = await _repository.GetRestaurantsByOutcode(outcode);
return Ok(new RestaurantModel()
{
Result = result
});
}
// GET api/sorted
[System.Web.Http.Route("~/api/sorted")]
public List<Restaurant> GetSorted(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
return _repository.GetSortedRestaurantsByOutcode(outcode);
}
This is my repository with a new method to sort the result:
public class RestaurantRepository : IRestaurantRepository
{
private readonly IContext _context;
public RestaurantRepository(IContext context)
{
_context = context;
}
public Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
return _context.GetRestaurantsByOutcode(outcode);
}
public List<Restaurant> GetSortedRestaurantsByOutcode(string outcode)
{
return _context.GetRestaurantsByOutcode(outcode).Result.Restaurants
.OrderBy(x => x.Name).ToList();
}
}
This is my DAL to call the third party API:
public async Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
using (var client = new HttpClient())
{
ConfigureHttpClient(client);
var response = await client.GetAsync(
$"restaurants?q={WebUtility.UrlEncode(outcode)}");
return response.IsSuccessStatusCode
? await response.Content.ReadAsAsync<ApiResult>()
: null;
}
}
You have a mix-match of sometimes you use async/await and other times you don't. Async / await (can and does by default) ensures that the call resumes on the calling thread so the context is resulted. You need to allign your code so you make use of the async/await in the whole stack. Otherwise you are creating a deadlock for your self.
[System.Web.Http.Route("~/api/sorted")]
// missing async in signature (not good if you are calling it with await in your controller)
public async Task<List<Restaurant>> GetSorted(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
// added await in call
return await _repository.GetSortedRestaurantsByOutcode(outcode);
}
DAL
public class RestaurantRepository : IRestaurantRepository
{
private readonly IContext _context;
public RestaurantRepository(IContext context)
{
_context = context;
}
// added async and await
public async Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
return await _context.GetRestaurantsByOutcode(outcode);
}
// added async and await
public async Task<List<Restaurant>> GetSortedRestaurantsByOutcode(string outcode)
{
// here you were not using await but then using result even though you were calling into a method marked as async which in turn used an await. this is where you deadlocked but this the fix.
return (await _context.GetRestaurantsByOutcode(outcode)).Restaurants
.OrderBy(x => x.Name).ToList();
}
}

Categories

Resources