Model normalization before model validation in Asp.Net Core 2.0+












0















I'm using automatic model validation (see "Better Input Processing") to keep my controllers clean; so:



[HttpPost]
[ProducesResponseType(typeof(Product), 201)]
public IActionResult Post([FromBody] Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
product = _repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


becomes:



[HttpPost]
[ProducesResponseType(201)]
public ActionResult<Product> Post(Product product)
{
_repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


However, I do have a few models that have a phonenumber property. I would like to 'normalize' these before the model validation is invoked. What I mean is that I want to normalize these properties (of type string) from all kinds of input like:




  • +31 23 456 7890

  • (023) 4567890

  • 023 - 4567 890

  • ...


To E.164 notation:




  • +31234567890


So in whatever form a user enters a phonenumber, before validation is invoked I want to be sure it's always in E.164 form ('normalized'). How this normalization is done is irrelevant (I use libphonenumber if you insist). As a second, maybe less convoluted, example I can imagine a string to be always upper-/lowercased before validation is invoked.



What would be the correct, or best, way to invoke my normalization process before the validation is invoked? Would I have to write some middleware?



Also relevant: my models contain attributes so the normalizer knows which properties to normalize (and how):



class ExampleModel {

public int Id { get; set; }

public string Name { get; set; }

[NormalizedNumber(NumberFormat.E164)]
public string Phonenumber { get; set; }
}


I guess the middleware(? or whatever the solution is going to be) can then take a model, figure out if any of the properties (recursively) have the attribute and invoke the normalizer if needed.










share|improve this question




















  • 1





    I would think the earliest accessible point would be in a custom model binder.

    – Nkosi
    Nov 21 '18 at 17:23











  • Custom model binder would be the best way to handle this. Take a look at this dotnetcoretutorials.com/2016/12/28/…

    – Paresh
    Nov 21 '18 at 17:32











  • Thanks both! I'll have a look into a custom model binder!

    – RobIII
    Nov 21 '18 at 17:38











  • @Paresh It's looking very promising; however, my models can be quite complex and I'd like to invoke the original modelbinder binding and after that invoke my 'formatter'. The link doesn't show how to invoke the 'original' modelbinder from the custom binder correctly so I can, after it did it's work, change all 'tagged' properties. Do you happen to have a good resource for that?

    – RobIII
    Nov 22 '18 at 10:30











  • I have posted a follow up question since I can't get this to work the way I'd like to.

    – RobIII
    Nov 26 '18 at 15:26
















0















I'm using automatic model validation (see "Better Input Processing") to keep my controllers clean; so:



[HttpPost]
[ProducesResponseType(typeof(Product), 201)]
public IActionResult Post([FromBody] Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
product = _repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


becomes:



[HttpPost]
[ProducesResponseType(201)]
public ActionResult<Product> Post(Product product)
{
_repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


However, I do have a few models that have a phonenumber property. I would like to 'normalize' these before the model validation is invoked. What I mean is that I want to normalize these properties (of type string) from all kinds of input like:




  • +31 23 456 7890

  • (023) 4567890

  • 023 - 4567 890

  • ...


To E.164 notation:




  • +31234567890


So in whatever form a user enters a phonenumber, before validation is invoked I want to be sure it's always in E.164 form ('normalized'). How this normalization is done is irrelevant (I use libphonenumber if you insist). As a second, maybe less convoluted, example I can imagine a string to be always upper-/lowercased before validation is invoked.



What would be the correct, or best, way to invoke my normalization process before the validation is invoked? Would I have to write some middleware?



Also relevant: my models contain attributes so the normalizer knows which properties to normalize (and how):



class ExampleModel {

public int Id { get; set; }

public string Name { get; set; }

[NormalizedNumber(NumberFormat.E164)]
public string Phonenumber { get; set; }
}


I guess the middleware(? or whatever the solution is going to be) can then take a model, figure out if any of the properties (recursively) have the attribute and invoke the normalizer if needed.










share|improve this question




















  • 1





    I would think the earliest accessible point would be in a custom model binder.

    – Nkosi
    Nov 21 '18 at 17:23











  • Custom model binder would be the best way to handle this. Take a look at this dotnetcoretutorials.com/2016/12/28/…

    – Paresh
    Nov 21 '18 at 17:32











  • Thanks both! I'll have a look into a custom model binder!

    – RobIII
    Nov 21 '18 at 17:38











  • @Paresh It's looking very promising; however, my models can be quite complex and I'd like to invoke the original modelbinder binding and after that invoke my 'formatter'. The link doesn't show how to invoke the 'original' modelbinder from the custom binder correctly so I can, after it did it's work, change all 'tagged' properties. Do you happen to have a good resource for that?

    – RobIII
    Nov 22 '18 at 10:30











  • I have posted a follow up question since I can't get this to work the way I'd like to.

    – RobIII
    Nov 26 '18 at 15:26














0












0








0


1






I'm using automatic model validation (see "Better Input Processing") to keep my controllers clean; so:



[HttpPost]
[ProducesResponseType(typeof(Product), 201)]
public IActionResult Post([FromBody] Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
product = _repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


becomes:



[HttpPost]
[ProducesResponseType(201)]
public ActionResult<Product> Post(Product product)
{
_repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


However, I do have a few models that have a phonenumber property. I would like to 'normalize' these before the model validation is invoked. What I mean is that I want to normalize these properties (of type string) from all kinds of input like:




  • +31 23 456 7890

  • (023) 4567890

  • 023 - 4567 890

  • ...


To E.164 notation:




  • +31234567890


So in whatever form a user enters a phonenumber, before validation is invoked I want to be sure it's always in E.164 form ('normalized'). How this normalization is done is irrelevant (I use libphonenumber if you insist). As a second, maybe less convoluted, example I can imagine a string to be always upper-/lowercased before validation is invoked.



What would be the correct, or best, way to invoke my normalization process before the validation is invoked? Would I have to write some middleware?



Also relevant: my models contain attributes so the normalizer knows which properties to normalize (and how):



class ExampleModel {

public int Id { get; set; }

public string Name { get; set; }

[NormalizedNumber(NumberFormat.E164)]
public string Phonenumber { get; set; }
}


I guess the middleware(? or whatever the solution is going to be) can then take a model, figure out if any of the properties (recursively) have the attribute and invoke the normalizer if needed.










share|improve this question
















I'm using automatic model validation (see "Better Input Processing") to keep my controllers clean; so:



[HttpPost]
[ProducesResponseType(typeof(Product), 201)]
public IActionResult Post([FromBody] Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
product = _repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


becomes:



[HttpPost]
[ProducesResponseType(201)]
public ActionResult<Product> Post(Product product)
{
_repository.AddProduct(product);
return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}


However, I do have a few models that have a phonenumber property. I would like to 'normalize' these before the model validation is invoked. What I mean is that I want to normalize these properties (of type string) from all kinds of input like:




  • +31 23 456 7890

  • (023) 4567890

  • 023 - 4567 890

  • ...


To E.164 notation:




  • +31234567890


So in whatever form a user enters a phonenumber, before validation is invoked I want to be sure it's always in E.164 form ('normalized'). How this normalization is done is irrelevant (I use libphonenumber if you insist). As a second, maybe less convoluted, example I can imagine a string to be always upper-/lowercased before validation is invoked.



What would be the correct, or best, way to invoke my normalization process before the validation is invoked? Would I have to write some middleware?



Also relevant: my models contain attributes so the normalizer knows which properties to normalize (and how):



class ExampleModel {

public int Id { get; set; }

public string Name { get; set; }

[NormalizedNumber(NumberFormat.E164)]
public string Phonenumber { get; set; }
}


I guess the middleware(? or whatever the solution is going to be) can then take a model, figure out if any of the properties (recursively) have the attribute and invoke the normalizer if needed.







c# asp.net-web-api asp.net-web-api2 asp.net-core-2.1 model-validation






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 '18 at 17:20







RobIII

















asked Nov 21 '18 at 17:13









RobIIIRobIII

6,0291858




6,0291858








  • 1





    I would think the earliest accessible point would be in a custom model binder.

    – Nkosi
    Nov 21 '18 at 17:23











  • Custom model binder would be the best way to handle this. Take a look at this dotnetcoretutorials.com/2016/12/28/…

    – Paresh
    Nov 21 '18 at 17:32











  • Thanks both! I'll have a look into a custom model binder!

    – RobIII
    Nov 21 '18 at 17:38











  • @Paresh It's looking very promising; however, my models can be quite complex and I'd like to invoke the original modelbinder binding and after that invoke my 'formatter'. The link doesn't show how to invoke the 'original' modelbinder from the custom binder correctly so I can, after it did it's work, change all 'tagged' properties. Do you happen to have a good resource for that?

    – RobIII
    Nov 22 '18 at 10:30











  • I have posted a follow up question since I can't get this to work the way I'd like to.

    – RobIII
    Nov 26 '18 at 15:26














  • 1





    I would think the earliest accessible point would be in a custom model binder.

    – Nkosi
    Nov 21 '18 at 17:23











  • Custom model binder would be the best way to handle this. Take a look at this dotnetcoretutorials.com/2016/12/28/…

    – Paresh
    Nov 21 '18 at 17:32











  • Thanks both! I'll have a look into a custom model binder!

    – RobIII
    Nov 21 '18 at 17:38











  • @Paresh It's looking very promising; however, my models can be quite complex and I'd like to invoke the original modelbinder binding and after that invoke my 'formatter'. The link doesn't show how to invoke the 'original' modelbinder from the custom binder correctly so I can, after it did it's work, change all 'tagged' properties. Do you happen to have a good resource for that?

    – RobIII
    Nov 22 '18 at 10:30











  • I have posted a follow up question since I can't get this to work the way I'd like to.

    – RobIII
    Nov 26 '18 at 15:26








1




1





I would think the earliest accessible point would be in a custom model binder.

– Nkosi
Nov 21 '18 at 17:23





I would think the earliest accessible point would be in a custom model binder.

– Nkosi
Nov 21 '18 at 17:23













Custom model binder would be the best way to handle this. Take a look at this dotnetcoretutorials.com/2016/12/28/…

– Paresh
Nov 21 '18 at 17:32





Custom model binder would be the best way to handle this. Take a look at this dotnetcoretutorials.com/2016/12/28/…

– Paresh
Nov 21 '18 at 17:32













Thanks both! I'll have a look into a custom model binder!

– RobIII
Nov 21 '18 at 17:38





Thanks both! I'll have a look into a custom model binder!

– RobIII
Nov 21 '18 at 17:38













@Paresh It's looking very promising; however, my models can be quite complex and I'd like to invoke the original modelbinder binding and after that invoke my 'formatter'. The link doesn't show how to invoke the 'original' modelbinder from the custom binder correctly so I can, after it did it's work, change all 'tagged' properties. Do you happen to have a good resource for that?

– RobIII
Nov 22 '18 at 10:30





@Paresh It's looking very promising; however, my models can be quite complex and I'd like to invoke the original modelbinder binding and after that invoke my 'formatter'. The link doesn't show how to invoke the 'original' modelbinder from the custom binder correctly so I can, after it did it's work, change all 'tagged' properties. Do you happen to have a good resource for that?

– RobIII
Nov 22 '18 at 10:30













I have posted a follow up question since I can't get this to work the way I'd like to.

– RobIII
Nov 26 '18 at 15:26





I have posted a follow up question since I can't get this to work the way I'd like to.

– RobIII
Nov 26 '18 at 15:26












1 Answer
1






active

oldest

votes


















0














Maybe you can use an approach like this using Formatter. I have used similar approach to convert all incoming dates to UTC format in my API



public class JsonModelFormatter : JsonMediaTypeFormatter
{
public override System.Threading.Tasks.Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
{

System.Threading.Tasks.Task<Object> baseTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);

if (baseTask.Result != null)
{
var properties = baseTask.Result.GetType().GetProperties();
foreach (var property in properties)
{
//Check Property attribute and decide if you need to format it
if (property.CustomAttributes.Where (x=> you condition here))
{
if (property.CanWrite && property.GetValue(baseTask.Result, null) != null)
{
var propValue = ((string)property.GetValue(baseTask.Result, null));
//Update propValue here
property.SetValue(baseTask.Result, newPropValue);
}
}
}

}
return baseTask;
}

public override bool CanReadType(Type type)
{
return true;
}
}





share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53417355%2fmodel-normalization-before-model-validation-in-asp-net-core-2-0%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    0














    Maybe you can use an approach like this using Formatter. I have used similar approach to convert all incoming dates to UTC format in my API



    public class JsonModelFormatter : JsonMediaTypeFormatter
    {
    public override System.Threading.Tasks.Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
    {

    System.Threading.Tasks.Task<Object> baseTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);

    if (baseTask.Result != null)
    {
    var properties = baseTask.Result.GetType().GetProperties();
    foreach (var property in properties)
    {
    //Check Property attribute and decide if you need to format it
    if (property.CustomAttributes.Where (x=> you condition here))
    {
    if (property.CanWrite && property.GetValue(baseTask.Result, null) != null)
    {
    var propValue = ((string)property.GetValue(baseTask.Result, null));
    //Update propValue here
    property.SetValue(baseTask.Result, newPropValue);
    }
    }
    }

    }
    return baseTask;
    }

    public override bool CanReadType(Type type)
    {
    return true;
    }
    }





    share|improve this answer




























      0














      Maybe you can use an approach like this using Formatter. I have used similar approach to convert all incoming dates to UTC format in my API



      public class JsonModelFormatter : JsonMediaTypeFormatter
      {
      public override System.Threading.Tasks.Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
      {

      System.Threading.Tasks.Task<Object> baseTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);

      if (baseTask.Result != null)
      {
      var properties = baseTask.Result.GetType().GetProperties();
      foreach (var property in properties)
      {
      //Check Property attribute and decide if you need to format it
      if (property.CustomAttributes.Where (x=> you condition here))
      {
      if (property.CanWrite && property.GetValue(baseTask.Result, null) != null)
      {
      var propValue = ((string)property.GetValue(baseTask.Result, null));
      //Update propValue here
      property.SetValue(baseTask.Result, newPropValue);
      }
      }
      }

      }
      return baseTask;
      }

      public override bool CanReadType(Type type)
      {
      return true;
      }
      }





      share|improve this answer


























        0












        0








        0







        Maybe you can use an approach like this using Formatter. I have used similar approach to convert all incoming dates to UTC format in my API



        public class JsonModelFormatter : JsonMediaTypeFormatter
        {
        public override System.Threading.Tasks.Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
        {

        System.Threading.Tasks.Task<Object> baseTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);

        if (baseTask.Result != null)
        {
        var properties = baseTask.Result.GetType().GetProperties();
        foreach (var property in properties)
        {
        //Check Property attribute and decide if you need to format it
        if (property.CustomAttributes.Where (x=> you condition here))
        {
        if (property.CanWrite && property.GetValue(baseTask.Result, null) != null)
        {
        var propValue = ((string)property.GetValue(baseTask.Result, null));
        //Update propValue here
        property.SetValue(baseTask.Result, newPropValue);
        }
        }
        }

        }
        return baseTask;
        }

        public override bool CanReadType(Type type)
        {
        return true;
        }
        }





        share|improve this answer













        Maybe you can use an approach like this using Formatter. I have used similar approach to convert all incoming dates to UTC format in my API



        public class JsonModelFormatter : JsonMediaTypeFormatter
        {
        public override System.Threading.Tasks.Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
        {

        System.Threading.Tasks.Task<Object> baseTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);

        if (baseTask.Result != null)
        {
        var properties = baseTask.Result.GetType().GetProperties();
        foreach (var property in properties)
        {
        //Check Property attribute and decide if you need to format it
        if (property.CustomAttributes.Where (x=> you condition here))
        {
        if (property.CanWrite && property.GetValue(baseTask.Result, null) != null)
        {
        var propValue = ((string)property.GetValue(baseTask.Result, null));
        //Update propValue here
        property.SetValue(baseTask.Result, newPropValue);
        }
        }
        }

        }
        return baseTask;
        }

        public override bool CanReadType(Type type)
        {
        return true;
        }
        }






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 26 '18 at 22:47









        PareshParesh

        6201615




        6201615






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53417355%2fmodel-normalization-before-model-validation-in-asp-net-core-2-0%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            If I really need a card on my start hand, how many mulligans make sense? [duplicate]

            Alcedinidae

            Can an atomic nucleus contain both particles and antiparticles? [duplicate]