0

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.

Community
  • 1
  • 1
Hoborg
  • 891
  • 12
  • 21
  • 3
    Can you elaborate on how you intend to cast "hello" to a Guid? – Ant P Oct 31 '14 at 11:42
  • 3
    You have two options here: either accept a string as input and converted afterwards according to your rules, or create a custom model binder. – Andrei V Oct 31 '14 at 11:43
  • @AntP It was just an exmaple. The general problem is to convert Base64-string to Guid and backwards. (In my case it is possible to cast Guid->Base64-string through extension method for Guid). – Hoborg Oct 31 '14 at 12:36
  • @AndreiV Would you be so kind to supply a link for appropriate manual on model binding or an advice? Notice that this binding should try to cast all strings to Guid if it's necessary and possible. And this conversion should be done every time any action with Guid parameters is triggered. (Conversion in the action is undesired because of code duplication, right?) – Hoborg Oct 31 '14 at 12:42
  • If your string is a valid Guid then it will bind. If not, you'll need a custom model binder as @AndreiV says. Plenty examples of how to do that around the web: http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder – Ant P Oct 31 '14 at 12:42
  • The custom model binder part is relatively easy. There are several ways to do it. Just google it and I'm sure you'll find enough examples. One of these methods is setting a model binding attribute. I have an example in [this answer](http://stackoverflow.com/questions/26136758/method-parameter-with-attribute-in-barkets/26136863#26136863). This, however, requires that you set the attribute every time you need it. I'm not sure, but you'd probably be able to automatically check the parameters using an action filter. – Andrei V Oct 31 '14 at 12:50
  • Thank you for helpful comments. I'll set a custom model binder. The broblem with model binding was that I didn't realize how to retrieve a name of the action's parameter. Now I've found that it is contained in bindingContext.ModelName when implementing public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext). – Hoborg Oct 31 '14 at 13:39
  • I have one question for @Hoborg, can you do this in normal c# method? – Mukund Oct 31 '14 at 13:47
  • @Mukund What do you mean by "this"? – Hoborg Oct 31 '14 at 13:53

4 Answers4

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

Community
  • 1
  • 1
Hoborg
  • 891
  • 12
  • 21
2

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
    }
Community
  • 1
  • 1
Max
  • 531
  • 6
  • 12
1

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.

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
  • Thanks for the answer. Today I hardly can remember the intent which was behind my question. I mean, if the question was about only assigning null to the Guid parameter if it was not valid, than your answer absolutely fits best. But it seems to me that I was looking for an example of custom model binder, thus I'd rather leave my answer as an accepted one. – Hoborg Dec 04 '16 at 19:05
0

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 "";
}
Aravindan
  • 855
  • 6
  • 15
  • Thank you for an answer. But my problem was to set up an automatic conversion for all corresponding actions. For sure this coversion shoud deals with incorrect data and handle it appropriate. – Hoborg Oct 31 '14 at 13:55