user2541688
user2541688

Reputation:

Moving logic from controller action to a "service layer" without using IoC/DI, UoW and repository patterns in ASP.NET MVC

Recently i've working on an ASP.NET MVC5 project, i dived right in and wrote all my logic right in the action method and after doing this for a few controllers i've noticed that i have been duplicating certain business rules and could do with being lifted out and shared between controllers.

From what i've read, the m in asp.net mvc is a layer consisting of entities, viewmodels and services, the latter holding all your shared business logic

now i'm trying to keep things as simple as possible, i don't want to wrap entity framework in some UoW/Repo and use it as-is, it is very unlikely that i'll stop using entity framework in this applications lifetime and i'm not doing unit tests and i'm not that bothered about tight coupling, so i don't feel i need an IoC container, but all the tutorials i've read seems to use either an IoC container or wraps dbcontext/ef in a UoW/Repo.

I've read that there should only be a single instance (which in the tutorials i've seen is managed via an IoC container) of DbContext per httprequest, would this be achieved by instantiating it in the controllers constructor and then passing that reference to any services needed in the controller and then disposing it at the end of the request? is this the correct way of managing dbcontext?

Controller example:

public class SupplierController : Controller
{
    private Meerkat3Context context;
    private SupplierService supplierService;
    private ratingService SupplierRatingService;

    public SupplierController()
    {
        // instantiate the dbcontext
        this.context = new Meerkat3Context();

        // pass dbcontext into the constructors of my services
        this.supplierService = New SupplierService(context);
        this.ratingService = New SupplierRatingService(context);
    }
    public ActionResult Index(Guid id)
    {
        var supplier = supplierService.getSupplier(id);
        // construct viewmodel
        return new SupplierIndexViewModel()
        {
            SupplierId = supplier.Id,
            SupplierName = supplier.Name,

            SupplierRating = ratingService.getHighestRating(supplier.Id),
            NearbySuppliers = supplierService.getNearbySuppliers(supplier.Id),
            // etc
        };
    }
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            context.Dispose();
        }
        base.Dispose(disposing);
    }
}

Service examples:

public class SupplierService
{
    private Meerkat3Context context;

    public SupplierService(Meerkat3Context context)
    {
        this.context = context;
    }
    public Supplier getSupplier(Guid id)
    {
        return context.Where(x => x.SupplierId == id)
                      .FirstOrDefault()
                      .Select(x => new Supplier()
                      {
                           Id = x.Id,
                           Name = x.Name
                           // etc
                      });
    }
    public Supplier getNearbySuppliers(Guid id)
    {
        return context.Suppliers.Where(x => context.SupplierAddresses
                                .Where(y => y.AddressTypeId == AddressTypes.Location)
                                .Select(z => z.Address.TownCity)
                                .Contains(x.SupplierAddresses
                                           .Where(y => y.AddressTypeId == AddressTypes.Location)
                                           .FirstOrDefault()
                                           .Address.TownCity)
                                 );
    }
}

public class SupplierRatingService
{
    private Meerkat3Context context;

    public RatingService(Meerkat3Context context)
    {
        this.context = context;
    }
    public SupplierRating getHighestRating(Guid id)
    {
        return context.SupplierRating
                      .Where(x => x.SupplierId == id)
                      .OrderBy(x => x.RatingValue)
                      .FirstOrDefault()
    }
}

Upvotes: 1

Views: 2060

Answers (3)

Adrian Iftode
Adrian Iftode

Reputation: 15663

With an IoC container your controller would look like.

 public class SupplierController : Controller
    {
        //the controller doesn't need to create the db context now
        //this concern is handled now by the IoC container

        private SupplierService supplierService;
        private RatingService SupplierRatingService;

        public SupplierController(SupplierService supplierService, RatingService ratingService)
        {
            // we don't have to pass the db context now to services, since we retrieve the services from the IoC container. The IoC container auto-wires the services 
            this.supplierService = supplierService;
            this.ratingService = ratingService;
        }
        public ActionResult Index(Guid id)
        {
            var supplier = supplierService.getSupplier(id);
            // construct viewmodel
            return new SupplierIndexViewModel()
            {
                SupplierId = supplier.Id,
                SupplierName = supplier.Name,

                SupplierRating = ratingService.getHighestRating(supplier.Id),
                NearbySuppliers = supplierService.getNearbySuppliers(supplier.Id),
                // etc
            };
        }
        // the controller doesn't need a dispose method since the IoC container will dispose the dbcontext for us
    }

You don't have to follow the Dependency Inversion Principle to use an IoC container, but you can count on a IoC container to create and to manage the lifetime of your services objects.

You configure the IoC container to create a single instance of a dbcontext per a web request. The good part is this is configurable and, if you later decide is better to have a different dbcontext instance per service, then you just change this in a single place and not in every controller and every action method where you use the new keyword.

Upvotes: 0

Ingenioushax
Ingenioushax

Reputation: 718

If you're trying to strip out the repeated code, this should be fairly simple. In VS you can highlight a section of code and use the hotkeys Ctrl+R,Ctrl+M for refactor, or you can do so by using the context menu highlight code section > right-click > Refactor > Extract Method.

If the usage of the repeated code can be replicated for all entities, you can create a static class that houses this common functionality.

public sealed class Utlities 
{
    public static CommonA() { }
    public static CommonB() { }
    ... etc...
}

And you can call them easily using Utilities.CommonA(). Another way to reduce redundancy is to use ViewModels. Basically create a copy of the entity you want to use as a ViewModel with additional properties required for the View. If the models have data in common, create a base class to inherit those commonalities from.

public class BaseViewModel 
{
    public Type Prop {get; set;}
    public Type Prop2 {get; set;}
    ...etc...
}

public class SpecificViewModel : BaseViewModel
{
    SpecificViewModel(Type Prop, Type Prop2) : base(Prop, Prop2, ...etc...) { }
    public Type specificProp {get; set;}
    ...etc...
}

If I understood your question correctly that is.

Upvotes: 1

Sergei Rogovtcev
Sergei Rogovtcev

Reputation: 5832

If what you want is simply moving out reusable logic then your approach is good enough. But please bear in mind that:

  1. it isn't testable (you can't isolate your dependencies and

  2. You're still duplicating the logic, even if it's simply an object construction logic (e.g., in every controller where you need SupplierService you'll have to instantiate Meerkat3Context as well). That can get quite tedious (and that's where DI comes in handy)

Upvotes: 0

Related Questions