Questions about repository pattern with Entity Framework Core - entity-framework-core

I have created an API that is using EF Core with a repository pattern and I have few questions:
Post method receives an email address and verify whether user exists on not.
If an email address does not exist in the User table, get the guest access details from the AccessManagement table and save in Entitlement table and return the details
If the entry exists, get the user access details and return them
IGeneralRepository:
public interface IGenrealRepository<TEntity> where TEntity : class , new()
{
IQueryable<TEntity> GetAll();
Task<TEntity> AddAsync(TEntity entity);
Task<TEntity[]> AddRangeAsync(TEntity[] entity);
TEntity Update(TEntity entity);
Task<int> CompleteAsync();
}
General repository:
public class GeneralRepository<TEntity> : IGenrealRepository<TEntity> where TEntity : class, new()
{
private MyDbContext _myDbContext;
public GeneralRepository(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
}
try
{
await _myDbContext.AddAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<TEntity[]> AddRangeAsync(TEntity[] entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddRangeAsync)} entity must not be null");
}
try
{
await _myDbContext.AddRangeAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<int> CompleteAsync()
{
return await _myDbContext.SaveChangesAsync();
}
public IQueryable<TEntity> GetAll()
{
try
{
return _myDbContext.Set<TEntity>();
}
catch (Exception ex)
{
throw new Exception($"Couldn't retrieve entities: {ex.Message}");
}
}
public TEntity Update(TEntity entity)
{
try
{
_myDbContext.Update<TEntity>(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be updated: {ex.Message}");
}
}
}
IUserService:
public interface IUserService
{
Task<User> CreateUser(string emailId);
Task<int> Complete();
}
UserService implementation:
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IAccessManagementRepository _accessManagementRepository;
public UserService(IUserRepository userRepository, IAccessManagementRepository accessManagementRepository)
{
_userRepository = userRepository;
_accessManagementRepository = accessManagementRepository;
}
public async Task<int> Complete()
{
return await _userRepository.CompleteAsync();
}
public async Task<User> CreateUser(string emailId)
{
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
if (user == null)
{
var entitlements = await _userAccessRepository.GetAll()
.Where( x => x.Default == true)
.Select( x => new UserEntitlement() {
Id = x.Id,
AccessName = x.AccessName
}).ToListAsync();
//saving User and Entitlement
user = new User()
{
EmailId = emailId,
UserEntitlements = entitlements
};
user = await _userRepository.AddAsync(user);
}
else
{
// Getting current User Entitlement
var entitlements = await _userRepository.GetAllUserEntitilements();
var entitlement = entitlements.Find(x => x.UserId == user.UserId);
user.UserEntitlements = entitlements;
}
return user;
}
}
API call:
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var result = await _userService.CreateUser(user.EmailId);
await _userService.Complete();
return CreatedAtAction(nameof(GetUser), new { emailId = result.EmailId }, result);
}
Questions:
Is my method UserService.CreateUser() implementation correct? Any better approach?
Is the below code is the best approach to filter?
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
How to get data from User and Entitlement table at one stretch? Something like below Include but can not use include because of an error
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.Include<UserEntitlement>()
.FirstOrDefault();
How to do insert to one table and update to another table in a single transaction?

Leo,
I prefer doing the validation of the email outside the CreateUser function
This comes with another function where you could add to IUserService where you can get the user by email GetUserByEmail.
Doing that you can possibly return a proper error or validation message before invoking the CreateUser at the API Call
For example
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var user = await _userService.GetUserByEmail(user.EmailId);
// or var userRegistered = await _userService.UserExistsByEmail(user.EmailId) returning a bool
// user registered?
if (user)
{
// The user already exists, return an error or
// You could update the UserEntitlements here or you could
// make an HttpPut where the user is updated do nothing here
}
....
}
An example
var user = _userRepository.GetAll()
.Include(x => x.UserEntitlements)
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
You can do it using UnitOfWork
Repository Pattern and Unit of Work

Related

How to perform edit action in ASP.net Core?

How to perform edit action in ASP.net Core? I have the following code for product detail view action i need help on creating action for edit page
//IProduct interface
namespace Proj.Core.App.Common.Product
{ public interface IProductService
{
Task<IList<ProductDTO>> GetProducts();
}
}
//Product Controller
public class ProductController : Controller
{
public IProductService ProductService { get; }
public ProductController(IProductService ProductService)
{
ProductService = ProductService;
}
//DetailAction
[HttpGet()]
public async Task<IActionResult> Detail(int id)
{
var ProductList= (await ProductService.GetProducts()).ToList();
var project = ProductList.FirstOrDefault(a => a.ID == id);
#ViewBag.Product_Code = product.productCode;
#ViewBag.Product_Name = product.productName;
return View();
}
}
how can i create action for Edit page action?
I left a comment that the question really needs more information to answer properly. However, maybe the stuff below might help to get you started.
Here is an implementation using the methods you already have.
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var productList = (await ProductService.GetProducts()).ToList();
var product = productList.FirstOrDefault(a => a.ID == id);
if (product == null)
{
return NotFound();
}
return View(product);
}
This is what typical entity framework implementation looks like. Extract what information you can from it.
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var product = await _context.Products.FindAsync(id);
if (product == null)
{
return NotFound();
}
return View(applications);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Product product)
{
if (ModelState.IsValid)
{
try
{
_context.Update(product);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(product.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(applications);
}
private bool ProductExists(int id)
{
return _context.Products.Any(e => e.Id == id);
}
Hope that helps.
Happy coding!!!
//DetailAction
[HttpPut()]
public async Task<IActionResult> Put(Product model)
{
..call your service or ...
return View();
}

Pass ID once to a controller and have all controller methods remember boolean check

I just created a simple web API using .NetCore 2.2 and Entity Framework.
I added a bit of security, by passing in a userID to each controller that the user accesses.
But I noticed that it starts getting messy when I have to add the userID to every controller in my app and the run my user check to make sure the user can access that content.
Below you'll see an example of what I mean.
I'm wondering, is there a way to add it once and then have every controller check for it?
Thanks!
[Route("api/[controller]")]
[ApiController]
public class EngineController : ControllerBase
{
private readonly engineMaker_Context _context;
public EngineController(engineMaker_Context context)
{
_context = context;
}
// GET: api/Engine
[HttpGet("{userID}")]
public async Task<ActionResult<IEnumerable<Engine>>> GetEngine(string userID)
{
if(!CanAccessContent(userID))
{
return Unauthorized();
}
return await _context.Engine.ToListAsync();
}
// GET: api/Engine/123/5
[HttpGet("{userID}/{id}")]
public async Task<ActionResult<Engine>> GetEngine(string userID, string id)
{
if(!CanAccessContent(userID))
{
return Unauthorized();
}
var engine = await _context.Engine.FindAsync(id);
if (engine == null)
{
return NotFound();
}
return engine;
}
// PUT: api/Engine/123/5
[HttpPut("{userID}/{id}")]
public async Task<IActionResult> PutEngine(string userID, string id, Engine engine)
{
if(!CanAccessContent(userID))
{
return Unauthorized();
}
if (id != engine.ObjectId)
{
return BadRequest();
}
_context.Entry(engine).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EngineExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
private bool CanAccessContent(string userID)
{
return _context.AllowedUsers.Any(e => e.UserId == userID);
}
}
You could try IAsyncAuthorizationFilter to check the userID.
IAsyncAuthorizationFilter
public class UserIdFilter : IAsyncAuthorizationFilter
{
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
var userId = context.RouteData.Values["userID"] as string;
if (!dbContext.Users.Any(u => u.Email == userId))
{
context.Result = new UnauthorizedResult();
}
return Task.CompletedTask;
}
}
Regiter UserIdFilter for all action.
services.AddMvc(options =>
{
options.Filters.Add(typeof(UserIdFilter));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

What should my repository return on a http post when the posted id (foreign key) is wrong

Given the user sends a valid token to an api endpoint via fiddler/postman, he could post a resource (pupil) for a related resource (schoolclass).
When the schoolclass id
does not exist yet in the database
does exist already in the database but this schoolclass Id belongs to another user.
does exist in the database and belongs to the passed userId
Then
What would you change in the Controller and Repository class to make it work for all 3 cases using a REST api + repository pattern.
Controller:
[HttpPost("~/api/pupils")]
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
return Ok(pupil.Id);
}
Repository:
public async Task CreatePupil(Pupil pupil, int schoolclassCodeId, string userId)
{
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode != null)
{
schoolclassCode.Pupils.Add(pupil);
await context.SaveChangesAsync();
}
}
NOTE
At the moment the last of the 3 use cases is implemented!
From REST prospective you need to return 400 or 404 depending on your design.
If your route need to be like /classes/{id}/users/{id}/pupil I thing you need to use 404 in case user or class is wrong.
In case of separate route (as I can see in your question) I think this should be 400 code as request URL is pointing to valid resource but payload is invalid.
In both cases I think the batter error handling strategy here is to write some set of custom exceptions (like EntityNotFondException, EntityInvalidException, BusinessLogicException) and throw them from repository in case something is wrong. Then you can create some global action filter or OWIN middleware to catch those exceptions and translate them to correct response status codes with appropriate messages
Example:
public class NotFoundException : Exception
{
public NotFoundException(Type entityType)
: base($"Entity {entityType.Name} was not found")
{
}
}
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
public ApiExceptionFilterAttribute()
{
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var exception = actionExecutedContext.Exception;
if (exception == null)
return;
if (exception is HttpResponseException)
return;
var entityNotFoundException = exception as NotFoundException;
if (entityNotFoundException != null)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, entityNotFoundException.Message);
return;
}
}
}
Usage:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if(schoolclassCode == null)
throw new NotFoundException(typeof(Schoolclass));
You can throw validation exceptions in the same way. E.g:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId);
if(schoolclassCode == null)
throw new InvalidModelStateException("Schoolclass was not found.")
if(schoolclassCode.UserId != userId)
throw new InvalidModelStateException("Schoolclass is owned by different user.")
... etc.
I always use Result classes for returning state from a service class (wouldn't implement that in Repository as it shouldn't contain business logic):
public class QueryResult
{
private static readonly QueryResult success = new QueryResult { Succeeded = true };
private readonly List<QueryError> errors = new List<QueryError>();
public static QueryResult Success { get { return success; } }
public bool Succeeded { get; protected set; }
public IEnumerable<QueryError> Errors { get { return errors; } }
public static QueryResult Failed(params QueryError[] errors)
{
var result = new QueryResult { Succeeded = false };
if (errors != null)
{
result.errors.AddRange(errors);
}
return result;
}
}
public class QueryResult<T> : QueryResult where T : class
{
public T Result { get; protected set; }
public static QueryResult<T> Suceeded(T result)
{
if (result == null)
throw new ArgumentNullException(nameof(result));
var queryResult = new QueryResult<T>
{
Succeeded = true,
Result = result
};
return queryResult;
}
}
public class QueryError
{
public string ErrorId { get; set; }
public string ErrorMessage { get; set; }
}
And use it like
var schoolclassCode = await context.Schoolclasses
.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode == null)
return QueryResult.Failed(new QueryError
{
ErrorId = 1,
ErrorMessage = "Invalid User Id"
});
Edit:
Just as an addition and rule of thumb
Services which operate on one or multiple entities and perform user input validation should return Result classes
Domain Models (which you don't seem to use, since you use a repository and Repository + Rich Domains doesn't work out well in real life applications) should throw exception (i.e. InvalidOperationException or ArgumentException, ArgumentNullException). Doing Result-types her will pollute the model and mix the separation of responsibility (Domain Model will suddenly also do validation instead only guarding against invalid state)
Using XxxResult type classes gives you an easy way to transport one or multiple errors back to the user, where an exception should act as an guard against your domain model getting into invalid state.
Edit 2
In response to the comments:
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
var result = await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok(pupil.Id);
}
Edit 3
Example with 3 parameters for let's say /api/schoolclasses/1/students/2/lessons/2 (Update an existing lesson to the student with the id 2 for the school class with id 1).
// on SchoolClasses Controller
[HttpPost("{schoolClassId:int}/students/{studentId:int}/lessons/{lessonId:int}")]
public async Task<IActionResult> Post([FromBody]Lessons lessonDto)
{
// rough input validation, do first to avoid db hits
if(!ModelState.IsValid)
return BadRequest(ModelState);
// best put logic into service classes i.e. SchoolClassService
var result = schoolClassService.UpdateLessonFor(schoolClassId, studentId, lessonDto)
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok();
}
Content of UpdateLessonsFor
List<ErrorMessage> errors = new List<ErrorMessage>();
// with .Include to include both student and all of his lessons
// does student exist?
// Hits db once and gets both, student and all lessons in a single query
var student = _context.SchoolClasses
.Include(sc => sc.Students)
.ThenInclude(s => s.Lessons)
.Where(sc => sc.SchoolClassId == schoolClassId)
.SelectMany(sc => sc.Students)
FirstOrDefault(s => s.StudentId == studentId);
if(student==null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 1, ErrorMessage = "Student or School Class not found" } );
// Doesn't hit the database, since lessons have been loaded with the above call
var lesson = student.Lessons.Any(l => l.LessonId = lessonId))
if(lesson == null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 2, ErrorMessage = "Lesson not found. " } );
// modify it
lesson.SomeValue = dto.SomeValue;
try
{
} catch(Exception ex) {
return QueryResult.Failed(new ErrorMessage { ErrorId = 3, ErrorMessage = "Couldn't update the lesson. Try again and if the error appears again, contact the administrator." } );
} finally {
return QueryResult.Suceeded;
// or if you also want to return a result
return QueryResult.Suceeded(lesson);
}
Also from the comments of the other answer: Don't put logic into your repository, that's what services are for when you use anemic domain (models have no logic, all in services) or have thin service layer and put most logic into domain service. But that's out of the scope.

InvokeApiAsync<HttpResponseMessage> returns null

Can someone explain my why that client (Xamarin.Forms PCL) call returns null?
HttpResponseMessage response = await OfflineSyncStoreManager.Instance.MobileAppClient.InvokeApiAsync<HttpResponseMessage>("ResetTruckAuftragWorkflow");
response is null. When I execute that in a console app it returns the
valid http response.
I use the latest stable ZUMO nugets in client and backend. There is my ZUMO backend code:
[Authorize]
[MobileAppController]
public class ResetTruckAuftragWorkflowController : ApiController
{
private readonly RcsMobileContext _rcsMobileContext;
private readonly TruckFahrerInfo _truckFahrerInfo;
public ResetTruckAuftragWorkflowController()
{
_rcsMobileContext = new RcsMobileContext();
_truckFahrerInfo = new TruckFahrerInfo(this.User as ClaimsPrincipal);
}
// POST api/ResetTruckAuftragWorkflow
[HttpPost]
public async Task<IHttpActionResult> PostAsync()
{
if (ModelState.IsValid)
{
using (var transaction = _rcsMobileContext.Database.BeginTransaction())
{
try
{
var truckAuftragList = _rcsMobileContext.TruckAuftrags.PerUserFilter(_truckFahrerInfo.FahrerId);
var truckAppIds = truckAuftragList?.Select(ta => ta.TruckAppId).ToArray();
if (truckAppIds != null)
{
foreach (var truckAppId in truckAppIds)
{
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_tawQueryTaskStatus10, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_tawQueryTaskStatus5, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_talQuery, truckAppId);
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_taQuery, truckAppId);
}
}
await _rcsMobileContext.Database.ExecuteSqlCommandAsync(_taQuery, _truckFahrerInfo.FahrerId);
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
return BadRequest($"Transaction failed: {e}");
}
}
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_rcsMobileContext.Dispose();
}
base.Dispose(disposing);
}
}
thanks
Eric
InvokeApiAsync decodes the body that is returned and de-serializes the JSON into type T. You should not use HttpResponseMessage for this purpose as it is not serializable.
If you don't care about the body, use the non-generic form of InvokeApiAsync.

PostSharp OnExceptionAspect + EF 6 DbUpdateException

I am using PostSharp to handle Entity Framework 6 exceptions. As you can see in the code below I am handling two different kinds of exceptions:
DbEntityValidationException
DbUpdateException
Now, my HandleExceptionAttribute is able to catch all DbEntityValidationException
But for some reason, HandleExceptionAttribute is never executed whenever EF throws a DbUpdateException
Here is my code so that you have better understanding:
HandleExceptionAttribute.cs
[Serializable]
public class HandleExceptionAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
Exception exception = args.Exception;
var validationException = exception as DbEntityValidationException;
if (validationException != null)
{
HandleDataValidationException(validationException);
}
var updateException = exception as DbUpdateException;
if (updateException != null)
{
HandleDataUpdateException(updateException);
}
throw exception;
}
private void HandleDataUpdateException(DbUpdateException exception)
{
Exception innerException = exception.InnerException;
while (innerException.InnerException != null)
{
innerException = innerException.InnerException;
}
throw new Exception(innerException.Message);
}
private void HandleDataValidationException(DbEntityValidationException exception)
{
var stringBuilder = new StringBuilder();
foreach (DbEntityValidationResult result in exception.EntityValidationErrors)
{
foreach (DbValidationError error in result.ValidationErrors)
{
stringBuilder.AppendFormat("{0} [{1}]: {2}",
result.Entry.Entity.ToString().Split('.').Last(), error.PropertyName, error.ErrorMessage);
stringBuilder.AppendLine();
}
}
throw new Exception(stringBuilder.ToString().Trim());
}
}
MyContext.cs
public class MyContext : DbContext
{
public MyContext () : base(Settings.Get(Settings.DB_CONNECTION_STRING)) { }
public DbSet<Subscriber> Subscribers { get; set; }
private void SetCreatedAtUpdatedAt()
{
foreach (DbEntityEntry entityEntry in ChangeTracker.Entries())
{
switch (entityEntry.State)
{
case EntityState.Added:
((IEntity) entityEntry.Entity).CreatedAt = DateTime.Now;
break;
case EntityState.Modified:
((IEntity) entityEntry.Entity).UpdatedAt = DateTime.Now;
break;
}
}
}
[HandleException]
public override int SaveChanges()
{
SetCreatedAtUpdatedAt();
return base.SaveChanges();
}
[HandleException]
public override Task<int> SaveChangesAsync()
{
SetCreatedAtUpdatedAt();
return base.SaveChangesAsync();
}
}
Action
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<JsonResult> Subscribe(string email)
{
string message = null;
bool success = false;
try
{
using (var context = new MyContext())
{
context.Subscribers.Add(
new Subscriber
{
Email = email
});
await context.SaveChangesAsync();
}
await _queueManager.Enque(
QueueNames.TASK_SEND_EMAIL,
new BrokeredMessage(email),
Settings.Get(Settings.SB_CN_TASKS_SEND));
success = true;
}
catch (Exception exception)
{
// Whenever there is a DbUpdateException, it does not get
// filtered and processed by PostSharp Exception Handler
// I have a unique index constraint on the "Email" field of the Subscriber.
// So when I try to add a duplicate subscriber, EF raises DbUpdateException
// This exception should have been filtered by PostSharp since I have
// overridden and decorated "SaveChangesAsync()" method in my DbContext
// with [HandleException]
// On the other hand, I also have [Required] for "Email" in my POCO.
// So, when I don't pass in any email address for the subscriber,
// EF raises DbEntityValidationException -- this does get processed
// by the PostSharp Exception Handler
message = exception.Message;
}
return Json(new {message, success});
}
PostSharp does not currently support async methods. They have announced that they will support async starting with PostSharp 3.1.