I have several classes that have inherited from one interface. I want the desired service to be loaded and used in the controller depending on the conditions.
Controller
public class GatewayController
{
private readonly IAction action;
public GatewayController(IAction action)
{
this.action = action;
}
[HttpPost("gateway")]
public async Task<ApiResult> Gateway(GatewayRequest gatewayRequest, CancellationToken cancellationToken)
{
try
{
var comId = gatewayRequest.CommandId;
switch (comId)
{
case (int) GuaranteeItemStatus.End:
return await action.Perform(gatewayRequest, cancellationToken); //must be use EndActionService
case (int) GuaranteeItemStatus.SendProduct:
return await action.Perform(gatewayRequest, cancellationToken); //must be use SendProductActionService
default:
return null;
}
}
catch (Exception ex)
{
return ApiResult.ToErrorModel("error");
}
}
}
Parent interface:
public interface IAction
{
Task<ApiResult> Perform(GatewayRequest gatewayRequest, CancellationToken cancellationToken);
}
Services:
1-SendProductActionService:
public class SendProductActionService:IAction
{
public async Task<ApiResult> Perform(GatewayRequest gatewayRequest, CancellationToken cancellationToken)
{
return ApiResult.ToSuccessModel("SendProduct");
}
}
2-EndActionService:
public class EndActionService:IAction
{
public async Task<ApiResult> Perform(GatewayRequest gatewayRequest, CancellationToken cancellationToken)
{
return ApiResult.ToSuccessModel("EndAction");
}
}
It's possible to register, and inject, an IEnumerable that contains all your IActions.
First, to identify which IAction reacts to which command, you can add a CommandId property:
public interface IAction
{
int CommandId { get; }
}
public class SendProductActionService : IAction
{
public int CommandId => (int)GuaranteeItemStatus.SendProduct;
}
public class EndActionService : IAction
{
public int CommandId => (int)GuaranteeItemStatus.End;
}
In your Startup.cs, you register all your actions:
services.AddScoped<IAction, SendProductActionService>();
services.AddScoped<IAction, EndActionService>();
Then in your controller, you inject all the IAction, and select the appropriate one when needed:
public class GatewayController
{
// map the command ID to the proper IAction
private readonly Dictionary<int, IAction> actions;
// inject all the services registered that implement IAction
public GatewayController(IEnumerable<IAction> actions)
{
this.actions = actions.ToDictionary(_ => _.CommandId);
}
[HttpPost("gateway")]
public async Task<ApiResult> Gateway(GatewayRequest gatewayRequest, CancellationToken cancellationToken)
{
// find the appropriate IAction
if (!actions.TryGetValue((int)gatewayRequest.CommandId, out var action)
return BadRequest();
return await action.Perform(gatewayRequest, cancellationToken);
}
}
In your startup.cs:
services.AddScoped<SendProductActionService>();
services.AddScoped<EndActionService>();
services.AddScoped<Func<GuaranteeItemStatus, IAction>>(serviceProvider => status =>
{
switch (status)
{
case GuaranteeItemStatus.SendProduct:
return serviceProvider.GetService<SendProductActionService>();
case GuaranteeItemStatus.End:
return serviceProvider.GetService<EndActionService>();
default:
throw new InvalidOperationException();
}
});
Your controller should be similar to this:
public class GatewayController
{
private readonly Func<GuaranteeItemStatus, IAction> actionProvider;
public GatewayController(Func<GuaranteeItemStatus, IAction> actionProvider)
{
this.actionProvider = actionProvider;
}
[HttpPost("gateway")]
public async Task<ApiResult> Gateway(GatewayRequest gatewayRequest, CancellationToken cancellationToken)
{
try
{
return await actionProvider((GuaranteeItemStatus)gatewayRequest.CommandId)
.Perform(gatewayRequest, cancellationToken);
}
catch (Exception ex)
{
return ApiResult.ToErrorModel("error");
}
}
}
Related
In the IEndpointFilter would like to get the route used.
app.MapGet("/get/{productId:guid}", ......
public class MyFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
//Getting it here /get/{productId:guid}
return await next.Invoke(context);
}
}
You can use HttpContext.GetEndpoint() method and then check if endpoint is RouteEndpoint:
class MyFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var endpoint = context.HttpContext.GetEndpoint();
if (endpoint is RouteEndpoint re1)
{
Console.WriteLine(re1.RoutePattern.RawText);
}
return await next(context);
}
}
Or do manually what GetEndpoint does itself:
var endpointFeature = context.HttpContext.Features.Get<IEndpointFeature>();
if (endpointFeature?.Endpoint is RouteEndpoint re)
{
Console.WriteLine(re.RoutePattern.RawText);
}
I create the BackgroundService:
public class CustomService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
//...
}
}
and I added to the project:
public class Startup
{
//...
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<CustomService>();
//...
}
//...
}
How can I find the CustomService from another class?
How to start it again?
Create an interface just for the call to StartAsync:
public interface ICustomServiceStarter
{
Task StartAsync(CancellationToken token = default);
}
public class CustomService : BackgroundService, ICustomServiceStarter
{
//...
Task ICustomServiceStarter.StartAsync(CancellationToken token = default) => base.StartAsync(token);
//...
}
Register the interface as a singleton:
public class Startup
{
//...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<ICustomServiceStarter, CustomService>();
}
//...
}
and inject ICustomServiceStarter when needed:
public class MyServiceControllerr : Controller
{
ICustomServiceStarter _starter;
public MyServiceController(ICustomServiceStarter starter)
{
_starter = starter;
}
[HttpPost]
public async Task<IActionResult> Start()
{
await _starter.StartAsync();
return Ok();
}
}
When it comes to controller's action, using "await BackgroundService.StartAsync" is the wrong way for long-running tasks.
For instance, the main ussue could be request's timeout depended on proxy settings.
Here is an example how to make your BackgroundService restartable.
BackgroundService implementation:
public class MyBackgroundService: BackgroundService
{
private volatile bool _isFinished = false;
private SemaphoreSlim _semaphore = new SemaphoreSlim(0,1);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_isFinished = false;
// DoSomeWork
_isFinished = true;
await WaitToRestartTask(stoppingToken);
}
private async Task WaitToRestartTask(CancellationToken stoppingToken)
{
// wait until _semaphore.Release()
await _semaphore.WaitAsync(stoppingToken);
// run again
await base.StartAsync(stoppingToken);
}
public void RestartTask()
{
if (!_isFinished)
throw new InvalidOperationException("Background service is still working");
// enter from _semaphore.WaitAsync
_semaphore.Release();
}
}
Controller's action (for instance):
public async Task<IActionResult> Restart()
{
var myBackgroundService= _serviceProvider.GetServices<IHostedService>()
.OfType<MyBackgroundService>()
.First();
try
{
myBackgroundService.RestartTask();
return Ok($"MyBackgroundService was restarted");
}
catch (InvalidOperationException exception)
{
return BadRequest(exception.Message);
}
}
I have a scenario, where I would like to Enable/Disable a PurchaseOrderItem
So I have written two separate Command and commandHandlers. Ideally I have to create base abstract class and need to move my logic to one place.
How I can achieve this?
You can do it like this:
Commands:
enum CommandType {
Disable,
Enable
}
class EnableCommand : IRequest {
}
class DisableCommand : IRequest {
}
class CommonCommand : IRequest {
public CommandType Type {get; set;}
}
Handlers:
class EnableCommandHandler : IRequestHandler<EnableCommand> {
private readonly IMediator _mediator;
public EnableCommandHandler(IMediator mediator) {
_mediator = mediator;
}
public async Task Handle(EnableCommand command, CancellationToken cancellationToken) {
await _mediator.Send(new CommonCommand {Type = CommandType.Enable});
}
}
class DisableCommandHandler : IRequestHandler<DisableCommand> {
private readonly IMediator _mediator;
public EnableCommandHandler(IMediator mediator) {
_mediator = mediator;
}
public async Task Handle(DisableCommand command, CancellationToken cancellationToken) {
await _mediator.Send(new CommonCommand {Type = CommandType.Disable});
}
}
class CommonCommandHandler : IRequestHandler<CommonCommand> {
private readonly IMediator _mediator;
public EnableCommandHandler(IMediator mediator) {
_mediator = mediator;
}
public async Task Handle(CommonCommand command, CancellationToken cancellationToken) {
... // some common logic
switch(command.Type) {
... // some specific logic
}
}
}
And if you don't want to create 3 handlers you can create one for all this commands:
class CommonCommandHandler : IRequestHandler<EnableCommand> , IRequestHandler<DisableCommand>, IRequestHandler<CommonCommand> {
public async Task Handle(EnableCommand command, CancellationToken cancellationToken) {
await Handle(new CommonCommand {Type = CommandType.Enable});
}
public async Task Handle(DisableCommand command, CancellationToken cancellationToken) {
await Handle(new CommonCommand {Type = CommandType.Disable});
}
public async Task Handle(CommonCommand command, CancellationToken cancellationToken) {
... // some common logic
switch(command.Type) {
... // some specific logic
}
}
}
For last options you actually don't need CommonCommand as IRequest. So idea is to re-use handlers.
You can solve this problem with one command/handler.
public enum PurchaseOrderStatus
{
Enabled,
Disabled
}
public class TogglePurchaseOrderItemStatusCommand : IRequest
{
public PurchaseOrderStatus Status { get; }
public TogglePurchaseOrderItemStatusCommand(PurchaseOrderStatus status)
{
Status = status;
}
}
public class TogglePurchaseOrderItemCommandHandler : IRequestHandler<TogglePurchaseOrderItemCommand>
{
public Task<Unit> Handle(TogglePurchaseOrderItemCommand request, CancellationToken cancellationToken)
{
//Execute Logic Here Based on request.Status
}
}
I am receiving a Microsoft.Azure.ServiceBus.ServiceBusException (message below with sensitive information removed) periodically in my queue receiver. The SAS key has send/listen access and the error seems inconsequential as processing continues as normal. However, the message is creating a signal to noise problem in my dashboards (receiving 10-70 errors per day). Any ideas on why this is happening? The listener is running in an Azure App Service, but I don't think that matters. I have adjusted my retry logic to use a RetryExponential with a 1 second to 1 minute backoff with 5 retries.
Request for guidance from SDK developers
Packages
Net Core 3.1
Microsoft.Azure.ServiceBus, Version=4.1.3.0, Culture=neutral, PublicKeyToken=7e34167dcc6d6d8c
Error Message
The link 'xxx;xxx:xxx:xxx:source(address:xxx):xxx' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim(s) are required to perform this operation. Resource: 'sb://xxx.servicebus.windows.net/xxx'.. TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04 The link 'xxx;xxx:xxx:xxx:source(address:xxx):xxx' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim(s) are required to perform this operation. Resource: 'sb://xxx.servicebus.windows.net/xxx'.. TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04
Source
internal delegate TClient ClientFactory<out TClient>(string connectionString, string entityPath,
RetryPolicy retryPolicy);
internal delegate Task OnMessageCallback<in TMessage>(TMessage message,
CancellationToken cancellationToken = default) where TMessage : ICorrelative;
internal sealed class ReceiverClientWrapper<TMessage> : IReceiverClientWrapper<TMessage>
where TMessage : ICorrelative
{
// ReSharper disable once StaticMemberInGenericType
private static readonly Regex TransientConnectionErrorRegex =
new Regex(
#"(The link '([a-f0-9-]+);([0-9]*:)*source\(address:([a-z0-9_]+)\):([a-z0-9_]+)' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim\(s\) are required to perform this operation. Resource: 'sb:\/\/([a-z0-9-_.\/]+)'.. TrackingId:([a-z0-9_]+), SystemTracker:([a-z0-9]+), Timestamp:([0-9]{4}(-[0-9]{2}){2}T([0-9]{2}:){2}[0-9]{2}) )+",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
private readonly IReceiverClient _receiverClient;
private readonly IMessageConverter<TMessage> _messageConverter;
private readonly ILogger _logger;
private readonly int _maximumConcurrency;
public ReceiverClientWrapper(IReceiverClient receiverClient, IMessageConverter<TMessage> messageConverter,
ILogger logger, int maximumConcurrency)
{
_receiverClient = receiverClient;
_messageConverter = messageConverter;
_logger = logger;
_maximumConcurrency = maximumConcurrency;
}
public Task SubscribeAsync(OnMessageCallback<TMessage> onMessageCallback,
OnFailureCallback onFailureCallback, CancellationToken cancellationToken = default)
{
var messageHandlerOptions = CreateMessageHandlerOptions(onFailureCallback, cancellationToken);
async Task Handler(Message message, CancellationToken token)
{
var convertedMessage = _messageConverter.Convert(message);
await onMessageCallback(convertedMessage, cancellationToken);
await _receiverClient.CompleteAsync(message.SystemProperties.LockToken);
}
_receiverClient.RegisterMessageHandler(Handler, messageHandlerOptions);
return Task.CompletedTask;
}
private MessageHandlerOptions CreateMessageHandlerOptions(OnFailureCallback onFailureCallback,
CancellationToken cancellationToken)
{
async Task HandleExceptionAsync(ExceptionReceivedEventArgs arguments)
{
var exception = arguments.Exception;
if (TransientConnectionErrorRegex.IsMatch(exception.Message))
{
_logger.LogWarning(exception, #"Transient connectivity error occurred");
return;
}
await onFailureCallback(exception, cancellationToken);
}
return new MessageHandlerOptions(HandleExceptionAsync)
{
AutoComplete = false,
MaxConcurrentCalls = _maximumConcurrency
};
}
public async ValueTask DisposeAsync()
{
await _receiverClient.CloseAsync();
}
}
internal sealed class SenderClientWrapper<TMessage> : ISenderClientWrapper<TMessage> where TMessage : ICorrelative
{
private readonly ISenderClient _senderClient;
private readonly IMessageConverter<TMessage> _messageConverter;
public SenderClientWrapper(ISenderClient senderClient, IMessageConverter<TMessage> messageConverter)
{
_senderClient = senderClient;
_messageConverter = messageConverter;
}
public Task SendAsync(TMessage message, CancellationToken cancellationToken = default)
{
var internalMessage = _messageConverter.Convert(message);
return _senderClient.SendAsync(internalMessage);
}
public Task SendAsync(IEnumerable<TMessage> messages, CancellationToken cancellationToken = default)
{
var internalMessages = messages
.Select(_messageConverter.Convert)
.ToImmutableArray();
return _senderClient.SendAsync(internalMessages);
}
public async ValueTask DisposeAsync()
{
await _senderClient.CloseAsync();
}
}
internal abstract class AbstractClientWrapperFactory
{
private const int MaximumRetryCount = 5;
private static readonly TimeSpan MinimumRetryBackOff = TimeSpan.FromSeconds(1);
private static readonly TimeSpan MaximumRetryBackOff = TimeSpan.FromMinutes(1);
protected AbstractClientWrapperFactory(IOptions<MessageBusConfiguration> options)
{
Options = options;
}
protected IOptions<MessageBusConfiguration> Options { get; }
protected static string GetEntityPath<TMessage>() where TMessage : class
{
var messageAttribute = typeof(TMessage).GetCustomAttribute<AbstractMessageAttribute>();
if (messageAttribute == null)
{
throw new ArgumentException($#"Message requires {nameof(AbstractMessageAttribute)}");
}
return messageAttribute.EntityName;
}
protected TClient CreateClientEntity<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class
{
var entityPath = GetEntityPath<TMessage>();
var retryPolicy = CreateRetryPolicy();
return clientFactory(Options.Value.ConnectionString, entityPath, retryPolicy);
}
protected static IQueueClient QueueClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new QueueClient(connectionString, entityPath, retryPolicy: retryPolicy);
}
private static RetryPolicy CreateRetryPolicy()
{
return new RetryExponential(MinimumRetryBackOff, MaximumRetryBackOff, MaximumRetryCount);
}
}
internal sealed class SenderClientWrapperFactory : AbstractClientWrapperFactory, ISenderClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
public SenderClientWrapperFactory(IMessageConverterFactory messageConverterFactory,
IOptions<MessageBusConfiguration> options) : base(options)
{
_messageConverterFactory = messageConverterFactory;
}
public ISenderClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateWrapper<TEvent, ITopicClient>(TopicClientFactory);
}
public ISenderClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private ISenderClientWrapper<TMessage> CreateWrapper<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : ISenderClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new SenderClientWrapper<TMessage>(clientEntity, messageConverter);
}
private static ITopicClient TopicClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new TopicClient(connectionString, entityPath, retryPolicy);
}
}
internal sealed class ReceiverClientWrapperFactory : AbstractClientWrapperFactory, IReceiverClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
private readonly ILogger<ReceiverClientWrapperFactory> _logger;
public ReceiverClientWrapperFactory(IOptions<MessageBusConfiguration> options,
IMessageConverterFactory messageConverterFactory,
ILogger<ReceiverClientWrapperFactory> logger) : base(options)
{
_messageConverterFactory = messageConverterFactory;
_logger = logger;
}
public IReceiverClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateReceiverClientWrapper<TEvent, ISubscriptionClient>(SubscriptionClientFactory);
}
public IReceiverClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateReceiverClientWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private IReceiverClientWrapper<TMessage> CreateReceiverClientWrapper<TMessage, TClient>(
ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : IReceiverClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new ReceiverClientWrapper<TMessage>(clientEntity, messageConverter, _logger,
Options.Value.MaximumConcurrency);
}
private ISubscriptionClient SubscriptionClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new SubscriptionClient(connectionString, entityPath, Options.Value.SubscriberName,
retryPolicy: retryPolicy);
}
}
internal sealed class RequestService<TRequest> : IRequestService<TRequest> where TRequest : class, IRequest
{
private readonly Lazy<ISenderClientWrapper<TRequest>> _senderClient;
private readonly Lazy<IReceiverClientWrapper<TRequest>> _receiverClient;
public RequestService(ISenderClientWrapperFactory senderClientWrapperFactory,
IReceiverClientWrapperFactory receiverClientWrapperFactory)
{
_senderClient =
new Lazy<ISenderClientWrapper<TRequest>>(senderClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
_receiverClient
= new Lazy<IReceiverClientWrapper<TRequest>>(receiverClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
}
public Task PublishRequestAsync(TRequest requestMessage, CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessage, cancellationToken);
}
public Task PublishRequestAsync(IEnumerable<TRequest> requestMessages,
CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessages, cancellationToken);
}
public Task SubscribeAsync(OnRequestCallback<TRequest> onRequestCallback, OnFailureCallback onFailureCallback,
CancellationToken cancellationToken = default)
{
return _receiverClient
.Value
.SubscribeAsync((message, token) => onRequestCallback(message, cancellationToken), onFailureCallback,
cancellationToken);
}
public async ValueTask DisposeAsync()
{
if (_senderClient.IsValueCreated)
{
await _senderClient.Value.DisposeAsync();
}
if (_receiverClient.IsValueCreated)
{
await _receiverClient.Value.DisposeAsync();
}
}
public Task ThrowIfNotReadyAsync(CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(ImmutableArray<TRequest>.Empty, cancellationToken);
}
}
This was never resolved using the original Nuget package, but the new Azure.Messaging.ServiceBus package does not appear to have the issue. I have opted to move to that.
I have partial interface
public partial interface ISponsoredService { GetX(), AddX() }
public partial interface ISponsoredService { GetY(), AddY() }
And partial implementations
public partial class SponsoredService : ISponsoredService {
public SponsoredService(
ISponsoredBrandsRepository sponsoredBrandRepository,
ISponsoredDiscountsRepository sponsoredDiscountsRepository) { }
}
public partial class SponsoredService { GetX(); AddX(); }
public partial class SponsoredService { GetY(); AddY(); }
And the problem is with these repositories because they use the same context injected in request scope like services and repositories.
public class SponsoredDiscountsRepository : BaseSponsoredRepository, ISponsoredDiscountsRepository
{
public SponsoredDiscountsRepository(
Context sponsoredContext) : base(sponsoredContext)
{
}
}
public class SponsoredBrandsRepository : BaseSponsoredRepository, ISponsoredBrandsRepository
{
public SponsoredBrandsRepository (
Context sponsoredContext) : base(sponsoredContext)
{
}
}
Ninject configuration:
kernel.Bind<Context>().ToSelf();
kernel.Bind<ISponsoredService>().To<SponsoredService>().InRequestScope();
kernel.Bind<ISponsoredBrandsRepository>().To<SponsoredBrandsRepository>().InRequestScope();
kernel.Bind<ISponsoredDiscountsRepository>().To<SponsoredDiscountsRepository>().InRequestScope();
And where is a problem?
When I execute AddX() and then GetX() in the same HTTP request the second operation(GetX()) hangs on the connection to the base. It lasts forever and never ends.
But if I do AddX() and then in other HTTP request GetX() it work.
Combination AddY() and in the same request GetX() do not work as well.
What is going on here? Context are not disposed because it's created in request scope. In the same request scope is created SponsoredService which contains repository1 and repository2 which contains created context.
Edit: Add implementation of repositories
These two repositories use one generic wchich contains:
protected readonly Context SponsoredContext;
protected readonly DbSet<TEntity> DbSet;
and all operations:
public virtual async Task<IEnumerable<TEntity>> GetAsync()
{
return await this.DbSet.ToListAsync();
}
GetX() { sponsoredBrandsRepository.GetAsync(); }
AddX() {
sponsoredBrandsRepository.GetAsync();
some operations
sponsoredBrandsRepository.AddAsync();
}
Edit add full code
WebAPI:
[RoutePrefix("api/sponsored")]
public partial class SponsoredController : BaseApiController
{
private readonly ISponsoredService _sponsoredService;
public SponsoredController(
ISponsoredService sponsoredService,
IBrandService brandService,
ICampaignsService discountService)
{
_sponsoredService = sponsoredService;
_discountService = discountService;
_brandService = brandService;
}
}
public partial class SponsoredController
{
private readonly IBrandService _brandService;
[Route("brands"), HttpGet]
public async Task<IHttpActionResult> GetBrands()
{
try
{
var viewModels = await GetBrandViewModels();
return Ok(viewModels);
}
catch (Exception e)
{
base.Log(e);
return InternalServerError();
}
}
[Route("brands"), HttpPost, ValidateModelStateFilter]
public async Task<IHttpActionResult> Post([FromBody] IEnumerable<SponsoredBrandAddOrUpdateViewModel> viewModels)
{
try
{
await this._sponsoredService.AddOrUpdate(viewModels.Select(vm => (SponsoredBrand)vm));
return Created("api/sponsored/brands", GetBrandViewModels());
}
catch (Exception e)
{
base.Log(e, viewModels);
return InternalServerError();
}
}
private async Task<List<SponsoredBrandListViewModel>> GetBrandViewModels()
{
var dbSponsoredBrands = await this._sponsoredService.GetSponsoredBrandsAsync();
var viewModels =
dbSponsoredBrands.Select(sponsoredBrand =>
{
var viewModel = (SponsoredBrandListViewModel)sponsoredBrand;
viewModel.Name = (this._brandService.GetBrandByBrandIdAsync(viewModel.BrandId).Result).Entity.Name;
return viewModel;
}).ToList();
return viewModels;
}
}
public partial class SponsoredController
{
private readonly ICampaignsService _discountService;
[Route("discounts"), HttpGet]
public async Task<IHttpActionResult> GetDiscounts()
{
try
{
var viewModels = await GetDiscountsViewModels();
return Ok(viewModels);
}
catch (Exception e)
{
base.Log(e);
return InternalServerError();
}
}
[Route("discounts"), HttpPost, ValidateModelStateFilter]
public async Task<IHttpActionResult> Post([FromBody] IEnumerable<SponsoredDiscountAddOrUpdateViewModel> viewModels)
{
try
{
await this._sponsoredService.AddOrUpdate(viewModels.Select(vm => (SponsoredDiscount)vm));
return Created("api/sponsored/discounts", GetDiscountsViewModels());
}
catch (Exception e)
{
base.Log(e, viewModels);
return InternalServerError();
}
}
private async Task<List<SponsoredDiscountListViewModel>> GetDiscountsViewModels()
{
var dbSponsoredBrands = await this._sponsoredService.GetSponsoredDiscountsAsync();
var viewModels =
dbSponsoredBrands.Select(sponsoredBrand =>
{
var viewModel = (SponsoredDiscountListViewModel)sponsoredBrand;
viewModel.Name = (this._discountService.GetCampaignByCampaignIdAsync(viewModel.DiscountId).Result)?.Entity?.Discount?.Name;
return viewModel;
}).ToList();
return viewModels;
}
}
Service:
public partial interface ISponsoredService
{
Task<IEnumerable<SponsoredDiscount>> GetSponsoredDiscountsAsync();
Task<IEnumerable<SponsoredDiscount>> AddOrUpdate(IEnumerable<SponsoredDiscount> sponsoredDiscounts);
}
public partial interface ISponsoredService
{
Task<IEnumerable<SponsoredDiscount>> GetSponsoredDiscountsAsync();
Task<IEnumerable<SponsoredDiscount>> AddOrUpdate(IEnumerable<SponsoredDiscount> sponsoredDiscounts);
}
public partial class SponsoredService : ISponsoredService
{
public SponsoredService(
ISponsoredBrandsRepository sponsoredBrandRepository,
ISponsoredDiscountsRepository sponsoredDiscountsRepository,
IBrandService brandService,
ICampaignsService discountsService)
{
_sponsoredBrandRepository = sponsoredBrandRepository;
_brandService = brandService;
_discountsService = discountsService;
_sponsoredDiscountsRepository = sponsoredDiscountsRepository;
}
}
public partial class SponsoredService
{
private readonly ISponsoredBrandsRepository _sponsoredBrandRepository;
private readonly IBrandService _brandService;
public async Task<IEnumerable<SponsoredBrand>> GetSponsoredBrandsAsync() => await this._sponsoredBrandRepository.GetAsync();
public async Task<IEnumerable<SponsoredBrand>> AddOrUpdate(IEnumerable<SponsoredBrand> sponsoredBrands)
{
// remove
var dbSponsored = await this.GetSponsoredBrandsAsync();
foreach (var dbS in dbSponsored)
{
if (!sponsoredBrands.Any(s => s.RelatedEntityId == dbS.RelatedEntityId))
{
await this.DeleteSponsoredBrand(dbS.Id);
}
}
// new
foreach (var newS in sponsoredBrands)
{
var brand = (await this._brandService.GetBrandByBrandIdAsync(newS.RelatedEntityId)).Entity;
brand.BrandRules = new List<BrandRule>
{
new BrandRule
{
IsHighlighted = true, Order = newS.Order, ValidTo = newS.To, ValidFrom = newS.From
}
}.ToList();
await this._brandService.UpdateAsync(brand);
}
this._sponsoredBrandRepository.Clear();
await this._sponsoredBrandRepository.Add(sponsoredBrands);
return null;
}
}
public partial class SponsoredService
{
private readonly ISponsoredDiscountsRepository _sponsoredDiscountsRepository;
private readonly ICampaignsService _discountsService;
public async Task<IEnumerable<SponsoredDiscount>> GetSponsoredDiscountsAsync()
=> await this._sponsoredDiscountsRepository.GetAsync();
public async Task<IEnumerable<SponsoredDiscount>> AddOrUpdate(IEnumerable<SponsoredDiscount> sponsoredDiscounts)
{
// remove
var dbSponsored = await this.GetSponsoredDiscountsAsync();
foreach (var dbS in dbSponsored)
if (!sponsoredDiscounts.Any(s => s.RelatedEntityId == dbS.RelatedEntityId))
await this.DeleteSponsoredDiscount(dbS.Id);
// new
foreach (var newS in sponsoredDiscounts)
if (!await this._discountsService.X(newS.RelatedEntityId, newS))
return null;
this._sponsoredDiscountsRepository.Clear();
await this._sponsoredDiscountsRepository.Add(sponsoredDiscounts);
return null;
}
}
Repositories:
public interface ISponsoredRepository<TEntity> : IBaseRepository<TEntity, int>
where TEntity : Sponsored.Sponsored
{
void Clear();
Task Add(IEnumerable<TEntity> entities);
}
public interface ISponsoredBrandsRepository : ISponsoredRepository<SponsoredBrand> { }
public interface ISponsoredDiscountsRepository : ISponsoredRepository<SponsoredDiscount> { }
public abstract class SponsoredRepository<TEntity> : ISponsoredRepository<TEntity>
where TEntity : Domain.Sponsored.Sponsored
{
protected readonly Context SponsoredContext;
protected readonly DbSet<TEntity> DbSet;
protected SponsoredRepository(Context sponsoredContext)
{
SponsoredContext = sponsoredContext;
DbSet = this.SponsoredContext.Set<TEntity>();
}
public virtual async Task<IEnumerable<TEntity>> GetAsync()
{
return await this.DbSet.ToListAsync();
}
public virtual void Clear()
{
this.DbSet.RemoveRange(this.DbSet);
this.SponsoredContext.SaveChanges();
}
public virtual async Task Add(IEnumerable<TEntity> entities)
{
this.DbSet.AddRange(entities);
await this.SponsoredContext.SaveChangesAsync();
}
}
public class SponsoredBrandsRepository : SponsoredRepository<SponsoredBrand>, ISponsoredBrandsRepository
{
public SponsoredBrandsRepository(
Context sponsoredContext) : base(sponsoredContext)
{
}
}
public class SponsoredDiscountsRepository : SponsoredRepository<SponsoredDiscount>, ISponsoredDiscountsRepository
{
public SponsoredDiscountsRepository(
Context sponsoredContext) : base(sponsoredContext)
{
}
}
ContexT:
public class Context
{
public virtual DbSet<SponsoredBrand> Brands { get; set; }
public virtual DbSet<SponsoredDiscount> Discounts { get; set; }
public Context()
{
}
}
IoC configuration(Ninject):
kernel.Bind<Context>().ToSelf();
kernel.Bind<ISponsoredService>().To<SponsoredService>().InRequestScope();
kernel.Bind<ISponsoredBrandsRepository>().To<SponsoredBrandsRepository>().InRequestScope();
kernel.Bind<ISponsoredDiscountsRepository>().To<SponsoredDiscountsRepository>().InRequestScope();
You're not binding Context InRequestScope, so by default it gets created as Transient scope, and therefore is probably not being disposed/closed.
more on Scopes:
https://github.com/ninject/ninject/wiki/Object-Scopes
Managing a single DbContext in a request is hard. You may have lots of different classes requesting it as a dependency and using it different ways. I ended up with a complex scoping mechanism to manage the DbContexts. However, it may be easier/better to inject a DbContext factory and use using to control the lifetime of the DbContext (unit of work pattern), or just new it up in your repository (and dispose it).
if you Google "entity framework dbcontext request", you will find many discussions and opinions about the matter. here is a good overview/intro: http://mehdi.me/ambient-dbcontext-in-ef6/