Web API Complex and primitive parameters in one action - c#

I have an ASP.NET Web API action:
[HttpPost]
public void test(myCustomObj Entity)
{
}
And the JSON data is:
{
"ID": "1",
"Name": "ilhan",
"surname": "aksu"
}
So far my code works well. However, when I add a new primitive parameter:
[HttpPost]
public void test(myCustomObj Entity, [FromBody] string strdata)
{
}
and when I post the following JSON:
{
"Entity": {
"ID": "1",
"Name": "ilhan",
"surname": "aksu"
},
"strdata": "testdata"
}
the server returns 500 Internal Server Error.
How can I format my JSON data or change my action method to fix this problem?

If you're posting json, you could accept a string parameter:
[HttpPost]
public void Test(string jsonString)
{
}
And maybe a serializer helper to avoid polluting the code:
public static class JsonSerializer
{
public static string Serialize<T>(T t) where T : class
{
return JsonConvert.SerializeObject(t);
}
public static T Deserialize<T>(string s) where T : class
{
return (T)JsonConvert.DeserializeObject(s, typeof(T));
}
}
Then in your method you can materialize the json payload:
[HttpPost]
public void Test(string jsonString)
{
var o = JsonSerializer.DeserializeObject(jsonString, typeof(MyObject));
// ...
}
And if you're returning json, it could be as follows:
[HttpGet]
public JsonResult GetTest()
{
var i = YourService.GetSomethingById(1);
iSerialized = JsonSerializer.Serialize(i);
return new JsonResult
{
ContentEncoding = System.Text.Encoding.UTF8,
ContentType = "application/json",
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = iSerialized
};
}

As always write a view model
public class MyViewModel : myCustomObj
{
public string Strdata { get; set; }
}
Now have your controller action take this view model as argument:
[HttpPost]
public void test(MyViewModel model)
{
...
}
and now you could hit your action with the following JSON payload:
{"ID":"1","Name":"ilhan","surname":"aksu","strdata":"testdata"}
and everything's gonna get properly bound.
Alternatively your view model might look like this:
public class MyViewModel
{
public myCustomObj Entity { get; set; }
public string Strdata { get; set; }
}
and now you could hit your action with this payload:
{"Entity":{"ID":"1","Name":"ilhan","surname":"aksu"},"strdata":"testdata"}
So it's basically up to you to decide how your view model will look like depending on the JSON payload that you would like to use to call your controller action. So, never think of having more than 1 action argument in a controller action. Always think in terms of how your view model will look like.
Yes, controller actions always take view models as arguments and always return view models. That's the correct design. In ASP.NET MVC and in ASP.NET Web API.

Related

Using model to save a list, works first time but second time it saves 62 count item list as 1 list item [duplicate]

I want to know, there is any technique so we can pass Model as a parameter in RedirectToAction
For Example:
public class Student{
public int Id{get;set;}
public string Name{get;set;}
}
Controller
public class StudentController : Controller
{
public ActionResult FillStudent()
{
return View();
}
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return RedirectToAction("GetStudent","Student",new{student=student1});
}
public ActionResult GetStudent(Student student)
{
return View();
}
}
My Question - Can I pass student model in RedirectToAction?
Using TempData
Represents a set of data that persists only from one request to the
next
[HttpPost]
public ActionResult FillStudent(Student student1)
{
TempData["student"]= new Student();
return RedirectToAction("GetStudent","Student");
}
[HttpGet]
public ActionResult GetStudent(Student passedStd)
{
Student std=(Student)TempData["student"];
return View();
}
Alternative way
Pass the data using Query string
return RedirectToAction("GetStudent","Student", new {Name="John", Class="clsz"});
This will generate a GET Request like Student/GetStudent?Name=John & Class=clsz
Ensure the method you want to redirect to is decorated with [HttpGet] as
the above RedirectToAction will issue GET Request with http status
code 302 Found (common way of performing url redirect)
Just call the action no need for redirect to action or the new keyword for model.
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return GetStudent(student1); //this will also work
}
public ActionResult GetStudent(Student student)
{
return View(student);
}
Yes you can pass the model that you have shown using
return RedirectToAction("GetStudent", "Student", student1 );
assuming student1 is an instance of Student
which will generate the following url (assuming your using the default routes and the value of student1 are ID=4 and Name="Amit")
.../Student/GetStudent/4?Name=Amit
Internally the RedirectToAction() method builds a RouteValueDictionary by using the .ToString() value of each property in the model. However, binding will only work if all the properties in the model are simple properties and it fails if any properties are complex objects or collections because the method does not use recursion. If for example, Student contained a property List<string> Subjects, then that property would result in a query string value of
....&Subjects=System.Collections.Generic.List'1[System.String]
and binding would fail and that property would be null
[HttpPost]
public async Task<ActionResult> Capture(string imageData)
{
if (imageData.Length > 0)
{
var imageBytes = Convert.FromBase64String(imageData);
using (var stream = new MemoryStream(imageBytes))
{
var result = (JsonResult)await IdentifyFace(stream);
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(serializer.Serialize(result.Data));
if (faceRecon.Success) return RedirectToAction("Index", "Auth", new { param = serializer.Serialize(result.Data) });
}
}
return Json(new { success = false, responseText = "Der opstod en fejl - Intet billede, manglede data." }, JsonRequestBehavior.AllowGet);
}
// GET: Auth
[HttpGet]
public ActionResult Index(string param)
{
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(param);
return View(faceRecon);
}
[NonAction]
private ActionResult CRUD(someModel entity)
{
try
{
//you business logic here
return View(entity);
}
catch (Exception exp)
{
ModelState.AddModelError("", exp.InnerException.Message);
Response.StatusCode = 350;
return someerrohandilingactionresult(entity, actionType);
}
//Retrun appropriate message or redirect to proper action
return RedirectToAction("Index");
}
i did find something like this, helps get rid of hardcoded tempdata tags
public class AccountController : Controller
{
[HttpGet]
public ActionResult Index(IndexPresentationModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Save(SaveUpdateModel model)
{
// save the information
var presentationModel = new IndexPresentationModel();
presentationModel.Message = model.Message;
return this.RedirectToAction(c => c.Index(presentationModel));
}
}

MVC5 WebApi 2 create not getting value from body ([FromBody])

I created a simple create Web API 2 action that will get an object from the post body and then will set it to the DAL layer. However no matter what I do using postman to get the object into the method, it always stays null.
The model looks like this:
namespace WebApi.Models
{
using System;
using System.Collections.Generic;
public partial class Classes
{
public int Id { get; set; }
public string ClassName { get; set; }
public int MaxStudents { get; set; }
}
}
My controller is as follows:
[HttpPost]
public IHttpActionResult CreateClass([FromBody] Classes classObj)
{
if (classObj == null)
{
return BadRequest("missing parameters.");
}
var newClass = new Classes()
{
ClassName = classObj.ClassName,
MaxStudents = classObj.MaxStudents
};
_context.Classes.Add(newClass);
_context.SaveChanges();
var newClassUrl = Url.Content("~/") + "/api/classes/";
return Created(newClassUrl, newClass);
}
Now when I use postman I tried two options.
option 1:
URL: http://localhost:53308/api/classes/
Headers: Content-Type: applications/json
[
"classObj": {
ClassName = "test"
MaxStudents = 100
}
]
option 2:
URL: http://localhost:53308/api/classes/
Headers: Content-Type: applications/json
ClassName = "test"
MaxStudents = 100
but in both cases classObj stays empty and it returns "missing parameters.". So obviously I'am missing something here.
What am I doing wrong?
Your payloads do not match the expectation of the action.
For example
[HttpPost]
public IHttpActionResult CreateClass([FromBody] Classes classObj) {
//...
}
Would expect JSON data that looks like this
{
"ClassName": "test"
"MaxStudents": 100
}
Also given that the model posted into the action is the same type added to the store there isn't really a need to create a new instance.
[HttpPost]
public IHttpActionResult CreateClass([FromBody] Classes classObj) {
if (classObj == null) {
return BadRequest("missing parameters.");
}
_context.Classes.Add(classObj);
_context.SaveChanges();
var newClassUrl = Url.Content("~/") + "/api/classes/" + classObj.Id.ToSTring();
return Created(newClassUrl, classObj);
}

MVC ModelState.IsValid=true with a null required property

I have this Model
public class ModelVM
{
private string _rD;
[Required]
public string RD
{
get
{
return _rD;
}
set
{
_rD = RCodes.Contains(value)? value : null;
}
}
private static List<string> RCodes = new List<string>
{
"OK",
"OTHER",
"ANOTHER"
};
}
In my MVC Controller
public class MyController : Controller
{
public ActionResult Index(ModelVM modelVM, FormCollection collection)
{
if (!ModelState.IsValid)
return Json(new
{
Result = "ERROR",
Message = "Missing fields."
});
return Json("OK");
}
}
I send: { RD: "Whatever" }
And in debugging ModelState.IsValid=true. I have a similar code on a WebApi Controller and works as I expect (modelstate.valid=false)
Do you have some ideas why MVC is doing that? or what is wrong with my code?
ModelState.IsValid tells you if any model errors have been added to ModelState.
In this case it is valid because there are no client side errors in the provided data that would affect ModelState.
You said...
I sent { RD: "Whatever" }
...which would mean that the model binder will look at the data sent and match the properties with the intended type. From a model binding perspective the [Required] validation was met because when the binder looked at the route value dictionary for the required property RD, it was provided by the client in the incoming data.
If you want to manually invalidate the state you can...
public ActionResult Index(ModelVM modelVM, FormCollection collection)
{
if(ModelState.IsValid) {
if(modelVM.RD == null) {
ModelState.AddModelError("RD", "RD is invalid.");
}
}
if (!ModelState.IsValid)
return Json(new
{
Result = "ERROR",
Message = "Missing fields."
});
return Json("OK");
}

MVC4 web api model not binding when called from angularjs

I have a action method with this signature:
public List<Member> Get([FromUri]MemberSearchModel searchModel)
The model looks like this:
public class MemberSearchModel
{
public string SearchBy { get; set; }
public string SearchValue { get; set; }
}
and im calling the api like this:
dataFactory.getMembers = function () {
var memberSearchModel = {
SearchBy: 'Name',
SearchValue: 'jaredites'
};
return $http.get(urlBase, memberSearchModel);
};
the method is hit but the model comes through with null values
Ive tried not using [FromUri] but then the model itself is null
Ive tried [FromBody] and the model comes through as null as well.
Is there something Im missing here?
It works when I call it specifying the params in the url
Please try this that :
dataFactory.getMembers = function () {
var memberSearchModel = {
SearchBy: 'Name',
SearchValue: 'jaredites'
};
return $http.get(urlBase, {params: memberSearchModel});
};

In ASP.NET MVC, deserialize JSON prior to or in controller's action method

I am working on a website that will post a JSON object (using jQuery Post method) to the server side.
{
"ID" : 1,
"FullName" : {
"FirstName" : "John",
"LastName" : "Smith"
}
}
At the same time, I wrote classes on the server side for this data structure.
public class User
{
public int ID { get; set; }
public Name FullName { get; set;}
}
public class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
When I run the website with following code in my controller class, the FullName property doesn't get deserialized. What am I doing wrong?
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Submit(User user)
{
// At this point, user.FullName is NULL.
return View();
}
I resolved my problem by implementing an action filter; code sample is provided below. From the research, I learned that there is another solution, model binder, as takepara described above. But I don't really know that pros and cons of doing in either approach.
Thanks to Steve Gentile's blog post for this solution.
public class JsonFilter : ActionFilterAttribute
{
public string Parameter { get; set; }
public Type JsonDataType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.ContentType.Contains("application/json"))
{
string inputContent;
using (var sr = new StreamReader(filterContext.HttpContext.Request.InputStream))
{
inputContent = sr.ReadToEnd();
}
var result = JsonConvert.DeserializeObject(inputContent, JsonDataType);
filterContext.ActionParameters[Parameter] = result;
}
}
}
[AcceptVerbs(HttpVerbs.Post)]
[JsonFilter(Parameter="user", JsonDataType=typeof(User))]
public ActionResult Submit(User user)
{
// user object is deserialized properly prior to execution of Submit() function
return View();
}
1.create custom model binder
public class UserModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
User model;
if(controllerContext.RequestContext.HttpContext.Request.AcceptTypes.Contains("application/json"))
{
var serializer = new JavaScriptSerializer();
var form = controllerContext.RequestContext.HttpContext.Request.Form.ToString();
model = serializer.Deserialize<User>(HttpUtility.UrlDecode(form));
}
else
{
model = (User)ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
return model;
}
}
2.add model binder in application_start event
ModelBinders.Binders[typeof(User)] = new UserModelBinder();
3.use jQuery $.get/$.post in view client JavaScript code.
<% using(Html.BeginForm("JsonData","Home",new{},FormMethod.Post, new{id="jsonform"})) { %>
<% = Html.TextArea("jsonarea","",new {id="jsonarea"}) %><br />
<input type="button" id="getjson" value="Get Json" />
<input type="button" id="postjson" value="Post Json" />
<% } %>
<script type="text/javascript">
$(function() {
$('#getjson').click(function() {
$.get($('#jsonform').attr('action'), function(data) {
$('#jsonarea').val(data);
});
});
$('#postjson').click(function() {
$.post($('#jsonform').attr('action'), $('#jsonarea').val(), function(data) {
alert("posted!");
},"json");
});
});
</script>
You could try Json.NET. The documentation is pretty good and it should be able to do what you need. You'll also want to grab JsonNetResult as it returns an ActionResult that can be used in ASP.NET MVC application. It's quite easy to use.
Json.NET also works well with Date serialization. More info regarding that can be found here.
Hope this helps.
Try this;
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Submit(FormCollection collection)
{
User submittedUser = JsonConvert.DeserializeObject<User>(collection["user"]);
return View();
}
After some research, I found Takepara's solution to be the best option for replacing the default MVC JSON deserializer with Newtonsoft's Json.NET. It can also be generalized to all types in an assembly as follows:
using Newtonsoft.Json;
namespace MySite.Web
{
public class MyModelBinder : IModelBinder
{
// make a new Json serializer
protected static JsonSerializer jsonSerializer = null;
static MyModelBinder()
{
JsonSerializerSettings settings = new JsonSerializerSettings();
// Set custom serialization settings.
settings.DateTimeZoneHandling= DateTimeZoneHandling.Utc;
jsonSerializer = JsonSerializer.Create(settings);
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object model;
if (bindingContext.ModelType.Assembly == "MyDtoAssembly")
{
var s = controllerContext.RequestContext.HttpContext.Request.InputStream;
s.Seek(0, SeekOrigin.Begin);
using (var sw = new StreamReader(s))
{
model = jsonSerializer.Deserialize(sw, bindingContext.ModelType);
}
}
else
{
model = ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
return model;
}
}
}
Then, in Global.asax.cs, Application_Start():
var asmDto = typeof(SomeDto).Assembly;
foreach (var t in asmDto.GetTypes())
{
ModelBinders.Binders[t] = new MyModelBinder();
}

Categories

Resources