/// <summary> /// This transfers error messages from the ValidationResult collection to the MVC modelState error dictionary. /// It looks for errors that have member names corresponding to the properties in the displayDto. /// This means that errors associated with a field on display will show next to the name. /// Other errors will be shown in the ValidationSummary /// </summary> /// <param name="status">The status that came back from the BizRunner</param> /// <param name="modelState">The MVC modelState to add errors to</param> /// <param name="displayDto">This is the Dto that will be used to display the error messages</param> public static void CopyErrorsToModelState <T>(this IStatusGeneric status, ModelStateDictionary modelState, T displayDto) { if (!status.HasErrors) { return; } var namesThatWeShouldInclude = PropertyNamesInDto(displayDto); foreach (var error in status.Errors) { if (!error.MemberNames.Any()) { modelState.AddModelError("", error.ErrorMessage); } else { foreach (var errorKeyName in error.MemberNames) { modelState.AddModelError( (namesThatWeShouldInclude.Any(x => x == errorKeyName) ? errorKeyName : ""), error.ErrorMessage); } } } }
private IStatusGeneric <int> //#A CallSaveChangesWithExceptionHandler (DbContext context, Func <int> callBaseSaveChanges) //#B { var status = new StatusGenericHandler <int>(); //#C do //#D { try { int numUpdated = callBaseSaveChanges(); //#E status.SetResult(numUpdated); //#F break; //#F } catch (Exception e) //#G { IStatusGeneric handlerStatus = null; //#H if (handlerStatus == null) //#I { throw; //#I } status.CombineStatuses(handlerStatus); //#J } } while (status.IsValid); //#K return(status); //#L }
public MDRDocument BizAction(MDRDocumentDto inputData) { if (string.IsNullOrWhiteSpace(inputData.Title)) { AddError("title is Required."); } if (string.IsNullOrWhiteSpace(inputData.FolderName)) { AddError("Folder Name is invalied."); } var defaultStatus = _dbStatusAccess.GetDefaultStatus(inputData.ProjectId); if (defaultStatus == null) { AddError("Default MDR Status Not Exit."); } IStatusGeneric <MDRDocument> desStatus = null; if (!HasErrors) { desStatus = MDRDocument.CreateMDRDocument(inputData.Title, inputData.Description, inputData.WorkPackageId, inputData.Code, defaultStatus.Id, inputData.ProjectId, inputData.Type); var mdr = desStatus.Result; mdr.CreateMDRStatus("CREATE MDR", defaultStatus.Id, inputData.FolderName); _dbAccess.Add(desStatus.Result); CombineErrors(desStatus); } return(HasErrors ? null : desStatus.Result); }
public void BizAction(PunchGoDto inputData) { var punch = _dbAccess.GetPunch(inputData.Id); if (punch == null) { AddError("Could not find the punch. Someone entering illegal ids?"); return; } if (punch.CheckDate.HasValue) { AddError("punch is approved and do not allow for modify"); return; } IStatusGeneric status = null; if (!punch.ClearDate.HasValue) { status = punch.UpdateClear(inputData.ClearBy, inputData.ClearDate); } else if (!punch.CheckDate.HasValue) { status = punch.UpdateApprove(inputData.CheckBy, inputData.ApproveBy, inputData.CheckDate); } CombineErrors(status); Message = $"punch is update: {punch.ToString()}."; }
/// <summary> /// This ensures there is a UserToRole linking the userId to the given roleName /// </summary> /// <param name="userId"></param> /// <param name="roleName"></param> public async Task AddRoleToUserAsync(string userId, string roleName) { IStatusGeneric <UserToRole> status = await UserToRole.AddRoleToUserAsync(userId, roleName, _repository); if (status.IsValid) { //we assume there is already a link to the role is the status wasn't valid await _repository.AddAsync(status.Result); } }
/// <summary> /// This allows you to return a CreatedAtRoute result for a Create /// </summary> /// <param name="status"></param> /// <param name="createdRoute"></param> /// <returns></returns> public static IActionResult Response(this IStatusGeneric status, CreatedAtRouteResult createdRoute) { if (status.IsValid) { return(createdRoute); } //it has errors return(CreateBadRequestObjectResult(status.Errors.Select(x => x.ErrorResult))); }
/// <summary> /// This allows you to return a CreatedAtRoute result for a Create /// </summary> /// <param name="status"></param> /// <param name="controller"></param> /// <param name="routeName">The values needed to work with the HttpGet to return the correct item</param> /// <param name="routeValues"></param> /// <param name="dto"></param> /// <returns></returns> public static ActionResult <T> Response <T>(this IStatusGeneric status, ControllerBase controller, string routeName, object routeValues, T dto) { if (status.IsValid) { return(controller.CreatedAtRoute(routeName, routeValues, dto)); } //it has errors return(CreateBadRequestObjectResult(status.Errors.Select(x => x.ErrorResult))); }
/// <summary> /// This is a spacial form of SaveChanges that returns an <see cref="T:IStatusGeneric{int}"/> status /// </summary> /// <param name="acceptAllChangesOnSuccess">normal SaveChanges option</param> /// <returns>Status, with a Result that is the number of updates down by SaveChanges</returns> public IStatusGeneric <int> SaveChangesWithStatus(bool acceptAllChangesOnSuccess = true) { if (_eventsRunner == null) { throw new GenericEventRunnerException($"The {nameof(SaveChangesWithStatus)} cannot be used unless the event runner is present"); } StatusFromLastSaveChanges = _eventsRunner.RunEventsBeforeAfterSaveChanges(this, () => ChangeTracker.Entries <EntityEvents>(), () => base.SaveChanges(acceptAllChangesOnSuccess)); return(StatusFromLastSaveChanges); }
/// <summary> /// This copies errors for general display where we are not returning to a page with the fields on them /// </summary> /// <param name="status"></param> /// <param name="modelState"></param> public static void CopyErrorsToModelState(this IStatusGeneric status, ModelStateDictionary modelState) { if (!status.HasErrors) { return; } foreach (var error in status.Errors) { modelState.AddModelError("", error.ErrorMessage); } }
/// <summary> /// This is a spacial form of SaveChangesAsync that returns an <see cref="T:IStatusGeneric{int)"/> status /// </summary> /// <param name="acceptAllChangesOnSuccess"></param> /// <param name="cancellationToken"></param> /// <returns>Status, with a Result that is the number of updates down by SaveChangesAsync</returns> public async Task <IStatusGeneric <int> > SaveChangesWithStatusAsync(bool acceptAllChangesOnSuccess = true, CancellationToken cancellationToken = default) { if (_eventsRunner == null) { throw new GenericEventRunnerException($"The {nameof(SaveChangesWithStatusAsync)} cannot be used unless the event runner is present"); } StatusFromLastSaveChanges = await _eventsRunner.RunEventsBeforeAfterSaveChangesAsync(this, () => ChangeTracker.Entries <EntityEvents>(), () => base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken)); return(StatusFromLastSaveChanges); }
/// <summary> /// This transfers error messages from the ValidationResult collection to the MVC modelState error dictionary. /// It looks for errors that have member names corresponding to the properties in the displayDto. /// This means that errors associated with a field on display will show next to the name. /// Other errors will be shown in the ValidationSummary /// </summary> /// <param name="status">The status that came back from the BizRunner</param> /// <param name="modelState">The MVC modelState to add errors to</param> /// <param name="displayDto">This is the Dto that will be used to display the error messages</param> /// <param name="modelName">When using razor pages you need to prefix the member name by the name of the model's property</param> public static void CopyErrorsToModelState <T>(this IStatusGeneric status, ModelStateDictionary modelState, T displayDto, string modelName = null) { if (status.IsValid) { return; } if (displayDto == null) { status.CopyErrorsToModelState(modelState); return; } CopyErrorsWithFilterOnDto(status.Errors.Select(x => x.ErrorResult), modelState, displayDto, modelName); }
/// <summary> /// If the status has no errors then it will HTTP response with the status code provided in the /// validStatusCode property and the message from the status /// otherwise it will returns a HTTP 400 with the error information in the standard WebAPI format /// </summary> /// <param name="status"></param> /// <param name="validStatusCode">HTTP status code for non-error status</param> /// <returns></returns> public static ActionResult <WebApiMessageOnly> ResponseWithValidCode(this IStatusGeneric status, int validStatusCode) { if (status.IsValid) { return new ObjectResult(new WebApiMessageOnly(status)) { StatusCode = validStatusCode } } ; //it has errors return(CreateBadRequestObjectResult(status.Errors.Select(x => x.ErrorResult))); }
//I only have to override these two version of SaveChanges, as the other two versions call these /// <summary> /// EF Core's SaveChanges, but with domain event handling added /// Throws an exception if any of the BeforeSave event handlers return a status with an error in it. /// </summary> /// <param name="acceptAllChangesOnSuccess"></param> /// <returns>number of writes done to the database</returns> public override int SaveChanges(bool acceptAllChangesOnSuccess) { if (_eventsRunner == null) { return(base.SaveChanges(acceptAllChangesOnSuccess)); } StatusFromLastSaveChanges = SaveChangesWithStatus(acceptAllChangesOnSuccess); if (StatusFromLastSaveChanges.IsValid) { return(StatusFromLastSaveChanges.Result); } throw new GenericEventRunnerStatusException(StatusFromLastSaveChanges); }
public Book AddPromotion(AddRemovePromotionDto dto) { var book = _context.Find <Book>(dto.BookId); if (book == null) { throw new InvalidOperationException($"Could not find the book with Id of {dto.BookId}."); } Status = book.AddPromotion(dto.ActualPrice, dto.PromotionalText); if (Status.HasErrors) { return(null); } _context.SaveChanges(); return(book); }
//I only have to override these two version of SaveChanges, as the other two versions call these /// <summary> /// EF Core's SaveChanges, but with domain event handling added /// Throws an exception if any of the BeforeSave event handlers return a status with an error in it. /// </summary> /// <param name="acceptAllChangesOnSuccess"></param> /// <returns>number of writes done to the database</returns> public override int SaveChanges(bool acceptAllChangesOnSuccess) { if (_eventsRunner == null) { return(base.SaveChanges(acceptAllChangesOnSuccess)); } StatusFromLastSaveChanges = _eventsRunner.RunEventsBeforeAfterSaveChanges(this, () => ChangeTracker.Entries <EntityEvents>(), () => base.SaveChanges(acceptAllChangesOnSuccess)); if (StatusFromLastSaveChanges.IsValid) { return(StatusFromLastSaveChanges.Result); } throw new GenericEventRunnerStatusException(StatusFromLastSaveChanges); }
/// <summary> /// This allows statuses to be combined. Copies over any errors and replaces the Message if the currect message is null /// If you are using Headers then it will combine the headers in any errors in combines /// e.g. Status1 with header "MyClass" combines Status2 which has header "MyProp" and status2 has errors. /// The result would be error message in status2 would be updates to start with "MyClass>MyProp: This is my error message." /// </summary> /// <param name="status"></param> public IStatusGeneric <T> CombineStatuses(IStatusGeneric <T> status) { if (!status.IsValid) { _errors.AddRange(string.IsNullOrEmpty(Header) ? status.Errors : status.Errors.Select(x => new ErrorGeneric(Header, x))); } if (IsValid && status.Message != DefaultSuccessMessage) { Message = status.Message; } StatusCode ??= status.StatusCode; return(this); }
/// <summary> /// EF Core's SaveChanges, but with domain event handling added /// Throws an exception if any of the BeforeSave event handlers return a status with an error in it. /// </summary> /// <param name="acceptAllChangesOnSuccess"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override async Task <int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { if (_eventsRunner == null) { return(await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken).ConfigureAwait(false)); } StatusFromLastSaveChanges = await SaveChangesWithStatusAsync(acceptAllChangesOnSuccess, cancellationToken) .ConfigureAwait(false); if (StatusFromLastSaveChanges.IsValid) { return(StatusFromLastSaveChanges.Result); } throw new GenericEventRunnerStatusException(StatusFromLastSaveChanges); }
/// <summary> /// EF Core's SaveChanges, but with domain event handling added /// Throws an exception if any of the BeforeSave event handlers return a status with an error in it. /// </summary> /// <param name="acceptAllChangesOnSuccess"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override async Task <int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { if (_eventsRunner == null) { return(await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken).ConfigureAwait(false)); } StatusFromLastSaveChanges = await _eventsRunner.RunEventsBeforeAfterSaveChangesAsync(this, () => ChangeTracker.Entries <EntityEvents>(), () => base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken)); if (StatusFromLastSaveChanges.IsValid) { return(StatusFromLastSaveChanges.Result); } throw new GenericEventRunnerStatusException(StatusFromLastSaveChanges); }
/// <summary> /// This adds a role if not present, or updates a role if is present. /// </summary> /// <param name="roleName"></param> /// <param name="description"></param> /// <param name="permissions"></param> public async Task AddOrUpdateRoleToPermissionsAsync(string roleName, string description, params Permission[] permissions) { IStatusGeneric <RoleToPermissions> status = await RoleToPermissions.CreateRoleWithPermissionsAsync( roleName, description, permissions, _repository); if (status.IsValid) { //Note that CreateRoleWithPermissions will return a invalid status if the role is already present. await _repository.AddAsync(status.Result); } else { await UpdateRoleAsync(roleName, description, permissions); } }
public static string CopyErrorsToString(this IStatusGeneric status, ModelStateDictionary modelState) { string lstErrors = ""; foreach (var error in status.Errors) { lstErrors += error.ErrorMessage + "\n"; } foreach (var state in modelState) { foreach (var error in state.Value.Errors) { lstErrors += error.ErrorMessage + "\n"; } } return(lstErrors); }
public IImmutableList <ValidationResult> AddQuestionGroup(SurveyDto dto) { var survey = _context.Find <Survey>(dto.Id); if (survey == null) { Status.AddError($"Could not find Survey with an Id of {dto.Id}"); return(Status.Errors); } var questionGroups = _questionMapper.Map(dto.QuestionGroupsDtos, _context); Status = survey.AddQuestionGroups(questionGroups, _context); if (Status.HasErrors) { return(Status.Errors); } _context.SaveChanges(); return(null); }
private async Task <IStatusGeneric <int> > CallSaveChangesWithExceptionHandlerAsync(DbContext context, Func <Task <int> > callBaseSaveChangesAsync) { var status = new StatusGenericHandler <int>(); do { try { context.ChangeTracker.AutoDetectChangesEnabled = false; status.SetResult(await callBaseSaveChangesAsync().ConfigureAwait(false)); break; //This breaks out of the do/while } catch (Exception e) { IStatusGeneric exceptionStatus = null; if (_config.ExceptionHandlerDictionary.TryGetValue(context.GetType(), out var exceptionHandler)) { exceptionStatus = exceptionHandler(e, context); } if (exceptionStatus == null) { //This means the SaveChangesExceptionHandler doesn't cover this type of Concurrency Exception throw; } //SaveChangesExceptionHandler ran, so combine its error into the outer status status.CombineStatuses(exceptionStatus); } finally { context.ChangeTracker.AutoDetectChangesEnabled = true; } //If the SaveChangesExceptionHandler fixed the problem then we call SaveChanges again, but with the same exception catching. } while (status.IsValid); return(status); }
/// <summary> /// This is used to create a Message-only response from new GenericServices /// </summary> /// <param name="status"></param> public WebApiMessageOnly(IStatusGeneric status) { Message = status.Message; }
/// <summary> /// This will return a result value, with the status Message. There are three possibilities: /// 1. If there are no errors and the result is not null it will return a HTTP response with the status code provided /// in the validStatusCode property, plus a json containing the message from the status and the results object /// 2. If there are no errors but result is null it will return a HTTP 204 (NoContent) with the status Message /// 3. If there are errors it returns a HTTP 400 with the error information in the standard WebAPI format /// </summary> /// <typeparam name="T"></typeparam> /// <param name="status"></param> /// <param name="results"></param> /// <param name="validStatusCode">The status code to return when the status has no errors and the result is not null</param> /// <param name="nullResultStatusCode">Optional, default is 204: The status code to return if the there ar no errors, but the result is null</param> /// <returns></returns> public static ActionResult <WebApiMessageAndResult <T> > ResponseWithValidCode <T>(this IStatusGeneric status, T results, int validStatusCode, int nullResultStatusCode = ResultIsNullStatusCode) { if (status.IsValid) { return new ObjectResult(new WebApiMessageAndResult <T>(status, results)) { StatusCode = results == null ? nullResultStatusCode : validStatusCode } } ; //it has errors return(CreateBadRequestObjectResult(status.Errors.Select(x => x.ErrorResult))); }
public static void CheckResponseWithValidCode <T>(this ActionResult <WebApiMessageAndResult <T> > actionResult, IStatusGeneric status, T results, int validStatusCode, int nullResultStatusCode = CreateResponse.ResultIsNullStatusCode) { actionResult.ShouldNotBeNull(); var result = actionResult.Result as ObjectResult; if (status.IsValid) { result.ShouldNotBeNull(); result.StatusCode.ShouldEqual(results == null ? nullResultStatusCode : validStatusCode); var returnClass = result.Value as WebApiMessageAndResult <T>; returnClass?.Message.ShouldEqual(status.Message); returnClass?.Results.ShouldEqual(results); } else { CheckErrorResponse(result as BadRequestObjectResult, status.Errors.Select(x => x.ErrorResult)); } }
public static void CheckResponse <T>(this ActionResult <WebApiMessageAndResult <T> > actionResult, IStatusGeneric status, T results) { actionResult.CheckResponseWithValidCode <T>(status, results, CreateResponse.OkStatusCode); }
public static void CheckResponseWithValidCode(this ActionResult <WebApiMessageOnly> actionResult, IStatusGeneric status, int validStatusCode) { actionResult.ShouldNotBeNull(); var result = actionResult.Result as ObjectResult; if (status.IsValid) { actionResult.ShouldNotBeNull(); Assert.NotNull(result); result.StatusCode.ShouldEqual(validStatusCode); var returnClass = result.Value as WebApiMessageOnly; returnClass?.Message.ShouldEqual(status.Message); } else { CheckErrorResponse(result as BadRequestObjectResult, status.Errors.Select(x => x.ErrorResult)); } }
public static void CheckResponse(this ActionResult <WebApiMessageOnly> actionResult, IStatusGeneric status) { actionResult.CheckResponseWithValidCode(status, CreateResponse.OkStatusCode); }
/// <summary> /// This runs the events before, during and after the base SaveChangesAsync method is run /// </summary> /// <param name="context">The current DbContext</param> /// <param name="callBaseSaveChangesAsync">A function that is linked to the base SaveChangesAsync in your DbContext</param> /// <param name="cancellationToken"></param> /// <returns>Returns the status with the numUpdated number from SaveChanges</returns> public async Task <IStatusGeneric <int> > RunEventsBeforeDuringAfterSaveChangesAsync(DbContext context, Func <Task <int> > callBaseSaveChangesAsync, CancellationToken cancellationToken) { async Task <IStatusGeneric <int> > RunTransactionWithDuringSaveChangesEventsAsync() { var localStatus = new StatusGenericHandler <int>(); //If there is a current transaction then use that, otherwise using var transaction = context.Database.CurrentTransaction == null ? await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false) : null; var duringPreStatus = await _eachEventRunner.RunDuringSaveChangesEventsAsync(context, false, true) .ConfigureAwait(false); localStatus.CombineStatuses(duringPreStatus); var transactionSaveChanges = await CallSaveChangesWithExceptionHandlerAsync(context, callBaseSaveChangesAsync) .ConfigureAwait(false); if (localStatus.CombineStatuses(transactionSaveChanges).HasErrors) { return(localStatus); } localStatus.SetResult(transactionSaveChanges.Result); var duringPostStatus = await _eachEventRunner.RunDuringSaveChangesEventsAsync(context, true, true) .ConfigureAwait(false); if (localStatus.CombineStatuses(duringPostStatus).HasErrors) { return(localStatus); } if (transaction != null) { await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); } return(localStatus); } var status = new StatusGenericHandler <int>(); var hasDuringEvents = _eachEventRunner.SetupDuringEvents(context); status.CombineStatuses(await _eachEventRunner.RunBeforeSaveChangesEventsAsync(context, true).ConfigureAwait(false)); if (!status.IsValid) { return(status); } context.ChangeTracker.DetectChanges(); //This runs any actions adding to the config that match this DbContext type RunAnyAfterDetectChangesActions(context); //Call SaveChangesAsync with catch for exception handler IStatusGeneric <int> callSaveChangesStatus; if (!hasDuringEvents) { //No need for a transaction as no During event. Therefore just call SaveChanges callSaveChangesStatus = await CallSaveChangesWithExceptionHandlerAsync(context, callBaseSaveChangesAsync) .ConfigureAwait(false); } else if (context.Database.CurrentTransaction == null && context.Database.CreateExecutionStrategy().RetriesOnFailure) { //There is no existing transactions AND we have to handle retries, then we need to wrap the transaction in a retry callSaveChangesStatus = await context.Database.CreateExecutionStrategy().ExecuteAsync(async x => await RunTransactionWithDuringSaveChangesEventsAsync().ConfigureAwait(false), cancellationToken); context.ClearDuringEvents(); //clear During events after a successful transaction } else { callSaveChangesStatus = await RunTransactionWithDuringSaveChangesEventsAsync().ConfigureAwait(false); context.ClearDuringEvents(); //clear During events after a successful transaction } if (status.CombineStatuses(callSaveChangesStatus).HasErrors) { return(status); } do { try { status.SetResult(await callBaseSaveChangesAsync().ConfigureAwait(false)); break; //This breaks out of the do/while } catch (Exception e) { IStatusGeneric exceptionStatus = null; if (_config.ExceptionHandlerDictionary.TryGetValue(context.GetType(), out var exceptionHandler)) { exceptionStatus = exceptionHandler(e, context); } if (exceptionStatus == null) { //This means the SaveChangesExceptionHandler doesn't cover this type of Concurrency Exception throw; } //SaveChangesExceptionHandler ran, so combine its error into the outer status status.CombineStatuses(exceptionStatus); } //If the SaveChangesExceptionHandler fixed the problem then we call SaveChanges again, but with the same exception catching. } while (status.IsValid); await _eachEventRunner.RunAfterSaveChangesEventsAsync(context, true).ConfigureAwait(false); return(status); }
/// <summary> /// This will return a HTTP 200 with the status message if Valid, /// otherwise it will returns a HTTP 400 with the error information in the standard WebAPI format /// </summary> /// <param name="status"></param> /// <returns></returns> public static ActionResult <WebApiMessageOnly> Response(this IStatusGeneric status) { return(status.ResponseWithValidCode(OkStatusCode)); }