I am rewriting/ moving a website from ASP MVC to ASP MVC Core. This application has a dynamic menu which depends on the logged in user. In order to build the menu, each controller derives from a custom BaseController who sets in ViewBag menu items, an later, in Layout, those items are retrieved and passed as arguments to a PartialView.
public BaseController:Controller
{
public BaseController()
{
...
ViewBag.Menu=Utils.GetMenu();
...
}
}
I don't want to use the same logic as the lads who wrote the old code. So I thought to use a ViewComponent to render the menu. But I have a problem with this approach. In Invoke method I need to query for the menu items. Right now I get a DbContext instance from the service provider (HttpContext.RequestServices), and I use it to query whatever data I need. But the Invoke function is called asynchronously from Layout and I know that it is not very good to send DbContext to async methods:
<cache expires-after="#TimeSpan.FromHours(2)" enabled="true">
#await Component.InvokeAsync(typeof(Admin.ViewComponents.MeniuViewComponent))
</cache>
Is this a good approach? Is it safe to get a DbContext (registered as Scoped in Startup) in Invoke method (or any other async method or action) and use it? And if it is not a good idea, how should I deal with this kind of situations where I need data from db in async methods?
I had the case in my project to get the DbContext inside a HostedService from asp.net core.
I injected the IServiceProvider inside the constructor and build my own scope, to get the registered DbContext:
private readonly IServiceProvider _serviceProvider;
public BaseController(IServiceProvider provider){
_serviceProvider = provider;
}
private void DoSth(){
using var scope = _serviceProvider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<YOUR_DbContext_CLASS>();
... //do something great stuff here with the database
}
I think you can directly dependency inject the DbContext in the ViewComponent
public class MenuViewComponent : ViewComponent
{
private readonly MenuDbContext _context;
public TopMenuViewComponent(MenuDbContext context)
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var model = _context.MenuItems.ToList();
return View("Default", model);
}
}
For more details, refer to the doc.
Related
I have the problem that I want to add my service CourseService as Singleton and I want to add only once my initial data like this:
context.Courses.Add(new Course { ... });
But it turned out that every time when I upload my page with courses my initial data added again and again to my database. So I see the same courses on my page as much time as I upload this page. Can't understand where is the problem.
My interface:
public interface ICourseService
{
IEnumerable<CourseDto> GetCourses();
Task<IEnumerable<CourseDto>> GetCoursesAsync();
}
My service:
public class CourseService : BaseService<Course, CourseDto>, ICourseService
{
public CourseService(IMapper mapper, DataContext context) : base(mapper,context)
{
context.Courses.Add(new Course { ... });
context.Courses.Add(new Course { ... });
context.SaveChanges();
}
public IEnumerable<CourseDto> GetCourses() { ... }
public async Task<IEnumerable<CourseDto>> GetCoursesAsync() { ... }
}
My controller:
public class CourseController : Controller
{
private readonly CourseService _courseService;
public CourseController(CourseService courseService)
{
_courseService = courseService;
}
[Route("courses")]
public async Task<IActionResult> GetCourses()
{
var courses = await _courseService.GetAllAsync();
return View("CourseList", courses);
}
}
And I added my service as Singleton in the method ConfigureServices:
services.AddSingleton<ICourseService, CourseService>();
services.AddEntityFrameworkSqlite().AddDbContext<DataContext>();
AddDbContext by default adds context with Scoped lifetime, so it can't be resolved in singleton services. Possible workarounds:
register ICourseService as scoped
register context as scoped or transient (would not recommend)
inject IServiceScopeFactory and use it to create scope and resolve context from the scope (on each method call), like in this answer
Personally I would go with the first approach.
Read more:
Service lifetimes
Data seeding in EF Core
I am taking small lookup table values from Entity Framework database and storing in MemoryCache. Currently using both ways, regular memoryCache, and Singleton MemoryContainer seen here Asp.Net Core: Use memory cache outside controller.
In Home Controller, this saves ProductTypes in a MemoryCache, and we see values store correctly in debug window (ProductType, ProductName, etc).
public class HomeController : Controller
{
public IMemoryCache _memoryCache;
public readonly StoreContext _storeContext;
public MemoryContainer _memoryContainer;
public HomeController(StoreContext storeContext, IMemoryCache memoryCache, MemoryContainer memoryContainer)
{
_storeContext= storeContext;
_memoryCache = memoryCache;
_memoryContainer = memoryContainer;
}
public IActionResult Index()
{
var productTypes = storeContext.ProductTypes;
_memoryCache.Set("ProductTypesKey", productTypes );
_memoryContainer._memoryCache.Set("ProductTypesKey2", test);
return View(); //both values store correctly
}
Then when going to ProductController,
public class ProductsController : Controller
{
public StoreContext _storeContext;
public IMemoryCache _memoryCache;
public MemoryContainer _memoryContainer;
public ProductsController(StoreContext storeContext, IMemoryCache memoryCache, MemoryContainer memoryContainer)
{
_storeContext = storeContext;
_memoryCache = memoryCache;
_memoryContainer = memoryContainer;
}
public async Task<IActionResult> Details(int? id)
{
var test = _memoryCache.Get<DbSet<ProductTypes>>("ProductTypesKey");
var test2 = _memoryContainer._memoryCache.Get<DbSet<ProductTypes>>("ProductTypesKey2");
I see the following error result in MemoryCache for both, how can this be fixed?
How do to make sure MemoryCache will get/store correctly with DbContext, regardless of going from Controller to Controller?
"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: 'StoreContext'."
Other Code:
public class MemoryContainer
{
public IMemoryCache _memoryCache { get; set; }
public MemoryContainer(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
}
Startup.cs
services.AddMemoryCache();
services.AddSingleton<MemoryContainer>();
Other Resources:
MVC Net Core Pass MemoryCache to All Controllers
DbSet<T> implements IQueryable so those ProductTypes aren't really inside your application until materialized.
A simple ToList() will do the job:
var productTypes = storeContext.ProductTypes.ToList();
Then, inside the other controller:
var test = _memoryCache.Get<List<ProductTypes>>("ProductTypesKey");
Calling ToList() on a DbSet without any filters isn't the best approach in all scenarios because it will load the whole sql table behind the DbSet. However, I guess this is kind of what you are trying to do, just make sure that you never do this on a table with a lot of data.
Note: you are seeing the data in debug window, because expanding IQueryable results actually performs sql queries.
I am using ASP.NET Core. How can I use session variables in a static method?
In ASP.NET, this looked like this:
protected static string AssignSession()
{
return HttpContext.Current.Session["UserName"].ToString();
}
protected void Page_Load(object sender, EventArgs e)
{
Session["UserName"] = "super user";
}
When I try that in ASP.NET Core, I get the following error:
An object reference is required for the non-static field, method,
or property 'ControllerBase.HttpContext'.
The answer is generally: You don’t.
In ASP.NET Core, you pretty much avoid static code. Instead, ASP.NET Core uses dependency injection to make services available as dependencies and to control their lifetime.
A static utility class in ASP.NET would probably translate to a singleton service in ASP.NET Core. Using that is very straightforward; you start by creating a non-static service that does whatever you want to do. Since this is using dependency injection, you can just depend on other services as well:
public class MyService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void SetSomeSessionValue(string value)
{
var httpContext = _httpContextAccessor.HttpContext;
httpContext.Session["example"] = value;
}
}
You can do whatever you want there. The IHttpContextAccessor is used to retrieve the current HttpContext.
Then, you need to register your service with the dependency injection container. You do that in the ConfigureServices method in your Startup.cs:
services.AddSingleton<MyService>();
// we also add the HttpContextAccessor, in case it wasn’t already (implicitly) registered before
services.AddHttpContextAccessor();
And now, you can depend on this MyService within controllers or other services by simply adding it as a constructor argument:
public class HomeController
{
private readonly MyService _myService;
public HomeController(MyService myService)
{
_myService = myService;
}
public IActionResult Index()
{
_myService.SetSomeSessionValue("test");
return View();
}
}
Now, you have a non-static service that has clear dependencies and which you can test properly.
That all being said, many constructs already have access to the current HttpContext and as such to the session. For example, in controllers, Razor pages, or even Razor views, you can just access the HttpContext directly as it is an instance variable.
So if you are not building some reusable utility code, you don’t actually need to create a service for this. You could just for example create a (non-static) utility method within your controller that then accesses the HttpContext directly.
If i have a controller that receives and processes the action selected by the user, and then want to use another controller to store all database related logic, what is the correct way to connect these controllers while allowing the 2nd controller to interact with the database context.
At the moment I have it working with creating a database context in the first controller and then parsing that to the database controller when I connect the two using DI, but hopefully someone could show me the correct way to do this.
public class TestController : Controller
{
private readonly DatabaseContext context;
private Database.UserController userDatabaseController;
public TestController(DatabaseContext db)
{
context = db;
userDatabaseController = new Database.UserController(context);
}
}
database controller
public class UserController : Controller
{
private readonly DatabaseContext context;
public UserController(DatabaseContext ctx)
{
context = ctx;
}
public bool RegisterUser(Models.DatabaseModels.UserModel model)
{
try
{
context.Users.Add(model);
context.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
}
startup.cs
services.AddDbContext<DatabaseContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
databasecontext
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)
{ }
public DbSet<DatabaseModels.UserModel> Users { get; set; }
}
The "correct" way is: you don't. A controller should never directly call into another controller. You can redirect to a new route that maps to a different controller, but that's it. If you have some common logic that needs to be shared, then that should be factored out into a completely different class that both controllers can utilize.
If you're finding that you need to call Controller methods from another Controller, you probably need to refactor your code. Controllers should have very little logic in them, which usually just involves calling a Service layer and then constructing a ViewModel from the data.
My advice would be to do some reading on the Service Layer pattern and the Repository pattern (sometimes called the Manager pattern).
I have a single ASP.NET 5.0 (vnext) project where I am implementing both a Web Api and an Mvc front end. I want my Mvc controller to call the Web Api controller, which is working just fine. I built the api based on the example at http://www.asp.net/vnext/overview/aspnet-vnext/create-a-web-api-with-mvc-6, and it is working great. The Mvc front end can call the WebApi controller successfully, but the ITodoRepository doesn't get provided by the dependency injection framework when I instantiate it from the Mvc controller.
public class Startup
{
public void Configure(IApplicationBuilder app, ILoggerFactory logFactory)
{
...
app.UseServices(services =>
{
services.AddSingleton<ITodoRepository, TodoRepository>();
});
...
[Route("api/[controller]")]
public class TodoController : Controller
{
/* The ITodoRepository gets created and injected, but only when the class is activated by Mvc */
TodoController(ITodoRepository repository)
{
_repository = repository;
}
[HttpGet]
public IEnumerable<TodoItem> Get()
{
return _repository.AllItems;
}
...
public class HomeController : Controller
{
public IActionResult Index()
{
var tc = new TodoController(/* have to create my own ITodoRepository here */);
return View(tc.Get());
}
...
I was able to add an ITodoRepository to the HomeController with the [Activate] attribute, and then pass that to the constructor for the TodoController, but that doesn't pass the smell test to me. Home Controller shouldn't have to have or even know about those.
Is there another way to create the TodoController instance that will invoke the DI logic and provide the dependencies?
If you're concerned about code smell, the main concern should be about having one controller calling another controller.
Controllers are meant to be called in two scenarios:
By the system (i.e. MVC)
By your unit tests
Instead, I recommend having both controllers call a business logic component that itself might use dependency injection to acquire its dependencies, and that each controller perhaps use dependency injection to acquire the business logic dependency as well.
public class HomeController : Controller {
public HomeController(IMyAppBusinessLogic bll) { ... }
}
public class WebApiController : Controller {
public WebApiController(IMyAppBusinessLogic bll) { ... }
}
public class MyAppBusinessLogic : IMyAppBusinessLogic {
public MyAppBusinessLogic(ITodoRepository repository) { ... }
}
Any middleware registered using app.UseServices are available only within the scope of a web request. There is no web request context when you are trying to instantiate the webapi controller directly from your MVC app and therefore the dependencies will not be resolved.
It's normal to create an execution context manually for the purposes of unit testing. Not sure which DI framework are you using but I do something like the following in my project (OWIN not vNext) which is using SimpleInjector
public static void UseInjector(this IAppBuilder app, Container container)
{
// Create an OWIN middleware to create an execution context scope
app.Use(async (context, next) =>
{
using (var scope = container.BeginExecutionContextScope())
{
await next.Invoke();
}
});
}