I'm having an issue where a value seems to be being dropped between two controller ActionResults. I'm creating request as a new ValuationRequest and adding 4 values as below.
The WriteLine correctly shows ValuationType as "lettings"
request = new ValuationRequest
{
ValuationType = new SearchType[] { SearchType.lettings },
Postcode = model.Postcode,
FromDate = DateTime.Now.AddHours(24),
ToDate = DateTime.Now.AddDays(14)
};
Debug.WriteLine("ValTypeBefore:" + request.ValuationType[0].ToString());
return RedirectToAction("select-appointment", request);
However, when I pass request through to the next ActionResult shown below, and immediately try to Debug.WriteLine again, it errors as this value is null. The other 3 fields are being carried across perfectly.
[ActionName("select-appointment")]
public ActionResult SelectAppoinment(ValuationRequest request, ValuationModel model)
{
Debug.WriteLine("ValTypeAfter:" + request.ValuationType[0].ToString());
var valuationAppointments = WebServiceUtility.GetValuationAppointments(request);
Any ideas why this would happen?
The 'request' is being passed through, but just ValuationType is being dropped.
Code for ValuationRequest class below:
public partial class ValuationRequest {
private string postcodeField;
private string officeCodeField;
private System.DateTime fromDateField;
private System.DateTime toDateField;
private int durationField;
private bool durationFieldSpecified;
private int interludeField;
private bool interludeFieldSpecified;
private SearchType[] valuationTypeField;
Cheers
It might have to do with JSON Serialization, which only serializes public Properties.
If you redirect, your request will get serialized and then desiralized auto-magically using (usually) JSON. So you will need public Properties with getter and setter for all data you want to transmit. Also an empty Constructor.
But if those Actions are in the same controller, why don't you just call the Action Method, like any other method in c#?
So instead of
return RedirectToAction("select-appointment", request);
You write
return SelectAppoinment(request, model);
I know, not exactly what you asked for, but at least it might be a workaround.
Related
I've got a new API that I'm building with ASP.NET Core, and I can't get any data POST'ed to an endpoint.
Here's what the endpoint looks like:
[HttpPost]
[Route("StudentResults")]
public async Task<IActionResult> GetStudentResults([FromBody]List<string> userSocs, [FromBody]int collegeId)
{
var college = await _collegeService.GetCollegeByID(collegeId);
// var occupations = await _laborMarketService.GetOccupationProgramsBySocsAndCollege(userSocs, college);
return Ok();
}
And here's what my payload that I'm sending through Postman looks like:
{
"userSocs": [
"291123",
"291171",
"312021",
"291071",
"152031",
"533011"
],
"collegeId": 1
}
I'm making sure that I have postman set as a POST, with Content-Type application/json. What am I doing wrong?
You get always null because you need to encapsulate all your post variables inside only one object. Like this:
public class MyPostModel {
public List<string> userSocs {get; set;}
public int collegeId {get; set;}
}
and then
public async Task<IActionResult> GetStudentResults([FromBody] MyPostModel postModel)
If the model is null, check:
1) Where the data is sent: body, form? and based on that add the decorator to the action. For ex:
[HttpPost]
public JsonResult SaveX([FromBody]MyVM vm) { ... }
2) Check ModelState: if it's invalid the vm will not be bound so it will be null.
if (ModelState.IsValid) { ... }
Another reason for the model binding to fail (always null) is if the data type for a property doesn't match. For example here is a simple model:
public class MyService {
public string JobId { get; set; }
public int ServiceType {get; set;}
}
And here is some json that doesn't match:
{"JobId":1, "ServiceType":1}
I got caught with this when I was retrieving the JobId using jquery's .data function, it was automatically converting it to an int. Fixed it by using .attr function instead.
Also, make sure those variables inside your parameter class are declared as Public, (or they'll just keep returning as null)..
If you want to send two or more models, you should use this example:
[HttpPost]
public async Task<ActionResult> addUsuario([FromBody] Newtonsoft.Json.Linq.JObject datos)
{
Usuarios user = datos["usuario"].ToObject<Usuarios>();
Empresas empresa = datos["empresa"].ToObject<Empresas>();
return Json(await _srv.addUsuario(user, empresa));
}
I know it is not related to your case, still, I am posting my answer here.
It is a silly mistake that I had done in my code. I just copied one of my Get requests and changed it to a Post request, and forgot to decorate the parameter with [FromBody]. If anyone else is having the same problem, please make sure that you are decorating the parameter with [FromBody].
[HttpPost]
public IApiResponse Update([FromBody] User user) {
if (user == null) return new ApiBadRequestResponse(ModelState);
return _userService.Post(user) ? new ApiOkResponse(user) : new ApiResponse(500);
}
Assuming the [FromBody] class is made up of primitive data types;
[FromBody] is public
[FromBody] has an empty constructor ()
[FromBody] is serializable.
Make sure that your data transfer object has "Public" as Access modifier and also your oject properties have getter and setter methods.
For me, all values were coming in as null because my object had an enum that was not being parsed. If you have having this problem, I'd recommend turning on enum parsing at the Startup.cs per this SO
In my case it was using Newtonsoft.Json in my body object with JsonProperty attributes for some reason. Changing in to System.Text.Json.Serialization and JsonPropertyName attribute solved the problem
I'm trying to convert a .NET object into a JSON string, because I want to be able to read the content of this object in the client side.
Here is my controller code:
public ActionResult Index()
{
IRightsManager rightsInfo = new RightsManager();
string userId = "ynz362897";
string json = JsonConvert.SerializeObject(rightsInfo.GetSectorsForUser(userId));
Session["test"] = json;
return View();
}
GetSectorsForUser returns an object which have only one attributes, a list of of another object. Here is the model:
public class Sector
{
public string Code { get; set; }
public string Name { get; set; }
public Sector(string code, string name)
{
this.Code = code;
this.Name = name;
}
}
public class RightsList
{
public IList<Sector> Sectors;
public RightsList(IList<Sector> sectors)
{
this.Sectors = sectors;
}
}
Here is GetSectorsForUser code:
public RightsList GetSectorsForUser(string userId)
{
IRightsManagerDB rightsManager = new RightsManagerDB();
RightsList rightsList = new RightsList(rightsManager.GetSectorsForUser(userId));
return(rightsList);
}
The result currently produced by my code is:
"{\"Sectors\":[{\"Code\":\"01\",\"Name\":\"FME\"},{\"Code\":\"02\",\"Name\":\"DML\"}]}"
Which is unreadable with a for in jQuery client side. I am stuck on this for hours, and I cant find any solutions.
Here is the client code:
var sectors = #Session["Sectors"];
$.each(sectors, function (i, item) {
$('#comboSector').append($('<option>', {
text: item.Name,
value : item.Code
}));
});
If you're sending the object through AJAX...
ASP.NET MVC handles JSON serialization for you. This means that you don't need the:
string json = JsonConvert.SerializeObject(rightsInfo.GetSectorsForUser(userId));
line. What happens is that you serialize the object yourself, and then ASP.NET MVC serializes it one more time, leading to the actual result, that is a string serialized as JSON. In other words:
The first serialization leads to {"Sectors": ...,
The serialization of the previous string leads to "{\"Sectors\": ....
If you're embedding the object in JavaScript within HTML...
It seems like this is what you are actually doing, and:
var sectors = #Session["Sectors"];
is a Razor file. This is a very weird approach (mixing languages, dynamically generating JavaScript, accessing the session from your view¹), but, well, let's assume you know what you are doing.
What happens here is that sectors variable points to a string which contains the JSON-serialized object, not the object itself. If you need to get the object, do:
var sectorsObj = JSON.parse(sectors);
$.each(sectorsObj, ...
JSON.parse decodes a JSON-serialized object. Similarly, JSON.stringify converts an object to its JSON representation.
¹ Accessing the session from your view like you do is not only an abuse of the MVC model and refactoring-unfriendly, but also technically wrong. Limit the view to the contents of the model, and eventually the view bag, when relevant. Avoid using global variables, session, request/response object, etc.
#MainMa Thanks for your answer. Like you said, it was not very clear technically for me. I did a bit of research and clean up my code according to standards. Now that I have a better understanding of Ajax, here is how I fixed my problem.
This is my ajax request client side.
$(document).ready(function () {
$.ajax({
url: '/Home/GetSectors',
type: 'GET',
dataType: 'json',
success: function (json) {
$.each(json, function (idx, sector) {
$('#comboSector').append($('<option>', {
text: sector.Name,
value: sector.Code
}));
})
},
error: function () {
}
});
})
Which is answered by my controller server side:
[HttpGet]
public JsonResult GetSectors()
{
Sector[] sectors = sectorManager.GetSectorsForUser("cn873284").ToArray();
return Json(sectors, JsonRequestBehavior.AllowGet);
}
Now my combo is initialized with parameter sent by the server. No more use of Session.
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)
{
}
From the oddity in my question, I suspect I'm not going in the right direction.
Supposed I have a view that has a paginated list among several other items. On first load, the list is loaded with the first page of the list (this is where I'm attempting to call my JsonResult method in the controller from the model).
public class FooListViewModel
{
public FooListViewModel()
{
DateTime today = DateTime.Today;
DateTime later = DateTime.Today.AddDays(5);
// Here I need to make call to my JsonResult method
// in the controller to populate fooItems
}
public IEnumerable<FooItem> fooItems { get; private set; }
public IEnumerable<DateTime> dates { get; private set; }
}
In controller
[HttpGet]
public JsonResult GetItems(DateTime start, DateTime end)
{
var fooItems = domainServices.Foo.GetAllFooItems();
// Add predicates to filter between start and end dates.
return Json(fooItems, JsonRequestBehavior.AllowGet);
}
On each page button click, it will reload ONLY the list with another call to the JsonResult method in the controller via AJAX, but this has already been done.
It's easier to just simulate the button click on the client when the page just loads, then you only have one routine to do all the getting data, inserting into page and correctly formatting it.
Otherwise, create a GetData function that your controller routine (Index?) calls and your GetJSON routine calls to get whatever data you need. The index method will stick this in the model to pass to the view, and the GetJSON routine just returns the result as json.
Sample code:
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.data = GetData();
return View(model);
}
public JsonResult GetJson(DateTime startDate,DateTime endDate)
{
var result=GetData(startDate,endDate);
return Json(result);
}
private IEnumerable<MyData> GetData(DateTime startDate=null,DateTime endDate=null)
{
return something;
}
You really shouldn't be calling action methods in any way other than via a HTTP request (since what you get back should he a HTTP response). This is, in a sense, like asking your server-side code to send a request to a page, which is all backwards.
If you have logic that you need in both in your controller and in your model constructor, you should probably be abstracting this logic from your presentation layer, exposing it in your business layer and just consuming it in both places:
public class FooProvider
{
public List<Foo> GetFilteredFoos (/* whatever you need */)
{
// filter and return foos
}
}
Controller:
public JsonResult GetItems(DateTime start, DateTime end)
{
var fooItems = domainServices.Foo.GetFilteredFoos(/* some params */);
return Json(fooItems);
}
Model:
public FooListViewModel()
{
DateTime today = DateTime.Today;
DateTime later = DateTime.Today.AddDays(5);
var ds = DomainServices();
fooItems = ds.Foo.GetFilteredFoos(/* some params */);
}
Here, I assume that DomainServices.Foo is an instance of a class called FooProvider.
Having said this, however, I would avoid having this kind of login in your view model at all, where possible. Why not simply make the call in the controller when you first initialize the model?
public ActionResult Index()
{
var model = FooListViewModel();
model.fooItems = ds.Foo.GetFilteredFoos(/* things */);
return View(model);
}
Then, update via AJAX as normal.
I want to pass more then one parameter from RedirectToAction method
how can I pass?
My One Action Method
[HttpPost, ActionName("SelectQuestion")]
public ActionResult SelectQuestion(string email,List<QuestionClass.Tabelfields> model)
{
List<QuestionClass.Tabelfields> fadd = new List<QuestionClass.Tabelfields>();
for (int i = 0; i < model.Count; i++)
{
if (model[i].SelectedCheckbox == true)
{
List<QuestionClass.Tabelfields> f = new List<QuestionClass.Tabelfields>();
fadd.Add(model[i]);
}
}
return RedirectToAction("Question", new { email = email, model = fadd.ToList() });
}
My another Action Method
[HttpGet]
public ActionResult Question(string email,List<QuestionClass.Tabelfields> model)
{
}
I am not getting values in model.
You cannot pass a collection of complex objects in urls when redirecting.
One possibility would be to use TempData:
TempData["list"] = fadd.ToList();
return RedirectToAction("Question", new { email = email});
and then inside the Question action:
var model = TempData["list"] as List<QuestionClass.Tablefields>;
The way that I solved this problem was to serialize the list to a JSON object using the JsonConvert method from the Newtonsoft.Json nuget package. Then the serialized list can be passed as a parameter and then deserialized again to re-create the original list.
So in your SelectQuestion method you would use this code:
return RedirectToAction("Question",
new {
email = email,
serializedModel = JsonConvert.SerializeObject(fadd.ToList())
});
And in your Question method, you would use this code to deserialize the object.
[HttpGet]
public ActionResult Question(string email, string serializedModel)
{
// Deserialize your model back to a list again here.
List<QuestionClass.Tabelfields> model = JsonConvert.DeserializeObject<List<QuestionClass.Tabelfields>>(serializedModel);
}
Important, this adds the model as a query string parameter to your url, so only do this with really simple small objects, otherwise your url will be too long.
This is probably not even active anymore, but I'll leave how I did it here to maybe help someone else.
I solved this using a simple Redirect instead of a RedirectToAction:
List<int> myList = myListofItems;
var list = HttpUtility.ParseQueryString("");
myList.ForEach(x => list.Add("parameterList", x.ToString()));
return Redirect("/MyPath?" + list);
Then, on your other method:
public ActionResult Action(List<int> parameterList){}
RedirectToAction method Returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
You should either keep the data in a temporary storage like TempData / Session . TempData uses Session as the backing storage.
If you want to keep it real Stateless, you should pass an id in the query string and Fetch the List of items in your GET Action. Truly Stateless.
return RedirectToAction("Question", new { email = email,id=model.ID });
and in your GET method
public ActionResult Question(string email,int id)
{
List<QuestionClass.Tabelfields> fadd=repositary.GetTabelFieldsFromID(id);
//Do whatever with this
return View();
}
Assuming repositary.GetTabelFieldsFromID returns a List of TabelFields from the Id