Asp.Net Web Api - posting UK date formats

2020-06-16 03:07发布

问题:

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

回答1:

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.



回答2:

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);
        }
    }
} 


回答3:

There's an example of localising the jQuery date picker to en-gb here: http://weblogs.asp.net/hajan/archive/2010/10/05/integration-of-jquery-datepicker-in-asp-net-website-localization-part-3.aspx

Also, I see you tried setting the culture to en-GB, but I don't know if you tried setting the UI Culture as well in Web.Config (and I don't know if this affects the jQuery bundles in MVC or not):

<globalization requestEncoding="utf-8" responseEncoding="utf-8" culture="en-GB" uiCulture="en-GB"/>

... or if that doesn't work (heh), why not pass the value as a string and then parse it in your api controller:

using System.Globalization;

// fetch the en-GB culture
CultureInfo ukCulture = new CultureInfo("en-GB");
// pass the DateTimeFormat information to DateTime.Parse
DateTime myDateTime = DateTime.Parse("StringValue" ,ukCulture.DateTimeFormat);