What is the recommended way to handle the validation following scenario with Breeze?
Scenario: I have an entity with startDate, endDate and useDate fields. If useDate is true, then I want to validate
startDate and endDate are valid dates and that endDate >
startDate.
The approach suggested in the Breeze documentation is to create a custom Validator and register it on the entity. The issue I see with this approach is that I would then need to manually call validateEntity() every time one of those three properties changes in order to have the validation errors added to the entity's error collection. The other recommended method of adding a custom validator on a single property doesn't provide access to the entity (just the current property value) so this isn't a great option either.
I could subscribe to each Knockout property changed event on the entity and then invoke validateEntity() but this seems clumsy.
Is there any way to add a custom entity-level validator to an entity but associate it with one or more properties so that when one of the associated properties change, the entity-level validator is invoked? It seems like Breeze is missing a common validation use case scenario here, but I'm probably missing something.
Thanks, Richard
I think you could apply your custom validator function against each of the startDate, endDate and useDate fields so that changes to any of those properties would trigger the same logic.
Looking at the Breeze code it looks like the context object passed to your validation function will have an entity property so you can access the other properties on the entity (not tested).
After that you could, if desired, also use the code from this post to copy those validation functions to become knockout.validation rules for binding in your UI.
Related
Im trying to find out which attributes of an entity have been changed.
As far I have seen, there is a PersistenceSession with a method to check an object if an attribute isDirty. But its always true because it never registers the old object.
So if I take the demo from the QuickGuide and override the update method in the CoffeeBeanRepository:
/**
* #param \Acme\Demo\Domain\Model\CoffeeBean $coffeeBean
*/
public function update($coffeeBean) {
\TYPO3\Flow\var_dump($this->persistenceSession->isDirty($coffeeBean, 'name'), "name changed before");
parent::update($coffeeBean);
\TYPO3\Flow\var_dump($this->persistenceSession->isDirty($coffeeBean, 'name'), "name changed after");
}
... its always TRUE (both), despite I didn't change anything.
Anyone an idea/reference how this can be accomplished?
I am using it for a REST API where a user can't update several fields and on editing of some fields additional actions have to be executed.
The persistenceSession is part of the generic persistence backend of Flow and is neither maintained, nor really used unless you explicitly deactivate doctrine. Hence persistenceSession will not help you, because all entities are considered new for the persistenceSession as you noticed.
With doctrine you need to get the entity changeset from the "UnitOfWork", which you can get from an injected \Doctrine\Common\Persistence\ObjectManager. See also Is there a built-in way to get all of the changed/updated fields in a Doctrine 2 entity
However, this is a suboptimal solution and a hacky work-around at best. If you need to track changes to your entity, it should be an explicit part of your domain model. For example make your setters record a changed properties list, when the given value is different from the current.
When done, you could even optimize doctrines change tracking on the way with that: http://doctrine-orm.readthedocs.org/en/latest/reference/change-tracking-policies.html#notify
A few weeks ago, I've asked how to save Many-To-Many associations with breeze.
Ward Bell came up with this nice solution: breeze: many-to-many issues when saving
I've implemented his solution and it works really well. One issue I've come up with recently though, is how to track errors ?
Taking Ward's example, we manipulate UserRoleVm instances. Therefore validationErrorsChanged will not be triggered for this property.
How could I use breeze to raise an error if say, the parent entity does not have at least one UserRoleVm entity in its collection ?
The UserRoleVm is a regular JavaScript object. It is not a Breeze entity and so does not participate in the Breeze validation support. There is no obvious way to make it do so (at least not obvious to me). Almost anything I can dream up would be more complicated than writing traditional, view-based validation.
What kind of validation do you need? In the example that I put together, the user can only add and remove roles (the equivalent of super powers). There is no way the user can touch any value of the corresponding mapping entity (which may not even exist yet).
When I turn my imagination loose, I speculate about the rules governing how many roles the user can have or whether certain combinations of rule are allowed ... or disallowed. Is that what you mean?
If I had such rules, I'd build validation logic into the outer ViewVM (not the UserRoleVMs) ... the VM that supervises the user's actions. This logic would be quite apart from the Breeze validation logic that you register in metadata ... the validation rules implemented by Breeze inside each entity's EntityAspect.
Ultimately, I would have Breeze validations too ... probably entity validations on the parent User entity type ... so that I could guard against an actual attempt to save an invalid UserRole combination.
But such Breeze validation rules wouldn't kick in until you tried to save. While the user is working with "item VMs" (the UserRoleVms), the validation rules would be defined and implemented separately by the ViewVM in good old vanilla JavaScript.
Such is my thinking at the moment.
Following Ward's advice, I have:
-added the following code for forcing User entity's state to be modified whenever a UserRoleVM is added or removed:
$scope.user.entityAspect.setModified();
-added a custom validator for validating the UserRoles collection on the User entity:
function notEmptyCollectionValidator() {
var name = "notEmptyCollectionValidator";
var validator = new breeze.Validator(name, function (value) {
if (!value || value.length === 0) {
return false;
} else {
return true;
}
});
return validator;
}
breeze.Validator.registerFactory(notEmptyCollectionValidator, 'notEmptyCollectionValidator');
var entityType = metadataStore.getEntityType('User');
entityType.getProperty('userRoles').validators.push(breeze.config.functionRegistry['Validator.notEmptyCollectionValidator']());
Now when I hit the save button, the validation occurs on the userRoles collection. If no userRole was selected, I get a validation error and I show a * next to the control in th UI.
Obviously, that does not work for OnChange validation. I don't know yet how I'm going to achieve that.
i would like to create new entities which use the default values defined in the model.
i've checked the retrieved metadata, and the default values are there:
{"name":"LastName","type":"Edm.String","maxLength":"50","unicode":"true","fixedLength":"false","defaultValue":"admin:
Nachname"},
however they are not taken into consideration when creating a new entity.
This is a bug in Breeze that should be fixed in the next release, out in about week. When this fix gets in then breeze will honor any defaultValues it finds in the EntityFramework data model.
One problem though is while it is easy to get 'defaultValues' into a Model First Entity Framework model via the properties editor, it's actually difficult to get it into a Code First EF model, unless you use fluent configuration. Unfortunately, EF ignores the [DefaultValue] attribute when constructing Code First model metadata.
One workaround that you can use now is to poke the 'defaultValue' directly onto any dataProperty. Something like:
var customerType = myEntityManager.metadataStore.getEntityType("Customer");
var fooProperty = customerType.getProperty("foo");
fooProperty.defaultValue = 123;
In my database User table I have DataTime field called DateDeleted - which is null while user exists and is set to the proper value when user "is deleted".
I wonder if there is a way to introduce IsDeleted property for User entity so that
http://odata/service.svc/Users(1)/IsDeleted
will return true or false depending on whether DateDeleted is set or not
My research in google hasn't got any results and I am almost sure it is not possible to implement through odata. Am I right?
With the built in providers this is not possible on the WCF DS side of things. You might be able to somehow do this on the EF side (expose it as a property of the EF entity), but I'm not sure if that's possible.
On the WCF DS side, you would have to implement a custom provider in order to do this. Which may be quite a lot of work unfortunately. If you're interested see this for starters: http://blogs.msdn.com/b/alexj/archive/2010/01/07/data-service-providers-getting-started.aspx.
What Shawn refers to above is a method on the custom provider interface.
You can specify the value you want by implementing the method DataServiceQueryProvider.GetPropertyValue.
Please find the reference here:
http://msdn.microsoft.com/en-us/library/system.data.services.providers.idataservicequeryprovider.getpropertyvalue.aspx
The method takes two parameters, the entity object (a User instance) and the resource property (in this case "IsDeleted"). You can try to get the property value of "DataDeleted" from the entity object, and return the value of "IsDeleted" as you want.
I'm working on my first ASP.NET MVC (beta for version 3) application (using EF4) and I'm struggling a bit with some of the conventions around saving a new record and updating an existing one. I am using the standard route mapping.
When the user goes to the page /session/Evaluate they can enter a new record and save it. I have an action defined like this:
[ActionName("Evaluate")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EvaluateSave(EvaluteSessionViewModel evaluatedSession)
{
}
When they save I grab an entity off the view model and attach it to my context and save. So far, so good. Now I want the user to be able to edit this record via the url /session/Evaluate/1 where '1' is the record ID.
Edit: I have my EF entity attached as a property to the View Model.
If I add an overloaded method, like this (so I can retrieve the '1' portion automatically).
[ActionName("Evaluate")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EvaluateSave(ID, EvaluteSessionViewModel evaluatedSession)
{
}
I get an "The current request for action 'Evaluate' on controller type 'SessionsController' is ambiguous between the following action" error. I'm not sure why they're ambiguous since they look unique to me.
I decided that I was just going to skip over this issue for now and see if I could get it to update an existing record, so I commented out the EvaluateSave that didn't have the ID parameter.
What I'd like to do is this:
// Load the original entity from EF
// Rebind the postback so that the values posted update the entity
// Save the result
Since the entity is populated as the parameter (evaluatedSession) the rebinding is happening too soon. But as I look at the approach I'd like to take I realized that it opens my code up to hacking (since a user could add in fields into the posted back page and these could override the values I set in the entity).
So it seems I'm left with having to manually check each field to see if it has changed and if it has, update it. Something like this:
if (evaluatedSession.MyEntity.myField <> savedSession.myField)
savedSession.myField = evaluatedSession.MyEntity.myField;
Or, save a copy of the entity and make sure none of the non-user editable ones have changed. Yuck.
So two questions:
First: how do I disambiguate the overloaded methods?
Second: is there a better way of handling updating a previously saved record?
Edit: I guess I could use something like Automapper...
Edit 9/22/2010 - OK, it looks like this is supposed to work with a combination of two items: you can control what fields bind (and specifically exclude some of them) via the [Bind(Exclude="field1,field2")] attribute either on the class level or as part of the method doing the saving, ex.
public ActionResult EvaluateSave([Bind(Exclude="field1")] EvaluateSessionViewModel evaluatedSession)
From the EF side of things you are supposed to be able to use the ApplyCurrentValues() method from the context, ex.
context.ApplyCurrentValues(savedEval.EntityKey.EntitySetName, evaluatedSession);
Of course, that doesn't appear to work for me. I keep getting "An object with a key that matches the key of the supplied object could not be found in the ObjectStateManager. Verify that the key values of the supplied object match the key values of the object to which changes must be applied.".
I tried attaching the original entity that I had just loaded, just in case it wasn't attached to the context for some reason (before ApplyCurrentValues):
context.AttachTo(savedEval.EntityKey.EntitySetName, savedEval);
It still fails. I'm guessing it has something to do with the type of EF entity object MVC creates (perhaps it's not filled in enough for EF4 to do anything with it?). I had hoped to enable .NET framework stepping to walk through it to see what it was attempting to do, but it appears EF4 isn't part of the deal. I looked at it with Reflector but it's a little hard for me to visualize what is happening.
Well, the way it works is you can only have one method name per httpverb. So the easiest way is to create a new action name. Something like "Create" for new records and "Edit" for existing records.
You can use the AntiForgeryToken ( http://msdn.microsoft.com/en-us/library/dd492767.aspx ) to validate the data. It doesn't stop all attempts at hacking but it's an added benefit.
Additional
The reason you can only have one action name per httpverb is because the model binders only attempt to model bind and really aren't type specific. If you had two methods with the same action name and two different types of parameters it can't just try and find the best match because your intent might be clearly one thing while the program only sees some sort of best match. For instance, your might have a parameter Id and a model that contains a property Id and it might not know which one you intend to use.