I have written a custom attribute but it does not seem to work client side. It only works on the server when i call the ModelState.IsValid() method. I read somewhere online that i needed to register the custom attribute on the application start method but it was not clear. Please help.
public class MaximumAmountAttribute : ValidationAttribute
{
private static string defErrorMessage = "Amount available '$ {0:C}' can not be more than loan amount '$ {1:C}'";
private string MaximumAmountProperty { get; set; }
double minimumValue = 0;
double maximumValue = 0;
public MaximumAmountAttribute(string maxAmount)
: base(defErrorMessage)
{
if (string.IsNullOrEmpty(maxAmount))
throw new ArgumentNullException("maxAmount");
MaximumAmountProperty = maxAmount;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
PropertyInfo otherPropertyInfo = validationContext.ObjectInstance.GetType().GetProperty(MaximumAmountProperty);
if (otherPropertyInfo == null)
{
return new ValidationResult(string.Format("Property '{0}' is undefined.", MaximumAmountProperty));
}
var otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue != null && !string.IsNullOrEmpty(otherPropertyValue.ToString()))
{
minimumValue = Convert.ToDouble(value);
maximumValue = Convert.ToDouble(otherPropertyValue);
if (minimumValue > Convert.ToDouble(otherPropertyValue.ToString()))
{
return new ValidationResult(string.Format(defErrorMessage, minimumValue, maximumValue));
}
}
}
return ValidationResult.Success;
}
}
Creating server side validation with custom validation attribute does not "trasnfer" the validation rules to the client browser (rendering custom javascript validation function).
You will have to write the validation logic as client script too. There are some things you must do:
Make sure the element (input) that has to be validated on the client looks like that:
<input data-val-MaximumAmount="Validation error massage" />
The data-val-XXX attribute holding the error message is needed. Html.TextBoxFor is doing the same (adding such attributes to the html elements rendered).
You must create and register client side validation that way:
(function ($) {
// Creating the validation method
$.validator.addMethod('MaximumAmount', function (value, element, param) {
if (...) // some rule. HERE THE VALIDATION LOGIC MUST BE IMPLEMENTED!
return false;
else
return true;
});
// Registering the adapter
$.validator.unobtrusive.adapters.add('MaximumAmount', function (options) {
var element = options.element,
message = options.message;
options.rules['MaximumAmount'] = $(element).attr('data-val-MaximumAmount');
if (options.message) {
options.messages['MaximumAmount'] = options.message;
}
});
})(jQuery);
// Binding elements to validators
$(function () {
$(':input[data-val-MaximumAmount]').each(function () {
$.validator.unobtrusive.parseElement(this, true);
});
});
Related
I was trying to pass the parameters to HTML file stored in android Asset Folder. I was passing the parameters to the function written in java script on my HTML file. But at certain times, I'm getting Exception, which I find difficult to sort out the issue.
Exception::
`I/chromium: [INFO:CONSOLE(1)] "Uncaught SyntaxError: missing ) after argument list", source: file:///android_asset/templateOffer.html (1)`.
Java script Code in HTML file:
function setWineDetails(tempOffer,wineBrnd,wineName,
wineCurrency,winePrice,placeLineOne,PlaceLineTwo,userName,wineMtchVal){
document.getElementById("usrname").innerHTML = userName;
document.getElementById("wineTpe").innerHTML = tempOffer;
document.getElementById("wine_brnd_id").innerHTML = wineBrnd;
document.getElementById("wine_name_id").innerHTML = wineName;
document.getElementById("wine_currcy_id").innerHTML = wineCurrency;
document.getElementById("wine_price_id").innerHTML = winePrice;
if (placeLineOne != = "" || placeLineOne != = null) {
document.getElementById("place_line_one_id").innerHTML = placeLineOne;
document.getElementById("place_line_second_id").innerHTML = PlaceLineTwo;
}
if (wineMtchVal == "" || wineMtchVal == null) {
document.getElementById("wine-percentages").style.visibility = 'hidden';
} else {
document.getElementById("wine-percentages").style.visibility = 'visible';
document.getElementById("wineMtch_id").innerHTML = wineMtchVal;
}
}
function setImage(wineImage){
document.getElementById("wineImage_id").src = wineImage;
}
function setValuesToOfferView(offerPercentage,offerExpiry){
document.getElementById("offer_per_id").innerHTML = offerPercentage;
document.getElementById("offer_expiry_id").innerHTML = offerExpiry;
}
passing parameteres::
private void loadWebViewContent(){
offerWebView.getSettings().setJavaScriptEnabled(true);
offerWebView.setWebViewClient(new WebViewClient(){
public void onPageFinished(WebView view, String url){
//Here you want to use .loadUrl again
//on the webview object and pass in
//"javascript:<your javaScript function"
offerWebView.loadUrl("javascript:setWineDetails('"+offerTemp+"','"+wineBrand+"','"+wineName+"','"+wineCurrency+"','"+winePrice+"','"+placeLineOne+"','"+PlaceLineTwo+"','"+userName+"','"+wineMatch+"')");
offerWebView.loadUrl("javascript:setValuesToOfferView('"+offerPercentage+"','"+offerExpiry+"')"); //if passing in an object. Mapping may need to take place
offerWebView.loadUrl("javascript:setImage('"+wineImage+"')"); //if passing in an object. Mapping may need to take place
}
});
offerWebView.loadUrl("file:///android_asset/templateOffer.html");
}
I have tried to execute JavaScript on an external url (ie: http://facebook.com) using WebView from Visual Studio Mac 2019, and so far no results.
To do so, I have tried to follow along with the official tutorial here https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview, and also tried a simpler one here: https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/
Here is what I did with explanations:
On my shared folder, I created an HybridWebView class with the following code:
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(Func<string, Task<string>>),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get => (string)GetValue(UriProperty);
set
{
SetValue(UriProperty, value);
}
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
public Func<string, Task<string>> ExecuteJavascript
{
get { return (Func<string, Task<string>>)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
From The macOS project which I use to test my cross-platform app, I tried the following custom renderer:
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as HybridWebView;
if (webView != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.ExecuteJavascript.ToString())));
}
}
}
To note that the following part wouldn't work:
var webView = e.NewElement as HybridWebView;
if (webView != null)
webView.ExecuteJavascript = (js) =>
{
return Task.FromResult(this.ExecuteJavascript(js)); // issue at ExecuteJavascript with following error ('HybridWebViewRenderer' does not contain a definition for 'ExecuteJavascript' ), hence replaced by Control.LoadRequest ...
};
From my ViewModel, I did the following:
public Func<string, Task<string>> EvaluateJavascript { get; set; }
public async Task OnConnectTapped()
{
Console.WriteLine("on connect tapped");
// passing the url onto a connection service
var hybridWebView = new HybridWebView
{
Uri = "https://facebook.com/"
};
//hybridWebView.InvokeAction("document.getElementById('td');");
//var result = await hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
var result = await hybridWebView.ExecuteJavascript("document.cookie;");
Console.WriteLine("result is {0}", result);
}
Here is the error when trying to execute my code:
System.NullReferenceException: Object reference not set to an instance of an object
at MyApp.ViewModel.MainModel.OnConnectTapped () [0x00031] in .../../././/ViewModel/MainModel.cs:451
at .......<.ctor>g__c5|48_9 () [0x0001f] in /../../../.cs:143
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/external/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021
at Foundation.NSAsyncSynchronizationContextDispatcher.Apply () [0x00002] in /Library/Frameworks/Xamarin.Mac.framework/Versions/6.6.0.12/src/Xamarin.Mac/Foundation/NSAction.cs:178
at at (wrapper managed-to-native) AppKit.NSApplication.NSApplicationMain(int,string[])
at AppKit.NSApplication.Main (System.String[] args) [0x00040] in /Library/Frameworks/Xamarin.Mac.framework/Versions/6.6.0.12/src/Xamarin.Mac/AppKit/NSApplication.cs:100
at redacted.macOS.MainClass.Main (System.String[] args) [0x00017] in /Users/dom-bruise/Projects/redacted/redacted.macOS/Main.cs:11
For me, it could either be because I can't execute external pages, or the part where I replaced by the following messing up my attempt.
if (webView != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.ExecuteJavascript.ToString())));
}
My main goal here is to have my app execute JavaScript underneath the hood on pages using WebView, and fill in forms automatically calling back C# from my app.
I have made a custom data annotation attribute which verifies whether an email already exists in my database like this:
public class ValidateEmail : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
using (var ctx = new myCtx())
{
if (value != null)
{
var valueAsString = value.ToString().ToLower();
IEnumerable<string> email = ctx.Users.Where(x => x.Email != null).Select(x => x.Email);
if (email.Contains(valueAsString))
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
}
And in my view model I set it like this:
[ValidateEmail(ErrorMessage = "Email exists")]
[Required(ErrorMessage = "Required")]
[RegularExpression(#"^([\w\.\-]+)#([\w\-]+)((\.(\w){2,3})+)$", ErrorMessage = "Invalid Email")]
public string Email { get; set; }
This works perfectly when the page reloads...But I would like now to change this so that I enable client side validation and message displaying without reloading the page itself...
How can I modify this validation attribute to make it compliable with jquery's unobstrusive validation in .NET MVC?
Based on #Sayan's link, I've implemented something like this:
public class ValidateEmail : ValidationAttribute, IClientValidatable
{
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "emailvalidate";
yield return rule;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
using (var ctx = new myContext())
{
if (value != null)
{
var valueAsString = value.ToString().ToLower();
IEnumerable<string> email = ctx.Users.Where(x => x.Email != null).Select(x => x.Email);
if (email.Contains(valueAsString))
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
}
and client side:
$.validator.addMethod("emailvalidate", function (value, element) {
{
console.log("value: " + value + " " + element);
}});
$.validator.unobtrusive.adapters.add("emailvalidate", function (options) {
//adding rules for validator
options.rules["emailvalidate"] = true;
if (options.message) {
options.messages["emailvalidate"] = options.message;
}
});
But whatever I insert now in the email field as email like:
myemail#ymail.xyz
I get the error that email exists ?
You can implement IClientValidatable in your ValidateEmail validation attribute to supply the data-val-xxx attributes to the client side.
Then you can write the jQuery unobtrusive validator and adapter to validate the field value on client side using the data-val-xxx rendered in HTML.
Make sure to return true (truthy value) or false (falsy value) from the jQuery validator based on whether the field value is valid or not respectively.
Lastly, include this custom jQuery validator script to your view.
You can find more details here. Though this blog post presents a slightly complicated scenario, but seeing how to use IClientValidatable and how to write jQuery unobtrusive validator is enough.
Hope this is helpful.
I am working on custom validation in mvc. I am using requiredif attribute. It’s working on server side but not on client side.
RequiredIfAttribute.cs
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
protected RequiredAttribute _innerAttribute;
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public bool AllowEmptyStrings
{
get
{
return _innerAttribute.AllowEmptyStrings;
}
set
{
_innerAttribute.AllowEmptyStrings = value;
}
}
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
_innerAttribute = new RequiredAttribute();
DependentProperty = dependentProperty;
TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
// trim spaces of dependent value
if (dependentValue != null && dependentValue is string)
{
dependentValue = (dependentValue as string).Trim();
if (!AllowEmptyStrings && (dependentValue as string).Length == 0)
{
dependentValue = null;
}
}
// compare the value against the target value
if ((dependentValue == null && TargetValue == null) ||
(dependentValue != null && (TargetValue.Equals("*") || dependentValue.Equals(TargetValue))))
{
// match => means we should try validating this field
//if (!_innerAttribute.IsValid(value))
if(value==null)
// validation failed - return an error
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
//public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
//{
//}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty);
// unfortunately this will have the name of the current field appended to the beginning,
// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
// want to get the context as though it was one level higher (i.e. outside the current property,
// which is the containing object, and hence the same level as the dependent property.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
return depProp;
}
public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
//IEnumerable<ModelClientValidationRule> IClientValidatable.GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
string targetValue = (TargetValue ?? "").ToString();
if (TargetValue is bool)
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
}
requiredif.js
$(function () {
alert('hii');
$.validator.addMethod('requiredif', function (value, element, parameters) {
alert(value);
var id = '#' + parameters['dependentproperty'];
alert(id);
// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
targetvalue = (targetvalue == null ? '' : targetvalue).toString();
// get the actual value of the target control
// note - this probably needs to cater for more
// control types, e.g. radios
var control = $(id);
var controltype = control.attr('type');
var actualvalue =
(controltype === 'checkbox' || controltype === 'radio') ?
control.attr('checked').toString() :
control.val();
// if the condition is true, reuse the existing
// required field validator functionality
if ($.trim(targetvalue) === $.trim(actualvalue) || ($.trim(targetvalue) === '*' && $.trim(actualvalue) !== ''))
return $.validator.methods.required.call(
this, value, element, parameters);
return true;
});
$.validator.unobtrusive.adapters.add(
'requiredif',
['dependentproperty', 'targetvalue'],
function (options) {
options.rules['requiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['requiredif'] = options.message;
});
});
Model
[Required]
public bool IsFeederSelected { get; set; }
[RequiredIf("IsFeederSelected", true, ErrorMessage = "You must enter purchase date")]
[Display(Name = "Meter Name")]
public List<string> SelectedMeterName { get; set; }
I would like to know how I can achieve the same, any small inputs on the same is also greatly appreciated.
Thanks in advance.
I am using ServiceStack.Client to consume, the data pushed by my server(which is an aspx page).
Below is the code which i use to consume the data using ServiceStack Client:
using System;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using ServiceStack;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ServerEventConnect connectMsg = null;
var msgs = new List<ServerEventMessage>();
var commands = new List<ServerEventMessage>();
var errors = new List<Exception>();
var client = new ServerEventsClient("https://testing.leadsquared.com/ReferralCampaign/Demo")
{
OnConnect = e => PrintMsg(e),
OnCommand = e => PrintCmdMsg(e),
OnMessage = e => PrintCmMsg(e),
OnException = e => PrintExMsg(e)
}.Start();
Console.Read();
}
private static void PrintCmMsg(ServerEventMessage e)
{
if (e != null)
{
PrintMsg(e.Data);
}
}
private static void PrintExMsg(Exception e)
{
if (e != null)
{
PrintMsg(e.Message);
}
}
private static void PrintCmdMsg(ServerEventMessage e)
{
if (e != null)
{
PrintMsg(e.Data);
}
}
private static void PrintMsg(ServerEventConnect e)
{
if (e!=null)
{
PrintMsg(e.Data);
}
}
private static void PrintMsg(string x)
{
Console.WriteLine(x);
}
}
}
When I run my code , the client does print any message on the console.
The ConnectionDisplayName property is "(not connected)".
If i subscribe to the same URL using javascript EventSource, i get the notifications.
My requirement is that I would want to consume the data by my server in C#.
How can I achieve this?
Firstly the url needs to be the BaseUri where ServiceStack is hosted, i.e. the same url used in JavaScript ServerEvents Client, e.g:
var client = new ServerEventsClient(BaseUrl).Start();
It's not clear if /ReferralCampaign/Demo is the BaseUri or not.
You will also want to call Connect() to wait for the client to make a connection, e.g:
await client.Connect();
Then to see message events you'll need to call a ServiceStack Service that publishes a Notify* Event on IServerEvents API which you can use with a separate JsonServiceClient or the ServiceClient available in ServerEventsClient, e.g:
client.ServiceClient.Post(new PostRawToChannel {
From = client.SubscriptionId,
Message = "Test Message",
Channel = channel ?? "*",
Selector = "cmd.announce",
});
This is an example calling the Chat PostRawToChannel ServiceStack Service:
public class ServerEventsServices : Service
{
public IServerEvents ServerEvents { get; set; }
public void Any(PostRawToChannel request)
{
// Ensure the subscription sending this notification is still active
var sub = ServerEvents.GetSubscriptionInfo(request.From);
if (sub == null)
throw HttpError.NotFound("Subscription {0} does not exist".Fmt(request.From));
// Check to see if this is a private message to a specific user
if (request.ToUserId != null)
{
// Only notify that specific user
ServerEvents.NotifyUserId(request.ToUserId, request.Selector, request.Message);
}
else
{
// Notify everyone in the channel for public messages
ServerEvents.NotifyChannel(request.Channel, request.Selector, request.Message);
}
}
}
I also recommend looking at the C# ServerEventTests for complete stand-alone examples using C# ServerEventClient.