Bubble a failed request validation to a controller in MVC - asp.net

I don't want to disable validation, however it would be great to display a message to a user. Whilst I think it is highly unlikely that a user will ever have a legitimate need to include &# in a text field, I can see someone typing in a free text field something starting with a <.
Is there a way to detect that a validation exception would be thrown and instead display it as a validation message?

Here is the way I resolved this issue:
Create a validation rule, say potentiallyDangerousRequestRule:
var potentiallyDangerousRequestRegex = /[<>]|&#/, // <, >, &#
potentiallyDangerousRequestErrorMessage = 'The error message';
$.validator.addMethod('potentiallyDangerousRequestRule', function (value) {
if (value == '')
return true;
return !potentiallyDangerousRequestRegex.test(value);
}, potentiallyDangerousRequestErrorMessage);
$.validator.unobtrusive.adapters.addBool('potentiallyDangerousRequestRule');
Call validate method on the form element you want to validate:
$('form').validate({errorClass: 'input-validation-error'});
Add the rule to elements, for instance all text inputs and textareas:
$('input:text, textarea').each(function () {
$(this).rules('add', { potentiallyDangerousRequestRule: true });
});
Make sure you call validate method on the form before applying the rule.

Related

put validation on page item through Dynamic Action

In this page I have put plsql code on click of Create Assignment button. There is a constraint on column "Assigned To" which usualy fires when that field left blank.
I want to put a validation so that I can get my own alert message which should be user readable. Also after that message submit process should be ignored.
I tried with field name :P11_Assignment NULL in the condition of PLSQL code in Dynamic Action but it is not working.
Please advise with a solution.
Before the PL/SQL Action add an Execute Javascript action.
You will want to use apex.message.* function like the following:
apex.message.clearErrors
apex.message.showErrors
Oracle Documentation link
Example:
apex.message.clearErrors();
if ($v("P11_Assignment").trim() == '') {
apex.message.showErrors({
type: "error",
location: [ "page", "inline" ],
pageItem: "P11_Assignment",
message: 'Must have a Value',
unsafe: false
});
return false; /* This is important, it stops the next action(s) from running. */
}
putting IF ELSE condition in the begining of plsql code
IF field is empty THEN don't execute the plsql code and change the field item to a value.
On value change of that item put an alert message in Dynamic Action.

Redux saga: take every action where error is true

Is there a possibility to specify whether the action has its error field set to true?
const response = function*() {
yield takeEvery("CLIENT_RESPONSE", handleResponse);
}
However, we don't know whether the action with type CLIENT_RESPONSE has its error field set to true or not.
I know I can check this in the handleResponse but that seems to be more work than it should. For instance, the handleResponse might get complex because for both the non-error and error case I need to write a lot of code (i.e. I want to have different handlers for both cases).
So is there a way to specify to only take that action when error is set to true?
According to Saga API reference, the pattern (first argument) of takeEvery can be String, Array or Function.
You can achieve what you want by passing a function:
const response = function*() {
yield takeEvery(action => (action.type === "CLIENT_RESPONSE" && !action.error), handleResponse);
}

Symfony3: How to update boolean to false using PATCH method

I have a big entity and a big form. When updating my entity, I only render parts of my form, through ajax calls. On client side, I'm using Jquery and html5 FormData, so I can also send files within my form. To make sure the fields that are not rendered won't be set to null in the process, I'm using PATCH method.
So when a field is not present in the request, it's left as is by Symfony.
But when the field I update is a boolean (rendered a a checkbox) that was set to true and I want to set it to false, it's not passed in the request, so my update is ignored.
Is there an easy way to force unchecked checkboxes to appear in the request?
EDIT
I found a way to force unchecked checkboxes to appear in the request, thanks to Felix Kling's comment on this question :
$("input:checkbox:not(:checked)").each(function() {
formData.set($(this).attr('name'), formData.has($(this).attr('id')) ? '1' : '0');
});
Unfortunately, this didn't solve my problem, because of Symfony's behaviour:
- When using PUT, if the boolean field appears in the request, it's set to true, regardless of its value (even if it's "0" or "false").
- When using PATCH method, the fields not appearing in the request are ignored.
Could that be solved with DataTransformer? (I've never used it)
You are absolutely right, Symfony will ignore it if method is PATCH because of this line in Request Handler:
$form->submit($data, 'PATCH' !== $method);
Now, I would generally suggest that you use a PUT request if that is an option, but if it isn't then second argument to FormInterface::submit($submittedData, $clearMissing = true) is what you're after.
The "proper" way would probably be to make your own implementation of Symfony\Component\Form\RequestHandlerInterface which would force $clearMissing to be true.
Other, way is a lot easier but might not work for all use-cases: use $form->submit() directly.
If you have the following code:
$form->handleRequest($request);
You can do:
$form->submit($request->get($form->getName()), true);
You can also omit second parameter since true is the default value
Here goes a working solution, that could be improved.
To force unchecked checkboxes to appear in the request, thanks to Felix Kling's comment on this question, I've added this js before my ajax request :
$("input:checkbox:not(:checked)").each(function() {
formData.set($(this).attr('name'), formData.has($(this).attr('id')) ? '1' : '0');
});
Then, on the Symfony side, I had to override the BooleanToStringTransformer behaviour, that returns true for whatever string and false only for null value. Making a change in the last line, we now return false if the value doesn't match the value defined for true ("1" by default). So if the value returned by the form is "0", we get false, as expected.
public function reverseTransform($value)
{
if (null === $value) {
return false;
}
if (!is_string($value)) {
throw new TransformationFailedException('Expected a string.');
}
return ($this->trueValue === $value); // initially: "return true;"
}
Following the docs, I made my own DataTransformer, as well as a custom AjaxCheckboxType
Unfortunately, it seems that Symfony uses both DataTransformers (mine and the original one), one after the other, so it didn't work. In the docs they extend TextType not CheckboxType, that must explain the problems I encountered.
I ended up copying and pasting the whole CheckboxType class in my own AjaxCheckboxType, only changing the DataTransformer's call in order to use mine.
A much nicer solution would be to totally override the DataTransformer, but I don't know how.
Symfony handles this out of the box, just prepare your PATCH-payload properly :)
The Symfony CheckboxType, at least in the current version 3.3 (seems like since 2.3, see update below), accepts an input value of null, interpreted as "not checked" (as you can see in lines 3-5 of the snippet in Roubi's really helpful answer).
So in your client-side AJAX-PATCH-controller you set the value of of your (dirty) unchecked checkbox-field in your application/merge-patch+json payload to null and everything is fine. No form extensions overwriting CheckboxType's behavior needed at all.
Problem is: I think, you cannot set values of HTTP-POST-payload to null, so this only works with JSON (or other compatible) payload within the request body.
A simple demo
To demonstrate this, you can use this simplified test controller:
/**
* #Route("/test/patch.json", name="test_patch")
* #Method({"PATCH"})
*/
public function patchAction(\Symfony\Component\HttpFoundation\Request $request)
{
$form = $this->createFormBuilder(['checkbox' => true, 'dummyfield' => 'presetvalue'], ['csrf_protection' => false])
->setAction($this->generateUrl($request->get('_route')))
->setMethod('PATCH')
->add('checkbox', \Symfony\Component\Form\Extension\Core\Type\CheckboxType::class)
->add('dummyfield', \Symfony\Component\Form\Extension\Core\Type\TextType::class)
->getForm()
;
$form->submit(json_decode($request->getContent(), true), false);
return new \Symfony\Component\HttpFoundation\JsonResponse($form->getData());
}
For PATCH-requests with Content-Type: application/merge-patch+json or in this case also any valid JSON-payload, the following will happen:
Submitting the checkbox with null value
{"checkbox": null}
will overwrite the checkbox to false:
{"checkbox": false, "dummyfield": "presetvalue"}
and submitting the checkbox with its original value
{"checkbox": "1"}
will set the checkbox to true (was also true before)
{"checkbox": true, "dummyfield": "presetvalue"}
and no submitted value for the checkbox
{"dummyfield": "requestvalue"}
will leave the checkbox in its initial true-state and only overwrites the dummyfield:
{"checkbox": true, "dummyfield": "requestvalue"}
This is how a PATCH request should work, no extra hidden inputs needed. Just prepare your JSON-payload on the client-side properly and you are fine.
OK, but what about the expanded ChoiceType/EntityType?
For expanded ChoiceType (or child types of it like EntityType), which renders checkboxes or radiobuttons and expects a simple list of the checked checkboxes/radiobuttons values within the submitted payload, this simple solution doesn't work. I implemented a form extension, adding an event listener for PRE_SUBMIT on those fields, setting the non submitted checkboxes/radiobuttons to null. This event listener must be called after the closure-listener of CheckboxType, transferring the simple list ["1", "3"] to a hash with checkbox-values as keys and values. A priority of -1 workes for me. So ["1" => "1", "3" => "3"] coming out of the closure gets ["1" => "1", "2" => null, "3" => "3"] after my listener. The listener of my PatchableChoiceTypeExtension looks basically like this:
$builder->addEventListener(
\Symfony\Component\Form\FormEvents::PRE_SUBMIT,
function (\Symfony\Component\Form\FormEvent $event) {
if ('PATCH' === $event->getForm()->getRoot()->getConfig()->getMethod()
&& $event->getForm()->getConfig()->getOption('expanded', false)
) {
$data = $event->getData();
foreach ($event->getForm()->all() as $type) {
if (!array_key_exists($type->getName(), $data)) {
$data[$type->getName()] = null;
}
}
ksort($data);
$event->setData($data);
}
}, -1
);
Update: have a look at this comment within the submit-method in /Symfony/Component/Form/Form.php (it is there since Symfony 2.3):
// Treat false as NULL to support binding false to checkboxes.
// Don't convert NULL to a string here in order to determine later
// whether an empty value has been submitted or whether no value has
// been submitted at all. This is important for processing checkboxes
// and radio buttons with empty values.
Update 2017-09-12: Radiogroups must be handled the same way as Checkboxgroups, so my listener handles both. Selects and multi selects work correctly out of the box.

ASP.NET MVC: Save multiple values on autocomplete

I have a mysql database with the tables "deliverables", "tags" and "deliverables_has_tags". I want to link tags to a deliverable.
This is what I do in my javascript file:
<script type="text/javascript" language="javascript">
$(function () {
var object = {};
$.ajax({
type: "GET",
url: "/Deliverable/Tags",
dataType: "json",
success: function (data) {
object.tags = data;
}
});
function split(val) {
return val.split(/,\s*/);
}
function extractLast(term) {
return split(term).pop();
}
$("#tags")
// don't navigate away from the field on tab when selecting an item
.bind("keydown", function (event) {
if (event.keyCode === $.ui.keyCode.TAB &&
$(this).data("ui-autocomplete").menu.active) {
event.preventDefault();
}
})
.autocomplete({
minLength: 0,
source: function (request, response) {
// delegate back to autocomplete, but extract the last term
response($.ui.autocomplete.filter(
object.tags, extractLast(request.term)));
},
focus: function () {
// prevent value inserted on focus
return false;
},
select: function (event, ui) {
var terms = split(this.value);
// remove the current input
terms.pop();
// add the selected item
terms.push(ui.item.value);
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(", ");
return false;
}
});
});
</script>
I can add multiple tags in my textbox.
But now I want to save this in my repository.
In my Action method in controller:
repository.AddDeliverable(model.Title, model.Description, model.UsernameID, data, datatwo, model.VideoUrl, model.AfstudeerrichtingID, model.ProjectID);
Tags action:
public JsonResult Tags()
{
var data = (repository.GetTags()).ToArray();
return Json(data, JsonRequestBehavior.AllowGet);
}
In my repository:
public IQueryable<string> GetTags()
{
return from tag in entities.tags
orderby tag.tag_name
select tag.tag_name;
}
I have no clue how to save this in my database.
Can anybody help me?
If I correctly understood your question, you have implemented your tag handling as follows:
There is MVC action method that returns the view with input placeholder containing no data
The placeholder itself is probably input type=text with id=tags
On 'dom ready' you fire ajax request to retrieve your tags from database, json-serialized as array; when it arrives you store it to tags variable (no error handling(!))
At the same time you decorate your input with jqueryui autocomplete that reacts on user input and returns items from the tags variable
Since input already contains tags (comma separated), your filter is first letters of the last tag
So, you have a situation when user has input a few comma separated tags (probably some of them can be new) and now wants to save it to the database. For each input, if that is a known tag you have to store it to "deliverables_has_tags". If there is a new tag, you have to store it both to "tags" and "deliverables_has_tags".
Most common scenario would be having a 'Save' button to start saving process.
Let's analyze what you have to do in the process.
1) Button click
On button click you use js to convert your comma separated tags string
using logic like split(term) to the array, and serialize it. You can
do serialization using serializeArray and manually create JSON
object, or serialize the whole form using
$('#yourForm').serialize(). I would choose the first option
because that way I get more control over JSON format and avoid
problems with MVC default model binder.
2) Ajax call
When the JSON object is ready to be sent, you fire an ajax POST
request to your MVC POST action method. When you save state always
avoid GET because new versions of browsers can scan thru your page and
actively preload urls using GET requests. You don't want this here. Of
course, use your data as a data-parameter in the ajax call.
3) Action method
When the request arrives, you have to process it in your controller
using a new action method. Typically in this case you will have
something like public JsonResult SaveTags(SaveTagsModel saveTags) {
... } which saves tags using your repository and returns result that
says something like 'OK' or 'ERROR' (sth like
response.isSaved=true/false). Tricky part can be designing view model
according to your JSON object - this could help. And regarding
collections this could be valuable info.
When saving, use transaction to ensure everything is saved at once.
First check if each tag exists in the database and insert those who
don't exist. After that, check for each tag if there is appropriate
n-n relation in deliverables_has_tags and insert it if there isn't.
I believe that you should use same repository encapsulation for both
operations.
In the post action, include FormCollection collection as argument and gather your tags from that. There is no automatic way. You could implement some custom model binding, but that is probably not worth the effort.

ASPxGridview - 'interrupting' row insert events

Hoping someone can point me in the right direction
Using DevExpress ASPxGridView and the Edit form.
I need to 'interrupt' the RowInserting events to warn the user if there's already a record matching their information and allow them to continue or cancel.
I've added the check (and a cancel) to the OnRowInserting event and am using customJSProperties to trigger the popup on the callback.
But I'm stuck on how to get the popups 'yes' button to resume (or restart) the Row Insert.
Is there a way of triggering the editform update event again from client side code?
Or do I need a completely different approach?
First of all, I found this article Use "yes" / "no" in edit mode for boolean value
Second of all, I hope your all rows has a unique value like ID. If so, I sugget a way like this;
Use OnRowInserting function of ASPxGridview. (Find here code examples etc.)
Check your inserting ID is already in your data store or not. (With running a query)
If in your data store or not, use XtraMessageBox like;
XtraMessageBox.Show(“Content”, “Title”, MessageBoxButtons.YesNo);
before that, add DevExpress.XtraEditors namespace. Then you can use it like;
DialogResult myDialogResult;
myDialogResult = XtraMessageBox.Show("Content", "Title", MessageBoxButtons.YesNo);
if (myDialogResult == DialogResult.Yes)
{
//yes was clicked
}
if (myDialogResult == DialogResult.No)
{
//no was clicked
}
Hope it gives you an idea. And If you have Devexpress Licence, you can ask in Devexpress Support. They are really quick and helpful.
You can solve this with custom HttpHandler. Something like this:
press Save button on your edit form
Save initiates httphandler call (from javascript) with data needed for validation (tablename, id). With jquery you can call http handler like this:
if handler returns true continue with save, otherwise show alert with OK/Cancel
if user chooses OK continue with Save
Javascript call with http handler would look like this:
$.ajax({
async: false,
cache: false,
url: 'YourHttpHandler.ashx',
data: { tableName: "your table name", record_id: your_id },
success:
function (data, textStatus, xmlHttpRequest)
{
if(data.result==true)
if(confirm('Continue?'))
{
// continue with save
}
}
});

Resources