At the moment I have a form where users create a new object. This is then passed to the controller as JSON.
How can I create a new object from this JSON to insert into the DB that I have, without doing
var x = new CustomObject {
ExampleField = JSONProperty,
ExampleField2 = JSONProperty2
};
repo.Create(x);
In general, you need something like this:
[HttPost]
public ActionResult CreateCustomer(string json)
{
var customer = JsonConvert.DeserializeObject<Customer>(json);
repo.Create(customer);
return View(customer);
}
An action method that takes as a parameter your json.
Use the JsonConvert.DeserializeObject method (I have supposed that you use
the Newtonsoft.Json library, the most used JSON framework for .NET.
The customer hew is an hypothetical object. You can follow the same approach for any custom object.
Last but not least this method return a View. This is optional, you can define another return type and return whatever you want.
Related
I just converted a project to .net core and one of the changes is that I can now only have 1 [FromBody] attribute passed to a controller action. Previously I was passing in a JSON object into the body like so:
{
property1: 1,
property2: 2
}
and could receive those 2 properties in the controller like so:
public doSomething([FromBody]int property1, [FromBody]int property2)
now I have to create a new class for the parameters that get passed in through the body:
public class DoSomethingParams {
public int property1;
public int property2;
}
public doSomething([FromBody]DoSomethingParams bodyParams)
This creates a whole lot more code, especially when I have to pass in 10 or more parameters for a call, and it feels very inefficient to me.
In Javascript there are destructuring operations that I could use one aforementioned object like this:
let property1 = {property1};. Is there any similar alternative in C#? I read about tuple deconstructing in C# v7 but I'm not quite sure that it helps me here (eg. adding [FromBody](int property1, int property2) as an argument doesn't work. (Identifier expected error))
Is there a more efficient way to deconstruct the JSON object I'm passing in besides creating a class skeleton for it? Thanks for the help!
You need to create a custom model provider to achieve that. check this package it does just that: https://github.com/m6t/M6T.Core.TupleModelBinder
You just need to add the custom binder to your binder providers at the first position:
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new TupleModelBinderProvider());
})
and then you will be able to use the Tuple deconstruct:
public doSomething((int property1, int property2, string other) bodyParams) {
// bodyParams.property1
// bodyParams.property2
// bodyParams.other
}
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>
{}
My application features a query builder which allows users to create a SQL query with a varying select clause. I need the results to be returned in JSON by WebAPI.
There are a huge number of fields that could feature in the select.
The only approach I have found is to use:
var results = db.Database.SqlQuery<SomeObjectWhichDefinesAllPossibleFields>(sql);
return Ok(results);
Which relies on me maintaining a SomeObjectWhichDefinesAllPossibleFields class which does as it name suggests. I would like a more dynamic solution as more fields may be added in the future.
Is there another way to get WebAPI to serialize a SqlQuery with a varying select clause
Web Api Repository, can use this:
public IList<Models.SalesTargetDetail> getSalesTarget(int SpId){
System.Text.StringBuilder sbSql = new System.Text.StringBuilder();
sbSql.Append(#"select Cast(STD.Qty as varchar(10)) as Qty from tblSalesTarget ST inner join tblSalesTargetDetail STD on ST.Id=STD.SalesTargetId where 1=1");
sbSql.Append(" and STD.SalesTargetId=" + SpId);
var data = this.UnitOfWork.Context.Database.SqlQuery<Models.SalesTargetDetail>(sbSql.ToString()).ToList<Models.SalesTargetDetail>();
return data.ToList<Models.SalesTargetDetail>();
}
And the Model is:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
namespace NNG.NCPML.Api.Models{
//[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class SalesTargetDetail{
public int SalesTargetDetailId{get; set;}
public string SalesTargetId{get; set;}
public int ItemInfoId{get; set;}
public string ItemName{get; set;}
}
}
In WebAPI you can suppress fields that are null from being serialized. So you could start out with the dreaded SomeObjectWhichDefinesAllPossibleFields class, initialize all properties to null, and only set the properties that are requested in the select clause. You still have to maintain the large class, but if you only select/request two fields, only two fields will be serialized and returned in the JSON result.
You could also use ExpandoObject, cast as an IDictionary, and dynamically add properties depending on the fields requested. Here is an example:
Example API Url: http://localhost/api/select/id,title,firstName,lastName
(add using System.Dynamic;)
[Route("api/select/{fieldList}")]
[HttpGet]
public object Dynamic(string fieldList)
{
dynamic expando = new ExpandoObject();
var dictionary = expando as IDictionary<string, Object>;
string[] fields = fieldList.Split(',');
foreach (var field in fields)
{
dictionary.Add(field, "code to get the field value goes here");
}
return expando;
}
In the end I used TypeBuilder to construct a dynamic Type to pass to the SqlQuery method as per this http://www.codeproject.com/Articles/206416/Use-dynamic-type-in-Entity-Framework-SqlQuery
This dynamic type was serialized to JSON nicely by web-api.
The only limitation is that it is necessary to know the dynamic field types (whether string, int etc) however in my application that was possible.
I have a controller that adds or edits an entity which is sent to the controller as a JSON object.
Here is the controller:
public JsonResult SaveOrUpdateUser(User user)
{
Collection.Save(user);
//Collection is MongoDB.Driver.MongoCollection<User>
return Json(user);
}
If user is a new user, everything works perfectly. The mongo driver sets the ID like it should, and returns the new user with their ID set. However if the user is an existing user, and I'm updating something, mongo thinks it's a new user because MVC doesn't seem to be able to automatically set the ObjectId from the JSON I'm sending.
The JSON for an existing user looks like this (edited for brevity)
{
"Id": "5230d5c5eae61521585eda99",
"Username": "someone#domain.com",
"Password": "newpassword"
}
And the C# User class:
public class User
{
public ObjectId Id {get; set;}
public string Username {get; set;}
public string Password {get; set;}
}
However, user, when parsed by MVC, looks like this:
ObjectId Id = ObjectId(000000000000000000000000);
string Username = "someone#domain.com";
string Password = "newpassword";
As such, as far as the mongo driver is concerned, this is a new entity. So, is there a way to control how MVC parses the JSON into a User so that the ObjectId is properly set if it exists?
I've never had much luck using the default Model Binder for deserializing JSON. Try using Newtonsoft's library, I believe it is included with MVC 4 projects in Visual Studio 2012. If not you should be able to find it in the Nuget Package Manager.
You can either create a custom Model Binder for User and deserialize the object there or you could make the user parameter to you SaveOrUpdateUser a string instead and then do the deserialization there.
In either case the code is simply:
Newtonsoft.Json.JsonConvert.DeserializeObject<User>(jsonString)
Newtonsoft's JSON deserialization is pretty sophisticated and I was very impressed with how it just worked, even on deserializing some JSON into a custom object that had a Dictionary of another custom type as one of its properties. That being said, if it is not able to figure it out with the default configuration you can help it out by creating a custom JsonConverter. I have not done this myself so I will have to test out an example before I can provide guidance on that.
After some more searching, I found out how to handle this. I wanted to avoid manually deserializing the JSON object for each request (even if I had a generic factory method), and much preferred the object to be automatically bound by the controller.
The answer is to create a custom model binder that overrides BindModel() in System.Web.Mvc. Since this is an issue for any entity I have stored in Mongo, and not just the User class, my custom model binder needed to be generic so that I wouldn't have to create a largely redundant model binder for each entity I have.
Here is what this class looks like:
public class MongoEntityDataBinder<T> : DefaultModelBinder where T: IDataEntity
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string body;
using(var stream = controllerContext.HttpContext.Request.InputStream)
{
stream.Seek(0, SeekOrigin.Begin);
using(var reader = new StreamReader(stream))
{
body = reader.ReadToEnd();
}
}
if(string.IsNullOrEmpty(body)) return null;
try
{
return JsonConvert.DeserializeObject<T>(body);
}
catch(Exception)
{
return null;
}
}
}
Next, this custom model binder needs to be registered, so in Global.asax:
ModelBinders.Binders.Add(typeof(MongoEntityDataBinder<IDataEntity>), new MongoEntityDataBinder<IDataEntity>());
And finally, my controller now looks like this:
public JsonResult SaveOrEditUser([ModelBinder(typeof(MongoEntityDataBinder<User>))] User user)
{
//Function body
}
One final thing to note is that Json.NET doesn't inherently know how to serialize/deserialize ObjectId either, so I also needed to override Newtonsoft.Json.JsonConverter.WriteJson() and Newtonsoft.Json.JsonConverter.ReadJson() and then apply [JsonConverter(typeof(ObjectIdConverter))] to the Id property in IDataEntity
This article was an excellent starting point: ASP.NET Custom Model Binder
I have a helper class that is passed an array of values that is then passed to a new class from my Model. How do I verify that all the values given to this class are valid? In other words, how do I use the functionality of ModelState within a non-controller class.
From the controller:
public ActionResult PassData()
{
Customer customer = new Customer();
string[] data = Monkey.RetrieveData();
bool isvalid = ModelHelper.CreateCustomer(data, out customer);
}
From the helper:
public bool CreateCustomer(string[] data)
{
Customter outCustomer = new Customer();
//put the data in the outCustomer var
//??? Check that it's valid
}
You could use the data annotations validation outside of an ASP.NET context:
public bool CreateCustomer(string[] data, out Customer customer)
{
customer = new Customer();
// put the data in the customer var
var context = new ValidationContext(customer, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
return Validator.TryValidateObject(customer, context, results, true);
}
Don't use ModelState outside of a controller. I can't see what Monkey.RetrieveData() does but in general I would not pass a) plain data from the HTTPRequest and b) untyped data like string-arrays to your backend. Let the web-framework check the incomming data and instanciate typed classes to use in the backend. Note that checking for HTML injection (XSS scripting etc.) must be done manually if you apply your data manually.
Instead use model-binders etc. and pass typed data (eg. Customer class instances) to your backend. There is an older post from Scott Gu that shows this for MVC1: http://weblogs.asp.net/scottgu/archive/2008/09/02/asp-net-mvc-preview-5-and-form-posting-scenarios.aspx
In your example let the model binding of MVC create your customer and apply the field values required (see link above how that pattern works). You then give your Customer instance to your backend where additional validation checks can be done based on your typed Customer instance (eg. manually or with data annotations).