2

I have the following action in my controller:

[HttpGet]
public ActionResult Office(GestionOffice model)
{
    ModelState.Clear();
    model.Initialize();
    return View(model);
}

I don't need in action by GET, validations are made. This would help me in the performance of the call by GET.

This would be my ideal case:

[HttpGet]
[NotValidateModel]
public ActionResult Office(GestionOffice model)
{
    model.Initialize();
    return View(model);
}

Thank you.

Edit

NotValidateModel clarified that does not exist, would attribute the case to avoid validations.

The reason to move the model in action is to MOCK the model

Edit II

I have my action with POST, I need is to receive in my action by GET, the model without validation, to successfully complete the testing of the action in the controller

[HttpGet]
[NotValidateModel]
public ActionResult Office(GestionOffice model)
{
    model.Initialize();
    return View(model);
}

[HttpPost]
[ActionName("Office")]
[NotValidateModel]
public ActionResult OfficePost(GestionOffice model)
{
    if(ModelState.IsValid)
    {
        model.Save();
        return RedirectToAction("List");
    }

    model.Initialize();
    return View(model);
}

Edition on the solution of @Mark

As I in my view I have a few calls to actions, I had to create a key with the action and the controller.

Custom ModelMetaData

public class CustomModelMetaData : ModelMetadata
{
    public CustomModelMetaData(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
        : base(provider, containerType, modelAccessor, modelType, propertyName)
    {
    }

    public override IEnumerable<ModelValidator> GetValidators(ControllerContext context)
    {
        var itemKey = this.CreateKey(context.RouteData);
        if (context.HttpContext.Items[itemKey] != null && bool.Parse(context.HttpContext.Items[itemKey].ToString()) == true)
        {
            return Enumerable.Empty<ModelValidator>();
        }

        return base.GetValidators(context);
    }

    private string CreateKey(RouteData routeData)
    {
        var action = (routeData.Values["action"] ?? null).ToString().ToLower();
        var controller = (routeData.Values["controller"] ?? null).ToString().ToLower();
        return string.Format("NoValidation_{0}_{1}", controller, action);
    }
}

Filter

public class NoValidationAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var itemKey = this.CreateKey(filterContext.ActionDescriptor);
        filterContext.HttpContext.Items.Add(itemKey, true);
    }

    private string CreateKey(ActionDescriptor actionDescriptor)
    {
        var action = actionDescriptor.ActionName.ToLower();
        var controller = actionDescriptor.ControllerDescriptor.ControllerName.ToLower();
        return string.Format("NoValidation_{0}_{1}", controller, action);
    }
}

Edit filter

there may be a case of having a foreach in the main view to call several partial views with the attribute NoValidation. in this case, includes a control to check for the key. since it includes the name of the controller and action in the key, this key is almost unique, it can only be repeated in the case described

public class NoValidationAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var itemKey = this.CreateKey(filterContext.ActionDescriptor);
        if (!filterContext.HttpContext.Items.Contains(itemKey))
        {
            filterContext.HttpContext.Items.Add(itemKey, true);
        }
    }

    private string CreateKey(ActionDescriptor actionDescriptor)
    {
        var action = actionDescriptor.ActionName.ToLower();
        var controller = actionDescriptor.ControllerDescriptor.ControllerName.ToLower();
        return string.Format("NoValidation_{0}_{1}", controller, action);
    }
}
andres descalzo
  • 14,887
  • 13
  • 64
  • 115
  • 3
    An FYI: it is wrong to have a GET with a model. You should pass the GET with an id and then, in your method you retrieve the model like so `MyModel model = MyModel.Initialize(id)` – amb Jul 06 '12 at 18:46
  • @amb ok but I need to testing the controller, and make the `MOCK` of model – andres descalzo Jul 06 '12 at 18:49
  • @amb: Using a model with a GET in *this* instance is the wrong idea, but it's perfectly fine in other areas. You might have a GET that requires a few parameters that need to be validated. – rossisdead Jul 06 '12 at 20:56
  • @rossisdead I agree but from "might have" to "let's just insert another parameter" and in the end "a lot of parameters" is a short distance... Not to mention that GET has a very restrictive length. It's useful to think GET in the area of RESTful services and follow the guidelines. – amb Jul 06 '12 at 21:04

4 Answers4

2

[SEE THE UPDATES BELOW ALSO THE EDITS MENTIONED BY @ANDRES IN QUESTION]

What you are trying to do is little difficult to achieve and I'm not sure why you want this basically for. I've tried to accomplish through custom ModelMetaData and filter (not tested completely).

We can't read the attributes that are decorated over the actions from a model binder or a metadata provider so we need a filter. The filter put some value in the HttpContext.Items and we can retrieve that from the custom metadata provider (http://stackoverflow.com/questions/6198155/asp-net-mvc-modelbinder-getting-action-method).

Filter

public class NoValidationAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
      // please see the edits in question to see the key generation
      filterContext.HttpContext.Items.Add("NoValidation", true);
    }
}

Custom ModelMetaData

public class CustomModelMetaData : ModelMetadata
{
    public CustomModelMetaData(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) :
      base(provider, containerType, modelAccessor, modelType, propertyName)
    {
    }

    public override System.Collections.Generic.IEnumerable<ModelValidator> GetValidators(ControllerContext context)
    {
      if (context.HttpContext.Items["NoValidation"] != null && bool.Parse(context.HttpContext.Items["NoValidation"].ToString()) == true)
        return Enumerable.Empty<ModelValidator>();

      return base.GetValidators(context);
    }
}

Custom ModelMetaDataProvider

public class CustomModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(System.Collections.Generic.IEnumerable<Attribute> attributes,
      Type containerType, Func<object> modelAccessor,
      Type modelType,
      string propertyName)
    {
      return new CustomModelMetaData(this, containerType, modelAccessor, modelType, 
         propertyName); 
    }
}

Global.asax.cs

ModelMetadataProviders.Current = new CustomModelMetaDataProvider();

Usage

[HttpGet]
[NoValidation]
public ActionResult Office(GestionOffice model)
{
   ...
}

================================= UPDATES =====================================

Instead of creating a CustomModelMetaData by inheriting ModelMetadata I think it would be good to inherit from DataAnnotationsModelMetadata.

public class CustomModelMetaData : DataAnnotationsModelMetadata
{
    public CustomModelMetaData(DataAnnotationsModelMetadataProvider provider, Type containerType,   
            Func<object> modelAccessor, Type modelType, string propertyName,            
            DisplayColumnAttribute displayColumnAttribute) :            
    base(provider, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute)
    {
    }

    public override IEnumerable<ModelValidator> GetValidators(ControllerContext context)
    {
        var itemKey = this.CreateKey(context.RouteData);

        if (context.HttpContext.Items[itemKey] != null && 
           bool.Parse(context.HttpContext.Items[itemKey].ToString()) == true)
        {
             return Enumerable.Empty<ModelValidator>();
        }

        return base.GetValidators(context);
    }

    private string CreateKey(RouteData routeData)
    {
       var action = (routeData.Values["action"] ?? null).ToString().ToLower();
       var controller = (routeData.Values["controller"] ?? null).ToString().ToLower();
       return string.Format("NoValidation_{0}_{1}", controller, action);
    }
}

In the CustomModelMetaDataProvider you have to set some properties.

public class CustomModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
      Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
      var displayColumnAttribute = new List<Attribute>(attributes).OfType<DisplayColumnAttribute>().FirstOrDefault();

      var baseMetaData = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

      // is there any other good strategy to copy the properties?
      return new CustomModelMetaData(this, containerType, modelAccessor, modelType,  propertyName, displayColumnAttribute)
      {
        TemplateHint = baseMetaData.TemplateHint,
        HideSurroundingHtml = baseMetaData.HideSurroundingHtml,
        DataTypeName = baseMetaData.DataTypeName,
        IsReadOnly = baseMetaData.IsReadOnly,
        NullDisplayText = baseMetaData.NullDisplayText,
        DisplayFormatString = baseMetaData.DisplayFormatString,
        ConvertEmptyStringToNull = baseMetaData.ConvertEmptyStringToNull,
        EditFormatString = baseMetaData.EditFormatString,
        ShowForDisplay = baseMetaData.ShowForDisplay,
        ShowForEdit = baseMetaData.ShowForEdit,
        Description = baseMetaData.Description,
        ShortDisplayName = baseMetaData.ShortDisplayName,
        Watermark = baseMetaData.Watermark,
        Order = baseMetaData.Order,
        DisplayName = baseMetaData.DisplayName,
        IsRequired = baseMetaData.IsRequired
      };
    }
}
VJAI
  • 32,167
  • 23
  • 102
  • 164
  • Wow, this is what I wanted, thanks. I test it and then confirm you – andres descalzo Jul 06 '12 at 21:08
  • I make a few modifications in my view because I have many "@ Html.Action (..)" and all have the attribute "novalidation" – andres descalzo Jul 18 '12 at 15:42
  • In the method `CreateMetadata` of `DataAnnotationsModelMetadataProvider` create object `ModelMetadata` with all attributes as the description or name. but now with `CustomModelMetaDataProvider` not created all these properties. I had to copy the code `DataAnnotationsModelMetadataProvider.CreateMetadata` to get the same functionality. – andres descalzo Jul 19 '12 at 20:43
  • I think another question for another problem on the same issue http://stackoverflow.com/questions/11595826/no-validated-model-to-receive-data-by-get – andres descalzo Jul 22 '12 at 00:45
  • *I had to copy the code DataAnnotationsModelMetadataProvider.CreateMetadata to get the same functionality* I'll take a look at this and update you – VJAI Jul 22 '12 at 02:24
  • @andresdescalzo I updated my answer, please let me know if you have any questions – VJAI Jul 22 '12 at 03:10
  • gracias, vi este link http://stackoverflow.com/a/7316457/138071 para copiar objetos. Pero el problema que tengo ahora es al recibir datos por "GET", en ese caso se evaluan las validaciones. (http://stackoverflow.com/q/11595826/138071) – andres descalzo Jul 23 '12 at 12:58
1

Yes, you use the [ValidateInput(false)] attribute like this:

[ValidateInput(false)]
[HttpGet]
public ActionResult Office(GestionOffice model)
{
    model.Initialize();
    return View(model);
}
amb
  • 1,599
  • 2
  • 11
  • 18
  • I tried with `ValidateInput`, but equally valid properties and contains errors in ModelState. as the properties required – andres descalzo Jul 06 '12 at 19:25
  • So what exactly are you trying to do since you validate in a post? Use also the `[ValidateInput(false)]` on post to disable automatic validation but don't use ModelState.IsValid because in this way you manually call to validate. The validation process happens in POST not in GET – amb Jul 06 '12 at 19:46
0

If you want to use without validation in production code, you will need to disable the client side validation, since on the server side it will be disabled.

modify web.config as fallows:

<appSettings>
    <add key="ClientValidationEnabled" value="false" />
</appSettings>
amb
  • 1,599
  • 2
  • 11
  • 18
  • 1
    This only avoids client-side validation – andres descalzo Jul 06 '12 at 19:39
  • well if you want to use without validation in production code you will need to disable the client side validation since on the server side, it will be disabled. So yes, you will need this. – amb Jul 06 '12 at 19:53
  • 1 - you should do only one answer, so I can comment on a single response. 2 - I need to control the data in the POST, I do not need is to control the model in GET, only that – andres descalzo Jul 06 '12 at 20:12
  • 1. I will from now on. 2. If you need to control the data in the POST, then follow my last answer, the one that modified you POST method. The validation in ASP.NET mvc happens _only_ in POST. – amb Jul 06 '12 at 20:16
0

The validation happens in POST so you must remove Model.IsValid and add [ValidateInput(false)]

[HttpPost]
[ValidateInput(false)]
[ActionName("Office")]
public ActionResult OfficePost(GestionOffice model)
{        
    model.Save();
    return RedirectToAction("List");
}

Now the post will succeed but you must be careful if you insert into a database.

amb
  • 1,599
  • 2
  • 11
  • 18