简体   繁体   中英

Web Api 2: Custom parameter validation (typeconverter)

In Web Api 2, a method on a controller can have a number of parameters supplied from the URI, eg string converted to int and automatic web error message returned if string could not be converted to int.

I would like to do the same with a custom type. I have created a class that holds just a DateTime object and I want to make a custom conversion of the web URI string argument to this DateTime object. For this I implemented a TypeConverter. It works. My problem is that if I give it a bad date string, I get no web error and my method gets a null pointer. This is my model with the typeconverter:

[TypeConverter(typeof(DkDateTimeConverter))]
public class DkDateTime
{
    private static readonly ILog log4 = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public DateTime dkDateTime;

    public static bool TryParse(string s, out DkDateTime result)
    {
        log4.Debug("TryParse");
        result = null;

        var culture = new CultureInfo("da");
        try
        {
            result = new DkDateTime()
            {
                dkDateTime = DateTime.ParseExact(s, "dd-MM-yyyy HH:mm:ss", culture)
            };
            return true;
        }
        catch
        {
            return false;
        }
    }
}

class DkDateTimeConverter : TypeConverter
{
    private static readonly ILog log4 = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        log4.Debug("CanConvertFrom");
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        log4.Debug("ConvertFrom");
        if (value is string)
        {
            DkDateTime dkDateTime;
            if (DkDateTime.TryParse((string)value, out dkDateTime))
            {
                return dkDateTime;
            }
            else
            {
                log4.Debug("Exception");
                throw new NotSupportedException();
            }
        }
        return base.ConvertFrom(context, culture, value);
    }
}

My Controller has this method as entry point:

public IHttpActionResult Get(Guid messageid, int statuscode, DkDateTime sentdate, DkDateTime donedate)

I was under the impression that if ConvertFrom caused an exception, this would be reflected by web api and an web error message would be sent back to the caller. If I give "2a" as statuscode, I do get a web error like this:

<Error>
<Message>The request is invalid.</Message>
</Error>

How do I trigger the same error message with my typeconverter? Perhaps a typeconverter cannot help, but which way should I then look?

What you are doing is correct and it is by design. Think of what you would expect from an invalid Guid in the messageid param. Same thing happens there. Either the route wont match or you get an invalid request. In order to have more control over the model state validation & error message you should inherit from IModelBinder and build your custom DkDateTimeModelBinder .

Check this post: Parameter Binding in ASP.NET Web API

Update with example .

public class DkDateTime
{
    public DateTime dkDateTime;

    public static bool TryParse(string s, out DkDateTime result) {
        result = null;
        var dateTime = default(DateTime);
        if (DateTime.TryParseExact(s, "dd-MM-yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTime)) {
            result = new DkDateTime { dkDateTime = dateTime };
            return true;
        }
        return false;
    }
}

 public class DkDateTimeModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
        if (bindingContext.ModelType != typeof(DkDateTime)) {
            return false;
        }

        ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (val == null) {
            return false;
        }

        string key = val.RawValue as string;
        if (key == null) {
            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Wrong value type");
            return false;
        }

        DkDateTime result;
        if (DkDateTime.TryParse(key, out result)) {
            bindingContext.Model = result;
            return true;
        }

        bindingContext.ModelState.AddModelError(
            bindingContext.ModelName, "Cannot convert value to DkDateTime");
        return false;
    }
}

Then in the controller:

public class ExampleController : ApiController
{
    [Route("test")]
    public async Task<IHttpActionResult> GetDk([ModelBinder(typeof(DkDateTimeModelBinder))]DkDateTime sendDate) {
        if (!ModelState.IsValid) {
            return BadRequest(ModelState);
        }
        return Ok(sendDate);
    }
}

Now when I test with url http://localhost:4814/example/test?senddate=dsfasdafsd I get the expected respose below

Status 400 Bad Request

{
    "Message": "The request is invalid.",
    "ModelState": {
        "sendDate": [
            "Cannot convert value to DkDateTime"
        ]
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM