ASP.NET MVC: types cast in model binding

2019-08-28 05:09发布

问题:

Is there a way to cast parameter in the controller's action from one type to another in ASP.NET MVC 5?

Example. Action method is the following:

public string Test(Guid input) {
    return "";
}

If the method is invoked with parameters "input=hello" then I get the error with the message: "The parameters dictionary contains a null entry for parameter 'input' of non-nullable type 'System.Guid' for method 'System.String Test(System.Guid)' in 'RealtyGuide.WebSite.Controllers.HomeController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."

What I want is try to cast the parameter to Guid according to specific (custom) rules. Is it a question on model binding? What are the possible ways to solve this task? Sorry for my English.

About the answers. If you just want assign null to a Guid parameter if it is invalid, then look at this answer. If you are looking for an example of custom model binder, then look at this and this answers.

回答1:

in reply to Hoborg's example, https://stackoverflow.com/posts/26676337/revisions the following will return null values or the Guid parameter, if it is desired.

public class NullableGuidBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType == typeof(Guid?))
        {
            var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            string input = valueResult.AttemptedValue;
            if (string.IsNullOrEmpty(input) || input == "0")
            {
                // return null, even if input = 0
                // however, that is dropdowns' "guid.empty")
                // base.BindModel(...) would fail converting string to guid, 
                // Guid.Parse and Guid.TryParse would fail since they expect 000-... format

                // add the property to modelstate dictionary
                var modelState = new ModelState { Value = valueResult };
                bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
                return null;
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }
}

binding as follows in your controller

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> SelectAction(
        [ModelBinder(typeof(NullableGuidBinder))] Guid? id)
    {
        // your stuff
    }


回答2:

According to the comments from @AndreiV and @AntP the decisions are:

  1. if the string is a correct Guid-string then it is binded automatically (nothing else is needed),

  2. if the string is not a correct Guid-string one should

2.1. make conversion in the action's body (to my mind it entails code duplication), or

2.2. set up the custom (user-defined) model binder. The following is the code for the last approach (model binder).

// This is an example. 
// Note the returning of null which is undesired.
// Also it does not deal with exceptions handling. Use it only as a stub.
    public class ExtendedModelBinder : DefaultModelBinder {
            public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
                if (!(bindingContext.ModelType == typeof(Guid)))
                    return base.BindModel(controllerContext, bindingContext);
                if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
                    return null;
                string input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
                if (string.IsNullOrEmpty(input))
                    return null;
                Guid g;
                if (Guid.TryParse(input, out g))
                    return g;
                var bytes = HttpServerUtility.UrlTokenDecode(s);
                var result = new Guid(bytes);
                return result;
            }
        }

Registration in Application_Start is necessary:

ModelBinders.Binders.Add(typeof(Guid), new RealtyGuide.WebSite.Extensions.MyModelBinder());

One may use attributes instead of registration of the binder globally (see here), but I'll not use it, because it entails unnecessary code duplication in my task.

Refs: 1, 2, 3



回答3:

I have stumbled upon this question when searching about Guid binding in action with a graceful fallback when the value is not valid. If the actual provided value does not have to be known, using a nullable Guid (Guid?) should to the trick. E.g. (this is tested in ASP.NET Web API):

[Route("Test")]
[HttpGet]
public string Test(Guid? input = null)

If a valid Guid is provided, input will be populated. Otherwise, it will be null.



回答4:

Trying input="helo" is an wrong method. Guid must contain 32 bit number value example (12345678910111213141516171819202)

Try this input = 12345678910111213141516171819202, By giving value like 32 digit number the Guid value accepts it. then the the method will not display the same error.

var input = 12345678910111213141516171819202;

public string Test(Guid input) {
return "";
}