I have a small problem with the WebApi.
Problem:
If I want to post a model using JSON, I can add as many members I want, as long as the members defined in model are present.
Question:
How can I trigger an exception, if an undefined member is present in my Json object. Is this achievable without a custom JsonConverter?
What I'm looking for is a generic solution, not a convertion for every different model.
Example:
Model:
public class Person
{
[Required]
public string Name { get; set; }
}
Api Controller:
public class PersonController : ApiController
{
public HttpResponseMessage Post(Person person)
{
if (person != null)
{
if (ModelState.IsValid)
{
//do some stuff
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
Json posts (body)
{"Name":"Joe"} --> valid
{"Name":"Joe","InvalidMember","test","Name","John"} --> also valid. In this case I want to trigger an Exception. Because if you look at it, it doesn't match my modeldefinition exactly.
One thing you could try is playing around with this setting:
config.Formatters.JsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
It should give you an invalid model state when there are extra properties that aren't recognized in the JSON.
Related
The scenario is something like this:
I have this web-api which will handle a variety of payment gateways, and I want to have the same endpoint for all of those.
So, what I have in mind is to get some json data like this:
{
"OperationId":"N0004",
"Generic_Object_That_Will_Change_According_ToThe_GateWay":
{
"Sale_id":1000,
"CodUser":"1000040",
"Email":"teste#teste.com"
}
}
Or this, for some other payment gateway
{
"OperationId":"N044444",
"Generic_Object_That_Will_Change_According_ToThe_GateWay":
{
"Token":1000,
"UserSettings":{
id: "4563345",
name: "Average Joe"
}
}
}
What I want to do is to transform this "Generic_Object_That_Will_Change_According_ToThe_GateWay" in the specific object for each payment gateway (paypal, or some other), becase each one is completely different, but I don't want that to affect the way the client will call this API - I want it to be as flexible as possible, in a way that you just have to pass whatever data in this Generic_Object_That_Will_Change_According_ToThe_GateWay, and I will then cast it to the proper object and then call another endpoint(like an aggragate microservice design) passing this newly created object.
My idea so far, was creating some class with a generic property like this
public class Payment<Gateway>
{
public int OperationId{ get; set; }
public Gateway paymentGateWay{ get; set; }
}
And this property paymentGateWay could be typed according the available payment Gateways.
And then maybe I could get this data in the API method as Object, and do the necessary casts
[Route("api/payment")]
[HttpPost]
public string Compra(Object payment) {
But, to be honest, I don't know if I'm in the right way.
I already know that I can't have a generic method in a web-api endpoint - so what would be the correct way to get this data in my endpoint considering that a part of this json data is flexible/generic and may be cast to a few different objects.
To summarize, I want to handle json data that can be deserialized to a few different known objects, but I don't want to have a different method in my API to handle each one this possible data scenarios.
if you want a generic method in webapi you have to use JObject
something like the following
public void Post([FromBody] JObject testJObject)
{
//here you have to do some additional work in order to parse and get it working for generic entity
}
in addition to this, you can use the Schema validator against any received request and use the factory pattern in order to create the correct object
here an example
var json =
" {\"OperationId\":\"N0004\",\"Generic_Object_That_Will_Change_According_ToThe_GateWay\":{\"Sale_id\":1000,\"CodUser\":\"1000040\"}}";
JsonSchema paypalschema = new JsonSchema();
paypalschema.Type = JsonSchemaType.Object;
paypalschema.Properties = new Dictionary<string, JsonSchema>
{
{"OperationId", new JsonSchema {Type = JsonSchemaType.String}},
{
"Generic_Object_That_Will_Change_According_ToThe_GateWay",
new JsonSchema {Type = JsonSchemaType.Object,Properties = new Dictionary<string, JsonSchema>
{
{"Sale_id", new JsonSchema {Type = JsonSchemaType.Integer}},
{"CodUser", new JsonSchema {Type = JsonSchemaType.String}},
}}
}
};
JObject requestObject = JObject.Parse( json);
bool valid = requestObject.IsValid(paypalschema);
if (valid)
{
//create your GatewayObject here
}
//else check another gateway object
Consider using JObject or String as your input (And then converting to JObject.) Then you can do some type or data checking before casting. Here's an example shows how they use a pre-defined 'type' value provided, but in lieu of that, you can instead look in the JObject for the 'parts' of each unique provider's payload to determine which type to use.
You can have a generic controller to implement the method and instance-controllers, which inherit of the generic controller:
// I'll rename Gateway to TGateway according to the fact, that it is a generic Type parameter.
public class Payment<TGateway>
{
public int OperationId{ get; set; }
public TGateway paymentGateWay{ get; set; }
}
GenericController:
// Don't add a RouteAttribute to this Controller.
public class GenericController<TGateway>: ApiController
{
// The implementation of the method. No RouteAttribute.
[HttpPost]
public string Compra(Payment<TGateway> payment) {...}
}
InstanceController:
// No need to override the method. RouteAttribute.
[Route("api/payment/"+typeof(AGateway).Name)]
public class AGatewayController : GenericController<AGateway>
{}
This is in OWIN & .Net 4.5.2
Using debug I'm proving this controller's method is being called by the web request.
My thing is the request body contains a JSON stringified object:
"{ 'id':'12', 'text1':'hello', 'test2':'world' }"
Which is applicably encoded as it is transferred on the line.
I've tried so many things I'm so confused now.
How do I get the decoded string so I can JSON.Parse() that or better yet get .Net to just given me an object?
In one version (long ago now) I had a defined type for this object. If I need that great, not a high challenge. But if I only have the JSON object from the string that's fine too.
public class cController : ApiController {
[HttpPut]
public string put(string id) {
var bdy = this.Request.Content;
//Console.WriteLine("PUT containers {0}", body);
return string.Empty;
}
}
In case it helps, the bdy.ContentReadStream is null. I don't know if this is good, bad, or important. Maybe Request.Content isn't the way to go but seems to me like if I'm going to read the body as a stream then it shouldn't be null.
I also tried working through System.Web.HttpContext. If that is somehow the answer I have no problem going back to that. But I couldn't find the secret sauce.
Pass the desired model as a parameter to the action and the frame work should be able to parse it provided it is valid JSON
public class cController : ApiController {
[HttpPut]
public IHttpActionResult Put(string id,[FromBody] Model body) {
if(ModelState.IsValue) {
return Ok(body.text1);
}
return BadRequest();
}
}
Where Model is defined as
public class Model {
public string id { get; set; }
public string text1 { get; set; }
public string test2 { get; set; }
}
In a POST call to a WebApi I am trying to return a Created(newobject) thing. But there is no signature for Created in ApiController that can only take the object and do the rest.
It works fine if I return something like:
return Created(newobject.blahid.ToString(), newobject);
or if I do a
return CreatedAtRoute("DefaultApi", new { controller = ControllerContext.ControllerDescriptor.ControllerName, id = newobject.blahid.ToString()}, newobject);
I want to simplify this to:
return Created(newobject);
I would need to implement a method in a BaseController
public class BaseController : ApiController
{
protected new CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var id = GetId(content);//need help here
return base.Created(id, content);
}
}
I don't want to worry about the Unique Identifier for an object being called differently in different models e.g. myobjguid, someblahguid etc. I would just want to find it out and mark it as "id".
say if my model is
public class Model_A
{
public List<Model_A> ChildModels { get; set; }
[LookForThisAttribute]//I want something like this
public Guid Model_AGuid { set; get; }
public Guid ? ParentGuid { set; get; }
public List<SomeOtherObject> OtherObjects { set; get; }
}
Is there an attribute([LookForThisAttribute]) or something I can set on all my models to specify that this is the guy to be assumed as THE unique identifier if I ever look for it.
Just like the [Key] attribute in Entity Framework. No matter what you call it, Entity Framework know its going to be the primary key.
So the GetId(T content) method can take the object and return the value of the property that has a [LookForThisAttribute] set?
I ended up writing my own Attribute and then looking up for it in the BaseController.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class UniqueIdAttribute: Attribute
{
}
And in the BaseController Created method:
protected CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var props =typeof(T).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UniqueIdAttribute)));
if (props.Count() == 0)
{
//log this
return base.Created(Request.RequestUri.ToString(), content);
}
var id = props.FirstOrDefault().GetValue(content).ToString();
return base.Created(new Uri(Request.RequestUri + id), content);
}
Mark Gravell's post here helped me with getting the value of the property that has my custom attribute:
How to get a list of properties with a given attribute?
Along with a corresponding unit test for the controllers works fine for me.
Now I can just call Created(anyobject); from all ApiControllers without bothering about the different names people put for their IDs as long as they decorate it with my custom attribute.
I am currently trying to write a Web API application where one of the parameters I'd like to validate is a query parameter (that is, I wish to pass it in in the form /route?offset=0&limit=100):
[HttpGet]
public async Task<HttpResponseMessage> GetItems(
int offset = 0,
int limit = 100)
{
if (!ModelState.IsValid)
{
// Handle error
}
// Handle request
}
In particular, I want to ensure that "offset" is greater than 0, since a negative number will cause the database to throw an exception.
I went straight for the logical approach of attaching a ValidationAttribute to it:
[HttpGet]
public async Task<HttpResponseMessage> GetItems(
[Range(0, int.MaxValue)] int offset = 0,
int limit = 100)
{
if (!ModelState.IsValid)
{
// Handle error
}
// Handle request
}
This does not cause any errors at all.
After a lot of painful debugging into ASP.NET, it appears to me that this may be simply impossible. In particular, because the offset parameter is a method parameter rather than a field, the ModelMetadata is created using GetMetadataForType rather than GetMetadataForProperty, which means that the PropertyName will be null. In turn, this means that AssociatedValidatorProvider calls GetValidatorsForType, which uses an empty list of attributes even though the parameter had attributes on it.
I don't even see a way to write a custom ModelValidatorProvider in such a way as to get at that information, because the information that this was a function parameter seems to have been lost long ago. One way to do that might be to derive from the ModelMetadata class and use a custom ModelMetadataProvider as well but there's basically no documentation for any of this code so it would be a crapshoot that it actually works correctly, and I'd have to duplicate all of the DataAnnotationsModelValidatorProvider logic.
Can someone prove me wrong? Can someone show me how to get validation to work on a parameter, similar to how the BindAttribute works in MVC? Or is there an alternative way to bind query parameters that will allow the validation to work correctly?
You can create a view request model class with those 2 properties and apply your validation attributes on the properties.
public class Req
{
[Range(1, Int32.MaxValue, ErrorMessage = "Enter number greater than 1 ")]
public int Offset { set; get; }
public int Limit { set; get; }
}
And in your method, use this as the parameter
public HttpResponseMessage Post(Req model)
{
if (!ModelState.IsValid)
{
// to do :return something. May be the validation errors?
var errors = new List<string>();
foreach (var modelStateVal in ModelState.Values.Select(d => d.Errors))
{
errors.AddRange(modelStateVal.Select(error => error.ErrorMessage));
}
return Request.CreateResponse(HttpStatusCode.OK, new { Status = "Error",
Errors = errors });
}
// Model validation passed. Use model.Offset and Model.Limit as needed
return Request.CreateResponse(HttpStatusCode.OK);
}
When a request comes, the default model binder will map the request params(limit and offset, assuming they are part of the request) to an object of Req class and you will be able to call ModelState.IsValid method.
For .Net 5.0 and validating query parameters:
using System.ComponentModel.DataAnnotations;
namespace XXApi.Models
{
public class LoginModel
{
[Required]
public string username { get; set; }
public string password { get; set; }
}
}
namespace XXApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
[HttpGet]
public ActionResult login([FromQuery] LoginModel model)
{
//.Net automatically validates model from the URL string
//and gets here after validation succeeded
}
}
}
if (Offset < 1)
ModelState.AddModelError(string.Empty, "Enter number greater than 1");
if (ModelState.IsValid)
{
}
In my ASP.NET MVC app, I have an interface which acts as the template for several different view models:
public interface IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
Validator Validate();
}
So, my view models are defined like this:
public interface MyViewModel1 : IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
// Properties specific to MyViewModel1 here
public Validator Validate()
{
// Do ViewModel-specific validation here
}
}
public interface MyViewModel2 : IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
// Properties specific to MyViewModel2 here
public Validator Validate()
{
// Do ViewModel-specific validation here
}
}
Then I currently have a separate controller action to do the validation for each different type, using model binding:
[HttpPost]
public ActionResult MyViewModel1Validator(MyViewModel1 model)
{
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
[HttpPost]
public ActionResult MyViewModel2Validator(MyViewModel2 model)
{
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
This works fineābut if I had 30 different view model types then there would have to be 30 separate controller actions, all with identical code apart from the method signature, which seems like bad practice.
My question is, how can I consolidate these validation actions so that I can pass any kind of view model in and call it's Validate() method, without caring about which type it is?
At first I tried using the interface itself as the action parameter:
public ActionResult MyViewModelValidator(IMyViewModel model)...
But this didn't work: I get a Cannot create an instance of an interface exception. I thought an instance of the model would be passed into the controller action, but apparently this is not the case.
I'm sure I'm missing something simple. Or perhaps I've just approached this all wrong. Can anyone help me out?
The reason why you cannot use the interface is because of serialization. When a request comes in it only contains string key/value pairs that represent the object:
"Client1.Name" = "John"
"Client2.Name" = "Susan"
When the action method gets invoked the MVC runtime tries to create values to populate the method's parameters (via a process called model binding). It uses the type of the parameter to infer how to create it. As you've noticed, the parameter cannot be an interface or any other abstract type because the runtime cannot create an instance of it. It needs a concrete type.
If you want to remove repeated code you could write a helper:
[HttpPost]
public ActionResult MyViewModel1Validator(MyViewModel1 model)
{
return ValidateHelper(model);
}
[HttpPost]
public ActionResult MyViewModel2Validator(MyViewModel2 model)
{
return ValidateHelper(model);
}
private ActionResult ValidateHelper(IMyViewModel model) {
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
However, you will still need a different action method for each model type. Perhaps there are other ways you could refactor your code. It seems the only difference in your model classes is the validataion behavior. You could find a different way to encode the validation type in your model class.
You could check this: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx.
This is caused because DefaultModelBinder has no way of knowing what concrete type of IMyViewModel should create.
For solution that, you create custom model binder and indicate how to create and bind an instance of interface.
I think I would create an abstract base class that implemented IMyViewModel. I would make Validate an abstract method and require overriding in my concrete view models that inherited from MyAbstractViewModel. Inside your controller, you can work with the IMyViewModel interface if you want, but binding and serialization really needs a concrete class to bind. My $.02.
You could consider using a base class instead of the interface.