RemoteAttribute validator does not fire server-side

ぐ巨炮叔叔 提交于 2019-12-07 11:38:44

问题


It appears that the RemoteAttribute validator introduced in ASP.NET MVC 3 does not validate on the server-side, only via JavaScript. If you turn off JS in your browser, you will find that on model binding, the validation controller action (that you specified when decorating a model property with the RemoteAttribute) will not be hit. In fact, if you inspect the source code for the RemoteAttribute, you will find the IsValid methods just returns true in all cases.

This seems to be quite an omission - I think most people would assume that the RemoteAttribute would work like all the other built-in validators and validate on both client-side and server-side. Instead, you must manually call your remote validation logic in your controller action.

Are people aware of this and has anyone tried to address it?

I have subclassed RemoteAttribute and overridden the IsValid method where I have access to RouteData, RouteName and Routes as well as a GetUrl method that returns the action URL. I was thinking about using reflection to call the action and get the result so I can see if it is valid or not, but are there any built-in methods that I can use without resorting to reflection?


回答1:


Maybe its not the best code. If you have some recommendations please let me know. Developed @MVC4

Model property with custom attribute

[CustomRemote("ValidateIP", "Validation", ErrorMessage = "Input is not a valid IP")]

Subclassed RemoteAttribute

/// <summary>
/// Custom Remote Attribute for Client an Server validation.
/// </summary>
public class CustomRemoteAttribute : RemoteAttribute
{
    /// <summary>
    /// List of all Controllers on MVC Application
    /// </summary>
    /// <returns></returns>
    private static List<Type> GetControllerList()
    {
        return Assembly.GetCallingAssembly().GetTypes().Where(type => type.IsSubclassOf(typeof(Controller))).ToList();
    }

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    protected CustomRemoteAttribute()
    {
    }

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    public CustomRemoteAttribute(string routeName)
        : base(routeName)
    {
    }

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    public CustomRemoteAttribute(string action, string controller)
        : base(action, controller)
    {
    }

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    public CustomRemoteAttribute(string action, string controller, string areaName)
        : base(action, controller, areaName)
    {
    }

    /// <summary>
    /// Overridden IsValid function
    /// </summary>
    /// <param name="value"></param>
    /// <param name="validationContext"></param>
    /// <returns></returns>
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // Find the controller passed in constructor
        var controller = GetControllerList().FirstOrDefault(x => x.Name == string.Format("{0}Controller", this.RouteData["controller"]));
        if (controller == null)
        {
            // Default behavior of IsValid when no controller is found.
            return ValidationResult.Success;
        }

        // Find the Method passed in constructor
        var mi = controller.GetMethod(this.RouteData["action"].ToString());
        if (mi == null)
        {
            // Default behavior of IsValid when action not found
            return ValidationResult.Success;
        }

        // Create instance of the controller to be able to call non static validation method
        var instance = Activator.CreateInstance(controller);

        // invoke the method on the controller with value
        var result = (JsonResult)mi.Invoke(instance, new object[] { value });

        // Return success or the error message string from CustomRemoteAttribute
        return (bool) result.Data ? ValidationResult.Success : new ValidationResult(base.ErrorMessageString);
    }
}

Validation Controller Code

/// <summary>
/// Controller for Client and Server validation
/// <remarks>disable OutputCache</remarks>
/// </summary>
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public class ValidationController : Controller
{
    /// <summary>
    /// !!!!!!!!!!!!!!!!!! Needed for instance creation in custom attribute !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    /// </summary>
    public ValidationController()
    {
    }

    /// <summary>
    /// IP regex pattern of my choice
    /// </summary>
    const string IpPattern = @"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";

    /// <summary>
    /// MAC regex pattern of my choice
    /// </summary>
    const string MacPattern = "^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$";

    /// <summary>
    /// Validate IP
    /// </summary>
    /// <param name="ip">IP param is only submited on Serverside validation!!!</param>
    /// <returns>Validation Result</returns>
    public JsonResult ValidateIP(string ip)
    {
        // Check if ip and httpcontext is null to dodge NullReferenceException on Server side validation
        if (string.IsNullOrEmpty(ip) && HttpContext == null)
        {
            return Json(false, JsonRequestBehavior.AllowGet);
        }

        /* Use IP on Serverside validation 
         * Use Querystring Param 0 to get IP from Client side vaildation without the need for the correct Id of input control */
        string checkip = string.IsNullOrEmpty(ip) ? HttpContext.Request.QueryString[0] : ip;
        if (string.IsNullOrEmpty(checkip))
        {
            return new JsonResult { Data = true, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
        }

        return new JsonResult
            {
                Data = Regex.IsMatch(checkip, IpPattern),
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
    }
}



回答2:


This is the intended behavior of remote validation. There is no way to know what the implementation of IsValid would be so it simply returns true. If you want server side validation for the RemoteAttribute you should override IsValid just like you have done.




回答3:


Remote validator often with an additional field. Here's the implementation for this case.

/// <summary>
/// Remote Attribute for Client an Server validation.
/// </summary>
public class RemoteWithServerSideAttribute : RemoteAttribute
{
    /// <summary>
    /// List of all Controllers on MVC Application
    /// </summary>
    /// <returns></returns>
    private static IEnumerable<Type> GetControllerList()
    {
        return Assembly.GetCallingAssembly().GetTypes().Where( type => type.IsSubclassOf( typeof(    Controller ) ) ).ToList();
    }

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    protected RemoteWithServerSideAttribute() {}

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    public RemoteWithServerSideAttribute( string routeName ) : base( routeName ) {}

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    public RemoteWithServerSideAttribute( string action, string controller ) : base( action, controller ){}

    /// <summary>
    /// Constructor of base class.
    /// </summary>
    public RemoteWithServerSideAttribute( string action, string controller, string areaName ) : base( action, controller, areaName ) {}

    /// <summary>
    /// Overridden IsValid function
    /// </summary>
    /// <param name="value"></param>
    /// <param name="validationContext"></param>
    /// <returns></returns>
    protected override ValidationResult IsValid( object value, ValidationContext validationContext )
    {
        // Find the controller passed in constructor
        var controller = GetControllerList().FirstOrDefault( x => x.Name == string.Format( "{0}Controller", this.RouteData["controller"] ) );
        if ( controller == null )
        {
            // Default behavior of IsValid when no controller is found.
            return ValidationResult.Success;
        }

        // Find the Method passed in constructor
        var mi = controller.GetMethod( this.RouteData["action"].ToString() );
        if ( mi == null )
        {
            // Default behavior of IsValid when action not found
            return ValidationResult.Success;
        }

        // Create instance of the controller to be able to call non static validation method
        var instance = Activator.CreateInstance( controller );

        // invoke the method on the controller with value and "AdditionalFields"
        JsonResult result;
        if ( !string.IsNullOrWhiteSpace( AdditionalFields ) )
        {
            var additionalField = validationContext.ObjectType.GetProperty( AdditionalFields )
                .GetValue( validationContext.ObjectInstance );
            result = (JsonResult) mi.Invoke( instance, new [] { value, additionalField } );
        }
        else
            result = (JsonResult)mi.Invoke( instance, new [] { value } );

        // Return success or the error message string from CustomRemoteAttribute
        string errorMessaqe = result.Data as string;
        if (errorMessaqe == null)
        {
            bool isValid;
            try
            {
                isValid = (bool) result.Data;
            }
            catch (Exception)
            {
                isValid = false;
            }
            return isValid ? ValidationResult.Success : new ValidationResult( base.ErrorMessageString );
        }
        else
            return new ValidationResult( errorMessaqe );    
    }
}



回答4:


As has been answered before, this is by design.

I have just stumbled upon a nice article on CodeProject - http://www.codeproject.com/Articles/361113/Extending-the-MVC-RemoteAttribute-to-validate-ser



来源:https://stackoverflow.com/questions/5393020/remoteattribute-validator-does-not-fire-server-side

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!