Property binding in AspNetCore - c#

I'm using:
return RedirectToAction("GetSchedule", new { requirements = preCheckParams.Requirements, weightValues = preCheckParams.WeightValues});
in my aspnetcore app. Next I want to reuse the values I pass to the anonymous object in another action:
public IActionResult GetSchedule(List<string> requirements, Dictionary<string, int> weightValues)
Strangely, the first value gets bound to the List in GetSchedule action, yet the second object, which is a dictionary, is empty. Are there any special rules regarding dictionaries in such cases?

You can't pass classes in the routeValues parameter of RedirectToAction.
RedirectToAction method return value is HTTP 302 (Status302Found) which produces a GET request to the specified action. Which means that all your parameters will be put in URL as query string.
List/Array of strings requirements can be passed in URL since it binds to ?requirements=value1&requirements=value2&.. etc in query string, but anything more complex than that cannot be bound, only primitive values.
You have several options that first come to my mind:
Serialize object to JSON and pass it as a string. This will result in ugly and confusing URL, but it's least painful way.
Use temporary storage if you don't require strictly stateless mechanism. Store before the action and retrieve the dictionary when you enter it.
If you can retrieve weight values from the backend, you may pass some identifier to query by it.

Related

How can I pass objects from one ActionResult to another in Asp.net MVC (C #)? [duplicate]

I want to pass object in RedirectToAction. This is my code:
RouteValueDictionary dict = new RouteValueDictionary();
dict.Add("searchJob", searchJob);
return RedirectToAction("SearchJob", "SearchJob", dict);
where searchJob is instance of SearchJob. But I don't get data on SearchJob action method. Instead I get querystring of searchJob = Entity.SearchJob. Please help me. What am I doing wrong?
You can not pass classes to the redirected actions like that. Redirection is done by means of URL. Url is a string, so it can not contain classes (serializing objects to url is really out of logic here)
Instead, you could use TempData
TempData["searchJob"] = searchJob;
return RedirectToAction ...;
and in Action redirected
Entity.SearchJob = (Entity.SearchJob)TempData["searchJob"] ;
After executing of the code above, TempData will not contain searchJob anymore. TempData is generally used for single time reading.
But I do not like the way above. If I were in your place and wanted to search jobs by name, I would add route parameters like
RouteValueDictionary dict = new RouteValueDictionary();
dict.Add("searchJobName", searchJob.JobName);
and receive it to action via parameter
public ActionResult SearchJob(string searchJobName)
{
... do something with the name
}
This way, you get better user and HTTP friendly URL and from the Action point of view, it would get all the parameters it needs from outside. This is better for testing, maintenance, etc.
You might try:
return RedirectToAction("SearchJob", "SearchJob", new RouteValueDictionary(searchJob))
Passing the searchJob object into the RouteValueDictionary constructor will decompose the searchJob object and pass each property of the SearchJob class as a top-level route value.
With the default model binder, an action defined as:
public ActionResult SearchJob(SearchJob searchJob)
Will receive a fully re-hydrated SearchJob object.
You can not pass classes to RedirectToAction method, if you want to pass an entire object in a querystring or via POST you can serialize the object using XML or JSON and deserialize the object in the receiver controller.
If you use this approach to be careful on the size of the object serialized.
Try to use Cross-Page Posting you can determin Prevoiuse page type, and use it object.

How to store a Func<T> variable into a HtmlHelper (Html.HiddenFor()) in order to post it back to the server

I'm trying to store a Func<T> or Expression<Func<T>> variable into a Html.HiddenFor(), inside a Razor view, in order to post it back to the controller, but it does not work as expected, as it's stored as a string, and comes back as null to the controller.
I need it to access particular properties of a model, and set them with other data passed to the controller at the same time.
I cannot find setter properties that I could store and use back in the controller to build the Func<object> variable (like I would with any complex object).
For instance, I send to the view, a model, containing a list of these:
new DataConflict<AppointmentEditModel, dynamic, dynamic>(x => x.StartDate, null, Start, appointment.StartDate)
then I store the lambda expression x => x.StartDate, which is a Expression<Func<T>>, like this (it seems to store it as a string):
#Html.HiddenFor(m => m.Appointment.ConflictList[i].DataProperty)
And when it comes back to the controller after submission, all the lambda containing properties are null.
I expect the controller to receive back the lambda expression containing object, it sent to the view.
Thank you.
As #Van has suggested, I've looked for a way to serialize the data, and I learned that this is not a good thing to do at all, to serialize lambda expressions.
I then have looked for another solution, and decided to use reflection, sending the member name to the client, and then back to the server.
Hope this help.

WebApi - UrlHelper.Route to GET with [FromUri] object parameter

I have an API action defined as the following:
[Route(Name="GetMembersTest"), HttpGet, ResponseType(typeof(MemberHeadersDto))]
public IHttpActionResult GetMembers[FromUri]MemberFilterDto filter, [FromUri]PagingOptionsDto paging)
This method works as expected, routing and all, requests are flowing through just fine. However, I'd like to supply a "NextUri" for paging so that the caller can just keep following NextUri until it is null to get all the results. I have to send back a uri to the same action, 1 page ahead, if that makes sense.
So I tried using UrlHelper.Route. This route is named "GetMembers" for the purpose of this example.
NextUri = Url.Route("GetMembers", new { filter, paging });
The problem is that instead of getting something like
/v1/members?filter.q=&filter.otherproperty=&paging.count=10&paging.startRow=11
I get
/v1/members?filter=WebApi.Models.MemberFilterDto&paging=WebApi.Models.PagingOptionsDto
It looks like UrlHelper.Route doesn't support complex types in the [FromUri] parameter of a GET Request. Is there anything I can do to get this functionality? My workaround right now is to take in all the Dto properties as individual parameters then build my Dtos from them on the server. This isn't ideal because if I ever add any more options I'd have to add more parameters to the action, which also makes the route value dictionary more fragile as well because it has to match with the method signature in UrlHelper.Route(routeName,routeValues).
Unfortunately, there is no way to pass in complex object to routing. Instead, you will need to pass in the simple properties individually.
I was not able to find a way to extend Url.Route, but that would be/have been your best option.

Receive nested anonymous objects in ASP.NET MVC controller

I need a ASP.NET MVC controller, which receives anonymous object from JS in JSON to iterate thru its properties. I used to do this, receiving Dictionary<string, object>. But now one of values is Array, and insted of
receivedDictionary[Photos] = [object, object, object]
it gets it as
receivedDictionary[Photos[0]] = object, receivedDictionary[Photos[1]] = object, receivedDictionary[Photos[2]] = object
I get not one dictionary entry with key = Photos and value = array, but many entries with key = Photos[x] and value = object.
How do I get it as one entry in dictionary or is there any better way to get it as dynamic anonymous object and iterate thru its properties just like in JS?
UPD: JSON looks like this:
{"fields":{"TotalFloors":"9","HouseNumber":"10","Photos":[{"id":0,"ParentID":0,"OriginalUrl":"py4s1y3uyqu","OriginalExt":".jpg","ThumbUrl":"2hn04w2lzuu","FormatUrls":{"WH_109_82_Url":"4cwjarqudvo","WH_766_454_Url":"oofm5qo21rr"}},{"id":0,"ParentID":0,"OriginalUrl":"t3csgq20iro","OriginalExt":".jpg","ThumbUrl":"j1uwwburmse","FormatUrls":{"WH_109_82_Url":"gm4qoery1u2","WH_766_454_Url":"a3c20re3g1d"}}],"Details":"Other details"}}
Controller definition:
[HttpPut]
public ActionResult restId(string className, int id, Dictionary<string, object> fields)
{
....
}
The JsonValueProvider used by the DefaultModelBinder seems to be treating array in an odd fashion in this case (based on the source here), so even a dynamic will most likely have the issue. (Don't think you'll have this issue in MVC 6 however)
However, calling the JavascriptSerializer directly (which funny enough is what the default provider uses) produces the results you're after:
var js = new JavaScriptSerializer();
var res = js.DeserializeObject(#"{'TotalFloors':'9','HouseNumber':'10','Photos':[...],'Details':'Other details'}");
To address your issue you could either alter the parameter to a string and run the above code in your action (obviously replacing the JSON string with the parameter), just means the JSON you're submitting from the front end would need to look more like:
// wrapping the JSON object in quotes to stringify it
{ 'fields' : "{ 'TotalFloors': '9', 'HouseNumber': .... }" }
Otherwise you could implement a custom JsonValueProvider like the one proposed here: https://json.codeplex.com/discussions/347099
The custom value provider is probably the cleaner solution.

sending json null to controller results in list with 0 elements

I am making a json request from the browser sending {Par: null} to my controller (C#):
public JsonResult MyControllerMethod(List<Guid> Par){
//do some stuff depending on whether Par is null
}
but Par comes into the controller as a List<Guid> with 0 items, is this the correct behaviour? I thought I had passed null back before in similar situations and it remained null.
Ok, had a look at the mvc binding code
For each parameter in the url, it attempts to bind an object
if it is an IList, then it creates the list then attempts to add objects to it, hence the empty list when passing back null.
So if the parameter isn't specified then it won't bind anything, thus making it null.
The answer is therefore that if you want null on the server side, just don't specify the parameter name on the client

Categories