Generic C# Repository, service and controller design - c#

Im learning about generics and was wondering about how a generic controller, service and ef core repo design would look like.
My case: lets say an incomming post request to add Smartphone and keyboard object to smartphone and keyboard tables
My repository setup is
public class GenericRepository<TEntity> : IGenericRepository<TEntity>
where TEntity : class, IProductGenericEntities
{
private readonly MyDbContext _db;
public GenericRepository(MyDbContext db)
{
_db = db;
}
public async Task<bool> AddProduct(TEntity entity)
{
try
{
_db.Set<TEntity>().AddAsync(entity);
return (await _db.SaveChangesAsync()) > 0;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
}
And my service
public class ProductService<TEntity> : IProductService<TEntity>
where TEntity : class
{
private readonly IGenericRepository<TEntity> _repo;
public ProductService(IGenericRepository<TEntity> repo)
{
_repo = repo;
}
public async Task<bool> AddProduct(TEntity entity)
{
return await _repo.AddProduct(entity);
}
}
And my Controller.cs
[ApiController]
[Route("api/[controller]")]
public class ProductController
{
private readonly IProductService<Keyboards> _keyService;
private readonly IProductService<Smartphones> _smartService;
public ProductController(IProductService<Keyboards> keyService, IProductService<Smartphones> smartService)
{
_keyService = keyService;
_smartService = smartService;
}
[HttpPost("Post-generated-items")]
public async Task<ActionResult> PostProducts(List<TEntity> entities)
{
foreach(var item in entities)
{
and sort the objects here
}
}
}
is it correct to initialize 2 of IProductServices and sort the incomming objects to their correct DI on the controller?
private readonly IProductService<Keyboards> _keyService;
private readonly IProductService<Smartphones> _smartService;
Is there a way to make it more automatic by detecting incomming object type and then initilize it all the way to repo so i dont need 2 of IProductService<>?
Or is it what im doing plain wrong with a generic service layor?

Ok, so your approach is completely valid, i would not worry about initializing two repositories, since they're essentially empty memory vise since they just take reference to existant DbContext which by default is registered with Scoped lifecycle.
There will be a time when you need to use several repositories to complete task at hand. I would suggest going for NON-generic services approach. This way you could make ProductsService which has all the needed generic repositories injected and can orchestrate their work to achieve use case goal.
You might as well look into UOW (Unit Of Work) pattern for even more complex situations.
Answering your question:
Is there a way to make it more automatic by detecting incomming object type and then initilize it all the way to repo so i dont need 2 of IProductService<>?
You might write some code that would do just that for you using Reflection, but i would suggest against doing so. By initializing your repositories specifically you make yourself less error prone and code becomes more self-documenting.
For example now you have a controller that asks DI for two services and that instantly set's you up for what's going on in this controller. On the other hand if everything would be generic, you would end up with one huge knot of spaghetti that "Does everything".

Related

C# pattern to extend NuGet interface

We have a common architecture for many of our projects, and this architecture requires some amount of boilerplate that is generic for every project. I'm trying to tie all this boilerplate into a single reusable NuGet package to make maintenance easier, but am running into issues with getting the DI to work with me.
Specifically, I'm struggling with the concept of services. In the NuGet, I'll have to define basic service interfaces so I can hook some pipelines to use these services. However, every application that will be using this NuGet will need to be able to extend these services with application specific methods.
Let's go over an example with the "User authentication pipeline", which should answer common questions like "Is this user in role x" and application specific questions like "Can this user modify z based on its owner y".
First, our application layer is structured based on CQRS using a common interface, which is implemented by every Query and Command:
public interface IApplicationRequestBase<TRet> : IRequest<TRet> { //IRequest from MediatR
Task<bool> Authorize(IUserServiceBase service, IPersistenceContextBase ctx);
void Validate();
}
IUserServiceBase is an interface providing access to the current user (I'm skipping the IPersistenceContextBase, which is just an empty interface):
public interface IUserServiceBase {
string? CurrentUserExternalId { get; }
bool IsUserInRole(params string[] roleNames);
...
And in the authentication pipeline
public class RequestAuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IApplicationRequestBase<TResponse> { //MediatR IPipelineBehavior
private readonly IUserServiceBase _userService;
private readonly IPersistenceContextBase _ctx;
public RequestAuthorizationBehaviour(IUserServiceBase userService, IPersistenceContextBase ctx) {
_userService = userService;
_ctx = ctx;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) {
if (await request.Authorize(_userService, _ctx)) {
return await next();
}
throw new UnauthorizedAccessException();
}
}
}
And finally the NuGet DI definition:
public static class DependencyInjection {
public static IServiceCollection AddApplicationInfra(this IServiceCollection services) {
...
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestAuthorizationBehaviour<,>));
return services;
}
}
All well and good in the NuGet side, now the application. This approach has me trying to extend the interfaces directly, and this is the easiest way to visualize what I wish to accomplish.
The application has a bunch of app-specific authorization checks, so we have a custom interface for that:
public interface IUserService : IUserServiceBase {
public string LocalUserIdClaimKey { get; }
Guid CurrentUserLocalId { get; }
/// <summary>
/// Shortcut for checking if the user has any role allowing read access to notifications
/// </summary>
bool CurrentUserCanReadNotifications { get; }
...
The UserService class implements all the functionality required in the IUserService interface, meaning the IUserServiceBase methods as well. It is defined in a different project (Infrastructure) than the interface (Application).
public class UserService : IUserService {
private readonly IHttpContextAccessor _contextAccessor;
public UserService(IHttpContextAccessor contextAccessor) {
_contextAccessor = contextAccessor;
}
public string? CurrentUserExternalId {
get {
var user = _contextAccessor.HttpContext.User;
if (user != null) {
return user.FindFirst(JwtClaimTypes.Subject)?.Value;
}
return null;
}
}
...
And finally, in our Command, where it all should come together:
public class UpdateSubsequentTreatmentFacilitiesCommand : IApplicationRequestBase<int> {
public async Task<bool> Authorize(IUserService service, IPersistenceContext ctx) {
//Application specific authorization check
}
public void Validate() {
}
Now, here we get a build error, stating that 'UpdateSubsequentTreatmentFacilitiesCommand' does not implement interface member 'IApplicationRequestBase<int>.Authorize(IUserServiceBase, IPersistenceContextBase)'. This is probably what I'm encountering here (though I still can't figure out why exactly...).
So, to reiterate:
Goal is to package common project boilerplate to a single NuGet
We need to be able to extend the services defined in the NuGet with application specific functionality
IApplicationRequestBase defines the type of the service parameter as IUserServiceBase, but UpdateSubsequentTreatmentFacilitiesCommand tried to use IUserService. OO programming and inheritance doesn't let you change method signatures.
If you can change IApplicationRequestBase, adding a TService generic parameter will let you get around it:
public interface IApplicationRequestBase<TRet, TService> : IRequest<TRet>
where TService is IUserServiceBase
{
Task<bool> Authorize(TService service, IPersistenceContextBase ctx);
void Validate();
}
public class UpdateSubsequentTreatmentFacilitiesCommand : IApplicationRequestBase<int, IUserService>
{
public async Task<bool> Authorize(IUserService service, IPersistenceContext ctx)
{
// method body
}
// rest of class
}
However, given that IUserService is an interface, if it is the only thing that extends/implements IUserServiceBase, then it sounds like a case of overengineering. There's a saying that perfection is the enemy of good. In other words, attempting to be too generic, too reusable, where it's not actually needed, is just slowing down progress. By all means, strive to have a high quality codebase, but you also need to be pragmatic.
If other apps that use IApplicationRequestBase have their own user service, not the same IUserService as your app, then you'll need to find another approach, given that C# is a strongly typed language. You could just typecast the IUserServiceBase into an IUserService in the method body. Rather than extending the interface, you could have an extension method. If you're creative, you might think of other approaches as well.
However, looking at IUserService, my guess is that it exists only to improve performance of checking certain commonly used roles. If I'm wrong and it's about convenience and not performance, then an extension method should be sufficient. If the concern is performance, then make sure that the implementation of IsUserInRole does caching. Looking up a string still won't be as fast as returning a property's backing field. But changing your software architecture to improve performance for something you haven't profiled to confirm that it is a performance bottleneck is the definition of premature optimization. If IsUserInRole does basic caching, you'll probably find the the performance is good enough, and helper/extension methods solve whatever readability/code quality issue you're trying to solve.

Autofac Lifetime scopes with external application

It'll probably take a few more questions to understand this subject quite well.
I've read a lot of documentations, yet I can't figure it out completely.
Say for example :
Singleton1 = resolved in lifetimeScope1
Singleton1 handles messages coming from Tcp/Ip.
DataService handles messages coming from web controllers, and it handles messages from Singleton1 (meaning - messages from Tcp/Ip).
In DataService there is a component registered as PerLifetimeScope (DbContext).
The things I can't understand is:
How will DataService know if it currently handles Http request (and should use instance 1 of DbContext) or Tcp/Ip (and use instance 2 of DbContext)?
My final goal is to configure Autofac to create me a new lifetime scope for each Tcp/Ip request I get as well. Just like it automatically does with Http requests!
Note:
If it'll be easier to understand with code, I'll put it, I just thought it could complicate things.
If anything I said was stupid or incorrect please fix me. Having hard time learning this.
Thanks!
Edit:
Adding code to explain my problem better:
public class Singleton1 : ISingleton1
{
private IDataService _dataService;
public Singleton1(IDataService dataService)
{
_dataService = dataService;
}
public void HandleExternalAddItemMessage(AddItemMessage msg)
{
_dataService.AddItem(msg.Item);
}
}
public interface IUnitOfWork : IDisposable
{
void Commit();
}
public EFUnitOfWork : IUnitOfWork
{
private DbContext _context;
public EFUnitOfWork(DbContext context)
{
_context = context;
}
public void Commit()
{
_context.SaveChanges();
}
}
public class DataService
{
private Func<Owned<IUnitOfWork>> _unitOfWorkFactory;
public (Func<Owned<IUnitOfWork>> unitOfWorkFactory)
{
_unitOfWorkFactory = unitOfWorkFactory;
}
// This method is called from both controllers and external Tcp/Ip calls. How do I do it - how do I set the context that is in the unit of work???
public void AddItem(Item item)
{
using(unitOfWork = _unitOfWorkFactory())
{
...
}
}
}
Startup code:
{
_container.RegisterType<IDataService, DataService>().SingleInstance();
_container.RegisterType<Singleton1, ISingleton1>().SingleInstance();
_container.RegisterType<EFUnitOfWork, IUnitOfWork>().PerDepnendecny();
_container.RegisterType<DbContext, MyDbContext>().InstancePerLifetimeScope();
}
This is a great article by Nicholas Blumhardt touching on many issues surrounding object lifetime including common pitfalls by developers attempting to own the object disposal in Autofac.
http://nblumhardt.com/2011/01/an-autofac-lifetime-primer/
I think what you're attempting to do is probably overly complicated and the model can probably be simplified however it is possible to register a new instance of an object by manually creating your own LifetimeScope.
public class Singleton1 : ISingleton1
{
private readonly ILifetimeScope _lifetimeScope;
public Singleton1(ILifetimeScope lifetimeScope)
{
_lifetimeScope = lifetimeScope;
}
public void HandleExternalAddItemMessage(AddItemMessage msg)
{
using(var scope = _lifetimeScope.BeginLifetimeScope())
{
var dataService = scope.Resolve<IDataService>();
dataService.AddItem(msg.Item);
}
}
}
As the article will explain you're now brewing up a nice batch of "scope soup".
Pay close attention to Nested lifetime scopes and Scope Sharing. Your usage of singletons is likely to trip you up.

Is it a good design to inject services as factories?

I have been reading Mark Seemann's excellent book on DI and hope to implement it in my next WPF project. However I have a query regarding object lifetime. So far, most examples seem to explain the repository pattern per request for MVC applications. In WPF there isn't really an alternative to this (I think). Seeing as the object graph of the entire application is constructed in the composition root, how can I make sure that my unit-of-work stuff is working properly. For example:
public class ContextFactory : IContextFactory
{
DBContext context;
public ContextFactory()
{
context = new MyDBContext();
}
public DBContext GetContext()
{
return context;
}
}
public class ItemOneRepository() : IItemOneRepository
{
DBContext context;
public ItemOneRepository(IContextFactory contextFactory)
{
this.context = contextFactory.GetContext();
}
public IEnumerable GetItems()
{
return context.ItemOnes;
}
}
public class ItemTwoRepository() : IItemTwoRepository
{
DBContext context;
public ItemTwoRepository(IContextFactory contextFactory)
{
this.context = contextFactory.GetContext();
}
public IEnumerable GetItemsByItemOneID(int itemOneID)
{
return context.ItemTwos.Where(i => i.itemOneID == itemOneID);
}
}
public class ThingService : IThingService
{
IItemOneRepository itemOneRepo;
IItemTwoRepository itemTwoRepo;
public ThingService(
IItemOneRepository itemOneRepository,
IItemTwoRepository itemTwoRepository)
{
itemOneRepo = itemOneRepository;
itemTwoRepo = itemTwoRepository;
}
public IEnumerable Things GetThing()
{
var ItemOnes = itemOneRepo.GetItems();
return ItemOnes.Select(i =>
new Thing(
i.FieldOne,
i.FieldFour,
itemRepoTwo.GetItemsByItemOneID(i.ID)
)
);
}
}
In this case the MyDBContext instance is created through ContextFactory in the composition root. ItemOneRepository and ItemTwoRepository are using the same unit-of-work (MyDBContext), but so is the rest of the application which is plainly wrong. What if I changed the repositories to accept a DBContext instead of ContextFactory and added a ThingServiceFactory class like:
public ThingServiceFactory : IThingServiceFactory
{
IContextFactory contextFactory;
public ThingServiceFactory(IContextFactory factory)
{
contextFactory = factory;
}
public IThingService Create()
{
MyDBContext context = contextFactory.Create();
ItemOneRepository itemOneRepo = new ItemOneRepository(context);
ItemOneRepository itemTwoRepo = new ItemTwoRepository(context);
return new ThingService(itemOneRepo, itemTwoRepo);
}
}
This is better as I can now pass the ThingServiceFactory to my ViewModels instead of an instance of ThingService (complete with DBContext). I can then create a unit-of-work whenever I need one and instantly dispose of it when I’ve finished. However, is this really the correct approach. Do I really need to write a factory for every unit-of-work operation I need? Surely there is a better way...
There's IMO only one good solution to this problem and that is to apply a command-based and query-based application design.
When you define a single ICommandHandler<TCommand> abstraction to define business transactions, you can inject closed versions of that interface into any form that needs this. Say for instance you have a "move customer" 'command' operation:
public class MoveCustomer
{
public Guid CustomerId;
public Address NewAddress;
}
And you can create a class that will be able to execute this command:
public class MoveCustomerHandler : ICommandHandler<MoveCustomer>
{
private readonly DBContext context;
// Here we simply inject the DbContext, not a factory.
public MoveCustomerHandler(DbContext context)
{
this.context = context;
}
public void Handle(MoveCustomer command)
{
// write business transaction here.
}
}
Now your WPF Windows class can depend on ICommandHandler<MoveCustomer> as follows:
public class MoveCustomerWindow : Window
{
private readonly ICommandHandler<MoveCustomer> handler;
public MoveCustomerWindows(ICommandHandler<MoveCustomer> handler)
{
this.handler = handler;
}
public void Button1Click(object sender, EventArgs e)
{
// Here we call the command handler and pass in a newly created command.
this.handler.Handle(new MoveCustomer
{
CustomerId = this.CustomerDropDown.SelectedValue,
NewAddress = this.AddressDropDown.SelectedValue,
});
}
}
Since MoveCustomerWindow lives for quite some time, it will drag on its dependencies for as long as it lives. If those dependencies shouldn't live that long (for instance your DbContext) you will be in trouble and Mark Seemann calls this problem Captive Dependency.
But since we now have a single ICommandHandler<TCommand> abstraction between our presentation layer and our business layer, it becomes very easy to define a single decorator that allows postponing the creation of the real MoveCustomerHandler. For instance:
public class ScopedCommandHandlerProxy<TCommand> : ICommandHandler<TCommand>
{
private readonly Func<ICommandHandler<TCommand>> decorateeFactory;
private readonly Container container;
// We inject a Func<T> that is able to create the command handler decoratee
// when needed.
public ScopedCommandHandlerProxy(
Func<ICommandHandler<TCommand>> decorateeFactory,
Container container)
{
this.decorateeFactory = decorateeFactory;
this.container = container;
}
public void Handle(TCommand command)
{
// Start some sort of 'scope' here that allows you to have a single
// instance of DbContext during that scope. How to do this depends
// on your DI library (if you use any).
using (container.BeginLifetimeScope())
{
// Create a wrapped handler inside the scope. This way it will get
// a fresh DbContext.
ICommandHandler<TCommand> decoratee =this.decorateeFactory.Invoke();
// Pass the command on to this handler.
decoratee.Handle(command);
}
}
}
This sounds a bit complex, but this completely allows you to hide the fact that a new DbContext is needed from the client Window and you hide this complexity as well from your business layer; you can simply inject a DbContext into your handler. Both sides know nothing about this little peace of infrastructure.
Of course you still have to wire this up. Without a DI library you do something like this:
var handler = new ScopedCommandHandlerProxy<MoveCustomerCommand>(
() => new MoveCustomerCommandHandler(new DbContext()),
container);
How to register this in a DI library is completely depending on the library of choice, but with Simple Injector you do it as follows:
// Register all command handler implementation all at once.
container.Register(
typeof(ICommandHandler<>),
typeof(ICommandHandler<>).Assembly);
// Tell Simple Injector to wrap each ICommandHandler<T> implementation with a
// ScopedCommandHandlerProxy<T>. Simple Injector will take care of the rest and
// will inject the Func<ICommandHandler<T>> for you. The proxy can be a
// singleton, since it will create the decoratee on each call to Handle.
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(ScopedCommandHandlerProxy<>),
Lifestyle.Singleton);
This is just one of the many advantages that this type of design gives you. Other advantages is that it makes much easier to apply all sorts of cross-cutting concerns such as audit trailing, logging, security, validation, de-duplication, caching, deadlock-prevention or retry mechanisms, etc, etc. The possibilities are endless.
ItemOneRepository and ItemTwoRepository are using the same
unit-of-work (MyDBContext), but so is the rest of the application
which is plainly wrong.
If your factory is registered with a transient lifecycle, you will get a new instance every time it's injected, which will be a new DBContext each time.
However, I would recommend a more explicit unit of work implementation:
public DBContext GetContext() //I would rename this "Create()"
{
return new MyDBContext();
}
And:
public IEnumerable GetItemsByItemOneID(int itemOneID)
{
using (var context = contextFactory.Create())
{
return context.ItemTwos.Where(i => i.itemOneID == itemOneID);
}
}
This gives you fine-grained control over the unit of work and transaction.
You might also ask yourself if the repositories are gaining you anything vs. just using the context directly via the factory. Depending on the complexity of your application, the repositories may be unnecessary overhead.

Design pattern for API entry point?

I'm creating a class library API that wraps business logic and access to an SQL Server database via Entity Framework 6.
I've designed it using the Unit of work and repository patterns.
The purpose is to make it easy to use and to unit test.
Business logic and validation will be performed in the service layer.
I will not use an IOC container because I feel that it would complicate the API
usage.
The project have 15 repositories and services
The current design is as follows:
Service Layer A -> Unit of work -> Repository A and or B
Service Layer B -> Unit of work -> Repository B and or A...
...
public class ServiceA : IServiceA, IService
{
private readonly IUnitOfWork unitOfWork;
public AssetService(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
...
public IList<DomainObjectA> GetAll()
{
return unitOfWork.RepositoryA.GetAll();
}
public void Dispose()
{
unitOfWork.Dispose();
}
...
}
public class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext context = new MyDbContext();
private IRepositoryA repositoryA;
private IRepositoryB repositoryB;
...
public IRepositoryA RepositoryA
{
get { return repositoryA = repositoryA ?? new RepositoryA(context); }
}
public IRepositoryB RepositoryB
{
get { return repositoryB = repositoryB ?? new RepositoryB(context); }
}
...
public void Save()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
public class RepositoryA : Repository, IRepositoryA
{
public RepositoryA(MyDbContext context)
: base(context) {}
public IList<DomainObjectA> GetAll()
{
return context.tblA.ToList().Select(x => x.ToDomainObject()).ToList();
}
...
}
Since this is an API that should be used by other projects, I need a nice and "fairly" easy to use interface for the user that consumes the API.
Because of this the UnitOfWork is created in this "public interface" between the user and the service layer, see below.
I also think it's best that the using-statement lies within the API so that the db-context is disposed properly and immediately after each service call.
I started out using the Proxy pattern for this:
Example:
public class ProxyA : Proxy, IServiceA
{
public IList<DomainObjectA> GetAll()
{
using (var service = GetService<ServiceA>())
return service.GetAll();
}
...
}
public abstract class Proxy
{
protected T GetService<T>() where T : IService
{
return (T)Activator.CreateInstance(typeof(T), new object[] { new UnitOfWork()});
}
}
But this would require me to create a proxy for each service. I could of course skip the service interface in the proxy and create a common proxy which handles all the services.
I've also looked at the Facade pattern but can't decide which pattern to use for this particular scenario.
My questions:
Is this a good approach or are there any other design patterns that will solve this problem?
Also, should there be one public API entry point or several, grouped by some business logic?
I see nothing wrong with your design and the patterns you use.
Regarding the proxy pattern it is your call if you want to use it or not. As you mention you have to create boiler plate code to create one for every service. If it is arguable if you want to use it only to hide the call to the db service, or you prefer to add that line of code every time you call the service (and make sure you do it to avoid leaks). Also you may consider if you may need to add extra functionality in the Proxy in the future, which will put extra weight to create the proxy option.
Regarding a single entry point or several, I would create a ServiceA, ServiceB, ServiceC etc (so several) grouped for business logic domains. Typically you'll have between 5-20 (just an approximate number to give an idea of the magnitude)
You may want to review the interface segregation principle which supports this idea
http://en.wikipedia.org/wiki/Interface_segregation_principle

Using a DbContext variable from one Controller to Another

Hi I am using MVC 4 and C# to develop an application that has two controllers:
The first one is called Business, it has a method called Create that calls a method called CreatePartner from another Controller named PartnerController.
public class BusinessController : Controller
{
private storeContext db = new storeContext();
public ActionResult Create(Business business)
{
//Some stuff here
PartnerController pt = new PartnerController();
pt.CreatePartner(int partner_id);
//Here is another stuff that uses db DbContext variable
return RedirectToAction("Index");
}
}
This is the second controller Called Partner
public class PartnerController : Controller
{
private storeContext db = new storeContext();
public void CreatePartner(int partner_id)
{
//Some interesting stuff
}
}
Each controllers has its Dispose() method
The Problem is: After I called the CreatePartnet method from Business controller I try to use the db variable again to save other data but it throws me the following exception:
The operation can not be completed because the DbContext has been disposed
-What is the best way to Use methods from one controller to another that has the same DbContext variable name?.
-Something strange happens: My stuff works locally but when I publish my code in the IIS server is when the app throws that exception.
Thanks!
Might I suggest an alternative approach?
Controllers are not very good places for business logic; that is they're not very good places for "doing stuff". It's often demonstrated in MVC tutorials and examples in this manner but it's really only good for getting into MVC quickly - it's not very good practice.
Furthermore Controllers aren't really supposed to have methods to be called - from themselves or called from another Controller. Controllers should really just contain their Actions.
Instead, extract your logic to an external class. A Service is a design pattern in which commonly used business logic is abstracted away. That way things can have a reference to the service and execute the logic without knowing anything about the implementation.
Observe:
IPartnerService
public interface IPartnerService
{
void CreatePartner(int partnerId);
}
DefaultPartnerService
public class DefaultPartnerService : IPartnerService
{
private StoreContext db;
public DefaultPartnerService()
{
db = new StoreContext();
}
public void CreatePartner(int partnerId)
{
// Something interesting
}
}
BusinessController
public class BusinessController : Controller
{
private IPartnerService _partnerService;
public BusinessController()
{
_partnerService = new DefaultPartnerService();
}
public ActionResult Create(Business business)
{
_partnerService.CreatePartner(business.PartnerId);
return RedirectToAction("Index");
}
}
Of course this approach is also greatly simplified for educational purposes. It's not best practice yet, but it might put you on the right track. Eventually you'll discover problems with this approach and you'll gravitate to reading about Repositories, Unit of Work, Dependency Injection and so on.

Categories

Resources