I have an "Customer" entity with several properties, one of them is "countryCode". When the country code gets set, it should be checked if it is valid. I could use the setter of Customer to do this validation but the problem is, there is a table "country" which holds the available country codes and as far as I know it is considered bad style to make an entity depended on any repositories.
I could let a service do the validation:
class CustomerUpdateService{
public function updateFromDto(Customer $customer, CustomerUpdateDto $updateDto): Customer
{
$countryCode = $updateDto->getCountryCode();
$country = $this->countryRepository->find($countryCode);
if (!isset($country){
throw new InvalidArgumentException('Unallowed country code ' . $countryCode);
}
$customer->setCountryCode($countryCode);
//update other properties
return $customer;
}
}
In my opion an entity should not allow to set invalid property states, so it has to to the validation by its own. But how to achieve this without generating unwanted dependencies? The only thing I can think of is passing the allowed countries to the setter:
class Customer{
public function setCountryCode(string $countryCode, array $availableCountries){
$availableCountryCodes = [];
foreach ($availableCountries as $country){
$availableCountryCodes[] = $country->getCode();
}
if (!in_array($countryCode, $availableCountryCodes)){
throw new InvalidArgumentException('Unallowed country code ' . $countryCode);
}
$this->countryCode = $countryCode;
}
}
Or passing a CountryCodeValidator to the setter:
class Customer{
public function setCountryCode(string $countryCode, CountryCodeValidator $validator){
$validator->validate($countryCode);
$this->countryCode = $countryCode;
}
}
Is this a legitimate solution? Are there better approaches?
Related
I am able to validate an entity using the code entity.entityAspect.validateEntity(). However, this does not validate navigation properties. My entiy has one-to-one and one-to-many relationship with out entities. I want to validate both the entity and its navigational properties. How can i do this with breeze?
EDIT
I have a class
public class ClassA{
public int id{get; set;}
public List<ClassB> navigationArray{get; set;}
}
public class ClassB{
public int myClass {get; set;}
[Foreign("myClass")]
public ClassA ClassA_E{get; set;}
}
I add an object O1 of ClassA to the entity manager; and add an object O2 of classB to the entity manager and set the property, ClassA_E to O1. All works well but when validating O1, O2 does not get validated
EntityAspect.validateEntity WILL validate navigation properties ( The code below was tested in breeze 1.4.17).
You can add your own validators to any navigation property: In the examples below assume a schema with "Customer" and "Order" entity types where each Customer has an nonscalar "orders" property and each "Order" has a scalar "customer" property.
In this case, the scalar "customer" navigation property on the Order type might have a validator registered like this:
var orderType = em.metadataStore.getEntityType("Order");
var custProp = orderType.getProperty("customer");
// validator that insures that you can only have customers located in 'Oakland'
var valFn = function (v) {
// v is a customer object
if (v == null) return true;
var city = v.getProperty("city");
return city === "Oakland";
};
var customerValidator = new Validator("customerValidator", valFn, { messageTemplate: "This customer's must be located in Oakland" });
custProp.validators.push(customerValidator);
where the validation error would be created by calling
myOrder.entityAspect.validateEntity();
And the nonscalar navigation property "orders" on the "Customer" type might have a validator registered like this:
var customerType = em.metadataStore.getEntityType("Customer");
var ordersProp = customerType.getProperty("orders");
// create a validator that insures that all orders on a customer have a freight cost > $100
var valFn = function (v) {
// v will be a list of orders
if (v.length == 0) return true; // ok if no orders
return v.every(function(order) {
var freight = order.getProperty("freight");
return freight > 100;
});
};
var ordersValidator = new Validator("ordersValidator", valFn, { messageTemplate: "All of the orders for this customer must have a freight cost > 100" });
ordersProp.validators.push(ordersValidator);
where the validation error would be created by calling
myCustomer.entityAspect.validateEntity();
In MVC3 Code First EF how do you associate one entity with another without creating a new one (many-to-many)?
So I have a many-to-many relationship between class1 and class2. I need class1 to hold many class2 and vice versa. However, class2 is independent; I have a list of them that I want to edit separately and then associate with a new class1.
When I pass my class2List to the controller( via AJAX and JSON), I checked and all the Ids of the class2s correspond to existing ids in the db, i.e. new class2s are not created.
Model
class
{
[key]
public int Id {set; get;}
}
class1 : class
{
private ICollection<class2> _class2s;
public virtual ICollection<class2> class2s
{
get { return _class2s ?? ( _class2s = new HashSet<class2>()); }
set { _class2s = value; }
}
}
class2 : class
{
private ICollection<class1> _class1s;
public virtual ICollection<class1> class1s
{
get { return _class1s ?? ( _class1s = new HashSet<class1>()); }
set { _class1s = value; }
}
}
Controller
public ActionResult SaveChanges(List<class2> class2List)
{
createNewClass2AndAssociateExistingClass2s(class2List);
SaveChangesToDb();
return View("ProductDetail", Model);
}
createNewClass2AndAssociateExistingClass2s(List<class2> class2List)
{
var newClass1 = newClass1()
{
class2s = class2List;
}
////UnitOfWork allows me to access several DbSets in one transaction
unitOfWork.Create(newClass1)
}
SaveChangesToDb()
{
unitOfWork.Commit();
}
What this does is create a new class1 (as it should) but instead of associating the existing class2s with it, it makes new class2s with new Ids and adds them to the database.
My question:
Does this have to do with how EF is reading my Id property from base class?
How would I be able to associate several existing class2s as a list with a new class1, without creating new class2s in the database?
Cheers
Ok so two things I learned from figuring this out:
I was inheriting from an abstract class when I should have been implementing an interface. This is a great idea if you have several entities that have a similar property such as "Id" and you want to do something like
T FindById<T>(int id) where T : IEntity
When making associations in EF, even if the Id matches an existing entry, it will not update that entry, unless EF is tracking that entry in the context, as it says here. What I needed to do was:
Add a method in the mapping layer that gets the entry by id that I
want from the repository
Copy the attributes of the new entry into that context entry
Return the context entry
Hope this helps someone
I am using the example at The Complete Guide To Validation In ASP.NET MVC 3 to create a RequiredIf validation attribute (it's about 1/3 down the page under the heading of "A more complex custom validator"). It all works fine with the exception of one scenario, and that is if I have the need to validate against a complex type. For example, I have the following model:
public class MemberDetailModel
{
public int MemberId { get; set; }
// Other model properties here
public MemberAddressModel HomeAddress { get; set; }
public MemberAddressModel WorkAddress { get; set; }
}
public class MemberAddressModel
{
public bool DontUse { get; set; }
// Other model properties here
[RequiredIf("DontUse", Comparison.IsEqualTo, false)]
public string StreetAddress1 { get; set; }
}
The problem is that when the attribute validation for the StreetAddress property is rendered, it get's decorated with the attribute of data-val-requiredif-other="DontUse". Unfortunately, since the address is a sub-type of the main model, it needs to be decorated with a name of HomeAddress_DontUse and not just DontUse.
Strangely enough, the validation works fine for server-side validation, but client-side unobtrusive validation fails with an JS error because JS can't find the object with a name of just "DontUse".
Therefore, I need to find a way to change the ModelClientValidationRequiredIfRule method to know that the property it is validating is a sub-type of a parent type, and if so, prepend the ParentType_ to the "otherProperty" field (e.g. otherProperty becomes HomeAddress_DontUse.
I have tried passing in typeof(MemberAddressModel) as a parameter of the attribute, but even when debugging the attribute creation, I can't seem to find any reference to the parent type of HomeAddress or WorkAddress from that type.
Based on the suggestion from The Flower Guy, I was able to come up with the following which seems to work. I simply modified the following in the customValidation.js file:
jQuery.validator.addMethod("requiredif", function (value, element, params) {
if ($(element).val() != '') return true;
var prefix = getModelPrefix(element.name); // NEW LINE
var $other = $('#' + prefix + params.other); // MODIFIED LINE
var otherVal = ($other.attr('type').toUpperCase() == "CHECKBOX") ? ($other.attr("checked") ? "true" : "false") : $other.val();
return params.comp == 'isequalto' ? (otherVal != params.value) : (otherVal == params.value);
});
I also added the following method to that file (within the JQuery block so as to be only privately accessible):
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1).replace(".","_");
}
Cannot do it exactly right now, but the problem is in the client javascript function:
jQuery.validator.addMethod("requiredif" ...
The js is not sophisticated enough to cope with complex view models where there may be a model prefix. If you take a look at Microsoft's jquery.validate.unobstrusive.js (in the Scripts folder over every MVC3 application), you will find some useful methods including getModelPrefix and appendModelPrefix. You can take a similar approach and change the requiredIf validation method - take a look at the equalto method in jquery.validate.unobstrusive.js for a helping hand.
I'm using SPMetal in order to generate entity classes for my sharepoint site and I'm not exactly sure what the best practice is to use when there are multiple content types for a single list. For instance I have a task list that contains 2 content types and I'm defining them via the config file for SPMetal. Here is my definition...
<List Member="Tasks" Name="Tasks">
<ContentType Class="LegalReview" Name="LegalReviewContent"/>
<ContentType Class="Approval" Name="ApprovalContent"/>
</List>
This seems to work pretty well in that the generated objects do inherit from WorkflowTask but the generated type for the data context is a List of WorkflowTask. So when I do a query I get back a WorkflowTask object instead of a LegalReview or Approval object. How do I make it return an object of the correct type?
[Microsoft.SharePoint.Linq.ListAttribute(Name="Tasks")]
public Microsoft.SharePoint.Linq.EntityList<WorkflowTask> Tasks {
get {
return this.GetList<WorkflowTask>("Tasks");
}
}
UPDATE
Thanks for getting back to me. I'm not sure how I recreate the type based on the SPListItem and would appreciate any feedback.
ContractManagementDataContext context = new ContractManagementDataContext(_url);
WorkflowTask task = context.Tasks.FirstOrDefault(t => t.Id ==5);
Approval a = new Approval(task.item);
public partial class Approval{
public Approval(SPListItem item){
//Set all properties here for workflowtask and approval type?
//Wouldn't there be issues since it isn't attached to the datacontext?
}
public String SomeProperty{
get{ //get from list item};
set{ //set to list item};
}
Linq2SharePoint will always return an object of the first common base ContentType for all the ContentTypes in the list. This is not only because a base type of some description must be used to combine the different ContentTypes in code but also it will then only map the fields that should definitely exist on all ContentTypes in the list. It is however possible to get access to the underlying SPListItem returned by L2SP and thus from that determine the ContentType and down cast the item.
As part of a custom repository layer that is generated from T4 templates we have a partial addition to the Item class generated by SPMetal which implements ICustomMapping to get the data not usually available on the L2SP entities. A simplified version is below which just gets the ContentType and ModifiedDate to show the methodology; though the full class we use also maps Modified By, Created Date/By, Attachments, Version, Path etc, the principle is the same for all.
public partial class Item : ICustomMapping
{
private SPListItem _SPListItem;
public SPListItem SPListItem
{
get { return _SPListItem; }
set { _SPListItem = value; }
}
public string ContentTypeId { get; internal set; }
public DateTime Modified { get; internal set; }
public virtual void MapFrom(object listItem)
{
SPListItem item = (SPListItem)listItem;
this.SPListItem = item;
this.ContentTypeId = item.ContentTypeId.ToString();
this.Modified = (DateTime)item["Modified"];
}
public virtual void MapTo(object listItem)
{
SPListItem item = (SPListItem)listItem;
item["Modified"] = this.Modified == DateTime.MinValue ? this.Modified = DateTime.Now : this.Modified;
}
public virtual void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
{
SPListItem originalItem = (SPListItem)originalListItem;
SPListItem databaseItem = (SPListItem)databaseObject;
DateTime originalModifiedValue = (DateTime)originalItem["Modified"];
DateTime dbModifiedValue = (DateTime)databaseItem["Modified"];
string originalContentTypeIdValue = originalItem.ContentTypeId.ToString();
string dbContentTypeIdValue = databaseItem.ContentTypeId.ToString();
switch(mode)
{
case RefreshMode.OverwriteCurrentValues:
this.Modified = dbModifiedValue;
this.ContentTypeId = dbContentTypeIdValue;
break;
case RefreshMode.KeepCurrentValues:
databaseItem["Modified"] = this.Modified;
break;
case RefreshMode.KeepChanges:
if (this.Modified != originalModifiedValue)
{
databaseItem["Modified"] = this.Modified;
}
else if (this.Modified == originalModifiedValue && this.Modified != dbModifiedValue)
{
this.Modified = dbModifiedValue;
}
if (this.ContentTypeId != originalContentTypeIdValue)
{
throw new InvalidOperationException("You cannot change the ContentTypeId directly");
}
else if (this.ContentTypeId == originalContentTypeIdValue && this.ContentTypeId != dbContentTypeIdValue)
{
this.ContentTypeId = dbContentTypeIdValue;
}
break;
}
}
}
Once you have the ContentType and the underlying SPListItem available on your L2SP entity it is simply a matter of writing a method which returns an instance of the derived ContentType entity from a combination of the values of the base type and the extra data for the missing fields from the SPListItem.
UPDATE: I don't actually have an example converter class as we don't use the above mapping extension to Item in this way. However I could imagine something like this would work:
public static class EntityConverter
{
public static Approval ToApproval(WorkflowTask wft)
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = wft.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = wft.SPListItem["field-name"];
return a;
}
}
Or you could put a method on a partial instance of WorkflowTask to return an Approval object.
public partial class WorkflowTask
{
public Approval ToApproval()
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = this.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = this.SPListItem["field-name"];
return a;
}
public LegalReview ToLegalReview()
{
// Create and return LegalReview as for Approval
}
}
In either situation you would need to determine the method to call to get the derived type from the ContentTypeId property of the WorkflowTask. This is the sort of code I would normally want to generate in one form or another as it will be pretty repetitive but that is a bit off-topic.
Usually it is possible to put any domain object into session ($GLOBALS['TSFE']->fe_user) as they get automatically serialized and deserialized correctly. But in some cases this fails (i suppose caused by circurlar references or the serialized data exceeds the limit of the database blob field).
I found a better approach, converting the domain objects to integer "ids" on serialization and getting the real domain objects back from repository on deserialization:
class PutMeIntoSession implements \Serializable {
protected $project = null;
public function getProject() {
return $this->project;
}
public function setProject(\Vendor\Ext\Domain\Model\Project $project = NULL) {
$this->project = $project;
}
public function serialize() {
$serialized = serialize(array(
'project' => $this->project ? $this->project->getUid() : 0
));
return $serialized;
}
public function unserialize($serialized) {
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$unserialized = unserialize($serialized);
$this->project = $objectManager->get(ProjectRepository::class)->findByUid($unserialized['project']);
}
}
This seems to work fine. But i don't know if there are smarter ways to achieve this or if there are any possible problems with my approach?