I want to call a method of controller A from controller B and get its return value.
How can I do that?
ControllerA:
[HttpGet]
public async Task<ParentModel> GetParentModel(string contractNumberPar)
{
try
{
// (...) - some code
var viewModel = new ParentModel
{
// (...) - some code
};
return viewModel;
}
catch (Exception ex)
{
throw;
}
}
ControllerB:
ParentModel viewModel = RedirectToAction(
"ControllerA",
"GetParentModel",
new
{
contractNumberPar = contractNumber
});
Error message:
Cannot implicitly convert type microsoft.aspnetcore.mvc.redirecttoaction to (....).ParentModel
You'd better create a common service,so that you can reuse it in both A and B controllers,here is a demo:
service:
public class MyService
{
public async Task<ParentModel> GetParentModel(string contractNumberPar)
{
try
{
// (...) - some code
var viewModel = new ParentModel
{
// (...) - some code
};
return viewModel;
}
catch (Exception ex)
{
throw;
}
}
}
register the service(before .net6 in Startup.cs):
services.AddScoped<MyAccountService>();
register the service(.net6,.net7 in Program.cs):
builder.Services.AddScoped<MyService>();
BController:
public class BController : Controller
{
private readonly MyService MyService;
public BController(MyService myService)
{
MyService = myService;
}
public async Task<IActionResult> IndexAsync()
{
string contractNumber="1";
ParentModel viewModel = await MyService.GetParentModel(contractNumber);
return Ok(viewModel);
}
}
Related
I'm attempting to write a unit test for my repository class for its Create method that uses DbContext and the Add method.
My idea was to get a count of the number of existing records. Call the Create method. Get the new count of records and check that its incremented by 1.
However, when running the unit test, it errors when calling the Add method with the following error:
{"Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DbContext'."}
I'm trying to understand why is this and how to overcome this?
public class MyDatabaseContext : DbContext
{
public MyDatabaseContext(DbContextOptions<MyDatabaseContext> options) : base(options)
{
}
public DbSet<Record> Records { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Record>();
}
}
public class Repository : IRepository
{
private readonly MyDatabaseContext _dbContext;
public Repository(MyDatabaseContext dbContext)
{
_dbContext = dbContext;
}
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
public IEnumerable<Record> GetAll()
{
try
{
using (_dbContext)
{
return _dbContext.Records.ToList();
}
}
catch (Exception ex)
{
return null;
}
}
}
public interface IRepository
{
Record Create(Record record);
IEnumerable<Record> GetAll();
}
Startup.cs:
services.AddDbContext<MyDatabaseContext>(opt => opt.UseInMemoryDatabase("memoryDb"));
services.AddScoped<IRepository, Repository>();
Unit test:
[TestMethod]
public async Task Create_Successfully()
{
var repository = new Repository(await GetDbContext());
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
private async Task<DbContext> GetDbContext()
{
var options = new DbContextOptionsBuilder<DbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
var context = new DbContext(options);
context.Database.EnsureCreated();
if (await context.Records.CountAsync() <= 0)
{
for (int i = 1; i <= 3; i++)
{
context.Records.Add(new Records());
await context.SaveChangesAsync();
}
}
return context;
}
You need to remove the using statements in the GetAll and Create methods:
public Record Create(Record record)
{
try
{
using (_dbContext)
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
}
catch (Exception ex)
{
return null;
}
}
To:
public Record Create(Record record)
{
try
{
var response = _dbContext.Records.Add(record); //erroring line
_dbContext.SaveChanges();
return response.Entity;
}
catch (Exception ex)
{
return null;
}
}
You don't need to worry about disposing the service as the conatiner will do that for you in production. In your tests you can do this if you want to clean things up:
[TestMethod]
public async Task Create_Successfully()
{
using (var context = await GetDbContext())
{
var repository = new Repository(context);
var existingRecords = repository.GetAll();
repository.Create(new Record());
var newRecords = repository.GetAll();
Assert.AreEqual(3, existingRecords.Count());
Assert.AreEqual(4, newRecords.Count());
}
}
I am using Asp .Net 5 to create a WebApi and I am trying to put all database operations in a separate class, the problem is I can't Use ApplicationDbContext by initiating a new object because it takes an argument in the constructor.
my context :
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
controller :
[Route("api/[controller]")]
[ApiController]
public class AttributesController : ControllerBase
{
[HttpPost]
[Route("GetAllAttributes")]
public async Task<AllAttributes> GetAllAttributes()
{
return await new Services.AttributeService().GetAll();
}
}
service :
public class AttributeService
{
private readonly ApplicationDbContext _db ;
public async Task<AllAttributes> GetAll()
{
try
{
var dbAttributes = await _db.Attributes.Where(attr=> (bool)attr.IsActive && !(bool)attr.IsDeleted && !(bool)attr.IsTrashed).ToListAsync();
if (dbAttributes.Count>0)
{
return new AllAttributes
{
Attributes = dbAttributes,
Message = new ResponseMessage
{
Message = "Success",
Code = 200
}
};
}
else
{
return new AllAttributes
{
Message = new ResponseMessage
{
Message = "Empty",
Code = 410
}
};
}
}
catch (Exception ex)
{
return new AllAttributes
{
Message = new ResponseMessage
{
Message = ex.Message,
Code = 500
}
};
}
}}
so when I call it like this I got NullReference Exception.
You will need to add AttributeService to the DI container. You can do this inside ConfigureServices method of Startup.cs:
services.AddScoped<AttributeService>();
Then you can inject the context in the constructor of AttributeService:
public class AttributeService
{
private readonly ApplicationDbContext _db ;
public AttributeService(ApplicationDbContext db)
{
_db = db;
}
...
}
I am having a simple controller which needs to be unit tested not integration tested. I just need a way to mock so that I can verify if receive method is called. We already have test against Receive(), so no need to verify what is going inside that method.
My code looks like
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
private IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
Refactor the CreateConnectionAndChannel function out into its own service
public interface IChannelProvider {
IChannel CreateConnectionAndChannel();
}
and have controller explicitly depend on that service
public class MessageController : Controller {
private readonly IChannelProvider channelProvider;
public MessageController(IChannelProvider channelProvider) {
this.channelProvider = channelProvider;
}
[HttpGet()]
public IActionResult Get() {
try {
var channel = channelProvider.CreateConnectionAndChannel();
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
} catch {
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
So now only the IChannelProvider needs to be mocked to test the controller in isolation.
I just need a way to mock so that I can verify if receive method is called.
public void Verify_Received_Called() {
//Arrange
var channel = new Mock<IChannel>();
channel
.Setup(_ => _.Receive())
.Returns("My mock value here");
var mockProvider = new Mock<IChannelProvider>();
mockProvider.Setup(_ => _.CreateConnectionAndChannel())
.Returns(channel.Object);
var controller = new MessageController(mockProvider.Object);
//Act
var result = controller.Get();
//Assert
channel.Verify(_ => _.Receive(), Times.AtLeastOnce);
}
The provider implementation could look like...
public class ChannelProvider : IChannelProvider {
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
public ChannelProvider(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail) {
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
}
public IChannel CreateConnectionAndChannel() {
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
In order to do this, you need to move your CreateConnectionAndChannel method to a separate dependency, for instance, ChannelFactory which implements IChannelFactory interface.
public interface IChannelFactory {
IChannel CreateConnectionAndChannel(QueueDetail queueDetail);
}
public class ChannelFactory : IChannelFactory {
public IChannel CreateConnectionAndChannel(QueueDetail queueDetail)
{
var factory = new Factory();
var adapter = factory.Connect(MessagingType.MQ, connectionDetail);
return adapter.BindQueue(queueDetail);
}
}
public class MessageController : Controller
{
private readonly ConnectionDetail connectionDetail;
private readonly QueueDetail queueDetail;
private readonly IChannelFactory channelFactory;
public MessageController(IOptions<ConnectionDetail> connectionDetail, IOptions<QueueDetail> queueDetail, IChannelFactory channelFactory)
{
this.connectionDetail = connectionDetail.Value;
this.queueDetail = queueDetail.Value;
this.channelFactory = channelFactory;
}
[HttpGet()]
public IActionResult Get()
{
try
{
var channel = channelFactory.CreateConnectionAndChannel(queueDetail);
var message = channel.Receive();
var hbaseKey = new HbaseKey { Key = new Guid(message) };
return Ok(hbaseKey);
}
catch
{
return StatusCode(500, "Exception occured while processing. Try again.");
}
}
}
After that you can mock your controller in test (using Moq for example):
[TestFixture]
public class TestMessageController
{
[Test]
public void TestGet()
{
var channelMock = new Mock<IChannel>(MockBehavior.Strict);
channelMock
.Setup(c => c.Receive())
.Returns(null);
var channelFactoryMock = new Mock<IChannelFactory>(MockBehavior.Strict);
channelFactory
.Setup(cf => cf.CreateConnectionAndChannel(It.IsAny<IOptions<QueueDetail>>()))
.Returns();
var controller = new MessageController(null, null, channelFactoryMock.Object);
controller.Get();
}
}
public async Task<ActionResult> Index()
{
var service = new CoreServiceFactory().GetImpersonatingService();
try
{
var data = new Impersonation()
{
ImpersonatingId = "dac733c3-01ad-447b-b0df-3a7c21fef90b",
UserId = "dac733c3-01ad-447b-b0df-3a7c21fef90b"
};
var imp = await service.Add(data);
}catch(Exception ex) { throw ex; }
return View();
}
Above is one of my controllers action method. And this works fine when the insertion is successful. This should fail if the data already exists in database(unique constraints). So when i intentionally try to make it fail(i manually add the same record in the db and then try to add it again via this action method) the action method goes into a loop or something, the exception is never thrown , chrome keeps me showing me the loading icon , looks like it went into some deadlock state. Can someone please help me understand why it goes into deadlock state when exception is thrown and how can i handle it?
Below are the reference methods
service.Add(data)
public async Task<Impersonation> Add(Impersonation t)
{
if (ValidateData(t))
{
using (var uow = GetUnitOfWork())
{
var r = GetRepository(uow);
var item = r.Add(t);
try
{
var ret = await uow.Save();
if (ret > 0)
{
return item;
}
else
{
return null;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
else
{
throw new ValidationException(null, "error");
}
}
uow.Save()
public class BaseUnitOfWork : IUnitOfWork
{
public DbContext _Context { get; private set; }
public BaseUnitOfWork(DbContext context)
{
this._Context = context;
}
public async Task<int> Save()
{
try
{
var ret = await this._Context.SaveChangesAsync();
return ret;
}catch(Exception ex)
{
throw ex;
}
}
}
Here is my suggestion: in uow.Save, log the error in the catch block and return zero (do not throw any exceptions).
public class BaseUnitOfWork : IUnitOfWork
{
public DbContext _Context { get; private set; }
public BaseUnitOfWork(DbContext context)
{
this._Context = context;
}
public async Task<int> Save()
{
try
{
var ret = await this._Context.SaveChangesAsync();
return ret;
}catch(Exception ex)
{
// log the error here
return 0;
}
}
}
I'm not sure if returning the null in the Add service is a good idea or not, you might need to handle that differently too.
I have lots of controllers methods in WebAPI similar to the following:
public IHttpActionResult Delete(int id)
{
var command = new DeleteItemCommand() { Id = id };
try
{
_deleteCommandHandler.Handle(command);
}
catch (CommandHandlerResourceNotFoundException)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
catch(CommandHandlerException)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
// More catches etc...
return Ok();
}
The command handlers (in this instance _deleteCommandHandler) is injected earlier in the execution and the commands may be built in the method or using WebApi's automatic method.
What I would like to do is to encapsulate the try/catch error handling in a private method and end up with a controller similar to:
public IHttpActionResult Delete(int id)
{
var command = new DeleteItemCommand() { Id = id };
return ExecuteCommand(x => _deleteCommandHandler.Handle(command));
}
I'm not sure what the signature of the private ExecuteCommand method should be though.
I think you can Invoke your action in a method like this:
public IHttpActionResult Delete(int id)
{
return ExecuteCommand(() => {
var command = new DeleteItemCommand() { Id = id };
_deleteCommandHandler.Handle(command);
});
}
private IHttpActionResult ExecuteCommand(Action action)
{
try
{
action.Invoke();
//or: action();
}
catch (CommandHandlerResourceNotFoundException)
{
return HttpResponseException(HttpStatusCode.NotFound);
}
catch (CommandHandlerException)
{
return HttpResponseException(HttpStatusCode.InternalServerError);
}
return Ok();
}
A good reference for HttpResponseException.
I would create a custom error handler filter, and handle all possible errors there in a centralized form. That way you can just throw whatever exception from the action methods, and then they will be caught at the filter where you can handle them and change the response accordingly.
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
The example is taken from this article where you can find the concept in more detail.
Here's a solution similar to shA.t's answer, but the exceptions are mapped in a dictionary and the try/catch logic is in an extension method:
public class TestController:ApiController
{
public IHttpActionResult Delete(int id)
{
return ExecuteCommand(() => {
var command = new DeleteItemCommand() { Id = id };
_deleteCommandHandler.Handle(command);
});
}
private IHttpActionResult ExecuteCommand(Action action)
{
return action.SafeInvoke();
}
}
public static class ActionExtensions
{
private static readonly Dictionary<Type, HttpStatusCode> _exceptionToStatusCodeLookup = new Dictionary<Type, HttpStatusCode>
{
{typeof(CommandHandlerResourceNotFoundException), HttpStatusCode.NotFound },
{typeof(CommandHandlerException), HttpStatusCode.InternalServerError },
};
public static IHttpActionResult SafeInvoke(this Action action)
{
try
{
action();
}
catch (Exception ex)
{
var statusCode = _exceptionToStatusCodeLookup.ContainsKey(ex.GetType()) ? _exceptionToStatusCodeLookup[ex.GetType()] : HttpStatusCode.InternalServerError;
return new HttpResponseException(statusCode);
}
return new OkResult();
}
}