/URF.NET

Unit of Work & Repositories Framework - .NET 4.x, ASP.NET, Entity Framework. 100% extensible & lightweight. Live demo: https://goo.gl/Z7bVZk

Primary LanguageC#MIT LicenseMIT

URF.NET    NuGet Badge

Unit-of-Work & Repository Framework | Official URF, Trackable Entities & Design Factory Team

Docs: goo.gl/6zh9zp | Subscribe URF Updates: @lelong37 | NuGet: goo.gl/WEn7Jm

This framework (over 100K+ total downloads) minimizes the surface area of your ORM technology from disseminating in your application. This framework was deliberately designed to be lightweight, small in footprint size, and non-intimidating to extend and maintain. When we say lightweight we really mean lightweight, when using this framework with the Entity Framework provider there are only 10 classes. This lightweight framework will allow you to elegantly, unobtrusively, and easily patternize your applications and systems with Repository, Unit of Work, and Domain Driven Design. To use Generic Repositories or not? The framework allows the freedom of both, generic repositories and the ability to add in your own domain specific repository methods, in short Unit of Work with extensible and generic Repositories.

Live demo: longle.azurewebsites.net

Architecture Overview (Sample Northwind Application with URF Framework)

Architecture Overview (Sample Northwind Application & Framework)

URF sample and usage in ASP.NET Web API

public class CustomerController : ODataController
{
    private readonly ICustomerService _customerService;
    private readonly IUnitOfWorkAsync _unitOfWorkAsync;

    public CustomerController(
        IUnitOfWorkAsync unitOfWorkAsync,
        ICustomerService customerService)
    {
        _unitOfWorkAsync = unitOfWorkAsync;
        _customerService = customerService;
    }

    // GET: odata/Customers
    [HttpGet]
    [Queryable]
    public IQueryable<Customer> GetCustomer()
    {
        return _customerService.Queryable();
    }

    // GET: odata/Customers(5)
    [Queryable]
    public SingleResult<Customer> GetCustomer([FromODataUri] string key)
    {
        return SingleResult.Create(_customerService.Queryable().Where(t => t.CustomerID == key));
    }

    // PUT: odata/Customers(5)
    public async Task<IHttpActionResult> Put(string key, Customer customer)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (key != customer.CustomerID)
        {
            return BadRequest();
        }

        customer.TrackingState = TrackingState.Modified;
        _customerService.Update(customer);

        try
        {
            await _unitOfWorkAsync.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!CustomerExists(key))
            {
                return NotFound();
            }
            throw;
        }

        return Updated(customer);
    }

    // POST: odata/Customers
    public async Task<IHttpActionResult> Post(Customer customer)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        customer.TrackingState = TrackingState.Added;
        _customerService.Insert(customer);

        try
        {
            await _unitOfWorkAsync.SaveChangesAsync();
        }
        catch (DbUpdateException)
        {
            if (CustomerExists(customer.CustomerID))
            {
                return Conflict();
            }
            throw;
        }

        return Created(customer);
    }

    //// PATCH: odata/Customers(5)
    [AcceptVerbs("PATCH", "MERGE")]
    public async Task<IHttpActionResult> Patch([FromODataUri] string key, Delta<Customer> patch)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Customer customer = await _customerService.FindAsync(key);

        if (customer == null)
        {
            return NotFound();
        }

        patch.Patch(customer);
        customer.TrackingState = TrackingState.Modified;

        try
        {
            await _unitOfWorkAsync.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!CustomerExists(key))
            {
                return NotFound();
            }
            throw;
        }

        return Updated(customer);
    }

    // DELETE: odata/Customers(5)
    public async Task<IHttpActionResult> Delete(string key)
    {
        Customer customer = await _customerService.FindAsync(key);

        if (customer == null)
        {
            return NotFound();
        }

        customer.TrackingState = TrackingState.Deleted;

        _customerService.Delete(customer);
        await _unitOfWorkAsync.SaveChangesAsync();

        return StatusCode(HttpStatusCode.NoContent);
    }

    // GET: odata/Customers(5)/CustomerDemographics
    [Queryable]
    public IQueryable<CustomerDemographic> GetCustomerDemographics([FromODataUri] string key)
    {
        return
            _customerService.Queryable()
                .Where(m => m.CustomerID == key)
                .SelectMany(m => m.CustomerDemographics);
    }

    // GET: odata/Customers(5)/Orders
    [Queryable]
    public IQueryable<Order> GetOrders([FromODataUri] string key)
    {
        return _customerService.Queryable().Where(m => m.CustomerID == key).SelectMany(m => m.Orders);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _unitOfWorkAsync.Dispose();
        }
        base.Dispose(disposing);
    }

    private bool CustomerExists(string key)
    {
        return _customerService.Query(e => e.CustomerID == key).Select().Any();
    }
}

Implementing Domain Logic with URF Service Pattern

All methods that are exposed from Repository<TEntity> in Service<TEntity> are overridable to add any pre or post domain/business logic. Domain business logic should be in the Service layer and not in Controllers or Repositories for separation of concerns.

  1. Create an Interface e.g. ICustomerService, which should always inherit IService<TEnttiy> e.g. IService<Customer>
  2. Implement the concrete implementation for your Interface e.g. CustomerService which implements ICustomerService
  3. If using DI & IoC, don't forget to wire up the binding of your Interface and Implementation e.g. container.RegisterType<ICustomerService, CustomerService>(), see next example for more details on wiring up DI & IoC.
public interface ICustomerService : IService<Customer>
{
    decimal CustomerOrderTotalByYear(string customerId, int year);
    IEnumerable<Customer> CustomersByCompany(string companyName);
    IEnumerable<CustomerOrder> GetCustomerOrder(string country);
}


public class CustomerService : Service<Customer>, ICustomerService
{
    private readonly IRepositoryAsync<Customer> _repository;

    public CustomerService(IRepositoryAsync<Customer> repository) : base(repository)
    {
        _repository = repository;
    }

    public decimal CustomerOrderTotalByYear(string customerId, int year)
    {
        // add any domain logic here
        return _repository.GetCustomerOrderTotalByYear(customerId, year);
    }

    public IEnumerable<Customer> CustomersByCompany(string companyName)
    {
        // add any domain logic here
        return _repository.CustomersByCompany(companyName);
    }

    public IEnumerable<CustomerOrder> GetCustomerOrder(string country)
    {
        // add any domain logic here
        return _repository.GetCustomerOrder(country);
    }

    public override void Insert(Customer entity)
    {
        // e.g. add any business logic here before inserting
        base.Insert(entity);
    }

    public override void Delete(object id)
    {
        // e.g. add business logic here before deleting
        base.Delete(id);
    }
}

URF Sample DI & IoC Configuration with Framework of your choice, exampled here using Microsoft Unity DI & IoC

UnityConfig.cs

public class UnityConfig
{
    private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
    {
        var container = new UnityContainer();
        RegisterTypes(container);
        return container;
    });

    public static IUnityContainer GetConfiguredContainer()
    {
        return container.Value;
    }

    public static void RegisterTypes(IUnityContainer container)
    {
        container
            // Register DbContext instead of IDataDataContext, which is now obsolete.
            //.RegisterType<IDataContextAsync, NorthwindContext>(new PerRequestLifetimeManager())
            .RegisterType<DbContext, NorthwindContext>(new PerRequestLifetimeManager())
            .RegisterType<IUnitOfWorkAsync, UnitOfWork>(new PerRequestLifetimeManager())
            .RegisterType<IRepositoryAsync<Customer>, Repository<Customer>>()
            .RegisterType<IRepositoryAsync<Product>, Repository<Product>>()
            .RegisterType<IProductService, ProductService>()
            .RegisterType<ICustomerService, CustomerService>()
            .RegisterType<INorthwindStoredProcedures, NorthwindContext>(new PerRequestLifetimeManager())
            .RegisterType<IStoredProcedureService, StoredProcedureService>();
    }
}

Roadmap: URF .NET Core (alpha-release) ETA: February 2018

  • Intial URF .NET Core release will ship with default provider Entity Framework Core, as mentioned, overtime we'll bring on more providers.
  • First and foremost, learning from current .NET URF through comments, suggestions, real world feedback from enterprise level teams, URF team's actual experiences working with other teams/projects as a guidance practice to improve while implementing new URF .NET Core version. This list is quite the list, far to much to list here, however we'll include and generate a list specific to each of our releases moving forward in URF .NET Core versions.
  • Easily implement customizations e.g. adding audit trails with adding a simple abstraction such as IAuditable to our Entity.cs base class for your Entities.
  • Quickly get up and running with URF under a minute, we've recently just started publishing URF to NuGet, we're already approaching 1000 downloads. Shipping URF as NuGet packages is definitely something we did not realize as an added value. We were mistaken that most teams would like to clone/fork and include URF projects directly in their solution for infinite customizations possibilities however this was actually not the case. In reality, most teams use URF as is, right out the box, with zero customizations.
  • Although we'll be shipping URF as NuGet packages, new .NET Core version we'll have a lazer focus on modularity. Meaning we will several URF pacakges to further open opportunities such as creating other providers for URF other than Entity Framework Core e.g. NHibernate, MongoDB, DocumentDb, Cosmos, etc...
  • Tooling, tooling, tooling. The new URF .NET Core version architeture and design will set the stage for tooling such as code generation with templating with .NET Handlbars and/or Razor. We are considering to have full parity across different runtimes .NET Core is able to run in e.g. Mac, Linux, Windows, etc. We will performing rigurous due-dilligence to ensure this (XPLAT).
  • URF .NET Core will be a completey seperate rewrite and entirely new product, will have deep throught in it's new arcitecture and design for microservices world e.g. Azure Functions, AWS Lambdas, etc...
  • URF Sample will be updated to Angular v5 and Kendo UI for Angular (Kendo UI for Angular is also a complete new product line, which is now built from the ground up with Angular vs. Kendo UI for AngularJS which was jQuery widgets wrapped with AngularJS directives)
  • URF Sample app will also demonstrate lastest versionf of OData running in ASP.NET Core Web API
  • Trackable Entities Team has already been included in current version of .NET URF, and will also be supported in .NET Core version. This is a very powerful feature, which allows Entites to self track their state (e.g. Added, Modified, Delete, None) without DbContext. This is extremely helful for tracking object graphs and maintaning all entity states in a complex object graph accross physical boundaries, again while doing so with zero dependency on DbContext and more importantly doing so with zero dependency on Entity Framework or any other data provider. We've already released Trackable Entities for TypeScript/Javascript as an NPM package, allowing teams to take advantage of self Tracking Entities not only across physical boundaries but across different runtimes as well. Most common usecase for this is SPA (Angular, ReactJS, Aurelia, Vue.js, etc...) while using Trackable Entities for TypeScript /Javascript and passing object graphs with their states to Web APIs using URF.
  • Many more improvements e.g. performance, fusioning on a few design patterns in URF's core e.g. striking the best compromise and balance between Visitor Pattern and Recursion when traversing any object graphs, and so much more and to many to list here, again, we'll include a list with every release in each versions release notes.

URF Features & Benefits

  • Repository Pattern - 100% extensible ready
  • Unit of Work Pattern - 100% atomic & transaction ready
  • Service Pattern - pattern for implementing business, domain specific logic with 100% separation of concerns e.g. ICustomerService, IOrderService
  • Minimize footprint of your ORM and data access layer
  • DI & IoC 100% ready
  • REST, Web API & OData 100% ready
  • 100% testable & mockable
  • 100% support for Stored Procedures
  • Repository Pattern supports IEnumerable and/or IQueryable
  • Trackable Entities - When using URF, entities are 100% automatically self tracking, states are automatically trackable (New, Updated, Deleted, Unchanged), allowing entity or complex object graph states to be trackable across physical boundaries and application layers. Entity state can be tracked in Angular all the way to Web API.
  • Full (Northwind) sample application (Angular, Web API, OData, Entity Framework, SQL)
  • 100% unit tests & integration tests - Integration tests, will drop and re-create NorthwindTest database everytime integration tests are ran