Transmit javascript object into controller action as dictionary - asp.net-mvc

Is there a way to pass this javascript object
Object { 35=true, 179=true, 181=true}
into a controller action as
Dictionary<int, bool>
I've checked the following methods:
var remoteUrl = "#Url.Action("UpdateProjectStructureSelection")" + "?tlpId=#Model.TopLevelProjectId";
$.post(remoteUrl, mapSiteSelectionChoices, function(callbackResult) {
alert(callbackResult);
});
and
var remoteUrl = "#Url.Action("UpdateProjectStructureSelection")" + "?tlpId=#Model.TopLevelProjectId";
$.post(remoteUrl, { values : mapSiteSelectionChoices }, function(callbackResult) {
alert(callbackResult);
});
However in both cases
public ActionResult UpdateProjectStructureSelection(int tlpId, Dictionary<int, bool> values)
has been called, but values was empty.
Since i've transfered more complex types into a controller action without writing a custom model binder i've been wondering whether i'm just doing something wrong here.
Is a custom model binder the only way here to get it as dictionary? (other than using JsonConvert + stringify on clientside)
Addition (This works but i'd love to avoid extra code) :
public ActionResult UpdateProjectStructureSelection(int tlpId, string values)
{
var dict = JsonConvert.DeserializeObject<Dictionary<int, bool>>(values);
}

Not with the object in that format. It would need to be in the following format to be used by the DefaultModelBinder
var data = { 'dict[0].key': '35', 'dict[0].value': 'True', 'dict[1].key': '179', 'values[1].value': 'True', dict[0].key': '8', 'dict[0].value': 'True' };
$.post(remoteUrl, data, function(callbackResult) {
and the controller
public ActionResult UpdateProjectStructureSelection(Dictionary<int, bool> dict)
As you noted, another option is to use a custom ModelBinder, for example this answer

Related

Get access to posted values in custom modelbinder

Currently I have an IActionFilter that accepts a List<T> as parameter. In that action method I examine the posted viewmodel values. It looks something like this:
[HttpPost]
public async Task<IActionResult> SavePage(List<BaseField> fields)
{
for (var i = 0; i < fields.Count; i++)
{
if (fields[i].Type == "bb")
{
var inputObj = new InputConfigViewModel();
await TryUpdateModelAsync(inputObj, $"fields[{i}]");
}
if (fields[i].Type == "ee")
{
var tObj = new TextareaConfigViewModel();
await TryUpdateModelAsync(tObj, $"fields[{i}]");
}
}
return RedirectToAction("Index", "Dashboard");
}
This works so far. But I would like to abstract this code away to a custom ModelBinder class.
public class BaseFieldModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Need access to "List<BaseField> fields"...
return Task.CompletedTask;
}
}
How can I get access the List<BaseField> fields values in my ModelBinder, like I can from the action method in my Controller?
Custom model binders work on objects and not generic lists. You cannot access the all list inside the binder, but you can access each individual object.
With this said, I don't believe you can abstract the code, because you don't have the ControllerContext you need to access the FormCollection inside the binder context, neither to execute the TryUpdateModelAsync call in order to get extra information from form post that is not present on the List<BaseField>. You just have the ModelBindingContext.
If you still want to try, you have a good working example here.
Here is the relevant part, where you can get the reference of the object:
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return TaskCache.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return TaskCache.CompletedTask;
}

How do I include a model with a RedirectToAction?

In the RedirectToAction below, I'd like to pass a viewmodel. How do I pass the model to the redirect?
I set a breakpoint to check the values of model to verify the model is created correctly. It is correct but the resulting view does not contain the values found in the model properties.
//
// model created up here...
//
return RedirectToAction("actionName", "controllerName", model);
ASP.NET MVC 4 RC
RedirectToAction returns a 302 response to the client browser and thus the browser will make a new GET request to the url in the location header value of the response came to the browser.
If you are trying to pass a simple lean-flat view model to the second action method, you can use this overload of the RedirectToAction method.
protected internal RedirectToRouteResult RedirectToAction(
string actionName,
string controllerName,
object routeValues
)
The RedirectToAction will convert the object passed(routeValues) to a query string and append that to the url(generated from the first 2 parameters we passed) and will embed the resulting url in the location header of the response.
Let's assume your view model is like this
public class StoreVm
{
public int StoreId { get; set; }
public string Name { get; set; }
public string Code { set; get; }
}
And you in your first action method, you can pass an object of this to the RedirectToAction method like this
var m = new Store { StoreId =101, Name = "Kroger", Code = "KRO"};
return RedirectToAction("Details","Store", m);
This code will send a 302 response to the browser with location header value as
Store/Details?StoreId=101&Name=Kroger&Code=KRO
Assuming your Details action method's parameter is of type StoreVm, the querystring param values will be properly mapped to the properties of the parameter.
public ActionResult Details(StoreVm model)
{
// model.Name & model.Id will have values mapped from the request querystring
// to do : Return something.
}
The above will work for passing small flat-lean view model. But if you want to pass a complex object, you should try to follow the PRG pattern.
PRG Pattern
PRG stands for POST - REDIRECT - GET. With this approach, you will issue a redirect response with a unique id in the querystring, using which the second GET action method can query the resource again and return something to the view.
int newStoreId=101;
return RedirectToAction("Details", "Store", new { storeId=newStoreId} );
This will create the url Store/Details?storeId=101
and in your Details GET action, using the storeId passed in, you will get/build the StoreVm object from somewhere (from a service or querying the database etc)
public ActionResult Details(string storeId)
{
// from the storeId value, get the entity/object/resource
var store = yourRepo.GetStore(storeId);
if(store!=null)
{
// Map the the view model
var storeVm = new StoreVm { Id=storeId, Name=store.Name,Code=store.Code};
return View(storeVm);
}
return View("StoreNotFound"); // view to render when we get invalid store id
}
TempData
Following the PRG pattern is a better solution to handle this use case. But if you don't want to do that and really want to pass some complex data across Stateless HTTP requests, you may use some temporary storage mechanism like TempData
TempData["NewCustomer"] = model;
return RedirectToAction("Index", "Users");
And read it in your GET Action method again.
public ActionResult Index()
{
var model=TempData["NewCustomer"] as Customer
return View(model);
}
TempData uses Session object behind the scene to store the data. But once the data is read the data is terminated.
Rachel has written a nice blog post explaining when to use TempData /ViewData. Worth to read.
Using TempData to pass model data to a redirect request in Asp.Net Core
In Asp.Net core, you cannot pass complex types in TempData. You can pass simple types like string, int, Guid etc.
If you absolutely want to pass a complex type object via TempData, you have 2 options.
1) Serialize your object to a string and pass that.
Here is a sample using Json.NET to serialize the object to a string
var s = Newtonsoft.Json.JsonConvert.SerializeObject(createUserVm);
TempData["newuser"] = s;
return RedirectToAction("Index", "Users");
Now in your Index action method, read this value from the TempData and deserialize it to your CreateUserViewModel class object.
public IActionResult Index()
{
if (TempData["newuser"] is string s)
{
var newUser = JsonConvert.DeserializeObject<CreateUserViewModel>(s);
// use newUser object now as needed
}
// to do : return something
}
2) Set a dictionary of simple types to TempData
var d = new Dictionary<string, string>
{
["FullName"] = rvm.FullName,
["Email"] = rvm.Email;
};
TempData["MyModelDict"] = d;
return RedirectToAction("Index", "Users");
and read it later
public IActionResult Index()
{
if (TempData["MyModelDict"] is Dictionary<string,string> dict)
{
var name = dict["Name"];
var email = dict["Email"];
}
// to do : return something
}
Another way to do it is to store it in the session.
var s = JsonConvert.SerializeObject(myView);
HttpContext.Session.SetString("myView", s);
and to get it back
string s = HttpContext.Session.GetString("myView");
myView = JsonConvert.DeserializeObject<MyView>(s);

Singleton Dictionary in ASP.NET MVC Controller?

I have a controller in an ASP.NET MVC application. The page has several filters on it, that are represented in the controller by a Dictionary object. The filter dictionary looks like the following:
Dictionary<string,bool> filterMap = new Dictionary<string,bool>();
filterMap.Add("Accepted",true);
filterMap.Add("Rejected",true);
filterMap.Add("Valid",true);
filterMap.Add("Invalid",true);
On the page, there are toggle buttons to set the values of there respect dictionary item(i.e. If the "Accepted" Button is toggled off, it changes the value on the dictionary item to false.
My problem I am experiencing is, every time the page is called, the dictionary resets in the constructors and is initialized to the above value.
I have the following in my constructor, but it doesn't help because the dictionary is reset and is null every time.
public MyController(){
if (FilterMap == null)
{
FilterMap = new Dictionary<string, bool> {{"Accepted", true}, {"Returned",true}, {"Valid", true},{"Invalid",true}};
}
}
The easiest option would be to save the FilterMap in Session:
private Dictionary<string, bool> FilterMap
{
get { return (Dictionary<string,bool>)Session["FilterMap"] ?? GetDefaultFilterMap(); }
set { Session["FilterMap"] = value; }
}
private static Dictionary<string, bool> GetDefaultFilterMap()
{
return new Dictionary<string, bool> {{"Accepted", true}, {"Returned",true}, {"Valid", true},{"Invalid",true}};
}
Then in your action to toggle the filters make sure you set FilterMap again to save the change in Session (not necessary if you use in-memory session state):
public ActionResult ShowReturned(bool show)
{
var filterMap = FilterMap;
filterMap["Returned"] = show;
FilterMap = filterMap;
}
Note that if your dictionary is only going to have these keys, you might as well use a real class with Accepted, Returned, etc. as properties.

JSON serialization, returning keys that have dashes in them?

I would like to return JSON from my controller which was generated from an anonymous type and contains dashes in the key names. Is this possible?
So if I have this:
public ActionResult GetJSONData() {
var data = new { DataModifiedDate = myDate.ToShortDateString() };
return Json(data);
}
On the client side I would like it to arrive serialized like this:
{ "data-modified-date" : "3/17/2011" }
My reason for wanting this is this Json data will ultimately become attributes on a DOM node, and I want to play nice and use the new HTML5 data attributes. I can just return { modifieddate: "3/17/2011" } and use it this way, but if I can become that little bit more conforming to standards I'd like to be.
I understand if I write my own JsonResult class that uses the WCF JSON Serializer on a non anonymous type, I can use theDataMemberAttribute to accomplish this. But that's a lot of overhead for such a simple desire.
I could also have the client massage the keys for me once it receives the data, but I'm hoping to avoid that too. All in all I'd rather just not follow standards than either of these workarounds.
You could use Json.NET and have full control over property names:
public ActionResult GetJSONData()
{
var obj = new JObject();
obj["data-modified-date"] = myDate.ToShortDateString();
var result = JsonConvert.SerializeObject(obj);
return Content(result, "application/json");
}
Obviously this code is screaming to be improved by introducing a custom action result:
public class JsonNetResult : ActionResult
{
private readonly JObject _jObject;
public JsonNetResult(JObject jObject)
{
_jObject = jObject;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
response.ContentType = "application/json";
response.Write(JsonConvert.SerializeObject(_jObject));
}
}
and then:
public ActionResult GetJSONData()
{
var obj = new JObject();
obj["data-modified-date"] = myDate.ToShortDateString();
return new JsonNetResult(obj);
}
I found the JavaScriptSerializer that JsonResult uses has a special case for Dictionaries. So if you just do:
var data = new Dictionary<string, string>
{
{ "data-modified-date", myDate.ToShortDateString() }
};
Then the resulting JSON is in the desired format.

Convert FormCollection to JSON

I have:
Function SaveAnswers(ByVal collection As FormCollection) As ActionResult
End Funciton
And I want to turn collection to JSON, I thought there was a serializer to do this but can't seem to find it?
Serialising a FormCollection object did not work for me, the keys serialized, but the values didn't.
I wanted to use an easy way to 'record' the FormCollection values to reuse in test cases. For this purpose, I created an extension method:
public static string ToJSON(this System.Web.Mvc.FormCollection collection)
{
var list = new Dictionary<string, string>();
foreach (string key in collection.Keys)
{
list.Add(key, collection[key]);
}
return new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(list);
}
There is. In c#:
return Json(object/array/whatever);
It returns a JsonResult, which is an ActionResult, so it "fits" into your function as it exists already.
James

Resources