NullReferenceException from JsonSerializer in POST method - c#

I'm trying to write a simple REST API in #C, but I ran into a problem with a rather unhelpful error message very early when writing a POST handler. I'm using .NET Core 3.0 and the project was created with the ASP.NET Core API template in Visual Studio.
This is a simplified version of my code that still triggers the issue (the classes are in separate Files in the full project):
[Route("api/blub")]
[ApiController]
public class BlubController : ControllerBase
{
[HttpGet]
public IEnumerable<Blub> Get()
{
return new Blub[0];
}
[HttpPost]
public ActionResult<Blub> PostBlub([FromBody] string[] paths)
{
return new Blub(paths);
}
}
public class Blub
{
public Blub(string[] paths)
{
this.Paths = paths;
StartedAt = DateTime.Now;
}
public string[] Paths { get; }
public DateTime StartedAt { get; }
}
The payload of my POST request is something like the following:
{ "paths": [ "abc", "def" ] }
I'm getting the following Exception when performing a POST request to my api/blub route:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Text.Json.JsonSerializer.HandleStartObject(JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
at System.Text.Json.JsonSerializer.ReadCore(JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan`1 buffer, JsonSerializerOptions options, ReadStack& readStack)
at System.Text.Json.JsonSerializer.ReadAsync[TValue](Stream utf8Json, Type returnType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(ModelBindingContext bindingContext)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
It doesn't matter whether my POST request contains the correct body or not, any POST requests seems to trigger this exception. The GET request works without issues. I tried to log to the console and set a breakpoint in my function, but as far as I can tell the exception is thrown even before my function is called.
Why is the JsonFormatter throwing an exception here, and how can I fix this?

You have an issue in type mapping, either change method signature to:
[HttpPost]
public ActionResult<Blub> PostBlub([FromBody] MyRequest request)
{
return new Blub(request.Paths);
}
public class MyRequest
{
public string[] Paths { get; set; }
}
Or change the payload to:
[ "abc", "def" ]

This simply means that some member/variable of some reference type
is dereferenced by using and of its instance non-static members,
which requires this member/variable to be non-null, but in fact it
appears to be null. Simply if it execute it under debugger, it will
stop the execution where the exception is thrown. Put a break point on
that line, restart the application and come to this point again.
Evaluate all references involved in next line and see which one is
null while it needs to be not null. After you figure this out, fix the
code: either make sure the member/variable is properly initialized to
a non-null reference, or check it for null and, in case of null,
do something else.
Use the below code snippet:
[HttpPost]
public ActionResult<Blub> PostBlub([FromBody] APIRequest request)
{
return new Blub(request.Paths);
}
public class APIRequest
{
public string[] Paths { get; set; }
}

Related

.NET 6 API System.InvalidOperationException

I have Autofac module like this on one class;
public class AutofacBusinessModule:Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ProductManager>().As<IProductService>();
builder.RegisterType<EfProductDal>().As<IProductDal>();
builder.RegisterType<CategoryManager>().As<ICategoryService>();
builder.RegisterType<EfCategoryDal>().As<ICategoryDal>();
builder.RegisterType<CustomerManager>().As<ICustomerService>();
builder.RegisterType<EfCustomerDal>().As<ICustomerDal>();
builder.RegisterType<RegionManager>().As<IRegionService>();
builder.RegisterType<EfRegionDal>().As<IRegionDal>();
builder.RegisterType<TerritoryManager>().As<ITerritoryService>();
builder.RegisterType<EfTerritoryDal>().As<ITerritoryDal>();
builder.RegisterType<ShipperManager>().As<IShipperService>();
builder.RegisterType<EfShipperDal>().As<IShipperDal>();
builder.RegisterType<EmployeeManager>().As<IEmployeeDal>();
builder.RegisterType<EfEmployeeDal>().As<IEmployeeDal>();
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces()
.EnableInterfaceInterceptors(new ProxyGenerationOptions()
{
Selector = new AspectInterceptorSelector()
}).SingleInstance();
}
my api code its there;
[Route("api/[controller]")]
[ApiController]
public class ProductsController : Controller
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet("getall")]
public IActionResult GetAllProducts()
{
var result = _productService.GetAll();
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
}
}
I add this on program.cs but didn't work, if lower I use .net 6 i fix it like that but i dont know how to fix .net 6 because startup doesnt exists anymore.
IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule(new AutofacBusinessModule());
});
How can I fix this?
it gives me that error
System.InvalidOperationException: Unable to resolve service for type 'TAO.Business.Abstract.IProductService' while attempting to activate 'TAO.WebApi.Controllers.ProductsController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
at lambda_method3(Closure , IServiceProvider , Object[] )
at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass7_0.b__0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.g__CreateController|0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:7019
User-Agent: PostmanRuntime/7.30.0
Accept-Encoding: gzip, deflate, br
Postman-Token: fbff062e-ca49-4f37-afc5-e8785afbd43c
Try the following code from migration doc:
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new AutofacBusinessModule()));
Note that the "old" generic hosting model still works in .NET 6, there is no requirement to use the new minimal hosting model (but you need to copy not only CreateHostBuilder method but also contents of old Program.Main into the new Program top-level statement file).

Unable to resolve service DbContext while attempting to activate controller

I have my own DbContext:
public class DreamsContext : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set; }
public DbSet<DreamPublication> DreamPublications { get; set; }
public DreamsContext(DbContextOptions<DreamsContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserAccount>().ToTable("dreams_user");
modelBuilder.Entity<DreamPublication>().ToTable("dream_publications");
base.OnModelCreating(modelBuilder);
}
}
where UserAccount and DreamPublication contain just a few fields with get and set.
In my startup I add this for the DbContext:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Models.DreamsContext>(options =>
options.UseSqlServer("server = SCAT\\SQLEXPRESS; database = dreams_web; Trusted_Connection=True ; MultipleActiveResultSets = true"));
}
And then I'm trying to inject my DbContext into a controller using DI:
private readonly Models.DreamsContext _context;
public SignUpController (Models.DreamsContext dbContext)
{
_context = dbContext;
}
But when I am trying to do something with this context I get an exception:
Unable to resolve service for type '(My DbContext)' while attempting to activate '(My controller)'
And I don't know what to do, on MSDN they do just this and everything works
Update. This is what written in console
System.InvalidOperationException: Unable to resolve service for type 'DreamWeb.Models.DreamsContext' while attempting to activate 'DreamWeb.Controllers.SignUpController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
at lambda_method25(Closure , IServiceProvider , Object[] )
at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass7_0.<CreateActivator>b__0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
The main problem was I was trying to configure services in Startup class, while I am using asp.net CORE so the main magic is going in program.cs
I moved everything to program.cs and everything is working fine. It cost me a few days, even it's such a little mistake
https://learn.microsoft.com/en-us/dotnet/architecture/porting-existing-aspnet-apps/app-startup-differences
According to this, startup should continue to work in asp.net core, but i had problems with it
I had the same problem I solved by adding the connection to the db in the main file of the Project Program.cs , add it after the line var builder :
// db connection
builder.Services.AddDbContext<Dbcontexclass>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnectionString"));
Add in appsettings.json :
"ConnectionStrings": {
"DefaultConnectionString": "SERVER=server;DATABASE=database;User ID=user;PASSWORD=password;"
}
fill the Connection String , Dbcontexclass , and it should work, I leave you the docs link ( https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/working-with-sql?view=aspnetcore-7.0&tabs=visual-studio )

I can't get records from .net core mvc web api database

I couldn't solve it in any way, can you please help?
I added web api to my .net core mvc project. I can return values as string or json, quite normally. But when it comes to the database, I have a problem. I am getting a null error. No matter how hard I searched, I couldn't figure it out. The smallest answer helps a lot.
[ApiController] When I add it, my app doesn't open at all. When I make a comment line, it opens. But I can't call database methods. The same operations can be called seamlessly in the webui layer.
Startup.cs
Appsettings
Can you please help?
Fail:
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.NullReferenceException: Object reference not set to an instance of an object.
at zfc.webapi.Controllers.zfc.GetReferrer() in C:\Users\mderv\Desktop\zfc\zfc.webapi\Controllers\zfc.cs:line 26
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Csproj
Repository
From your screenshots and error so far I am assuming you haven't register the service in startup.cs file correctly.
Here I am adding a complete example for you how you can access data from database using your architecture.
Abstract:
public interface IChatRepository
{
object GetDataFromDatabase();
}
Concrete:
public class ChatRepository : IChatRepository
{
private readonly AppDbContext _dbContext;
public ChatRepository(AppDbContext dbContext)
{
this._dbContext = dbContext;
}
public object GetDataFromDatabase()
{
var dataFromDatabase = _dbContext.PrinterJobs.ToList();
return dataFromDatabase;
}
}
Note: I have injected a dbContext class instance here to access database object. This dbContext, IChatRepository and ChatRepository need to be registered in startup.cs file.
Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()));
services.AddScoped<IChatRepository, ChatRepository>();
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
});
}
AppDbContext:
public class AppDbContext: DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<PrinterJob> PrinterJobs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PrinterJob>().ToTable("PrintJob");
}
internal object Query<T>(string v, string name, int employeeId)
{
throw new NotImplementedException();
}
}
PrinterJob sample model class:
public class PrinterJob
{
[Key]
public int PrinterId { get; set; }
public string PrinterName { get; set; }
public int PrintedBy { get; set; }
public int TotalPrint { get; set; }
}
Database script:
CREATE TABLE PrintJob
(
[PrinterId] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
[PrinterName] [nvarchar](100) NULL,
[PrintedBy] [int] NULL,
[TotalPrint] [int] NULL,
)
Note: add some sample data into the table once you create the table from about script.
appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "Server=YourServerName;Database=YourDatabaseName;Trusted_Connection=True;MultipleActiveResultSets=true"
}
Note: make sure you are referring a correct database to access your data accordingly.
Controller:
[Route("api/ZfcData")]
public class ZfcDataController : Controller
{
private readonly IChatRepository _chatRepository;
public ZfcDataController(IChatRepository chatRepository)
{
this._chatRepository = chatRepository;
}
[HttpGet("[action]")]
[Route("GetDataFromDatabase")]
[ActionName("GetDataFromDatabase")]
public object GetDataFromDatabase()
{
var callRepository = _chatRepository.GetDataFromDatabase();
return Ok(callRepository);
}
}
Output:
Hope it would guide you through, and help you to get resolve your problem and implementations.
You are using dependecy injection to use your chatRepository and the injection it's not well configured ,
services.AddScoped<InterfaceOfMyReposiory,ImplementationOfMyRepository>();
So let's suppose that your ChatRepository is :
public class ChatRepository :IChatRepository {
//some special impelementation
}
and you have an interface like this :
public interface IChatRepository {
// some great methods definitions
}
so in your startup.cs class you should configure it like this :
services.AddScoped<IChatRepository ,ChatRepository>();
you can check more in details here.
in your case this should solve the null exception :
ChatRepository : interface
EfChatRepository and EfGenericRepository : implementation
services.AddScoped<ChatRepository ,EfChatRepository>();
EfChatRepository inherit the implemntation of EfGenericRepository

SassKit.Multitenancy not compatible with AspNetCore 2.2?

I've created a .NET Core API Controller, and in the POST method, I'm trying to use the CreatedAtActionResult method to include the route as a Location Header in my response.
[ApiController, Route("v1/[controller]")]
public class WidgetController: ControllerBase
{
public WidgetController(IWidgetService service)
{
_service = service;
}
private readonly IWidgetService _service;
[HttpGet("{id}", Name = "GetSingle")]
public IActionResult GetSingle(Guid id)
{
var result = _service.Get(id);
return Ok(result);
}
[HttpPost]
public IActionResult Post(WidgetModel model)
{
var result = _service.Post(model);
return result == Guid.Empty
? (IActionResult) BadRequest("No changes saved.")
: CreatedAtAction(nameof(GetSingle),
new {id = result},
model);
}
}
When I start the application, the first call to POST from POSTman runs without problem. Both the object and the Location Header URL get generated as expected. However, if I try to hit that endpoint a second time while the code is still running, I get the following error:
System.InvalidOperationException: No service for type 'Microsoft.AspNetCore.Routing.IEndpointAddressScheme1[Microsoft.AspNetCore.Routing.RouteValuesAddress]' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.AspNetCore.Routing.DefaultLinkGenerator.GetEndpoints[TAddress](TAddress address)
at Microsoft.AspNetCore.Routing.DefaultLinkGenerator.GetPathByAddress[TAddress](HttpContext httpContext, TAddress address, RouteValueDictionary values, RouteValueDictionary ambientValues, Nullable1 pathBase, FragmentString fragment, LinkOptions options)
at Microsoft.AspNetCore.Routing.LinkGeneratorRouteValuesAddressExtensions.GetPathByRouteValues(LinkGenerator generator, HttpContext httpContext, String routeName, Object values, Nullable1 pathBase, FragmentString fragment, LinkOptions options)
at Microsoft.AspNetCore.Mvc.Routing.EndpointRoutingUrlHelper.Action(UrlActionContext urlActionContext)
at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action(IUrlHelper helper, String action, String controller, Object values, String protocol, String host, String fragment)
at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action(IUrlHelper helper, String action, String controller, Object values, String protocol, String host)
at Microsoft.AspNetCore.Mvc.CreatedAtActionResult.OnFormatting(ActionContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsync(ActionContext context, ObjectResult result)
at Microsoft.AspNetCore.Mvc.ObjectResult.ExecuteResultAsync(ActionContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at SaasKit.Multitenancy.Internal.TenantResolutionMiddleware1.Invoke(HttpContext context, ITenantResolver`1 tenantResolver)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
I have tried swapping out the CreatedAtAction with an Ok, and that 200 result will return as many times as I click the button in POSTman.
The code inside the Post method successfully processes, and it will move onto the OnActionExecuted method of a Filter that I created. I've tried looking through all of the properties of the ActionExecutedContext object and I don't see anything out of the ordinary. Once I get out of that empty OnActionExecuted method, the call returns the 500 status code and the above stack trace inside the error page. I do have an Exception filter, but that doesn't get touched.
Has anyone had issues with this before?
For reference, here is my Startup Configuration:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMultitenancy<AppTenant, CachingAppTenantResolver>();
services.AddAutoMapper()
.AddSingleton(ConfigureAutoMapper())
.AddMvc(options =>
{
options.Filters.Add<ValidatorActionFilter>();
options.Filters.Add<ErrorHandlingFilter>();
})
.AddFluentValidation(validation => {
validation.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
validation.ImplicitlyValidateChildProperties = true;
validation.RegisterValidatorsFromAssemblyContaining<WidgetModelValidator>();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
});
services.AddEntityFrameworkSqlServer()
.AddDbContext<MyContext>();
services.Configure<MultitenancyOptions>(configuration.GetSection("Multitenancy"));
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("http://localhost:4200").AllowAnyHeader().AllowAnyMethod();
});
});
ConfigureContainer(container);
return provider;
}
Try setting CompatibilityVersion.Version_2_0 or 2.1 instead of 2.2?
EndpointRouting is quite a big change under the covers of 2.2, and your 3rd party add-on might not be compatible.
Setting compatibility back to 2.1 will typically not require any code changes in your controller, so it's fairly low-cost.

Cannot access a disposed object for DbContext in .NET Core for Async method

I am having a weird problem in one of my microservice web api. My async GET methods throw a Cannot access a disposed object exception for my DbContext except for the very first time they are invoked. I tried looking online for an answer but nothing worked. I made sure my methods are not async void and I await the necessary calls. Since my POST and DELETE methods work fine, I am fairly certain that the real culprit is the IMapper instance. I think it might always point to the first instance of the DbContext and that is why the works the first time but not the ones after. Any help or pointers would be gladly appreciated
Here are some snapshots of the code.
Startup.cs
...
// Add AutoMapper
services.AddAutoMapper(new Assembly[] { typeof(AutoMapperProfile).GetTypeInfo().Assembly });
// Add DbContext using NoSQL Server Provider
services.AddDbContext<ProfileDbContext>(options =>
options.UseMongoDb(Configuration.GetConnectionString("TeamJobProfilesDatabase")));
...
MyController.cs
// GET api/profiles
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<ProfilesListViewModel>> GetAll()
{
return Ok(await Mediator.Send(new GetAllProfilesQuery()));
}
// GET api/profiles/{id}
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<ProfileViewModel>> Get(int id)
{
return Ok(await Mediator.Send(new GetProfileQuery { Id = id }));
}
GetAllProfilesQueryHandler.cs
public class GetAllProfilesQueryHandler : IRequestHandler<GetAllProfilesQuery, ProfilesListViewModel>
{
private readonly ProfileDbContext _context;
private readonly IMapper _mapper;
public GetAllProfilesQueryHandler(ProfileDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<ProfilesListViewModel> Handle(GetAllProfilesQuery request, CancellationToken cancellationToken)
{
return new ProfilesListViewModel
{
Profiles = await _context.Profiles.ProjectTo<ProfileLookupModel>(_mapper.ConfigurationProvider).ToListAsync(cancellationToken)
};
}
}
ProfileDbContext.cs
[MongoDatabase("profileDb")]
public class ProfileDbContext : DbContext
{
public ProfileDbContext(DbContextOptions<ProfileDbContext> options)
: base(options)
{
}
public DbSet<Domain.Entities.Profile> Profiles { get; set; }
}
Exception message
{
"error": [
"Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context 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: 'ProfileDbContext'."
],
"stackTrace": " at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()\r\n at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()\r\n at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.get_TrackQueryResults()\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.Create(Boolean async)\r\n at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](QueryModel queryModel)\r\n at Blueshift.EntityFrameworkCore.MongoDB.Storage.MongoDbDatabase.<>c__DisplayClass11_01.<CompileAsyncQuery>b__0(QueryContext queryContext)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.System.Collections.Generic.IAsyncEnumerable.GetEnumerator()\r\n at System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable1 source, TAccumulate seed, Func3 accumulator, Func2 resultSelector, CancellationToken cancellationToken) in D:\\a\\1\\s\\Ix.NET\\Source\\System.Interactive.Async\\Aggregate.cs:line 118\r\n at Profile.Application.Profiles.Queries.GetAllProfiles.GetAllProfilesQueryHandler.Handle(GetAllProfilesQuery request, CancellationToken cancellationToken) in C:\\Users\\Adam\\Repositories\\TeamJob\\TeamJob\\src\\Services\\Profile\\Profile.Application\\Profiles\\Queries\\GetAllProfiles\\GetAllProfilesQueryHandler.cs:line 24\r\n at MediatR.Pipeline.RequestPostProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next)\r\n at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next)\r\n at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next)\r\n at Profile.API.Controllers.ProfilesController.GetAll() in C:\\Users\\Adam\\Repositories\\TeamJob\\TeamJob\\src\\Services\\Profile\\Profile.API\\Controllers\\ProfilesController.cs:line 19\r\n at lambda_method(Closure , Object )\r\n at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()\r\n at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\r\n at System.Threading.Tasks.ValueTask1.get_Result()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()"
}
The problem is in Mediator.Send method. Mediator class stores request handlers in a static ConcurrentDictionary
private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();
and when the Send method is invoked, it uses GetOrAdd method on that dictionary.
var handler = (RequestHandlerWrapper<TResponse>)_requestHandlers.GetOrAdd(requestType, t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse))));
This means, if the request handler instance doesn't already exist in the dictionary, it creates a new instance (using Activator) and adds it to the dictionary, but if the request handler instance already exists in the dictionary, it uses the existing one (and this is the cause of your problem).
So, what exactly is causing your error?
The _requestHandlers dictionary is static, which means it lives through multiple requests, i.e. doesn't get disposed/garbage collected at the end of the request. Your ProfileDbContext, when registered using AddDbContext method, has a scoped lifetime, which means it is created once per request (and disposed at the end of the request). This means you can end up in a situation where _requestHandlers dictionary contains an instance of GetAllProfilesQueryHandler that has a reference on an exposed instance of ProfileDbContext.
Here is what happens:
First request arrives.
Mediator.Send(new GetProfileQuery { Id = id }) gets called.
Mediator.Send(new GetProfileQuery { Id = id }) doesn't find GetAllProfilesQueryHandler in _requestHandlers dictionary, so it instantiates it and also resolves its ProfileDbContext dependency.
The request ends. _context field (ProfileDbContext) in your GetAllProfilesQueryHandler indsance gets disposed (because it has a scoped lifetime), but the _requestHandlers dictionary (containing GetAllProfilesQueryHandler instance) doesn't get disposed (because it is static).
Another request arrives.
Mediator.Send(new GetProfileQuery { Id = id }) gets called again.
This time Mediator.Send(new GetProfileQuery { Id = id }) finds GetAllProfilesQueryHandler instance in _requestHandlers dictionary and uses the existing instance, whose _context field is disposed at the end of the previous request.
GetAllProfilesQueryHandler tries to access the disposed _context field, and gets the "Cannot access a disposed object" error.
Possible solution
Don't let Mediator.Send resolve GetAllProfilesQueryHandlers dependencies.
Maybe pass IServiceProvider serviceProvider to your GetAllProfilesQueryHandler and let it resolve its dependencies as needed:
public class GetAllProfilesQueryHandler : IRequestHandler<GetAllProfilesQuery, ProfilesListViewModel>
{
private readonly IServiceProvider _serviceProvider;
public GetAllProfilesQueryHandler(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<ProfilesListViewModel> Handle(GetAllProfilesQuery request, CancellationToken cancellationToken)
{
return new ProfilesListViewModel
{
ProfileDbContext context = (ProfileDbContext)this._serviceProvider.GetService(typeof(ProfileDbContext));
IMapper mapper = (IMapper)this._serviceProvider.GetService(typeof(IMapper));
Profiles = await context.Profiles.ProjectTo<ProfileLookupModel>(mapper.ConfigurationProvider).ToListAsync(cancellationToken)
};
}
}
Edit:
As #Lucian Bargaoanu pointed out in comments, you can resolve handlers through DI, as in https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection

Categories

Resources