I have stumbled up on some thing today. The below are my sample classes.
public class Employee
{
public string Name{get;set;}
Public Department Dept {get;set;}
public IList<Roles> Roles;
}
public Department{
public string Name{get;set;}
}
public Role {
public string Name{get;set;}
}
sampleApiController : ApiContrller{
public IEnumerable<string> Get(){
return new List<string>{"Pavan", "Josyula"};
}
public Employee GetEmp(int id){
Employee e = new Employee();
e.Dept = "IT";
e.Name="Pav";
IList<Roles> r = new IListRoles();
r.Add(new Role{Name="Integrator"});
e.Roles = r;
return e;
}
}
Now when I call this GetEmp Method from my broswer it is always giving me response in JSON format no matter what my content type in AcceptHeaders. But when I call my Get method, it returns collection of strings in XML format also it acts based on accept verb in request header. Can some body tell me the reason for this default JSON behaviour for custom types.
This is because XmlSerializer can't serialize IList<T>. Please read below answer for more details:
Differences in content negotiation between collections and single values in MVC 4
Related
I would like to bind an object in a controller through the body of a HTTP Post.
It works like this
public class MyModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException("No context found");
string modelName = bindingContext.ModelName;
if (String.IsNullOrEmpty(modelName)) {
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
string value = bindingContext.ValueProvider.GetValue(modelName).FirstValue;
...
The modelName is viewModel (honestly, I don't know why, but it works...)
My controller looks like this
[HttpPost]
[Route("my/route")]
public IActionResult CalcAc([ModelBinder(BinderType = typeof(MyModelBinder))]IViewModel viewModel)
{
....
i.e. it works, when I make this HTTP-Post request
url/my/route?viewModel=URLparsedJSON
I would like however to pass it through the body of the request, i.e.
public IActionResult Calc([FromBody][ModelBinder(BinderType = typeof(MyModelBinder))]IViewModel viewModel)
In my Modelbinder then, the modelName is "" and the ValueProvider yields null... What am I doing wrong?
UPDATE
Example; Assume you have an interface IGeometry and many implementations of different 2D shapes, like Circle: IGeometry or Rectangle: IGeometry or Polygon: IGeometry. IGeometry itself has the method decimal getArea(). Now, my URL shall calculate the area for any shape that implements IGeometry, that would look like this
[HttpPost]
[Route("geometry/calcArea")]
public IActionResult CalcArea([FromBody]IGeometry geometricObject)
{
return Ok(geometricObject.getArea());
// or for sake of completness
// return Ok(service.getArea(geometricObject));
}
the problem is, you cannot bind to an interface, that yields an error, you need a class! That's where the custom model binder is used. Assume your IGeometryalso has the following property string Type {get; set;}
the in the custom model binding you would simply search for that Type in the passed json and bind it to the correct implementation. Something like
if (bodyContent is Rectangle) // that doesn't work ofc, but you get the point
var boundObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Rectangle>(jsonString);
ASP.Net EF
In ASP.Net EF the custom model binding looks like this
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
here you get the body of the HTTPPost request like this
string json = actionContext.Request.Content.ReadAsStringAsync().Result;
in ASP.Net Core you don't have the actionContext, only the bindingContext where I can't find the body of the HTTP Post.
UPDATE 2
Ok, I found the body, see accepted answer. Now inside the controller method I really have an object from type IGeometry (an interface) that is instantiated inside the custom model binder! My controller method looks like this:
[HttpPost]
[Route("geometry/calcArea")]
public IActionResult CalcArea([FromBody]IGeometry geometricObject)
{
return Ok(service.getArea(geometricObject));
}
And my injected service like this
public decimal getArea(IGeometry viewModel)
{
return viewModel.calcArea();
}
IGeometry on the other hand looks like this
public interface IGeometry
{
string Type { get; set; } // I use this to correctly bind to each implementation
decimal calcArea();
...
Each class then simply calculates the area accordingly, so
public class Rectangle : IGeometry
{
public string Type {get; set; }
public decimal b0 { get; set; }
public decimal h0 { get; set; }
public decimal calcArea()
{
return b0 * h0;
}
or
public class Circle : IGeometry
{
public string Type {get; set; }
public decimal radius { get; set; }
public decimal calcArea()
{
return radius*radius*Math.Pi;
}
I found a solution. The body of a HTTP Post request using ASP.NET Core can be obtained in a custom model binder using this lines of code
string json;
using (var reader = new StreamReader(bindingContext.ActionContext.HttpContext.Request.Body, Encoding.UTF8))
json = reader.ReadToEnd();
I found the solution after looking at older EF projects. There the body is inside the ActionContext which is passed separately as an argument in the BindModel method. I found that the same ActionContext is part of the ModelBindingContext in ASP.Net Core, where you get an IO.Stream instead of a string (easy to convert :-))
I want to log the each action method parameter name and its
corresponding values in the database as key value pair. As part of
this, I am using OnActionExecuting ActionFilterAttribute, since it
will be the right place (OnActionExecuting method will get invoke for
all controller action methods call) to get Action Executing context.
I am getting the value for .Net types (string, int, bool). But I am
unable to get the value of the User defined types (custom types).
(ex: Login model). My model might have some other nested user
defined types as well.
I was trying to get the values of the user defined types but I am
getting the only class name as string. I hope we can do in
reflection.
Could you please anyone assist to resolve the issue. since I am new
to reflection. It will helpful to me. Thanks in Advance.
I need to get the name and value of these types in OnActionExecuting.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ActionParameter = new SerializableDictionary<string,string>();
if(filterContext.ActionParameter != null)
{
foreach(var paramter in filterContext.ActionParameter)
{
//able to get returnUrl value
//unable to get model values
ActionParameter.Add(paramter.Key, paramter.Value);
}
}
}
public ActionResult Login(LoginModel model, string returnUrl)
{
return View(model);
}
User defined type
public class LoginModel
{
public string UserName {get;set;}
public string Password {get;set;}
//User defined type
public UserRequestBase Request {get;set;}
}
//User defined type
public class UserRequestBase
{
public string ApplicationName {get;set;}
}
I am able to get the value of the returnUrl (login method param) in OnActionExecuting but not for model (login method param). I am able to see the values, but don't know how to access it, I used typeof even though I am unable to get it, but I need generic because i have 20 methods in controller so I could not only for LoginModel.
This answer isn't exactly what you want - based on your question - but I think it will work better for what want to accomplish. Quick aside...
Playing around with reflection and nested classes in this instance, lead to some SO (a propos?) errors for me...
So, a better path, maybe? Rather than trying to get/cast the property names, values (types?) from 'context.ActionParameters,` I found it was much easier to let a Json serialization do the work for me. You can then persist the Json object, then deserialize... pretty easy.
Anyway, here's the code:
using Newtonsoft.Json; // <-- or some other serialization entity
//...
public class LogActions : ActionFilterAttribute, IActionFilter
{
// Using the example -- LoginModel, UserRequestBase objects and Login controller...
void IActionFilter.OnActionExecuting(ActionExecutingContext context)
{
var param = (Dictionary<String, Object>)context.ActionParameters;
foreach (var item in param.Values)
{
string itemName = item.GetType().Name.ToString();
string itemToJson = JsonConvert.SerializeObject(item);
// Save JsonObject along with whatever other values you need (route, etc)
}
}
}
Then when you retrieve the Json object from the database you just have to deserialize / cast it.
LoginModel model = (LoginModel)JsonConvert.DeserializeObject(itemToJson, typeof(LoginModel));
From example:
public class LoginModel
{
public string UserName {get;set;}
public string Password {get;set;}
//User defined type
public UserRequestBase Request {get;set;}
}
//User defined type
public class UserRequestBase
{
public string ApplicationName {get;set;}
}
Controller used in example:
public ActionResult Login(LoginModel model, string returnUrl)
{
return View(model);
}
Hope this helps. If there are further issues with this please let me know and I will try to help.
Basically I'm trying to create a method in my webapi controller:
The method looks like this(the method body is relevant):
[HttpPost]
public HttpResponseMessage CpaLead([FromBody]CpaLeadVM model)
{
Here's the class declaration of the object being passed:
public class CpaLeadVM
{
public string UserIp = "";
public string UserCountry = "";
public double Earn = 0.0;
public string SurveyType = "";
}
The thing is; when I send a post request to the method, the model is always null.
The post request has the following data:
UserIp=hello
Earn=44.4
UserCountry=denmark
SurveyType=free
Shouldn't it be able to bind to the model or am I missing something here?
The problem is the "properties" you are trying to bind to are fields and not actual properties. The model binders and formatters in Web Api doesn't look at fields. If you change your model to:
public class CpaLeadVM
{
public CpaLeadVm()
{
UserIp = "";
UserCountry = "";
Earn = 0.0;
SurveyType = "";
}
public string UserIp {get;set;}
public string UserCountry {get;set;}
public double Earn {get;set;}
public string SurveyType {get;set;}
}
Your binding will work. As a side note, the [FromBody] attribute on your action is redundant since non-primitive values are bound from the request body by default.
As you may know, you can only get a single value from the body, which must be sent as "=value". See this article for more info
http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/
I'm not sure, but I think you could create your own model binder, which parses the body into your class. Another approach is to use JSON. Read more about that here
ASP.NET MVC 4 Post FromBody Not Binding From JSON
I have a WCF Service like following:
public class Service1 : IService1
{
public string GetData(Person person)
{
if (person != null)
{
return "OK";
}
return "Not OK!";
}
Here is my Person class:
[DataContract]
public class Person
{
[DataMember]
public int Age { get; set; }
[DataMember]
public string Name { get; set; }
}
And I'm calling service like that:
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>(new BindingParameterCollection());
factory.Open();
EndpointAddress address = new EndpointAddress(url);
IRequestChannel irc = factory.CreateChannel(address);
using (irc as IDisposable)
{
irc.Open();
string soapMessage = "<GetData><person><Age>24</Age><Name>John</Name></person></GetData>";
XmlReader reader = XmlReader.Create(new StringReader(soapMessage));
Message m = Message.CreateMessage(MessageVersion.Soap11,"http://tempuri.org/IService1/GetData", reader);
Message ret = irc.Request(m);
reader.Close();
return ret.ToString();
}
When I try to send complex type like Person as a parameter to GetData method, person object is coming null. But I have no problem when I send known type like integer, string etc. as a parameter.
How can I manage to send complex type as a parameter to the service method?
I ran into a similar situation, and we ended up changing the interface of the service to be the equivalent of:
public string GetData(string person)
And we did our own object serialization before calling the web service. Immediately within the web service method we would deserialize it, and proceed as normal.
I'm writing a middle-tier WCF service that needs to fetch some data from a back-end .asmx service then serve it up to the front-end as a JSON-encoded string.
I do this by adding a reference to the back-end service to my project, fetching data using that reference, then using a DataContractJsonSerializer to re-serialize the data, like this:
using (BackendService.BackendServicesSoapClient c = new BackendService.BackendServicesSoapClient())
{
// Get data from back-end
BackendService.SomeData data = c.GetData();
using (MemoryStream msData = new MemoryStream())
{
// Serialise data to a JSON-encoded string
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(BackendService.SomeData));
serializer.WriteObject(msData, data);
jsonEncodedData = System.Text.Encoding.UTF8.GetString(msData.GetBuffer(), 0, Convert.ToInt16(msData.Length));
}
}
Now, on the whole this works. However, the field names in the JSON-encoded string don't match those in the oringal SomeData class - they all have "Field" appended.
e.g.
contents of BackendService.Somedata:
Name : Joe
Age : 33
contents of JSON-encoded string
nameField : Joe
ageField : 33
It seems to be something to do with the proxy code that's auto-generated when I add the service reference to my project. If I look in the generated reference.cs I see definitions like this for the data class:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.225")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://example.com/Data/BackEnd/2010/11/")]
public partial class SomeData : object, System.ComponentModel.INotifyPropertyChanged {
private string nameField;
private string locationNameField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public string Name {
get {
return this.nameField;
}
set {
this.nameField = value;
this.RaisePropertyChanged("Name");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public string Age {
get {
return this.ageField;
}
set {
this.ageField = value;
this.RaisePropertyChanged("Age");
}
}
}
as compared to the class defined in the back-end service source as:
public class SomeData
{
public string Name { get; set; }
public string Age { get; set; }
}
Is there any way that I can have the field names on the front-end and back-end match?
The problem is that you are using the XML Serializer when generating the proxy and then trying to re-serialize with the DataContract(Json)Serializer. These use different attributes. Your class is not annotated with the DataContract attribute but rather with the Serializable attribute
For this to work you either need the DataContract attribute on the proxy class or you need to remove the Serializable attribute. Unfortunately off the top of my head I'm not sure how you can get the generated code to do either of these for you as you are using the XmlSerialier (because the downstream service is an .ASMX service).
For your own sanity its probably a good idea to create your own class that you control to create the JSON you require, map the data from the ASMX service into this class and then serialize to JSON using your class. This has the added benefit that if the asmx service changed for some reason it wouldn;t affect the JSON unless you wanted it to