locked
UNIT OF WORK & REPOSITORY PATTERN & ADO.NET STORED PROCEDURES RRS feed

  • Question

  • User-108026213 posted

    INTRO:  I'm fairly new to programming still and in a class I took, we used quite a bit of Repository pattern and I got experience using ADO.NET with stored procedures.  I want to build upon what I have already learned by implementing the Unit Of Work pattern along with Repository Pattern using ADO.NET stored procedures.  I want to achieve the following goal below.

    • I'm not using Generic repositories at this time.  I'm not there yet. 
    • I am also aware that Entity Framework is preferred, so I've been told, but again just not there yet. 
    • I created a DvdLibrary where I can Add, Update, Edit, and Delete dvds (standard CRUD). 
    • I have 4 tables.  Dvd, Rating, Director, and DvdDirector.
    • Created stored procedures  

    I put some questions and comments in the code.  I did not list all my classes just the main ones relating to UnitOfWork and Database Context.  I just want to confirm I and Committing, Rollingback and applying the use of the single database context correctly.  Any help you can provide is greatly appreciated!  Thank you!

    GOAL:  While repositories are used to create an abstraction layer between the data layer and the business layer of an application, the unit of work pattern coordinates the work of multiple repositories by enforcing a single database context class shared by all of them. The unit of work tracks changes to the objects extracted from a repository and persist any of these changes when we tell the unit of work to commit the changes.

    IUnitOfWork

    public interface IUnitOfWork : IDisposable
        {   //The purpose of IUnitOfWork is to define the contracts of the repositories we want to expose based on the entities we have in our ADOContext
    
            IDirectorRepository Director { get; }
            IDvdDirectorRepository DvdDirector { get; }
            IDvdRepository Dvd { get; }
            IRatingRepository Rating { get; }
    
            void Complete(); //call this method to commit our changes to the database
            
        }
    
    //IDisposable - the primary use of this interface is to release unmanaged resources. The garbage collector automatically releases the memory allocated to a managed object when that object is no longer used. However, it is not possible to predict when garbage collection will occur.
    
    //Use the Dispose method of this interface to explicitly release unmanaged resources in conjunction with the garbage collector. The consumer of an object can call this method when the object is no longer needed.

    UnitOfWorkADO - implements IUnitOfWork - Unsure if I did the Complete or Dispose method correctly

    public class UnitOfWorkADO : IUnitOfWork
        {
            private readonly IContext _context;
            private IDbTransaction _transaction;
    
            private IDvdRepository _dvd;
            private IDvdDirectorRepository _dvdDirector;
            private IRatingRepository _rating;
            private IDirectorRepository _director;
    
            public UnitOfWorkADO(IContext context)
            {
                //ContextADO object is injected and stored in a field
                _context = context;
    
                //storing the transaction - is it better to just use the context here?
                _transaction = context.Transaction;
            }
    
            //Each repository property checks whether the repository already exists. If not, it instantiates the repository, passing in the context instance. As a result, all repositories share the same context instance.
    
            //I'm using a factory to return the type of repository based on the RepositoryType key/value in Web.config.  Not sure this is actually needed because the UnitOfWork is specific to ADO
    
            public IDirectorRepository Director
            {
                get
                {
                    return _director ?? (_director = DirectorRepositoryFactory.GetRepository(_context));
                }
            }
    
            public IDvdDirectorRepository DvdDirector
            {
                get
                {
                    return _dvdDirector ?? (_dvdDirector = DvdDirectorRepositoryFactory.GetRepository(_context));
                }
            }
    
            public IDvdRepository Dvd
            {
                get
                {
                    return _dvd ?? (_dvd = DvdRepositoryFactory.GetRepository(_context));
                }
            }
    
            public IRatingRepository Rating
            {
                get
                {
                    return _rating ?? (_rating = RatingRepositoryFactory.GetRepository(_context));
                }
            }
    
            //Attempt to commit the changes else Rollback
            public void Complete()
            {
                try
                {
                    if (_transaction == null)
                    {
                         throw new InvalidOperationException("The transaction has already been committed");
                    }
                    else
                    {
                        _transaction.Commit();
                        _transaction = null;
                    }  
                    
                }
                catch (Exception ex)
                {
                    _transaction.Rollback();
                    throw ex;
                }
                
            }
    
            //using "Using" statement when calling UnitOfWorkFactory.Create() method
            //The purpose of Using statement is that when control will reach end of using it will dispose that object of using block and free up memory. its purpose is not only for auto connection close, basically it will dispose connection object and obviously connection also closed due to it.
            public void Dispose()
            {
                if (_transaction != null)
                {
                    _transaction.Dispose();
                    _transaction = null;
                }
    
            }
        }
    public interface IContext
        {
            IDbTransaction Transaction { get; }
            IDbCommand CreateCommand();
        }

    ContextADO - Implement IContext - should IContext implement IDisposable or is how I have it okay?

    public class ContextADO : IContext
        {
            //store our connection
            private readonly IDbConnection _connection;
    
            public ContextADO(IDbConnection connection)
            {
                //set connection and store in field
                _connection = connection;
                //Setting Transaction public property from IContext
                //The _connection.BeginTransaction() - begins a database transaction and associates it with the connection object.
                Transaction = _connection.BeginTransaction();
            }
    
            //Putting the Transaction into a public property so it can be passed to UnitOfWorkADO
            public IDbTransaction Transaction { get; private set; }
    
            public IDbCommand CreateCommand()
            {
                //Creates and returns a Command object associated with the connection.
                var cmd = _connection.CreateCommand();
    
                //cmd.Transaction = Gets or sets the transaction within which the Command object of a .NET Framework data provider executes.
                cmd.Transaction = Transaction;
    
                return cmd;
            }
    
            //Should IContext implement IDisposable like IUnitOfWork and then I can have a Dispose method???
            //public void Dispose()
            //{
            //    _connection.Dispose();
            //}
        }

    Example HomeController Action which calls on UnitOfWorkFactory

           [HttpPost]
            public ActionResult Add(DvdAddViewModel model)
            {
                if (!ModelState.IsValid)
                {                
                    using (var uow = UnitOfWorkFactory.Create())
                    {
                        model.Ratings = uow.Rating.GetAll();
                    }
    
                    return View(model);
                }
                else
                {
                    try
                    {
                        using (var uow = UnitOfWorkFactory.Create())
                        {
                            uow.Dvd.Insert(model.Dvd);
                            uow.Director.Insert(model.Director);
    
                            var dvdDirector = new DvdDirector()
                            {
                                DvdId = model.Dvd.DvdId,
                                DirectorId = model.Director.DirectorId
                            };
                            
                            uow.DvdDirector.Insert(dvdDirector);
                            uow.Complete();
    
                            return RedirectToAction("Index");
                        }
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }
                
            }

    UnitOfWork Factory

    //UnitOfWorkFactory determines which UnitOfWork to return based on the RepositoryType key/value in Web.config
            public static IUnitOfWork Create()
            {
                //Requests a Context which will be used with UnitOfWork
                var context = ContextFactory.Create();
    
                switch (Settings.GetRepositoryType())
                {
                    case "ADO":
                        return new UnitOfWorkADO(context);
                    default:
                        throw new Exception("Could not find valid RepositoryType configuration value");
                }
            }

    ContextFactory

    public static class ContextFactory 
        {
            //ContextFactory determines which Context to return based on the RepositoryType key/value in Web.config
            public static IContext Create()
            {            
                switch (Settings.GetRepositoryType())
                {
                    case "ADO":
                        return new ContextADO(OpenConnection()); //Open SqlConnection is passed into the Context
                    default:
                        throw new Exception("Could not find valid RepositoryType configuration value");
                }
            }
    
            //Creates a new SqlConnection and opens that connection
            private static IDbConnection OpenConnection()
            {
                var cn = new SqlConnection(Settings.GetConnectionString());
                cn.Open();
                return cn;
            }
        }

    One thing I did notice, that if the transaction fails (Mainly because I'm slowly stepping through the code in debug mode), nothing writes to the database, which is what is supposed to happen, however, the Id of the Dvd in the database is incremented.  Why would this happen?

    Thank you again for you help!

    - Adam

    Saturday, August 25, 2018 2:14 AM

Answers

  • User1120430333 posted

    One thing I did notice, that if the transaction fails (Mainly because I'm slowly stepping through the code in debug mode), nothing writes to the database, which is what is supposed to happen, however, the Id of the Dvd in the database is incremented. Why would this happen?

    About that, the Identity Auto Increment Count for a primary-key column is assigned by the Database engine,  and once it has been assigned to the record, the count is incremented by one.  There is no rollback on the count. The number is not used again if it has been assigned no matter if the transaction is successful,  or it is not successful.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, August 25, 2018 11:41 AM

All replies

  • User1120430333 posted

    To be honest, I am not a fan of the UoW or the Repository pattern.  However,  I have seen and used repositories based on business needs where there  is one repository per business need and the repository object were able to communicate with other using the behaviors of other repository objects, which used a persistence object in a persistence lawyer such as a Data Access Layer. 

     A generic repository using EF is kind of frowned upon now of days, but that's up to you and your needs if you want to use a generic repository using EF. 

    https://www.infoworld.com/article/3117713/application-development/design-patterns-that-i-often-avoid-repository-pattern.html

    https://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework-core/

    Myself, I continue to use the Data Access Object pattern,  and it doesn't matter if I am using EF or using ADO.NET with MS SQL Command objects, parameterized in-line T-SQL or parametrized  stored procedure, datareader  and custom type or a collection of custom types such as a Data Transfer Object in the Data Access Layer in the usage of the DAO pattern for CRUD operations with the database.

    https://blog.sapiensworks.com/post/2012/11/01/Repository-vs-DAO.aspx

    https://www.tutorialspoint.com/design_pattern/data_access_object_pattern.htm

    https://en.wikipedia.org/wiki/Data_transfer_object

    https://www.codeproject.com/Articles/1050468/Data-Transfer-Object-Design-Pattern-in-Csharp

    I also notice that you using try/catch in the code. You should consider using global exception handling, log the error and present a user friendly message. By using a global exception handling, you can remove all try/catches out of the code and let the GEH take care of the exception.

    https://stackify.com/csharp-catch-all-exceptions/

     You see an ASP.NET WebAPI using the DAL that is using the DAO pattern, and the DTO pattern is being used. Hey don't pay me no mind, and you do what's best for your needs, but you do have an alternative to the UoW and the Repository pattern.

    using System.Collections.Generic;
    using DAL;
    using Entities;
    using Microsoft.AspNetCore.Mvc;
    
    namespace ProgMgmntCore2Api.Controllers
    {
        [Produces("application/json")]
        [Route("api/[controller]")]
        [ApiController]
    
        public class ProjectController : ControllerBase, IProjectController
        {
            private readonly IDaoProject _daoProject;
    
            public ProjectController(IDaoProject daoProject)
            {
                _daoProject = daoProject;
            }
    
            [HttpGet]
            [Route("GetProjById")]
            public DtoProject GetProjectById(int id)
            {
                return  _daoProject.GetProjectById(id);
            }
            
            [HttpGet]
            [Route("GetProjsByUserId")]
            public List<DtoProject> GetProjectsByUserId(string userid)
            {
                return _daoProject.GetProjectsByUserId(userid);
            }
    
            [HttpPost]
            [Route("CreateProject")]
            public void Post_CreateProject(DtoProject dto)
            {
                _daoProject.CreateProject(dto);
            }
    
            [HttpPost]
            [Route("DeleteProject")]
            public void Post_DeleteProject(DtoId dto)
            {
                _daoProject.DeleteProject(dto.Id);
            }
    
            [HttpPost]
            [Route("UpdateProject")]
            public void Post_UpdateProject(DtoProject dto)
            {
                _daoProject.UpdateProject(dto);
            }
        }
    }
    
    ===============================================================
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Entities;
    
    namespace DAL
    {
        public interface IDaoProject
        {
            DtoProject GetProjectById(int id);
            List<DtoProject> GetProjectsByUserId(string userid);
            void CreateProject(DtoProject dto);
            void UpdateProject(DtoProject dto);
            void DeleteProject(int id);
        }
    }
    --------------------------------------------------------
    
    using System.Collections.Generic;
    using System.Linq;
    using System.Transactions;
    using DAL.Models.DB;
    using Entities;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Options;
    
    namespace DAL
    {
        public class DaoProject :IDaoProject
        {
            private readonly IOptions<ConnectionStrings> _options;
            
            public DaoProject(IOptions<ConnectionStrings> options)
            {
                _options = options;
            }
            public DtoProject GetProjectById(int id)
            {
                var dto = new DtoProject();
    
                using (var context = new ProjectManagementContext(_options))
                {
                    var project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault();
    
                    if (project == null) return dto;
                    dto.ProjectId = project.ProjectId;
                    dto.ClientName = project.ClientName;
                    dto.ProjectName = project.ProjectName;
                    dto.Technology = project.Technology;
                    dto.ProjectType = project.ProjectType;
                    dto.UserId = project.UserId;
                    dto.StartDate = project.StartDate;
                    dto.EndDate = project.EndDate;
                    dto.Cost = project.Cost;
                }
    
                return dto;
            }
    
            public List<DtoProject> GetProjectsByUserId(string userid)
            {
                var dtos = new List<DtoProject>();
    
                using (var context = new ProjectManagementContext(_options))
                {
                    
                    dtos = (from a in context.Projects.Where(a => a.UserId.Contains(userid))
                        select new DtoProject
                        {
                            ProjectId = a.ProjectId,
                            ClientName = a.ClientName,
                            ProjectName = a.ProjectName,
                            Technology = a.Technology,
                            ProjectType = a.ProjectType,
                            UserId = a.UserId,
                            StartDate = a.StartDate,
                            EndDate = a.EndDate,
                            Cost = a.Cost
                        }).ToList();
                }
    
                return dtos;
            }
    
            public void CreateProject(DtoProject dto)
            {
                using (var context = new ProjectManagementContext(_options))
                {
                    var project = new Projects
                    {
                        ClientName = dto.ClientName,
                        ProjectName = dto.ProjectName,
                        Technology = dto.Technology,
                        ProjectType = dto.ProjectType,
                        UserId = dto.UserId,
                        StartDate = dto.StartDate,
                        EndDate = dto.EndDate,
                        Cost = dto.Cost
                    };
    
                    context.Projects.Add(project);
                    context.SaveChanges();
               }
            }
    
            public void UpdateProject(DtoProject dto)
            {
                var project = new Projects();
                
                using (var context = new ProjectManagementContext(_options))
                {
                   project = (context.Projects.Where(a => a.ProjectId == dto.ProjectId)).SingleOrDefault();
                }
    
                if (project != null)
                {
                    project.ClientName = dto.ClientName;
                    project.ProjectName = dto.ProjectName;
                    project.Technology = dto.Technology;
                    project.ProjectType = dto.ProjectType;
                    project.UserId = dto.UserId;
                    project.StartDate = dto.StartDate;
                    project.EndDate = dto.EndDate;
                    project.Cost = dto.Cost;
                }
    
                using (var dbcontext = new ProjectManagementContext(_options))
                {
                    if (project == null) return;
                    dbcontext.Entry(project).State = EntityState.Modified;
                    dbcontext.SaveChanges();
                }
            }
    
            public void DeleteProject(int id)
            {
                Projects project;
    
                using (var context = new ProjectManagementContext(_options))
                {
                   project = (context.Projects.Where(a => a.ProjectId == id)).SingleOrDefault();
                }
    
                if (project == null) return;
    
                using (var newContext = new ProjectManagementContext(_options))
                {
                   
                    var tasks = new DaoTask(_options).GetTasksByProjectId(project.ProjectId);
                    using (TransactionScope scope = new TransactionScope())
                    {
                        foreach (var task in tasks)
                        {
                            new DaoTask(_options).DeleteTask(task.TaskId);
                        }
    
                        newContext.Entry(project).State = EntityState.Deleted;
                        newContext.SaveChanges();
    
                        scope.Complete();
                    }
                }
            }
        }
    }
    ===============================================
    using System;
    
    namespace Entities
    {
        public class DtoProject
        {
            public int ProjectId { get; set; }
            public string ClientName { get; set; }
            public string ProjectName { get; set; }
            public string Technology { get; set; }
            public string ProjectType { get; set; }
            public string UserId { get; set; }
            public DateTime StartDate { get; set; }
            public DateTime EndDate { get; set; }
            public decimal Cost { get; set; }
        }
    }
    


    using EF and using the DTO pattern. All DTO(s) are kept in a classlib project called Entities, and all projects that need to know about the DTO(s) have reference to the Entities project.  You'll also notice how the DAOProject calls upon the DAOTask to complete the delete process. 

    Saturday, August 25, 2018 11:33 AM
  • User1120430333 posted

    One thing I did notice, that if the transaction fails (Mainly because I'm slowly stepping through the code in debug mode), nothing writes to the database, which is what is supposed to happen, however, the Id of the Dvd in the database is incremented. Why would this happen?

    About that, the Identity Auto Increment Count for a primary-key column is assigned by the Database engine,  and once it has been assigned to the record, the count is incremented by one.  There is no rollback on the count. The number is not used again if it has been assigned no matter if the transaction is successful,  or it is not successful.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, August 25, 2018 11:41 AM
  • User475983607 posted

    IMHO, if you learn Entity Framework there is not need to write all the repo code as EF is a Unit of Work pattern.

    Saturday, August 25, 2018 12:57 PM
  • User-108026213 posted

    DA924

    Thank you for providing an alternative methodology, all the links, and an example.  Great stuff!  Being new to programming, I know one thing, I have a lot to learn, lol.  The class I took introduced us to the repository pattern and while researching, I stumbled upon the unit of work.  Since finishing the class, I'm trying to best position myself for jr level developer jobs.  I'm trying to determine what companies are looking for and trying to build upon my skill set to make myself more marketable.  The class instructor recommended I learn EF because I guess a lot of places use that.  I have no clue if that is true but that's what I've been told.   When I did a bunch of research for Unit Of Work, every example was using Generics.  I'm kind of glad to hear you say it is frowned upon.  I like having each individual repository.  We didn't learn much on exception handling but I will definitely be reviewing what you provided.  I need to learn more about that.  Any other suggestions you have on what areas I should focus on would be greatly appreciated!  Thanks again for your help and taking the time to review and respond to my questions.

    Saturday, August 25, 2018 6:00 PM
  • User-108026213 posted

    MGEBHARD

    I have seen a lot of debate on this topic and I don't know enough to even begin to form a stance but watch this YouTube video it may change your mind about whether EF is a Unit of Work pattern or Repository Pattern

    Repository Pattern with C# and Entity Framework, Done Right

    Saturday, August 25, 2018 6:04 PM
  • User475983607 posted

    I does not change my mind.  The argument is weak IMHO.  It suggests EF creates redundant code but that's a potential hazard for any pattern.

    Saturday, August 25, 2018 7:32 PM
  • User1120430333 posted

    The class I took introduced us to the repository pattern

    https://programmingwithmosh.com/entity-framework/common-mistakes-with-the-repository-pattern/

    The class instructor recommended I learn EF because I guess a lot of places use that. I have no clue if that is true but that's what I've been told.

    Yes a lot of companies use EF an ORM. Other companies use nHibernate a more mature ORM that was being used on the Linux and MS platforms before EF was introduced. So both ORM(s) are popular. Other companies use ADO.NET, MS SQL Server Command Objects, inline parmterized T-SQL or parmterized stored procedures, a datareader  and the DTO pattern of using a single DTO or a collection of DTO(s) in a List<T> for CRUD operations with a database.

    https://en.wikipedia.org/wiki/NHibernate

    Some are even using micro ORM like Dapper.

     https://www.infoworld.com/article/3025784/c-sharp/how-to-work-with-dapper-in-c.html

    https://www.c-sharpcorner.com/uploadfile/mahesh/datareader-in-ado-net/

    https://dotnetdaily.net/web-development/tutorials/ado-net-tutorial-create-update-delete-operations

    Any other suggestions you have on what areas I should focus on would be greatly appreciated!

    https://www.techopedia.com/2/29912/development/web-development/10-things-every-modern-web-developer-must-know

    Fullstack is popular on the job boards. 

    https://skillcrush.com/2017/02/27/front-end-back-end-full-stack/

    Learn design patterns and how to architect using them with Architectural Patterns and Styles The most popular Architectural  Patterns are Client/Server, Layered, N-Tier and a combination of styles. 

    https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ee658117(v=pandp.10)

    https://www.dofactory.com/net/design-patterns

    About the UI design patterns, becuase each pattern can be used in Web and Windows form solutions.

    https://www.codeproject.com/Articles/228214/Understanding-Basics-of-UI-Design-Pattern-MVC-MVP

    https://en.wikipedia.org/wiki/Separation_of_concerns

    https://www.c-sharpcorner.com/UploadFile/56fb14/understanding-separation-of-concern-and-Asp-Net-mvc/

    https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/overview/understanding-models-views-and-controllers-cs

    <copied>

    An MVC model contains all of your application logic that is not contained in a view or a controller. The model should contain all of your application business logic, validation logic, and database access logic. For example, if you are using the Microsoft Entity Framework to access your database, then you would create your Entity Framework classes (your .edmx file) in the Models folder.

    A view should contain only logic related to generating the user interface. A controller should only contain the bare minimum of logic required to return the right view or redirect the user to another action (flow control). Everything else should be contained in the model.

    In general, you should strive for fat models and skinny controllers. Your controller methods should contain only a few lines of code. If a controller action gets too fat, then you should consider moving the logic out to a new class in the Models folder

    <end>

    Concerning ASP.NET MVC, I am going to show you what I mean in code, becuase time and time again you see developers abusing the controller with  a controller doing database access and business logic, with a controllers with 1,000 lines of code and the developer has gone viewbag happy. It's not called Viewbag View Controller. It is called Model View Controller.

    Learn Test Driven Design TDD and Unit Testing as companies hire developers that know how to Unit Test learn the basics

    https://www.tutorialspoint.com/software_testing_dictionary/test_driven_development.htm

    https://www.tutorialspoint.com/software_testing_dictionary/unit_testing.htm

    https://www.telerik.com/products/mocking/unit-testing.aspx

    Popular mocking frameworks are Moq and Rhino Mocks

    Learn the basics, becuase they hire if you know it.

    https://www.codeproject.com/Articles/615139/An-Absolute-Beginners-Tutorial-on-Dependency-Inver

    https://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

    Popular IoC containers Unity and Castle Windsor

    About SOA learn the basics

    https://en.wikipedia.org/wiki/Service-oriented_architecture

    https://www.codeproject.com/Articles/515253/Service-Oriented-Architecture-and-WCF

    https://en.wikipedia.org/wiki/Microservices

    https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/microservice-application-layer-implementation-web-api

    I am not saying get this, but I will say that ever software developer needs to know the basic DBA 101 skills in order use a relational database, like MS SQL Server, Oracle, Sybase, etc. and etc. , or to use them and program against them effectively, becuase ORM(s) like EF and nHibernate are not database administration tools  no matter how much one thinks they are that only leads to the developer not knowing the basic skill set in using a relational database effectively, IMHO.  

    https://www.udemy.com/microsoft-sql-server-101/

    You can get Pluralsight and spend $39 a month, take all the courses on the topics I have talked about and more and download the source code too and have at it.

    If you learn half of the above stuff, you'll get a job and get one quickly too.  I predict that for you. You'll  be a young gunslinger on the scene ready and willing  :)

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc.Rendering;
    
    namespace ProgMgmntCore2UserIdentity.Models
    {
        public class ProjectViewModels
        {
            public class Project
            {
                public int ProjectId { get; set; }
    
                [Required(ErrorMessage = "Client Name is required")]
                [StringLength(50)]
                public string ClientName { get; set; }
    
                [Required(ErrorMessage = "Project Name is required")]
                [StringLength(50)]
                public string ProjectName { get; set; }
    
                [Required(ErrorMessage = "Technology is required")]
                [StringLength(50)]
                public string Technology { get; set; }
    
                [Required(ErrorMessage = "Project Type is required")]
                public string ProjectType { get; set; }
    
                [Required(ErrorMessage = "Start Date is required")]
                [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM-dd-yyyy}")]
                public DateTime? StartDate { get; set; }
    
                [Required(ErrorMessage = "End Date is required")]
                [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM-dd-yyyy}")]
                public DateTime? EndDate { get; set; }
    
                [Required(ErrorMessage = "Cost is required")]
                public decimal? Cost { get; set; }
    
                public List<SelectListItem> ProjectTypes { get; set; }
            }
           
            public List<Project> Projects { get; set; }
            
        }
    }
    
    ===============================================================
    
    namespace ProgMgmntCore2UserIdentity.Models
    {
        public interface IProjectModel
        {
            ProjectViewModels GetProjectsByUserId(string userid);
            ProjectViewModels.Project GetProjectById(int id);
            ProjectViewModels.Project Create();
            void Create(ProjectViewModels.Project project, string userid);
            ProjectViewModels.Project Edit(int id);
            void Edit(ProjectViewModels.Project project, string userid);
            void Delete(int id);
            ProjectViewModels.Project PopulateSelectedList(ProjectViewModels.Project project);
        }
    }
    
    ---------------------------------------------------------
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Entities;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.Extensions.Caching.Memory;
    using ProgMgmntCore2UserIdentity.WebApi;
    
    namespace ProgMgmntCore2UserIdentity.Models
    {
        public class ProjectModel : IProjectModel
        {
            private readonly IMemoryCache _memoryCache;
            private readonly IWebApi _webApi;
    
            public ProjectModel(IWebApi webApi, IMemoryCache memoryCache)
            {
                _memoryCache = memoryCache;
                _webApi = webApi;
            }
    
            public ProjectViewModels GetProjectsByUserId(string userid)
            {
                var vm = new ProjectViewModels {Projects = new List<ProjectViewModels.Project>()};
    
                var dtos = _webApi.GetProjsByUserIdApi(userid).ToList();
                
                vm.Projects.AddRange(dtos.Select(dto => new ProjectViewModels.Project()
                {
                    ProjectId = dto.ProjectId,
                    ClientName = dto.ClientName,
                    ProjectName = dto.ProjectName,
                    Technology = dto.Technology,
                    ProjectType = dto.ProjectType,
                    StartDate = dto.StartDate,
                    EndDate = dto.EndDate,
                    Cost = dto.Cost
                }).ToList());
    
                return vm;
            }
    
            public ProjectViewModels.Project GetProjectById(int id)
            {
                var responseDto = _webApi.GetProjByIdApi(id);
    
                var project = new ProjectViewModels.Project
                {
                    ProjectId = responseDto.ProjectId,
                    ClientName = responseDto.ClientName,
                    ProjectName = responseDto.ProjectName,
                    Technology = responseDto.Technology,
                    ProjectType = responseDto.ProjectType,
                    StartDate = responseDto.StartDate,
                    EndDate = responseDto.EndDate,
                    Cost = responseDto.Cost
                };
    
                return project;
            }
    
            public ProjectViewModels.Project Create()
            {
                var project = new ProjectViewModels.Project();
                return PopulateSelectedList(project);
            }
    
            public void Create(ProjectViewModels.Project project, string userid)
            {
                var dto = new DtoProject
                {
                    ProjectId = project.ProjectId,
                    ClientName = project.ClientName,
                    ProjectName = project.ProjectName,
                    ProjectType = project.ProjectType,  
                    Technology = project.Technology,
                    UserId = userid,
                    StartDate = (DateTime) project.StartDate,
                    EndDate = (DateTime) project.EndDate,
                    Cost = (decimal) project.Cost
                };
    
                _webApi.CreateProjectApi(dto);
            }
    
            public ProjectViewModels.Project Edit(int id)
            {
                var responseDto = _webApi.GetProjByIdApi(id);
    
                var project = new ProjectViewModels.Project
                {
                    ProjectId = responseDto.ProjectId,
                    ClientName = responseDto.ClientName,
                    ProjectName = responseDto.ProjectName,
                    Technology = responseDto.Technology,
                    ProjectType = responseDto.ProjectType,
                    StartDate = responseDto.StartDate,
                    EndDate = responseDto.EndDate,
                    Cost = responseDto.Cost
                };
    
                project = PopulateSelectedList(project);
    
                return project;
            }
    
            public void Edit(ProjectViewModels.Project project, string userid)
            {
                var dto = new DtoProject
                {
                    ProjectId = project.ProjectId,
                    ClientName = project.ClientName,
                    ProjectName = project.ProjectName,
                    ProjectType = project.ProjectType,
                    Technology = project.Technology,
                    UserId = userid,
                    StartDate = (DateTime) project.StartDate,
                    EndDate = (DateTime) project.EndDate,
                    Cost = (decimal) project.Cost
                };
    
                _webApi.UpdateProjectApi(dto); 
            }
    
            public void Delete(int id)
            {
                _webApi.DeleteProjectApi(new DtoId{Id = id});
            }
    
            public ProjectViewModels.Project PopulateSelectedList(ProjectViewModels.Project project)
            {
                bool isExist = _memoryCache.TryGetValue("DtoCache", out DtoCache dtocache);
    
                if (!isExist)
                {
                    dtocache = _webApi.GetCacheApi();
              
                    var cacheEntryOptions = new MemoryCacheEntryOptions()
                        .SetSlidingExpiration(TimeSpan.FromSeconds(30));
                    
                    _memoryCache.Set("DtoCache", dtocache, cacheEntryOptions);
                }
    
                project.ProjectTypes = new List<SelectListItem>();
    
                foreach (var pt in dtocache.ProjectTypes)
                {
                    var sli = new SelectListItem {Value = pt.Value, Text = pt.Text};
                    project.ProjectTypes.Add(sli);
                }
           
                var selected = (from a in project.ProjectTypes.Where(a => a.Value == project.ProjectType) select a)
                    .SingleOrDefault();
    
                if (selected != null)
                    selected.Selected = true;
    
                return project;
            }
        }
    }
    
    =============================================================
    
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Entities;
    
    namespace ProgMgmntCore2UserIdentity.WebApi
    {
        public interface IWebApi
        {
            List<DtoProject> GetProjsByUserIdApi(string userid);
            DtoProject GetProjByIdApi(int id);
            void CreateProjectApi(DtoProject dto);
            void UpdateProjectApi(DtoProject dto);
            void DeleteProjectApi(DtoId dto);
            List <DtoTask> GetTasksByProjIdApi(int id);
            DtoTask GetTaskByIdApi(int id);
            void CreateTaskApi(DtoTask dto);
            void UpdateTaskApi(DtoTask dto);
            void DeleteTaskApi(DtoId dto);
            DtoCache GetCacheApi();
        }
    }
    -------------------------------------------------
    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using Entities;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    
    namespace ProgMgmntCore2UserIdentity.WebApi
    {
        public class WebApi : IWebApi
        {
            #region ProjectApi
            
            public List<DtoProject> GetProjsByUserIdApi(string userid)
            {
                var dtoprojects = new List<DtoProject>();
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://progmgmntcore2api.com/api/project/GetProjsByUserId?userid=" + userid);
    
                    var response = client.GetAsync(uri).Result;
    
                    if (!response.IsSuccessStatusCode)
                        throw new Exception(response.ToString());
    
                    var responseContent = response.Content;
                    var responseString = responseContent.ReadAsStringAsync().Result;
    
                    dynamic projects = JArray.Parse(responseString) as JArray;
    
                    foreach (var obj in projects)
                    {
                        DtoProject dto = obj.ToObject<DtoProject>();
    
                        dtoprojects.Add(dto);
                    }
                }
    
                return dtoprojects;
            }
    
            public DtoProject GetProjByIdApi(int id)
            {
                DtoProject dto;
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://progmgmntcore2api.com/api/project/GetProjById?id=" + id);
                    HttpResponseMessage getResponseMessage = client.GetAsync(uri).Result;
    
                    if (!getResponseMessage.IsSuccessStatusCode)
                        throw new Exception(getResponseMessage.ToString());
    
                    var responsemessage = getResponseMessage.Content.ReadAsStringAsync().Result;
    
                    dynamic project = JsonConvert.DeserializeObject(responsemessage);
    
                    dto = project.ToObject<DtoProject>();
                }
    
                return dto;
            }
    
            public void CreateProjectApi(DtoProject dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message =
                        client.PostAsync("api/project/CreateProject", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            public void UpdateProjectApi(DtoProject dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message =
                        client.PostAsync("api/project/UpdateProject", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            public void DeleteProjectApi(DtoId dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message =
                        client.PostAsync("api/project/DeleteProject", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            #endregion
    
            #region TaskApi
    
            public List<DtoTask> GetTasksByProjIdApi(int id)
            {
                var dtotasks = new List<DtoTask>();
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://progmgmntcore2api.com/api/task/GetTasksByProjId?id=" + id);
    
                    var response = client.GetAsync(uri).Result;
    
                    if (!response.IsSuccessStatusCode)
                        throw new Exception(response.ToString());
    
                    var responseContent = response.Content;
                    var responseString = responseContent.ReadAsStringAsync().Result;
    
                    dynamic tasks = JArray.Parse(responseString) as JArray;
    
                    foreach (var obj in tasks)
                    {
                        DtoTask dto = obj.ToObject<DtoTask>();
    
                        dtotasks.Add(dto);
                    }
                }
    
                return dtotasks;
            }
    
            public DtoTask GetTaskByIdApi(int id)
            {
                DtoTask dto;
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://progmgmntcore2api.com/api/task/GetTaskById?id=" + id);
    
                    var response = client.GetAsync(uri).Result;
    
                    if (!response.IsSuccessStatusCode)
                        throw new Exception(response.ToString());
    
                    var responseContent = response.Content;
                    var responseString = responseContent.ReadAsStringAsync().Result;
    
                    dynamic task = JObject.Parse(responseString);
                    
                    dto = task.ToObject<DtoTask>();
                }
    
                return dto;
            }
    
            public void CreateTaskApi(DtoTask dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message = client.PostAsync("api/task/CreateTask", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            public void UpdateTaskApi(DtoTask dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message = client.PostAsync("api/task/UpdateTask", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            public void DeleteTaskApi(DtoId dto)
            {
                using (var client = new HttpClient { BaseAddress = new Uri("http://progmgmntcore2api.com") })
                {
                    string serailizeddto = JsonConvert.SerializeObject(dto);
    
                    var inputMessage = new HttpRequestMessage
                    {
                        Content = new StringContent(serailizeddto, Encoding.UTF8, "application/json")
                    };
    
                    inputMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    HttpResponseMessage message = client.PostAsync("api/task/DeleteTask", inputMessage.Content).Result;
    
                    if (!message.IsSuccessStatusCode)
                        throw new Exception(message.ToString());
                }
            }
    
            #endregion
    
            #region CacheApi
    
            public DtoCache GetCacheApi()
            {
                var dtocache = new DtoCache();
    
                using (var client = new HttpClient())
                {
                    var uri = new Uri("http://progmgmntcore2api.com/api/cache");
    
                    var response = client.GetAsync(uri).Result;
    
                    if (!response.IsSuccessStatusCode)
                        throw new Exception(response.ToString());
    
                    var responseContent = response.Content;
                    var responseString = responseContent.ReadAsStringAsync().Result;
    
                    var deserialized = JsonConvert.DeserializeObject<DtoCacheRoot>(responseString);
    
                    dtocache.ProjectTypes = deserialized.DtoCache.ProjectTypes;
                    dtocache.Durations = deserialized.DtoCache.Durations;
                    dtocache.Statuses = deserialized.DtoCache.Statuses;
                    dtocache.Resources = deserialized.DtoCache.Resources;
                }
    
                return dtocache;
            }
    
            #endregion
        }
    }
    
    ==============================================
    using System;
    using System.Linq;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using ProgMgmntCore2UserIdentity.Models;
    
    namespace ProgMgmntCore2UserIdentity.Controllers
    {
        public class ProjectController : Controller
        {
            private readonly IProjectModel _projectModel;
            private readonly IModelHelper _modelHelper;
           
            public ProjectController(IProjectModel projectModel,   IModelHelper modelHelper)
            {
                _projectModel = projectModel;
                _modelHelper = modelHelper;
            }
    
            // GET: Project
            [Authorize]
            public ActionResult Index()
            {
                return View(_projectModel.GetProjectsByUserId(User.Identity.Name));
            }
    
            [Authorize]
            public ActionResult Details(int id = 0)
            {
                return id == 0 ? null : View(_projectModel.Edit(id));
            }
    
            [Authorize]
            public ActionResult Create()
            {
                return View(_projectModel.Create());
            }
    
            [Authorize]
            [HttpPost]
            public ActionResult Create(ProjectViewModels.Project project, string submit)
            {
                if (submit == "Cancel") return RedirectToAction("Index");
    
                ValidateddlProjectTypes();
    
                project.ProjectType = (Request.Form["ddlProjectTypes"]);
    
                if (ModelState.IsValid && _modelHelper.IsEndDateLessThanStartDate(project, "Project"))
                    ModelState.AddModelError(string.Empty, "End Date cannot be less than Start Date.");
    
                if (!ModelState.IsValid) return View(_projectModel.PopulateSelectedList(project));
    
                _projectModel.Create(project, User.Identity.Name);
                return RedirectToAction("Index");
            }
    
            [Authorize]
            public ActionResult Edit(int id = 0)
            {
                return id == 0 ? null : View(_projectModel.Edit(id));
            }
    
            [Authorize]
            [HttpPost]
            public ActionResult Edit(ProjectViewModels.Project project, string submit)
            {
                if (submit == "Cancel") return RedirectToAction("Index");
    
                if (ModelState.IsValid && _modelHelper.IsEndDateLessThanStartDate(project, "Project"))
                    ModelState.AddModelError(String.Empty, "End Date cannot be less than Start Date.");
    
                if (!ModelState.IsValid) return View(_projectModel.PopulateSelectedList(project));
    
                var theproject = new ProjectViewModels.Project();
    
                theproject = project;
    
                theproject.ProjectType = Request.Form["ProjectType"];
    
                _projectModel.Edit(theproject, User.Identity.Name);
                return RedirectToAction("Index");
            }
    
            public ActionResult Delete(int id = 0)
            {
                if (id > 0) _projectModel.Delete(id);
    
                return RedirectToAction("Index");
            }
       
            public ActionResult Cancel()
            {
                return RedirectToAction("Index", "Home");
            }
    
            public ActionResult UploadFile(int id)
            {
                return RedirectToAction("Index", "Upload", new { id = id, type = "PM" });
            }
    
            private void ValidateddlProjectTypes()
            {
                if (Request.Form["ddlProjectTypes"] == string.Empty)
                  return;
           
                foreach (var key in ModelState.Keys.ToList().Where(key => ModelState.ContainsKey(key)))
                {
                    if (key != "ProjectType") continue;
                    ModelState[key].Errors.Clear();
                    ModelState[key].ValidationState = ModelValidationState.Valid;
                }
            }
        }
    }
    
    

    Sunday, August 26, 2018 4:27 AM
  • User-108026213 posted

     DA924 

    Thank you for your feedback and insight into the business.  I appreciate it!  I will be reviewing and learning as much as I can!  Thanks again!

    Sunday, August 26, 2018 10:25 PM
  • User1655654435 posted

    awsome post, DA924! I think the programmingwithmosh tutorials are quite bad. I don't see any point in adding too many abstractions and especially writing generic code that won't be used. there's no need to implement a delete method if you only going to use a get method.

    Friday, August 31, 2018 1:24 PM