ASP.NET MVC: types cast in model binding

2019-08-28 05:14发布

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.

4条回答
闹够了就滚
2楼-- · 2019-08-28 05:43

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.

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-08-28 05:44

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

查看更多
ゆ 、 Hurt°
4楼-- · 2019-08-28 05:49

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 "";
}
查看更多
劳资没心,怎么记你
5楼-- · 2019-08-28 05:55

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
    }
查看更多
登录 后发表回答