Is there a way to implement IoC httpclient via Unity - c#

I have a solution which has business service, business entities, data model layers, and an asp.net web API project. I am calling 3rd party web api in my business service layer. I followed this [link] (Using Unity to create a singleton that is used in my Class) in order to use IoC via Unity but I am getting null for httpclient.
public class Utilities : IUtilities
{
private static HttpClient _httpClient;
public Utilities(HttpClient httpClient)
{
_httpClient = httpClient;
}
public static async Task<HttpResponseMessage> CallRazer(GameRequest gameRequest, string url)
{
//Convert request
var keyValues = gameRequest.ToKeyValue();
var content = new FormUrlEncodedContent(keyValues);
//Call Game Sultan
var response = await _httpClient.PostAsync("https://test.com/"+url, content);
return response;
}
}
}
Here is Unity config:
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below.
// Make sure to add a Unity.Configuration to the using statements.
// container.LoadConfiguration();
// TODO: Register your type's mappings here.
// container.RegisterType<IProductRepository, ProductRepository>();
container.RegisterType<IGameServices, GameServices>().RegisterType<UnitOfWork>(new HierarchicalLifetimeManager());
container.RegisterType<IUtilities, Utilities>(new ContainerControlledLifetimeManager());
container.RegisterType<HttpClient, HttpClient>(new ContainerControlledLifetimeManager(), new InjectionConstructor());
}
Game Service added.
public class GameServices : IGameServices
{
private readonly UnitOfWork _unitOfWork;
/// <summary>
/// Public constructor.
/// </summary>
public GameServices(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
/// <summary>
/// Creates a product
/// </summary>
/// <param name="requestDto"></param>
/// <returns></returns>
public async Task<HttpResponseMessage> GamePurchase(RequestDto requestDto)
{
try
{
string htmlResponse = null;
HttpResponseMessage response = null;
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
//Transform DTO into GameRequest for calling Razer Initiate
var config = new MapperConfiguration(cfg => { cfg.CreateMap<RequestDto, GameRequest>(); });
var iMapper = config.CreateMapper();
var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
//Create signature
gameRequest = Utilities.CreateSignature(gameRequest, RequestType.Initiate);
//Unique reference ID
gameRequest.referenceId = Guid.NewGuid().ToString();
//Add request into database
_unitOfWork.GameRepository.Insert(gameRequest);
#region Call Razer initiate/confirm
response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");
//Read response
htmlResponse = await response.Content.ReadAsStringAsync();
var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);
//Adding response into database
_unitOfWork.GameResponseRepository.Insert(gameResponse);
if (gameResponse.initiationResultCode == "00")
{
//Create signature
var gameConfirmRequest = Utilities.CreateSignature(gameRequest, RequestType.Confirm);
}
#endregion
await _unitOfWork.SaveAsync();
scope.Complete();
}
return response;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public enum RequestType
{
Initiate = 0,
Confirm = 1
}
}

Related

.net services.AddHttpClient Automatic Access Token Handling

I am trying to write a Blazor app that uses client secret credentials to get an access token for the API. I wanted to encapsulate it in such a way that it handles the token fetching and refreshing behind the scenes. To achieve this, I created the following inherited class which uses IdentityModel Nuget package:
public class MPSHttpClient : HttpClient
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger Logger;
public MPSHttpClient(IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
public async Task<bool> RefreshTokens()
{
if (Tokens.RefreshToken == null)
return false;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
RefreshToken = Tokens.RefreshToken
});
Logger.LogInformation("Refresh Token Result {0}", result.IsError);
if (result.IsError)
{
Logger.LogError("Error: {0)", result.ErrorDescription);
return false;
}
Tokens.RefreshToken = result.RefreshToken;
Tokens.AccessToken = result.AccessToken;
Logger.LogInformation("Access Token: {0}", result.AccessToken);
Logger.LogInformation("Refresh Token: {0}" , result.RefreshToken);
return true;
}
public async Task<bool> CheckTokens()
{
if (await RefreshTokens())
return true;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
public new async Task<HttpResponseMessage> GetAsync(string requestUri)
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
var response = await base.GetAsync(requestUri);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
response = await base.GetAsync(requestUri);
}
}
return response;
}
}
The idea is to keep from having to write a bunch of redundant code to try the API, then request/refresh the token if you are unauthorized. I tried it at first using extension methods to HttpClient, but there was no good way to inject the Configuration into a static class.
So my Service code is written as this:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly MPSHttpClient _httpClient;
public EngineListService(MPSHttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
Everything compiles great. In my Startup, I have the following code:
services.AddScoped<TokenProvider>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Just to be complete, Token Provider looks like this:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
When I run the App, it complains that it can't find a suitable constructor for EngineListService in the call to services.AddHttpClient. Is there a way to pass AddHttpClient an actual instance of the IEngineListService. Any other way I might be able to achieve this?
Thanks,
Jim
I think that EngineListService should not be registered as a HttpClient in services and instead you should register MPSHttpClient.
This follows the "Typed Client" example in the documentation and uses IHttpClientFactory behind the scenes.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#typed-clients
When you use services.AddHttpClient the constructor needs a HttpClient parameter. That is how the HttpClientFactory initializes the HttpClient and then passes it into your service ready to go.
You can change your MPSHttpClient to not inherit HttpClient and instead add a HttpClient parameter to the constructor. You could also have it implement an interface like IMPSHttpClient
public class MPSHttpClient
{
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
HttpClient = httpClient;
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
}
You must remove these lines from MPSHttpClient and use the injected client.
// remove this
var client = new HttpClient();
In Startup add
services.AddHttpClient<MPSHttpClient>(client =>
{
// add any configuration
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Change EngineListService to a normal service registration as it is not a HttpClient
services.AddScoped<IEngineListService, EngineListService>()
Special thanks to #pinkfloydx33 for helping me solve this. This link that he shared https://blog.joaograssi.com/typed-httpclient-with-messagehandler-getting-accesstokens-from-identityserver/ was everything I needed. The trick was that there exists a class called DelegatingHandler that you can inherit and override the OnSendAsync method and do all of your token-checking there before sending it to the final HttpHandler. So my new MPSHttpClient class is as so:
public class MPSHttpClient : DelegatingHandler
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger<MPSHttpClient> Logger;
private readonly HttpClient client;
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger<MPSHttpClient> logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
client = httpClient;
}
public async Task<bool> CheckTokens()
{
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBearerToken(Tokens.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
request.SetBearerToken(Tokens.AccessToken);
response = await base.SendAsync(request, cancellationToken);
}
}
return response;
}
}
The big changes here are the inheritance and I used DI to obtain the HttpClient much like #Rosco mentioned. I had tried to override OnGetAsync in my original version. When inheriting from DelegatingHandler, all you have to override is OnSendAsync. This will handle all of your get, put, post, and deletes from your HttpContext all in one method.
My EngineList Service is written as if there were no tokens to be considered, which was my original goal:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly HttpClient _httpClient;
public EngineListService(HttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
The Token Provider stayed the same. I plan to add expirations and such to it, but it works as is:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
The ConfigureServices code changed just a bit:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<TokenProvider>();
services.AddTransient<MPSHttpClient>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
...
}
You instantiate MPSHttpClient as Transient, then reference it with the AddHttpMessageHandler call attached to the AddHttpClient call. I know this is different than how others implement HttpClients, but I learned this method of creating client services from a Pluralsight video and have been using it for everything. I create a separate Service for each entity in the database. If say I wanted to do tires, I would add the following to ConfigureServices:
services.AddHttpClient<ITireListService, TireListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
It will use the same DelegatingHandler so I can just keep adding services for each entity type while no longer worrying about tokens. Thanks to everyone that responded.
Thanks,
Jim

How to close Resharper test runner after finishing integration test in xUnit Test project (.NET Core)?

i am new to integration tests. I have an xUnit project in my solution which contains one test only.
Here's the definition of my test:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "testoweImie",
LastName = "testoweNazwisko",
MailAddress = "test#test.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
And the BaseIntegrationTest class definition:
public abstract class BaseIntegrationTest
{
private const string TestDatabaseName = "TestDatabase";
protected BaseIntegrationTest()
{
var appFactory = new WebApplicationFactory<Startup>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
RemoveDatabaseContextFromServicesCollectionIfFound<EventStoreContext>(services);
RemoveDatabaseContextFromServicesCollectionIfFound<GrantContext>(services);
services
.AddDbContext<EventStoreContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName))
.AddDbContext<GrantContext>(options =>
options.UseInMemoryDatabase(TestDatabaseName));
});
});
HttpClient = appFactory.CreateClient();
}
protected HttpClient HttpClient { get; }
protected static StringContent SerializeObject(object #object) =>
new StringContent(
JsonConvert.SerializeObject(#object),
Encoding.UTF8,
"application/json");
private static void RemoveDatabaseContextFromServicesCollectionIfFound<T>(IServiceCollection services)
where T : DbContext
{
var descriptor = services.SingleOrDefault(service =>
service.ServiceType == typeof(DbContextOptions<T>));
if (!(descriptor is null))
{
services
.Remove(descriptor);
}
}
}
When i run tests, it takes few seconds, and the test ends successfully. The problem is that Resharper Test Runner still runs, although i've already have collected results. what am i doing wrong here? Do i have to somehow dispose the HttpClient, after performing all tests? If so, how to achieve that? Thanks for any help.
It looks like you're actually booting the application inside the test rather than using the testhost (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1)
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;
public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Notice the IClassFixture stuff.

Middleware with Masstransit publish

I have .net core WEB API application with MassTransit (for implement RabbitMQ message broker). RabbitMQ-MassTransit configuration is simple and done in few line code in Startup.cs file.
services.AddMassTransit(x =>
{
x.AddConsumer<CustomLogConsume>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://rabbitmq/"), h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ExchangeType = ExchangeType.Fanout;
cfg.ReceiveEndpoint(host, "ActionLog_Queue", e =>
{
e.PrefetchCount = 16;
});
// or, configure the endpoints by convention
cfg.ConfigureEndpoints(provider);
}));
});
I am using dependency injection in my project solution for better code standard. Publish messages are works fine with controller dependency injection. But when I implement a custom middle ware for log actions, Masstransit failed to publish the message properly, it was created a additional queue with _error in RabbitMQ web console.
public class RequestResponseLoggingMiddleware
{
#region Private Variables
/// <summary>
/// RequestDelegate
/// </summary>
private readonly RequestDelegate _next;
/// <summary>
/// IActionLogPublish
/// </summary>
private readonly IActionLogPublish _logPublish;
#endregion
#region Constructor
public RequestResponseLoggingMiddleware(RequestDelegate next, IActionLogPublish logPublish)
{
_next = next;
_logPublish = logPublish;
}
#endregion
#region PrivateMethods
#region FormatRequest
/// <summary>
/// FormatRequest
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private async Task<ActionLog> FormatRequest(HttpRequest request)
{
ActionLog actionLog = new ActionLog();
var body = request.Body;
request.EnableRewind();
var context = request.HttpContext;
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body = body;
var injectedRequestStream = new MemoryStream();
var requestLog = $"REQUEST HttpMethod: {context.Request.Method}, Path: {context.Request.Path}";
using (var bodyReader = new StreamReader(context.Request.Body))
{
bodyAsText = bodyReader.ReadToEnd();
if (string.IsNullOrWhiteSpace(bodyAsText) == false)
{
requestLog += $", Body : {bodyAsText}";
}
var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText);
injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length);
injectedRequestStream.Seek(0, SeekOrigin.Begin);
context.Request.Body = injectedRequestStream;
}
actionLog.Request = $"{bodyAsText}";
actionLog.RequestURL = $"{request.Scheme} {request.Host}{request.Path} {request.QueryString}";
return actionLog;
}
#endregion
#region FormatResponse
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return $"Response {text}";
}
#endregion
#endregion
#region PublicMethods
#region Invoke
/// <summary>
/// Invoke - Hits before executing any action. Actions call executes from _next(context)
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
ActionLog actionLog = new ActionLog();
actionLog = await FormatRequest(context.Request);
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
actionLog.Response = await FormatResponse(context.Response);
await _logPublish.Publish(actionLog);
await responseBody.CopyToAsync(originalBodyStream);
}
}
#endregion
#endregion
}
configure Middleware in startup
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)
{
............
app.UseMiddleware<RequestResponseLoggingMiddleware>();
....................
}
Is there any additional configuration in startup for MassTransit to work with Middle Ware
Edit
IActionLogPublish
public interface IActionLogPublish
{
Task Publish(ActionLog model);
}
ActionLogPublish
public class ActionLogPublish : IActionLogPublish
{
private readonly IBus _bus;
public ActionLogPublish(IBus bus)
{
_bus = bus;
}
public async Task Publish(ActionLog actionLogData)
{
/* Publish values to RabbitMQ Service Bus */
await _bus.Publish(actionLogData);
/* Publish values to RabbitMQ Service Bus */
}
}
Edit
RabbitMQ Web Console
The middleware needs to put the original body back in the response.
Also the injected dependency works fine with controllers and not middleware as it may be registered with scoped lifetime.
In that case it should not be constructor injected into the middlewre but directly into the Invoke
Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors aren't shared with other dependency-injected types during each request. If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. The Invoke method can accept additional parameters that are populated by DI:
//...omitted for brevity
public RequestResponseLoggingMiddleware(RequestDelegate next) {
_next = next;
}
//...
private async Task<string> FormatResponseStream(Stream stream) {
stream.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(stream).ReadToEndAsync();
stream.Seek(0, SeekOrigin.Begin);
return $"Response {text}";
}
public async Task Invoke(HttpContext context, IActionLogPublish logger) {
ActionLog actionLog = await FormatRequest(context.Request);
//keep local copy of response stream
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream()) {
//replace stream for down stream calls
context.Response.Body = responseBody;
await _next(context);
//put original stream back in the response object
context.Response.Body = originalBodyStream; // <-- THIS IS IMPORTANT
//Copy local stream to original stream
responseBody.Position = 0;
await responseBody.CopyToAsync(originalBodyStream);
//custom logging
actionLog.Response = await FormatResponse(responseBody);
await logger.Publish(actionLog);
}
}
Reference Dependency injection in ASP.NET Core: Scoped Service lifetime
When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Don't inject via constructor injection because it forces the service to behave like a singleton. For more information, see Write custom ASP.NET Core middleware.
Emphasis mine
It is hard to tell from the description what error you are getting exactly. The middleware implementation looks complicated and it can be a source of the error. I would guess that you don't set stream position correctly or something. Corrections from #Nkosi may actually fix it.
If you say that IBus works correctly from controllers, which are created per request, you may want to try to implement IMiddleware interface in your middleware as described in this doc.
public class RequestResponseLoggingMiddleware : IMiddleware
{
IActionLogPublish logPublish;
public RequestResponseLoggingMiddleware(IActionLogPublish logPublish)
{
this.logPublish = logPublish;
}
// ...
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
//...
}
//...
}
In this case middleware will be registered as scoped or transient service and resolved for every request, same as controller. Which may also fix your issue if it relates to scoped services resolution.

How to fix the operation is not valid for the state of the transaction?

I am trying to implement an asp.net web API and it is my first time trying to implement such a project. I googled a lot.
Many times lost down the road because there is a lot of misguiding articles. Now I accomplished to implement the layered architecture. I have a business entity, business service, data models class library projects and a web API itself. As I said I googled a lot since I have no experience with web API, I decided to use generic repository and unit of work in my solution. My API is working butI get the following error when I load test with 400 users:
System.Transactions.TransactionException: The operation is not valid for the state of the transaction. ---> System.TimeoutException: Transaction Timeout. The underlying provider failed on Open.
I researched and asked experienced friends, they said I am not disposing of all the DBContexts I am creating. As I am using transient scope, a new DBcontext is created for every action call (controller construction), but unless I call context.Dispose() in the action the pools are not released.
Honestly, I am not sure how can I dispose of more.
Controller:
namespace Game.Controllers
{
[System.Web.Http.RoutePrefix("api/v2/game")]
public class GameController : ApiController
{
private readonly IGameServices _gameServices;
#region Public Constructor
public GameController(IGameServices gameServices)
{
_gameServices = gameServices;
}
#endregion
[HttpPost, Route("purchase")]
public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//Call Services
var gameConfirmResponse = await _gameServices.GamePurchase(game);
switch (gameConfirmResponse.StatusCode)
{
case HttpStatusCode.NotFound:
{
return NotFound();
}
case HttpStatusCode.InternalServerError:
{
return InternalServerError();
}
case HttpStatusCode.OK:
{
if (gameConfirmResponse.Content == null)
{
return new System.Web.Http.Results.ResponseMessageResult(
Request.CreateErrorResponse((HttpStatusCode) 222, new HttpError("No Results Found")));
}
var responseStream = await gameConfirmResponse.Content.ReadAsStringAsync();
var resultResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(responseStream);
if (resultResponse.coupons == null)
{
return new System.Web.Http.Results.ResponseMessageResult(
Request.CreateErrorResponse((HttpStatusCode) 222,
new HttpError("No Coupons Available for this Game")));
}
//Transform GameConfirmResponse into DTO for returning result
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
cfg.CreateMap<Coupon, CouponDto>();
});
var iMapper = config.CreateMapper();
var resultDto = iMapper.Map<GameConfirmResponse, GameConfirmResponseDto>(resultResponse);
return Ok(resultDto);
}
case HttpStatusCode.Unauthorized:
{
return Unauthorized();
}
case HttpStatusCode.RequestTimeout:
{
return InternalServerError();
}
}
return Ok(gameConfirmResponse);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
}
}
Service:
namespace BusinessService
{
public class GameServices : IGameServices, IDisposable
{
private readonly UnitOfWork _unitOfWork;
public GameServices(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<HttpResponseMessage> GamePurchase(RequestDto requestDto)
{
HttpResponseMessage response = null;
response = await CallRazerService(requestDto);
return response;
}
private async Task<HttpResponseMessage> CallRazerService(RequestDto requestDto)
{
HttpResponseMessage response = null;
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
//Added to see if I fix the error
TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
//Transform DTO into GameRequest for calling Razer Initiate
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<RequestDto, GameRequest>();
cfg.CreateMap<GameRequest, GameConfirmRequest>();
cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
cfg.CreateMap<Coupon, CouponDto>();
cfg.CreateMap<GameRequest, GameRequestDto>();
});
var iMapper = config.CreateMapper();
var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
//Unique reference ID
gameRequest.referenceId = Guid.NewGuid();
var gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
//Create signature
gameRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Initiate);
//Set service
gameRequest.service = "RAZER";
//Add initiation request into database
_unitOfWork.GameRepository.Insert(gameRequest);
#region Call Razer initiate/confirm
//Call Razer for initiation
response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");
//Read response
var htmlResponse = await response.Content.ReadAsStringAsync();
var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);
//Adding initiation response into database
_unitOfWork.GameResponseRepository.Insert(gameResponse);
if (gameResponse.initiationResultCode == "00")
{
gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
gameRequestDto.validatedToken = gameResponse.validatedToken;
//Create signature
var gameConfirmRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Confirm);
//Transform DTO into GameRequest for calling Razer Initiate
var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);
//Add confirm request into database
_unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);
//Call Razer for confirm
response = await Utilities.CallRazer(gameConfirmRequest, "purchaseconfirmation");
//Read response
htmlResponse = await response.Content.ReadAsStringAsync();
var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);
//Set service
gameConfirmResponse.service = "RAZER";
//Add confirm response into database
_unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);
}
#endregion
await _unitOfWork.SaveAsync();
scope.Complete();
}
return response;
}
public void Dispose()
{
_unitOfWork.Dispose();
}
}
}
Generic Repo (shortened):
namespace DataModels
{
public class GenericRepository<TEntity> where TEntity : class
{
internal GameContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(GameContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
#region Public member methods...
public virtual async Task<List<TEntity>> GetGamesAsync(
Expression<Func<TEntity, bool>> where)
{
return await dbSet.Where(where).ToListAsync();
}
public virtual async Task<List<T>> GetGameBankProducts<T, TType>(Expression<Func<TEntity, bool>> where,
Expression<Func<TEntity, TType>> groupBy, Expression<Func<IGrouping<TType, TEntity>, T>> select)
{
return await dbSet.Where(where)
.GroupBy(groupBy)
.Select(select)
.ToListAsync();
}
#endregion
}
}
Unit of Work:
namespace DataModels
{
public class UnitOfWork : IDisposable
{
private readonly GameContext _context;
private readonly GenericRepository<GameRequest> gameRequestRepository;
private readonly GenericRepository<GameResponse> gameResponseRepository;
private readonly GenericRepository<GameConfirmRequest> gameConfirmRequestRepository;
private readonly GenericRepository<GameConfirmResponse> gameConfirmResponseRepository;
private readonly GenericRepository<GameBank> gameBankRepository;
private readonly GenericRepository<GameBankPin> gameBankPinRepository;
private readonly GenericRepository<ConfirmCancel> confirmCancelRepository;
private readonly GenericRepository<Recon> reconRepository;
private readonly GenericRepository<Company> userRepository;
public UnitOfWork(GameContext context)
{
_context = context;
}
public GenericRepository<GameRequest> GameRepository
{
get
{
return this.gameRequestRepository ?? new GenericRepository<GameRequest>(_context);
}
}
public GenericRepository<GameResponse> GameResponseRepository
{
get
{
return this.gameResponseRepository ?? new GenericRepository<GameResponse>(_context);
}
}
public GenericRepository<GameConfirmRequest> GameConfirmRequestRepository
{
get
{
return this.gameConfirmRequestRepository ?? new GenericRepository<GameConfirmRequest>(_context);
}
}
public GenericRepository<GameConfirmResponse> GameConfirmResponseRepository
{
get
{
return this.gameConfirmResponseRepository ?? new GenericRepository<GameConfirmResponse>(_context);
}
}
public GenericRepository<GameBank> GameBankRepository
{
get
{
return this.gameBankRepository ?? new GenericRepository<GameBank>(_context);
}
}
public GenericRepository<GameBankPin> GameBankPinRepository
{
get
{
return this.gameBankPinRepository ?? new GenericRepository<GameBankPin>(_context);
}
}
public GenericRepository<ConfirmCancel> ConfirmCancelRepository
{
get
{
return this.confirmCancelRepository ?? new GenericRepository<ConfirmCancel>(_context);
}
}
public GenericRepository<Recon> ReconRepository
{
get
{
return this.reconRepository ?? new GenericRepository<Recon>(_context);
}
}
public GenericRepository<Company> UserRepository
{
get
{
return this.userRepository ?? new GenericRepository<Company>(_context);
}
}
public void Save()
{
_context.SaveChanges();
}
public async Task SaveAsync()
{
await _context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Unity config:
namespace Game
{
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer Container => container.Value;
#endregion
public static void RegisterTypes(IUnityContainer container)
{
// Default empty Time manager!
container.RegisterType<IGameServices, GameServices>().RegisterType<UnitOfWork>(new TransientLifetimeManager());
container.RegisterType<IUserValidate, UserValidate>(new TransientLifetimeManager());
}
}
}
People keep telling me the best practice is to use "using" but how can I use using in the controller for the Dbcontext? I couldn't find a way.
Update1:
I have been told that instead of transaction scope, I should use dbcontext transaciton (using(var dbContextTransaction = context.Database.BeginTransaction())). I couldn't find a way to use dbcontext transaction in my Service class. I am injecting unit of work in Service class.
Update2:
Should I implement inside of unit of work and remove transactionscope inside of the Service?
UOW Save:
public void Save(){
try{
using (var transaction = _context.Database.BeginTransaction()){
try
{
_context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}catch (Exception e)
{//TODO:Logging}}

Unity Container With Repository and UnitOfWork

I have an ASP.NET WEB API project that using the repository and unit of work patterns along with UnityContainer. I registered all my repositories and unit of work classes with PerRequestLifeTimeManager and everything is working great. Every client request to server work in a single transaction separated from other client requests. After the request is returned to the client, the DbContext is disposed.
My problem begins when I need to to perform heavy actions after I return a response to the client. The DbContext is disposed and I can't request the Unity Container for a new instance (Since no HttpContext exist ).
I read this post PerRequestLifetimeManager can only be used in the context of an HTTP request
And tried to implement 2 different UnityContainer
WebContainer - Register all classes using PerRequestLifeTimeManager
CorContainer - Register all classes using PerResolveLifetimeManager
I thought I solved the problem but I noticed that each repository I request from the CoreContainer using a different DbContext. Also the UnitOfWork has a different DbContext. This off course causing many error in my code.
This is the code I'm using to register my entities in UnityContainer. I have 2 Databases so some of the repository using the DbContext of the first database, and the other using the second DbContext. My UnitOfWork using both DbContexts
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> perRequestContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerRequestLifetimeManager>(container);
return container;
});
private static Lazy<IUnityContainer> perResolveContainer =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterCommonTypes<PerResolveLifetimeManager>(container);
//CNSDeployerService can
container.RegisterType<ICNSDeployerService, CNSDeployerService>(new PerResolveLifetimeManager());
return container;
});
public static IUnityContainer WebContainer => perRequestContainer.Value;
public static IUnityContainer CoreContainer => perResolveContainer.Value;
#endregion
/// <summary>
/// Please notice this configuration exist only inside the scope of a single request
/// See UnityWebApiActivator
/// </summary>
/// <param name="container"></param>
public static void RegisterCommonTypes<T>(IUnityContainer container) where T: LifetimeManager
{
container.RegisterType<ApplicationDbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<DbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
container.RegisterType<capNsubContext, capNsubContext>(Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, capNsubContext>("capNsubContext", Activator.CreateInstance<T>());
container.RegisterType<IDataContextAsync, LinetServerContext>("linnetDataContext", Activator.CreateInstance<T>());
container.RegisterType<IRepository<Entity>, Repository<Entity>>(Activator.CreateInstance<T>());
container.RegisterType<IRepositoryAsync<VideoTargetLanguage>, Repository<VideoTargetLanguage>>(Activator.CreateInstance<T>());
//Unity understand array by defaults so we just need to map it to IEnumerable
container.RegisterType<IEnumerable<IDataContextAsync>, IDataContextAsync[]>();
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(Activator.CreateInstance<T>());
container.RegisterType<UserManager<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<RoleManager<IdentityRole>>(Activator.CreateInstance<T>());
container.RegisterType<AccountController>(Activator.CreateInstance<T>());
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(Activator.CreateInstance<T>());
container.RegisterType<IOrderService, OrderService>(Activator.CreateInstance<T>());
container.RegisterType<IFFMpegService, FFMpegService>(Activator.CreateInstance<T>());
container.RegisterType<IVideoService, VideoService>(Activator.CreateInstance<T>());
container.RegisterType<IOrderItemService, OrderItemService>(Activator.CreateInstance<T>());
container.RegisterType<ILanguageService, LanguageService>(Activator.CreateInstance<T>());
container.RegisterType<IUserService, UserService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCaptionsService, CNSCaptionsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSTranslationsService, CNSTranslationsService>(Activator.CreateInstance<T>());
container.RegisterType<ICNSCapMoviesService, CNSMovieService>(Activator.CreateInstance<T>());
container.RegisterType<HttpClient, HttpClient>(Activator.CreateInstance<T>());
container.RegisterType<SimpleRefreshTokenProvider, SimpleRefreshTokenProvider>(Activator.CreateInstance<T>());
var capNsubEntityTypes = GetEntityFrameworkEntityTypesByContext<capNsubContext>();
var linnetEntityTypes = GetEntityFrameworkEntityTypesByContext<LinetServerContext>();
RegisterEntitiesRepostiories<T>(container, capNsubEntityTypes, "capNsubContext");
RegisterEntitiesRepostiories<T>(container, linnetEntityTypes, "linnetDataContext");
}
private static void RegisterEntitiesRepostiories<T>(IUnityContainer container, IEnumerable<Type> entities, string contextName)
where T:LifetimeManager
{
var iGenericRepositoryTypes = new[] { typeof(IRepositoryAsync<>), typeof(IRepository<>) };
foreach (var iGenericRepositoryType in iGenericRepositoryTypes)
{
foreach (var entityType in entities)
{
var iSpecificRepositoryType = iGenericRepositoryType.MakeGenericType(entityType);
var genericRepositoryType = typeof(Repository<>);
var specificRepositoryType = genericRepositoryType.MakeGenericType(entityType);
container.RegisterType(iSpecificRepositoryType, Activator.CreateInstance<T>(), new InjectionFactory(c =>
{
return Activator.CreateInstance(specificRepositoryType, c.Resolve<IDataContextAsync>(contextName), c.Resolve<IUnitOfWorkAsync>());
}));
}
}
}
private static IEnumerable<Type> GetEntityFrameworkEntityTypesByContext<T>() where T : DataContext
{
var capNsubContextType = typeof(T);
var capNsubDataAssembly = Assembly.GetAssembly(capNsubContextType);
var ef6EntityType = typeof(Repository.Pattern.Ef6.Entity);
return capNsubDataAssembly.GetTypes()
.Where(t => String.Equals(t.Namespace, capNsubContextType.Namespace, StringComparison.Ordinal) &&
t.IsSubclassOf(ef6EntityType));
}
}
[System.Web.Http.Authorize(Roles = "admin")]
[System.Web.Http.RoutePrefix("api/job")]
public class JobController : BaseApiController {
[System.Web.Http.Route("Create", Name = "Create")]
[System.Web.Http.HttpPost]
public IHttpActionResult Create(JobBindingModel createJobModal)
{
//We have to use the CoreContainer since cnsDeployer scope runs outside of the request
var cnsDeployer = UnityConfig.CoreContainer.Resolve<ICNSDeployerService>();
if (!ModelState.IsValid)
{
return BadRequest();
}
try
{
//This runs in the backround after we return the response to client
cnsDeployer.Deploy(createJobModal.ItemIds);
return Ok();
}
catch(Exception err)
{
return InternalServerError(err);
}
}
}
public class CNSDeployerService : ICNSDeployerService
{
private readonly IOrderItemService orderItemService;
private readonly ICNSCapMoviesService cnsMoviesService;
private readonly ICNSTranslationsService cnsTranslationsService;
private readonly IFFMpegService ffMpegService;
private readonly IUnitOfWorkAsync unitOfWorkAsync;
private readonly IVideoService videoService;
public CNSDeployerService(IOrderItemService orderItemService,
ICNSCapMoviesService cnsCapMoviesService,
ICNSTranslationsService cnsTranslationsService,
IFFMpegService ffMpegService,
IUnitOfWorkAsync unitOfWorkAsync,
IVideoService videoService)
{
this.orderItemService = orderItemService;
this.cnsMoviesService = cnsCapMoviesService;
this.cnsTranslationsService = cnsTranslationsService;
this.ffMpegService = ffMpegService;
this.unitOfWorkAsync = unitOfWorkAsync;
this.videoService = videoService;
}
public void Deploy(IEnumerable<Guid> orderItemIds)
{
try
{
InnerDeploy(orderItemIds);
}
catch
{
unitOfWorkAsync.Dispose();
}
}
private void InnerDeploy(IEnumerable<Guid> orderItemIds)
{
var orderItems = orderItemService.Queryable()
.Where(orderItem => orderItemIds.Any(itemId => orderItem.Id == itemId)
&& orderItem.IsInProcessQueue == false
&& !orderItem.TranslationId.HasValue)
.ToList();
if (orderItems.Count == 0)
{
unitOfWorkAsync.Dispose();
throw new ArgumentNullException("No valid orders was provided");
}
foreach ( var orderItem in orderItems)
{
orderItem.IsInProcessQueue = true;
orderItemService.Update(orderItem);
}
unitOfWorkAsync.SaveChanges();
var translationId = Guid.NewGuid();
var movieId = Guid.NewGuid();
var connectedMoviePath = cnsMoviesService.GetMoviePath(movieId);
var videosUrlList = orderItems
.Select(orderItem => orderItem.VideosTable.VideoUrl)
.ToList();
//Don't await on this task since we want concat to continue after request is returned
Task.Run(async () =>
{
try
{
await ffMpegService.ConcatVideos(videosUrlList, connectedMoviePath);
VideoUtils.CreateVideoImageAndReturnPath(connectedMoviePath);
var videosTotalDuration = videoService.GetVideosTotalDuration(orderItemIds);
var durationInSeconds = Convert.ToInt32((int)(videosTotalDuration / 1000));
await cnsMoviesService.CreateMovieRecordAsync(movieId, durationInSeconds);
await cnsTranslationsService.CreateTranslationRecordAsync(movieId, translationId, language: 1);
var index = 0;
foreach (var orderItem in orderItems)
{
orderItem.TranslationId = translationId;
orderItem.TranslationIndex = index++;
orderItem.IsInProcessQueue = false;
orderItemService.Update(orderItem);
}
await unitOfWorkAsync.SaveChangesAsync();
}
catch (Exception err)
{
//TODO: Handle error
}
finally
{
//Dispose db context
unitOfWorkAsync.Dispose();
}
});
}
}

Categories