Problem with double values binding - asp.net-mvc

In my project I want to allow users input double values in 2 formats: with using ',' or '.' as delimiter (I'm not interested in exponential form). By default value with delimiter '.' don't work.
I want this behavior works for all double properties in complex model objects (currently I work with collections of objects, that contains identifiers and values).
What i should use: Value Providers or Model Binders? Please, show code example of solving my problem.

You could use a custom model binder:
public class DoubleModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result != null && !string.IsNullOrEmpty(result.AttemptedValue))
{
if (bindingContext.ModelType == typeof(double))
{
double temp;
var attempted = result.AttemptedValue.Replace(",", ".");
if (double.TryParse(
attempted,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out temp)
)
{
return temp;
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
which could be registered in Application_Start:
ModelBinders.Binders.Add(typeof(double), new DoubleModelBinder());

Related

Custom ValidationAttribute for multiple data types

I have a ValidationAttribute like below which validates that a certain amount of values have been entered on a form. Currently it is only being used on a property with type short?[]
public class RequiredArrayLength : ValidationAttribute
{
public int TotalRequired { get; set; }
public override bool IsValid(object value)
{
if(value != null)
{
var array = value as short?[];
return array.Where(v => v.HasValue).Count() >= TotalRequired;
}
return false;
}
}
Is there a way I can modify this ValidationAttribute so it will work with other numeric arrays such as int?[]
One option would be to cast to IEnumerable (System.Collections namespace) and enumerate the collection to determine the number of items in the collection.
IEnumerable collection = value as IEnumerable;
if (collection!= null)
{
IEnumerator enumerator = collection.GetEnumerator();
int count = 0;
while(enumerator.MoveNext())
{
count++;
}
return count >= TotalRequired;
}
return false;
If you only want to count non-null values, then modify the code to
while(enumerator.MoveNext())
{
if (enumerator.Current != null)
{
count++;
}
}
If you specifically wanted to limit this to only numeric data types, you can use the .GetType() method of IEnumerable to test the type (for example, C# - how to determine whether a Type is a number).

Where is the SequenceRange from the walk-method from ExtendedDataModel defined?

The ExtendedDataModel from ajax4jsf uses a method called walk, looking like this:
public void walk(FacesContext ctx, DataVisitor dv, Range range, Object argument){}
This method is called several times in my application. Some topics on internet seem to say that it the latter is defined by the rows="x" in the xhtml. However, for me range is always defined as 0 (firstRow) - -1 (getRows).
So I was wondering where this range is defined, so I can figure out why the wrong parameters are passed to it. Debugging and googling hasn't helped me so far.
Range represents visible part of data displayed in table. If you have paginator, then paginator display which page (= from which row to which row) data is presented.
Problem can be in incorrect value of rows attribute of data table (for example rows attribute is missing).
Other place can be in incorrect implementation of data model. Object of data model class can be used as storage for data displayed in rich:dataTable.
Real example:
public class VSDataModel<RecordType> extends ExtendedDataModel<RecordType> implements Arrangeable {
private final FetchList<RecordType> list;
#Override
public void walk(FacesContext ctx, DataVisitor visitor, Range range, Object obj) {
try {
int firstRow = ((SequenceRange) range).getFirstRow();
int numberOfRows = ((SequenceRange) range).getRows();
if(list == null) {
throw new RuntimeException("Underlying list is null!");
}
if(list.getList() == null || firstRow != list.getFirstRow()) {
list.fetch(firstRow, numberOfRows);
}
for (RecordType elem : list.getList()) {
visitor.process(ctx, list.getPK(elem), obj);
}
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
It is used in Java private VSDataModel<Record> dependentList; and html
<rich:dataTable value="#{bean.dependentList}" rows="#{referenceData.recordsPerPage}">

ModelState.IsValid is false when I have a nullable parameter

I reproduced the issue I am having in a brand new MVC Web API project.
This is the default code with a slight modification.
public string Get(int? id, int? something = null)
{
var isValid = ModelState.IsValid;
return "value";
}
If you go to http://localhost/api/values/5?something=123 then this works fine, and isValid is true.
If you go to http://localhost/api/values/5?something= then isValid is false.
The issue I am having is that if you provide a null or omitted value for an item that is nullable, the ModelState.IsValid flags a validation error saying "A value is required but was not present in the request."
The ModelState dictionary also looks like this:
with two entries for something, one nullable, which I am not sure if it is significant or not.
Any idea how I can fix this so that the model is valid when nullable parameters are omitted or provided as null? I am using model validation within my web api and it breaks it if every method with a nullable parameter generates model errors.
It appears that the default binding model doesn't fully understand nullable types. As seen in the question, it gives three parameter errors rather than the expected two.
You can get around this with a custom nullable model binder:
Model Binder
public class NullableIntModelBinder : IModelBinder
{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(int?))
{
return false;
}
ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (val == null)
{
return false;
}
string rawvalue = val.RawValue as string;
// Not supplied : /test/5
if (rawvalue == null)
{
bindingContext.Model = null;
return true;
}
// Provided but with no value : /test/5?something=
if (rawvalue == string.Empty)
{
bindingContext.Model = null;
return true;
}
// Provided with a value : /test/5?something=1
int result;
if (int.TryParse(rawvalue, out result))
{
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Cannot convert value to int");
return false;
}
}
Usage
public ModelStateDictionary Get(
int? id,
[ModelBinder(typeof(NullableIntModelBinder))]int? something = null)
{
var isValid = ModelState.IsValid;
return ModelState;
}
Adapted from the asp.net page: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api for further reading and an alternative method to set it at the class(controller) level rather than per parameter.
This handles the 3 valid scenarios:
/test/5
/test/5?something=
/test/5?something=2
this first give "something" as null. Anything else (eg ?something=x) gives an error.
If you change the signature to
int? somthing
(ie remove = null) then you must explicitly provide the parameter, ie /test/5 will not be a valid route unless you tweak your routes as well.
You'll have to register a custom model-binder for nullable types as the default binder is calling the validator for nullable parameters as well, and the latter considers those empty values as invalid.
The Model Binder:
public class NullableModelBinder<T> : System.Web.Http.ModelBinding.IModelBinder where T : struct
{
private static readonly TypeConverter converter = TypeDescriptor.GetConverter( typeof( T ) );
public bool BindModel( HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext )
{
var val = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );
// Cast value to string but when it fails we must not suppress the validation
if ( !( val?.RawValue is string rawVal ) ) return false;
// If the string contains a valid value we can convert it and complete the binding
if ( converter.IsValid( rawVal ) )
{
bindingContext.Model = converter.ConvertFromString( rawVal );
return true;
}
// If the string does contain data it cannot be nullable T and we must not suppress this error
if ( !string.IsNullOrWhiteSpace( rawVal ) ) return false;
// String is empty and allowed due to it being a nullable type
bindingContext.ValidationNode.SuppressValidation = true;
return false;
}
}
Registration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// ...
var provider = new SimpleModelBinderProvider(typeof(int?), new NullableModelBinder<int>());
config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
// ...
}
}
Remove the default null value from the second parameter. The model binder will set it to null if it's something other than int.
I've found a working workaround for me (just exclude null values from data being sent - as opposed to sending values as nulls).
See https://stackoverflow.com/a/66712465/908608

Extracting route table from MVC app in T4 template

Does anyone have any ideas on how I might extract the route table from a MVC app in a T4 template ?
Ideally what Id like to do is create an instance of the 'MvcApplication : System.Web.HttpApplication' and get it to 'startup' so the routes are registered and I can just extract them from Routes.RouteTable.
Failing that, I thought about using reflection to find static classes with methods that follow the Register[xxxx]Route naming convention. Would work in a lot of cases.
Any other suggestions I might have missed ?
Edit - seems to be some confusion over the question. I know that T4 runs at design time. I know that routes are registered at runtime. This guy did something similar to what im looking to do - extract roues at design time but he forces you to register routes in a particular way so he can use reflection to read them back out. Wanted to avoid that if at all possible.
You can use library Microsoft.Web.Mvc in MVC futures that has method
ExpressionHelper.GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action)
It give you what you want.
Update: it can work without Asp.Net MVC but you need to copy realization of Microsoft.Web.Mvc.Internal.ExpressionHelper to your own class and remove restriction where TController:Controller from signature of GetRouteValuesFromExpression method:
public static class MyOwnExpressionHelper
{
public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) //where TController : Controller
{
if (action == null)
throw new ArgumentNullException("action");
MethodCallExpression call = action.Body as MethodCallExpression;
if (call == null)
throw new ArgumentException("MustBeMethodCall", "action");
string name = typeof(TController).Name;
if (!name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException("TargetMustEndInController", "action");
string str = name.Substring(0, name.Length - "Controller".Length);
if (str.Length == 0)
throw new ArgumentException("_CannotRouteToController", "action");
string targetActionName = GetTargetActionName(call.Method);
RouteValueDictionary rvd = new RouteValueDictionary();
rvd.Add("Controller", (object)str);
rvd.Add("Action", (object)targetActionName);
ActionLinkAreaAttribute linkAreaAttribute = Enumerable.FirstOrDefault<object>((IEnumerable<object>)typeof(TController).GetCustomAttributes(typeof(ActionLinkAreaAttribute), true)) as ActionLinkAreaAttribute;
if (linkAreaAttribute != null)
{
string area = linkAreaAttribute.Area;
rvd.Add("Area", (object)area);
}
AddParameterValuesFromExpressionToDictionary(rvd, call);
return rvd;
}
public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
{
if (expression.Body.NodeType == ExpressionType.Call)
return GetInputName((MethodCallExpression)expression.Body).Substring(expression.Parameters[0].Name.Length + 1);
else
return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
}
private static string GetInputName(MethodCallExpression expression)
{
MethodCallExpression expression1 = expression.Object as MethodCallExpression;
if (expression1 != null)
return MyOwnExpressionHelper.GetInputName(expression1);
else
return expression.Object.ToString();
}
private static string GetTargetActionName(MethodInfo methodInfo)
{
string name = methodInfo.Name;
if (methodInfo.IsDefined(typeof(NonActionAttribute), true))
{
throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.CurrentCulture,"An Error", new object[1]
{
(object) name
}));
}
else
{
ActionNameAttribute actionNameAttribute = Enumerable.FirstOrDefault<ActionNameAttribute>(Enumerable.OfType<ActionNameAttribute>((IEnumerable)methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), true)));
if (actionNameAttribute != null)
return actionNameAttribute.Name;
if (methodInfo.DeclaringType.IsSubclassOf(typeof(AsyncController)))
{
if (name.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
return name.Substring(0, name.Length - "Async".Length);
if (name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(string.Format((IFormatProvider)CultureInfo.CurrentCulture, "CannotCallCompletedMethod", new object[1]
{
(object) name
}));
}
return name;
}
}
private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call)
{
ParameterInfo[] parameters = call.Method.GetParameters();
if (parameters.Length <= 0)
return;
for (int index = 0; index < parameters.Length; ++index)
{
Expression expression = call.Arguments[index];
ConstantExpression constantExpression = expression as ConstantExpression;
object obj = constantExpression == null ? CachedExpressionCompiler.Evaluate(expression) : constantExpression.Value;
rvd.Add(parameters[index].Name, obj);
}
}
}

ConversionErrorInterceptor throws conversion error during quit/cancel (Struts 2)

The scenario of the problem is this
1) We map the struts field values to the dtos. The dtos contain integer fields which again are displayed on the screen.
2) Now I enter an incorrect value which gives conversion error for that integer field.
3) At that point in time I decide to quit the page(i.e press cancel), I get a conversion error. This is because the StrutsConversionErrorInterceptor gets called everytime.
Is there any way that I can skip the strutsConversionErrorInterceptor when I am calling a particular method the way we can skip validation using excludeMethods
Use this code to override Struts's StrutsConversionErrorInterceptor...
public class MyConversionErrorInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 1L;
public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";
protected Object getOverrideExpr(ActionInvocation invocation, Object value) {
ValueStack stack = invocation.getStack();
try {
stack.push(value);
return "'" + stack.findValue("top", String.class) + "'";
} finally {
stack.pop();
}
}
#Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext invocationContext = invocation.getInvocationContext();
Map<String, Object> conversionErrors = invocationContext.getConversionErrors();
ValueStack stack = invocationContext.getValueStack();
HashMap<Object, Object> fakie = null;
BaseAction baseAction = (BaseAction) invocation.getAction();
String buttonName = baseAction.getButtonName();
for (Map.Entry<String, Object> entry : conversionErrors.entrySet()) {
String propertyName = entry.getKey();
Object value = entry.getValue();
if (shouldAddError(propertyName, value)) {
String message = XWorkConverter.getConversionErrorMessage(propertyName, stack);
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
ValidationAware va = (ValidationAware) action;
if(buttonName.equalsIgnoreCas("Next")){
va.addFieldError(propertyName, message);
}
}
if (fakie == null) {
fakie = new HashMap<Object, Object>();
}
if(buttonName.equalsIgnoreCas("Next")){
fakie.put(propertyName, getOverrideExpr(invocation, value));
}
}
}
if (fakie != null) {
// if there were some errors, put the original (fake) values in
// place right before the result
stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
Map<Object, Object> fakie = (Map<Object, Object>) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
if (fakie != null) {
invocation.getStack().setExprOverrides(fakie);
}
}
});
}
return invocation.invoke();
}
protected boolean shouldAddError(String propertyName, Object value) {
if (value == null) {
return false;
}
if ("".equals(value)) {
return false;
}
if (value instanceof String[]) {
String[] array = (String[]) value;
if (array.length == 0) {
return false;
}
if (array.length > 1) {
return true;
}
String str = array[0];
if ("".equals(str)) {
return false;
}
}
return true;
}
}
You can specify you button names on which you want validation to fire. In above code I have used "Next" in code you can see
if(buttonName.equalsIgnoreCas("Next"))
Yes, you can skip calling the interceptor.
Just remove the interceptor definition from your action definition in struts.xml file.
i.e., remove <interceptor-ref name="conversionError"/>
Mainly this interceptor adds any error found in the ActionContext's conversionErrors map as a field error (provided that the action implements ValidationAware). In addition, any field that contains a validation error has its original value saved such that any subsequent requests for that value return the original value rather than the value in the action. This is important because if the value "abc" is submitted and can't be converted to an int, we want to display the original string ("abc") again rather than the int value (likely 0, which would make very little sense to the user).
After you removed this interceptor, if the struts failed to map the field with parameter of the object(i.e., from string to int), it throws result input action error.
This seems to be a better method to handle this scenario - using Conversion Validator. Repopulating Field upon conversion Error section is something very useful:
http://struts.apache.org/2.0.14/docs/conversion-validator.html

Resources