Model normalization before model validation in Asp.Net Core 2.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.
c# asp.net-web-api asp.net-web-api2 asp.net-core-2.1 model-validation
add a comment |
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
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
add a comment |
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
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
c# asp.net-web-api asp.net-web-api2 asp.net-core-2.1 model-validation
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
add a comment |
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
add a comment |
1 Answer
1
active
oldest
votes
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;
}
}
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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;
}
}
add a comment |
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;
}
}
add a comment |
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;
}
}
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;
}
}
answered Nov 26 '18 at 22:47
PareshParesh
6201615
6201615
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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