Laravel Nova creating a model with an action in newModel() - laravel

I'm in Laravel 8 with Nova 3.22. I have a table that contains a field that is populated automatically with a serial number inside a DB transaction, and in order to enforce that I have a CreateProduct action defined that wraps the creation of that resource. Everywhere I need to create a new instance of that model, I call the action, and I need to do the same in Nova. I've found the newModel() method to override, but it has two problems.
public static function newModel()
{
$instance = new CreateProduct(
new \App\Models\Sku(), //Placeholder
\App\Models\Product::STATUS_DEFAULT,
null
);
return $instance->handle();
}
Firstly, this method is called on create (displaying the input form) as well as store store operations. This means I end up with two calls to my action, creating spurious serial numbers in my DB. If I don't override newModel like this, it creates records that lack a serial number altogether. I have a choice of 0 or 2 calls, but I only want 1!
The second issue is related; when the form is displayed, it calls newModel, but I have to use placeholder data for required params of the action, as those fields have (obviously) not been set yet, but I need to replace those placeholders with the real submitted values the second time, and I'm not sure how I would do that.
I feel I must be missing something – is there something like newModel, but that is only called for a store operation?

Related

EF Core 2.1 trying to include primary key field in INSERT query when adding to DbContext and saving

In an ASP .Net Core 2.1 Web API (with a MySQL database and using Pomelo), when I add a new entity to the database in one of my controller actions, if the entity that is received by the API from the consuming client has a value in the primary key, it appears as though EF Core is trying to add the primary key instead of allowing the database to give it a new value.
So... in the database, I have a table called person which has an integer field called id which is set to PRIMARY KEY and AUTO-INCREMENT.
Model:
public partial class Person
{
public int? Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>(entity =>
{
entity.ToTable("person");
entity.HasKey(e => e.Id);
entity.Property(e => e.Id)
.HasColumnName("id")
.HasColumnType("int(11)");
entity.Property(e => e.Name)
.HasColumnName("name")
.HasColumnType("varchar(45)");
entity.Property(e => e.Surname)
.HasColumnName("surname")
.HasColumnType("varchar(45)");
}
}
Controller Action
// POST: api/Person
[HttpPost]
public async Task<IActionResult> AddPerson([FromBody]Person person)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
_context.Person.Add(person);
await _context.SaveChangesAsync();
return CreatedAtAction("GetPerson", new { id = person.Id }, person);
}
If I don't specifically clear the Id of the person before trying to insert it into the database (i.e. person.Id = null) then I get an exception complaining about duplicate primary key. Is this normal EF Core behavior? Or am I doing something wrong?
Frankly, yes, you are doing something wrong. For a whole host of reasons, you should never ever save an instance created from user input (i.e. the Person instance being passed into your action and created from the request body of the post) directly to your database. One such reason is that it causes havoc with ORMs like EF, which employ entity tracking to optimize queries.
Simply, this Person instance here is untracked - EF knows nothing about it. You then use Add to add it to your context, which signals EF to start tracking it as a new thing. When you later save, EF, then dutifully issues an insert statement, but since an id is included in that insert, you get a primary key conflict. What you wanted instead was for EF to do an update, but it doesn't know it should.
There's ways you can technically fix this. For example, you could use Attach rather than Add. That merely blindly tells EF that this is something it should track, without necessarily communicating that it should do anything with it. If you make any modifications to this instance after it is tracked, EF will change its change to "modified" and you'll end up with an update statement being issued when you save. However, if you're not making any changes, but just saving it directly, you'll also need to explicitly set it's state to "modified" or EF will essentially do nothing. The nice thing is that if you change the state on an untracked entity, then EF automatically attaches it to track said state, so you you don't need to do Attach manually. Long and short, you can clear the exception merely by replacing your Add line with:
_context.Entry(person).State = EntityState.Modified;
However, that then will cause a problem if you try to add a new person entirely. A bigger issue you have here is that you have one action doing double duty. According to REST, a POST is not replayable and should only be made to resources which or idempotent. Put more simply, you POST only to a resource like /api/person (rather than something like /api/person/1 and every time you do so a new person should be created. For an update, you should make a request to that actual resource, i.e /api/person/1 and the HTTP verb should be PUT, instead. The same PUT request to the same resource will always have the same result, which is the case for an update to a particular resource.
Theory aside, the simple point is that you should have two actions:
[HttpPost("")]
public async Task<IActionResult> AddPerson([FromBody]Person person)
[HttpPut("{id}")]
public async Task<IActionResult> UpdatePerson(int id, [FromBody]Person person)
Finally, even with all this, saving the person param directly puts too much trust in the user, when doing an update. There might be any number of properties an end-user should not be able to modify with an update (such as something like a "created" date, for example), but they can when you do this. In some ways worse, even if the user is not being malicious, you're still relying on them to post all the data for that entity. For example, if you did have a created date property, but the user doesn't post that with their update (honestly, why would you post a created date along with a request to update a resource), then it will have the effect of clearing that property out. If there's a default, it will be set back to that, and if not, you may actually get an exception on saving, if the column is NOT NULL.
Long and short, it's not a good idea. Instead, use a view model, DTO, or similar. That class should contain only properties you want to allow a user to modify or even to affect on create in the first place. Then, for the case of an update, you pull the resource fresh from the database, and map over the values from your param instance onto that. Finally, you save the version from the database back to the database. This ensures 1) the user cannot modify anything you do not explicitly allow, 2) the user only needs to post things they actually care about modifying, and 3) the entity will be properly tracked and EF will issue an update statement correctly on save.

Allow One setAttribute() to Prevent Another One

I have an entity which I've created two setAttribute functions for:
public function setStartAttribute($value) { }
and
public function setEndAttribute($value) { }
These attributes, start and end, are both datetimes which I check against some criteria in each of my setter function before allowing. Under certain conditions, I prevent or allow the start or end attributes to be updated.
I've hit a wall, however, in that if I prevent one of these from being updated, I need to prevent both. In other words, if the user tries to update the entity with a start date which is out of bounds, I need to prevent the start date from being updated, but I also need to prevent the end date from being update.
As these are two separate functions, I'm not sure how to use one to prevent the other in a case like this.
EDIT:
Since the answer is extremely obvious (just do it both in one function) without adding this extra info, I'll add that the part that makes this less straightforward is that I'm using Backpack for Laravel. Within the Backpack admin panel is the CRUD that lets me create or update my entity. I'm using the date_range field type to allow setting the start and end time/dates on my entity. It's upon saving this that I need to be able to pass both the start and end values to a function and validate them, prior to setting them on my entity. I found that creating the two separate functions above setStartAttribute() and setEndAttribute() allowed me to validate those values and choose whether to assign them to the entity, however I need to be able to use one unified function rather than two separate ones. It is this integration with Backpack which makes this problem less straightforward for me.
If those start and end attributes are connected somehow (one can't be set if another is invalid), you better make one method to set both of them. Something like this:
public function setStartAndEnd($start, $end)
{
if ($start is valid && $end is valid)
{
$this->start = $start;
$this->end = $end;
}
}
Which you can use as follows:
$entity->setStartAndEnd($date, $another_date);

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?
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
Create a custom MyCustomEFContextProvider that derives from EFContextProvider.
Give it an ErrorEntities property to hold the error object
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);
}
Catch an invalid entity inside BeforeSaveEntities
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));
Remove the invalid entity from the SaveMap so it won't be included in the actual save
Let the save continue
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.
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.

should a validation function access the repository directly?

I have the following in my application:
Action Orm entity (From telerik open access)
Repository(Of Action)
AppService(Holds an instance of the repository)
when I need to save an instance, I send the instance to the AppService. the AppService then calls a validator to validate the instance to save. the validator is based on http://codeinsanity.com/archive/2008/12/02/a-framework-for-validation-and-business-rules.aspx
(full code on https://github.com/riteshrao/ncommon)
so basically my save function in the AppService looks like this
Public Sub AddAction(ByVal Item As Data.Model.Action)
Contract.Requires(Of ArgumentNullException)(Item IsNot Nothing, "Item is nothing.")
Dim validateResult As Rules.ValidationResult = _ActionValidator.Validate(Item)
If Not validateResult.IsValid Then
Throw New Validation.ValidationException(validateResult)
End If
Try
_ActionRepository.Add(Item)
_unitOfWork.SaveChanges()
Catch ex As Exception
_unitOfWork.ClearChanges()
Throw New DataServiceException(ex.Message, ex)
End Try
End Sub
for checking properties of the Action item the sample code works great. my question begins when i need to make sure that the same action is not added twice to the DB for the same customer (ie. id is difference, name is the same and customer is the same)
as I see it I have a few options:
option 1: check for a duplicate action using something like
function(validatedItem) item.Customer.Actions.Any(function(item) item.id<>validatedItem.id andalso item.name=validatedItem.name))
basically I go from the action being saved back to the customer and then back to all his actions and check if any action exists with a different id and same name
the down sides are:
a. for this to work, when accessing the customer property of the item, the entire customer object is read from DB which is redundant in this case
b. the Any function is being evaluated on the client as item.Customer.Actions returns IList(Of Action)
Option 2: let the validation class have access to the action repository. then i could simply do something like
'assume I already have validatedItem
repository.Any(function(item) item.id<>validatedItem.id and item.customerid=validatedItem.customerid and item.name=validatedItem.name)
this will result in an Exists query being sent to the DB but the downside(?) is that the validation framework should not access the repository directly (as far as I have seen in the very few examples i could find that do use validation and ORM)
Option 3: let the validation class have access to the AppService and use the AppService to check for existence of a duplicate.
problems:
a. I create a circular reference (AppService->Validation Class->AppService)
b. I need to create a lot of useless functions in the AppService for loading items based on criteria that is only relevant for the validation
Any ideas what is the best course here?
The simplest is not to check duplicates in the database from your domain.
When a collection of entities is part of you aggregate then it is a non-issue since you would not permit the duplicate to be added to the collection. Since the aggregate is stored as a whole you would never run into the problem.
For scenarios where you do not want a duplicate, say, e-mail address and no collection of the entities is represented by an aggregate (such as the Users in a system) you can just let the database enforce the uniqueness. Simply pick up the exception and report back. In many instances your validation would not be able to enforce the uniqueness simply because it doesn't have/implement the required locks that a database system would have.
So I'd simply leave that up to the database.

ASP MVC3 keeping variable value through page refresh

Yo! As the topic says, I need to keep var's value through refresh. Thing is it's SessionKey. Other thing is it's generated automatically.
What I need to do is html <select> which won't lose data on refresh. Actually there're 2 <select>s which are filled programatically and you can pass data between them in real time. Then if I press save and page fails to validate these <select>s return to their original state. I already have it fixed, by keeping data in session and if it has certain key, <select>s are filled with correct data.
Why would I need automatically generated key? Well multi-tab working. If user would try to add 2+ new records to database at the same time (which is extreme, but possible), he needs to have that data kept under different keys so app can find desired stuff.
I could as well make client side validation, but... nope, just nope, too much work.
As for code, anything useful:
public ActionResult MethodUsedAfterPageLoad
{
...
Guid stronyGuid = Guid.NewGuid();
ViewData["strony"] = stronyGuid.ToString();
...
}
This way every refresh creates new Guid, but Guid is used as SessionKey!
If I do it following way:
public Class ControllerClass
{
private Guid stronyGuid;
...
}
This will reset variable, that's bad. Using static keyword is bad idea.

Resources