Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.Net Web Api - posting UK date formats

I want my users to be able to post dates to an asp.net web api controller in uk format, such as 01/12/2012 (1st Dec 2012).

From what i can tell by default, only us format is accepted.

Can i change something somewhere so that UK format is the default? I tried changing the globalization setting in the web.config but this had no effect.

Paul

like image 785
Paul Hinett Avatar asked Aug 30 '12 11:08

Paul Hinett


2 Answers

Done this using a custom model binder, which is slightly different to the model binders in MVC3:

public class DateTimeModelBinder : IModelBinder
    {

        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            var date = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;

            if (String.IsNullOrEmpty(date))
                return false;

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, bindingContext.ValueProvider.GetValue(bindingContext.ModelName));
            try
            {
                bindingContext.Model = DateTime.Parse(date);
                return true;
            }
            catch (Exception)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, String.Format("\"{0}\" is invalid.", bindingContext.ModelName));
                return false;
            }
        }
    }

And in my Global.asax.cs file, add this line to tell the api to use this model binder for DateTime values:

GlobalConfiguration.Configuration.BindParameter(typeof(DateTime), new DateTimeModelBinder());

Here is the method in my api controller:

public IList<LeadsLeadRowViewModel> Get([ModelBinder]LeadsIndexViewModel inputModel)

My LeadsIndexViewModel class had several DateTime properties which were now all valid UK date times.

like image 141
Paul Hinett Avatar answered Sep 19 '22 04:09

Paul Hinett


Well, I also wanted to solve this at a global level ... and tore out lots of hair in the process. It turns out there are no extension points in WebApi where one would hope to intercept the incoming form data and modify them as needed. Wouldn't that be nice. So, lacking just that, I dug as deep as I could into WebApi source code to see what I could come up with. I ended up reusing the class in charge of parsing form data to create the model. I added just a few lines to deal specifically with dates. That class, below, can be added to the configuration like this:

  private static void PlaceFormatterThatConvertsAllDatesToIsoFormat(HttpConfiguration config)
  {
             config.Formatters.Remove(
                 config.Formatters.FirstOrDefault(x => x is JQueryMvcFormUrlEncodedFormatter));

             config.Formatters.Add(
                 new IsoDatesJQueryMvcFormUrlEncodedFormatter());
}

The formatter:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using System.Web.Http.ModelBinding;

namespace WebApi.Should.Allow.You.To.Set.The.Binder.Culture
{
    // This class replaces WebApi's JQueryMvcFormUrlEncodedFormatter 
    // to support JQuery schema on FormURL. The reasong for this is that the 
    // supplied class was unreliable when parsing dates european style.
    // So this is a painful workaround ...
    /// <remarks>
    /// Using this formatter any string that can be parsed as a date will be formatted using ISO format
    /// </remarks>
    public class IsoDatesJQueryMvcFormUrlEncodedFormatter : FormUrlEncodedMediaTypeFormatter
    {

        // we *are* in Israel
        private static readonly CultureInfo israeliCulture = new CultureInfo("he-IL");

        public override bool CanReadType(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            return true;
        }

        public override Task<object> ReadFromStreamAsync(Type type
            , Stream readStream
            , HttpContent content
            , IFormatterLogger formatterLogger)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            if (readStream == null)
            {
                throw new ArgumentNullException("readStream");
            }

            // For simple types, defer to base class
            if (base.CanReadType(type))
            {
                return base.ReadFromStreamAsync(type, readStream, content, formatterLogger);
            }

            var result = base.ReadFromStreamAsync(
                                typeof(FormDataCollection), 
                                readStream, 
                                content, 
                                formatterLogger);

            Func<object, object> cultureSafeTask = (state) =>
            {
                var innterTask = (Task<object>)state;
                var formDataCollection = (FormDataCollection)innterTask.Result;
                var modifiedCollection = new List<KeyValuePair<string, string>>();

                foreach (var item in formDataCollection)
                {
                    DateTime date;
                    var isDate =
                        DateTime.TryParse(item.Value,
                                            israeliCulture,
                                            DateTimeStyles.AllowWhiteSpaces,
                                            out date);

                    if (true == isDate)
                    {
                        modifiedCollection.Add(
                            new KeyValuePair<string, string>(
                                item.Key,
                                date.ToString("o")));
                    }
                    else
                    {
                        modifiedCollection.Add(
                            new KeyValuePair<string, string>(
                                item.Key,
                                item.Value));
                    }
                }

                formDataCollection = new FormDataCollection(modifiedCollection);

                try
                {
                    return
                        formDataCollection.ReadAs(type, String.Empty, RequiredMemberSelector, formatterLogger);
                }
                catch (Exception e)
                {
                    if (formatterLogger == null)
                    {
                        throw;
                    }

                    formatterLogger.LogError(String.Empty, e);

                    return GetDefaultValueForType(type);
                }
            };

            return
                Task.Factory.StartNew(
                cultureSafeTask
                            , result);
        }
    }
} 
like image 41
David Perlman Avatar answered Sep 17 '22 04:09

David Perlman