Mike Goodwin
Mike Goodwin

Reputation: 8880

How do I do cross-entity server-side validation

In my application, I have cross-entity validation logic that requires me to look at the entire change set and I'm doing this using the BeforeSaveEntities override.

I can construct the right logic by examining the saveMap parameter, but what am I supposed to do if I find something invalid?

If I throw an exception, like I would for single entity validation in the BeforeSaveEntity override, the whole save is aborted and the error is reported to the client. But some of the entities might be valid so I would want to save those and only abort the invalid parts.

Because BeforeSaveEntities returns a saveMap, I think I should be able to remove the invalid entities from the change set and continue to save the valid entities, but then how do I report the invalid parts to the client?

Is it possible to do a partial save of only the valid entities and at the same time, report a sensible error to the client to describe the parts of the save that failed?

Upvotes: 0

Views: 135

Answers (2)

Ward
Ward

Reputation: 17863

Jay told you the way it is.

I wouldn't hold my breath waiting for Breeze to change because I think yours is a rare scenario and it isn't one we would want to encourage anyway.

But I'm weird and I can't stop thinking what I'd do if were you and I absolutely HAD to do it. I might try something like this.

Warning: this is pseudo-code and I'm making this up. I do not recommend or warrant this

  1. Create a custom MyCustomEFContextProvider that derives from EFContextProvider.

  2. Give it an ErrorEntities property to hold the error object

  3. Override (shadow) the SaveChanges method with another that delegates to the base

    public new CustomSaveResult SaveChanges(JObject saveBundle, 
                                     TransactionSettings transactionSettings = null) {
       var result = base.SaveChanges(saveBundle, transactionSettings);
       // learn about CustomSaveResult below
       return new CustomSaveResult(this.ErrorEntities, result);
    }
    
  4. Catch an invalid entity inside BeforeSaveEntities

  5. Pass it with error message to your custom ErrorEntities property

    You get to that property via the EntityInfo instance as in

    ((MyCustomEFContextProvider) info.ContextProvider).ErrorEntities.Add(new ErrorEntity(info, message));

  6. Remove the invalid entity from the SaveMap so it won't be included in the actual save

  7. Let the save continue

  8. The second line of your override SaveChanges method creates a new instance of your CustomSaveResult from the standard one and returns that to the caller.

    public class CustomSaveResult : SaveResult {
    
       public List ErrorEntities;
       public CustomSaveResult(List errorEntities, SaveResult result){
           // copy over everything
           this.Entities = result.Entities;
           this.KeyMappings = result.KeyMappings;
           this.Errors = this.Errors;
           // and now your error stuff
           this.ErrorEntities = errorEntities;
      }
    }
    

Let's assume the caller is your Web API controller's SaveChanges method. Well you don't have to change a thing but you might make it clear by explicitly returning your custom SaveResult:

    readonly MyCustomEFContextProvider _contextProvider = new MyCustomEFContextProvider();
    ...
    [HttpPost]
    public CustomSaveResult SaveChanges(JObject saveBundle) {
        return _contextProvider.SaveChanges(saveBundle);
    }

JSON.Net will happily serialize the usual material + your custom ErrorEntities property (be sure to make it serializable!) and send it to the Breeze client.

On the Breeze client you write your own variation on the stock Breeze Web API data service adapter. Yours does almost exactly the same thing as the Breeze version. But, when processing the save payload from the server, it also extracts this extra "error entities" material in the response and does whatever you want to do with it.

I don't know what that will be but now you have it.

See how easy that was? LOL.

Upvotes: 1

Jay Traband
Jay Traband

Reputation: 17052

Breeze does not currently support a save mechanism that both saves and returns an error at the same time. While possible this seems a bit baroque.

As you pointed out, you can

1) Throw an exception inside of the BeforeSaveEntities and fail the save. You can even specify which specific entity or entities caused the failure and why. In this case the entire save is aborted.

or

2) Remove 'bad' items from the saveMap within the BeforeSaveEntities and save only a subset of what was passed in. In this case you are performing a partial save.

But we don't support a hybrid of these two. Please add this to the Breeze User Voice if you feel strongly and we can see if other members of the community feel that this would be useful.

Upvotes: 0

Related Questions