ASP.NET MVC 5 Common actions for more controllers - asp.net

I have some controllers (and will be more) which share some actions like those:
public ActionResult DeleteConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult RestoreConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = false;
db.SaveChanges();
return RedirectToAction("Index");
}
Those action are part of SuppliersController. What this does is that when I delete or restore an object, it marks the object in the database as true for deleted field (and false when it is restored).
The same behavior is shared by many other controllers like CurrenciesController, ProductsController, etc...
In the code I showed you should see that my database entity is clearly specified (Supplier) and also the repository (Suppliers).
I want to find a way to this in a generic way. I want to create a custom controller and all other controllers that shares the same behavior will extended it. In this case ProductsController will extend my DeleteRestoreController.
How can I do this in a "generic" way?
db is a DbContext
public partial class LE: DbContext
{
public LE()
: base("name=LE")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<CategoryText> CategoryTexts { get; set; }
...
}
Categories also share the same behavior.

To go one step further
public abstract class DeleteRestoreController<T> : Controller
{
public virtual Action DeleteConfirmed(int id)
{
var dbset = db.Set<T>();
var s = dbset.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
}
then when defining your controller add the entity type
public class ProductsController : DeleteRestoreController<Supplier>
{
////blah
}

You can implement your DeleteRestoreController as an abstract class.
public abstract class DeleteRestoreController : Controller
{
private IRepository : Repository;
public DeleteRestoreController() { ... }
public DeleteRestoreController(IRepository Repository) { ... }
public virtual Action DeleteConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
}
If you need to differ from that behaviour in your ProductsController you can simply override that method.
public class ProductsController : DeleteRestoreController
{
public override void DeleteConfirmed()
{
//override the logic
}
}
You could always go one step further and implement a generic repository as well, but I've never gone beyond 6-8 controllers in my applications and didn't create one once.
EDIT I've just read in the comments, that the entities would change from Suppliers in the controllers, so implementing a base controller wouldn't make much sense, if you do not implement a generic interface as well. Robert Harvey has made a great point in stating the complexity has to go somewhere.

Related

EF Core with CosmosDB: OwnsOne and OwnsMany throw NullReferenceException

I'm working on a new project that uses CosmosDB and Entity Framework Core (via the Microsoft.EntityFrameworkCore.Cosmos NuGet package, version 5.0.7; the project itself is .NET Core 5). I'm new to both, and running into an issue I can't sort out.
In short, I need to save a complex object to the database. It's a big model that will have multiple collections of classes underneath it, each with their own properties and some with collections underneath them as well. I'm trying to configure EF with OwnsOne and OwnsMany to store these child objects underneath the top-level one. The code compiles, and will save to the database so long as all the owned objects are left empty. But whenever I put anything into an owned object, either with OwnsOne or OwnsMany, I get a pair of NullReferenceExceptions.
I've tried to strip my code down to the very basics. Here's how it currently looks.
Owner and owned classes:
public class Questionnaire
{
// Constructors
private Questionnaire() { }
public Questionnaire(Guid id)
{
Test = "Test property.";
TV = new TestQ();
Id = id;
}
public Guid Id { get; set; }
public string Test { get; set; }
public TestQ TV { get; set; }
// Public Methods
public void AddForm(Form f)
{
// not currently using this method
//Forms.Add(f);
}
}
public class TestQ
{
public TestQ()
{
TestValue = "test ownsone value";
}
public string TestValue { get; set; }
}
DbContext:
public class QuestionnaireDbContext : DbContext
{
public DbSet<Questionnaire> Questionnaires { get; set; }
public QuestionnaireDbContext(DbContextOptions<QuestionnaireDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultContainer(nameof(Questionnaires));
modelBuilder.Entity<Questionnaire>().HasKey(q => q.Id);
modelBuilder.Entity<Questionnaire>().OwnsOne(q => q.TV);
}
}
And the code from the service that calls the dbContext (note that this is based on a generic service that I didn't set up originally). The actual exceptions are thrown here.
public virtual TEntity Add(TEntity entity)
{
_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();
return entity;
}
Ultimately I need this to work with OwnsMany and a collection, but I figured it might be simpler to get it working with OwnsOne first. The key thing to note here is that if I comment out the line
TV = new TestQ();
in the Questionnaire class, the model persists correctly into CosmosDB. It's only when I actually instantiate an owned entity that I get the NullReferenceExceptions.
Any advice would be much appreciated! Thank you!
Well, I'm not sure why this is the case, but the issue turned out to be with how we were adding the document. Using this generic code:
public virtual async Task<TEntity> Add(TEntity entity)
{
_context.Entry(entity).State = EntityState.Added;
await _context.SaveChanges();
return entity;
}
was the issue. It works just fine if I use the actual QuestionnaireDbContext class like so:
context.Add(questionnaire);
await context.SaveChangesAsync();

How to create EF Core generic repository "on the fly" to use in MVC controller

Creating a generic repository for EF core is easy. Let's asume we have the following base class:
public class BaseRepository<T> where T : class
{
protected readonly MyContext Ctx = new ();
public IEnumerable<T> Entities()
{
return Ctx.Set<T>();
}
}
What I usually do is to create a derived repository for a specific DbSet, like
public virtual DbSet<MyDatabaseTable> Table { get; set; }
and
public class TableRepository : BaseRepository<myDatabaseTable> { }
and than use this in my controller like this
public JsonResult Read_MyDatabaseTable([DataSourceRequest] DataSourceRequest request)
{
return Json(new TableRepository().Entities().ToDataSourceResult(request));
}
What I WANT to do ist to have not repositiories for each table, instead to create the gernic type "on the fly" from a parameter like
public JsonResult Read_MyDatabaseTable([DataSourceRequest] DataSourceRequest request, string myTableType)
{
return Json(new BaseRepository<myTableType>().Entities().ToDataSourceResult(request));
}
Is there any way to handle this?

When do we need data classes?

Im using asp.net core. Here is the basic way to use model with controller.
public class BookController : Controller
{
private readonly ApplicationDbContext _context { get; set; }
public BookController(ApplicationDbContext context)
{
_context = context;
}
public IActionResult Create(Book model)
{
// adding new model
}
public IActionResult Edit(Book model)
{
// modifying the model
}
public IActionResult Delete(Book model)
{
// removing the model
}
}
My question: when shall/should I implement the code inside the controller? When shall/should I implement it in another class?
Something like this:
public interface IBook
{
int Add(Book book);
int Update(Book book);
int Remove(Book book);
}
public class BookData : IBook
{
private readonly ApplicationDbContext _context { get; set; }
BookData(ApplicationDbContext context)
{
_context = context
}
public int Add(Book model)
{
// ...
return _context.SaveChanges();
}
// other implements...
}
Then, calling it inside controller:
public IActionResult Create(Book model)
{
var bookData = new BookData(_context);
int result = bookData.Add(model);
// ...
}
For the interface, I think it may be useful for the case: I have many controllers that require same action/method names.
Example: MessageController requires 3 actions/methods at least (Create/Add, Edit/Update, Delete/Remove). It's same to NotificationController class, CommentController class...
So, the interface can be improved to:
public interface IMyService<T> where T : class
{
int Add(T model);
int Update(T model);
int Remove(T model);
}
public class MyService<T> : IMyService<T> where T : class
{
private readonly ApplicationDbContext _context { get; set; }
public MyService(ApplicationDbContext context)
{
_context = context;
}
public int Add(T model)
{
Type type = typeof(model);
if (type == typeof(Book))
{
// adding new book model
}
else if (type == typeof(Comment))
{
// adding new comment model
}
// ...
return -1;
}
// other implements...
}
Do I misunderstand something?
If I read it correctly with data classes you actually means repository (which is an abstraction over the persistence layer). You should always encapsulate persistence logic behind a class (be it via repository pattern, command/query pattern or request handler) and use it instead of directly using the context in your service classes.
That being said, you can directly inject your BookData to your controller instead of the ApplicationDbContext. One thing you should consider you lose in your current implementation is the Unit of Work pattern. Right now, every add will instantly persist the data.
This may not be what you want, so you should move the _context.SaveChanges(); outside of the Add/Remove/Update methods and call it explicitly. This allows you to insert i.e. 10 records and if one of them fails, nothing will be persisted to the database.
But if you call _context.SaveChanges(); after each insert and you get an error in the 8th (of 10) records, then 7 get persisted and 3 will be missing and you get inconsistent data.
Controller shouldn't contain any logic at all, only do short validation of the input model (ModelState.IsValid check) and if its okay, call the services which do all the logic and report the result back to the user. Only in very simple tutorials and guides logic is put into the controller action for reasons of simplicity. In real world applications you should never do that. Controllers are much harder to unit test than service classes.

Is there a way to get the current controller instance in ASP.NET 5?

Is there a way to do this using DI? I tried IScopedInstance<Controller> but this gives me null. Poked around aspnet's source code but didn't win. Any ideas?
I have a controller that accepts different IPaymentMethods. The IPaymentMethod can be a ViewComponent that can render Views. If the IPaymentMethod is a ViewComponent, I want it to use MVC's built-in model binding on post back.
public class XController : Controller
{
// ctor, props, ...
public IActionResult Checkout()
{
return View(new Model
{
PaymentMethodId = 1,
PaymentMethodType = typeof(MyPaymentMethod) // The razor file will use this type to render it as a ViewComponent
});
}
[HttpPost]
public IActionResult Checkout(Model model)
{
var paymentMethod = _paymentService.GetPaymentMethodById(model.PaymentMethodId);
paymentMethod.ProcessPayment();
// ..
}
}
This is where I need the controller to be injected. I wanted to make use of the built-in MVC validation and model binding.
public class MyPaymentMethod : IPaymentMethod
{
private Controller _currentController;
public MyPaymentMethod(IScopedInstance<Controller> controller)
{
_currentController = controller.Value;
}
public void ProcessPayment()
{
var model = new PaymentModel();
_currentController.TryUpdateModel(model, typeof(PaymentModel), null);
if (!_currentController.ModelState.IsValid)
{
return; // or exception
}
// Process Payment using model
}
public Task<IViewComponentResult> InvokeAsync()
{
// returns View
}
}
public interface IPaymentMethod
{
void ProcessPayment();
}
Since the model instance is required in the ProcessPayment method, why not simply pass it as a parameter?
[HttpPost]
public IActionResult Checkout(PaymentModel model)
{
var paymentMethod = _paymentService.GetPaymentMethodById(model.PaymentMethodId);
if (!ModelState.IsValid)
{
return; // or exception
}
paymentMethod.ProcessPayment(model);
// ..
}
public void ProcessPayment(PaymentModel model)
{
// Process Payment using model
}
Your service is taking on responsibilities that belong to the controller - namely checking ModelState.IsValid.
public interface IPaymentMethod
{
void ProcessPayment(PaymentModel model);
}
You may wish to also pass just the properties that are needed from the payment model, or you may wish to make an IPaymentModel interface to decouple your model from your PaymentService. In that case, your IPaymentModel would go into a shared layer.
public interface IPaymentMethod
{
void ProcessPayment(IPaymentModel model);
}
This no longer works with beta7
At this time of writing (beta6), this probably isn't supported and there is a good reason for it: Controllers in ASP.NET 5 does not need to inherit from the Controller class. I have, however, found a way for this to work using ActionFilters.
public class ScopeControllerActionFilterAttribute : ActionFilterAttribute
{
private readonly IScopedInstance<Controller> _controller;
public ScopeControllerActionFilterAttribute(IScopedInstance<Controller> controller)
{
_controller = controller;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (_controller.Value == null)
{
_controller.Value = context.Controller as Controller;
}
}
}
Note that depending on the stage of the http request lifecycle, the Value of IScopedInstance<Controller> may still be empty.

Design a class to be Unit testable

I am going though the Apress ASP.NET MVC 3 book and trying to ensure I create Unit Tests for everything possible but after spending a good part of a day trying to work out why edit's wouldn't save (see this SO question) I wanted to create a unit test for this.
I have worked out that I need to create a unit test for the following class:
public class EFProductRepository : IProductRepository {
private EFDbContext context = new EFDbContext();
public IQueryable<Product> Products {
get { return context.Products; }
}
public void SaveProduct(Product product) {
if (product.ProductID == 0) {
context.Products.Add(product);
}
context.SaveChanges();
}
public void DeleteProduct(Product product) {
context.Products.Remove(product);
context.SaveChanges();
}
}
public class EFDbContext : DbContext {
public DbSet<Product> Products { get; set; }
}
I am using Ninject.MVC3 and Moq and have created several unit tests before (while working though the previously mentioned book) so am slowly getting my head around it. I have already (hopefully correctly) created a constructor method to enable me to pass in _context:
public class EFProductRepository : IProductRepository {
private EFDbContext _context;
// constructor
public EFProductRepository(EFDbContext context) {
_context = context;
}
public IQueryable<Product> Products {
get { return _context.Products; }
}
public void SaveProduct(Product product) {
if (product.ProductID == 0) {
_context.Products.Add(product);
} else {
_context.Entry(product).State = EntityState.Modified;
}
_context.SaveChanges();
}
public void DeleteProduct(Product product) {
_context.Products.Remove(product);
_context.SaveChanges();
}
}
BUT this is where I start to have trouble... I believe I need to create an Interface for EFDbContext (see below) so I can replace it with a mock repo for the tests BUT it is built on the class DbContext:
public class EFDbContext : DbContext {
public DbSet<Product> Products { get; set; }
}
from System.Data.Entity and I can't for the life of me work out how to create an interface for it... If I create the following interface I get errors due to lack of the method .SaveChanges() which is from the DbContext class and I can't build the interface using "DbContext" like the `EFDbContext is as it's a class not an interface...
using System;
using System.Data.Entity;
using SportsStore.Domain.Entities;
namespace SportsStore.Domain.Concrete {
interface IEFDbContext {
DbSet<Product> Products { get; set; }
}
}
The original Source can be got from the "Source Code/Downloads" on this page encase I have missed something in the above code fragments (or just ask and I will add it).
I have hit the limit of what I understand and no mater what I search for or read I can't seem to work out how I get past this. Please help!
The problem here is that you have not abstracted enough. The point of abstractions/interfaces is to define a contract that exposes behavior in a technology-agnostic way.
In other words, it is a good first step that you created an interface for the EFDbContext, but that interface is still tied to the concrete implementation - DbSet (DbSet).
The quick fix for this is to expose this property as IDbSet instead of DbSet. Ideally you expose something even more abstract like IQueryable (though this doesn't give you the Add() methods, etc.). The more abstract, the easier it is to mock.
Then, you're left with fulfilling the rest of the "contract" that you rely on - namely the SaveChanges() method.
Your updated code would look like this:
public class EFProductRepository : IProductRepository {
private IEFDbContext context;
public EFProductRepository(IEFDbContext context) {
this.context = context;
}
...
}
public interface IEFDbContext {
IDbSet<Product> Products { get; set; }
void SaveChanges();
}
BUT... the main question you have to ask is: what are you trying to test (conversely, what are you trying to mock out/avoid testing)? In other words: are you trying to validate how your application works when something is saved, or are you testing the actual saving.
If you're just testing how your application works and don't care about actually saving to the database, I'd consider mocking at a higher level - the IProductRepository. Then you're not hitting the database at all.
If you want to make sure that your objects actually get persisted to the database, then you should be hitting the DbContext and don't want to mock that part after all.
Personally, I consider both of those scenarios to be different - and equally important - and I write separate tests for each of them: one to test that my application does what it's supposed to do, and another to test that the database interaction works.
I guess your current code looks something like this (I put in the interface):
public class EFProductRepository : IProductRepository {
private IEFDbContext _context;
// constructor
public EFProductRepository(IEFDbContext context) {
_context = context;
}
public IQueryable<Product> Products {
get { return _context.Products; }
}
public void SaveProduct(Product product) {
if (product.ProductID == 0) {
_context.Products.Add(product);
} else {
_context.Entry(product).State = EntityState.Modified;
}
**_context.SaveChanges();**
}
public void DeleteProduct(Product product) {
_context.Products.Remove(product);
**_context.SaveChanges();**
}
}
public class EFDbContext : DbContext, IEFDbContext {
public DbSet<Product> Products { get; set; }
}
public interface IEFDbContext {
DbSet<Product> Products { get; set; }
}
The problem is EFProductRepository now expects an object implementing the IEFDbContext interface, but this interface does not define the SaveChanges method used at the lines I put between the asteriskes so the compiler starts complaining.
Defining the SaveChanges method on the IEFDbContext interface solves your problem:
public interface IEFDbContext {
DbSet<Product> Products { get; set; }
void SaveChanges();
}

Resources