Pass entire object from view to controller in ASP.NET MVC 5 - c#

Is there a way to pass entire object from ASP.NET MVC 5 View to a Controller? This is my situation:
I have a View that displays all rows from a DB table
The view's model is IEnumerable
Each row has a link after it's data that leads to the scaffolded UPDATE view
Is there a way to pass the entire object to the Update controller method so it would initially fill the form inputs with the old data? Something like:
#Html.Action("Update me!", "Update", new { objectFromModelList })
And then in the controller
public ActionResult Update(MyType parameter)
{
return View(parameter);
}
Or something like that. Please help, I am new to this and can't find the answer anywhere.

Your objects could be so big! Query string's has a limitation on how much data you can pass via those based on the browser. You should consider passing a unique id value (of the record) and using which get the entire record from db in your action method and pass that to the view.
#foreach(var item in SomeCollection)
{
<tr>
<td> #Html.Action("Update me!", "Update", new { id = item.Id }) </td>
</tr>
}
and in the action method
public ActionResult Update(int id)
{
var item = GetItemFromId(id);
return View(item);
}
Assuming GetItemFromId method returns the method/view model from the unique id value. Basically you get the entire record using this unique id from your db table/repository.

Assuming that your Update View isn't of type IEnumerable...
You just need to pass the ID of the record that you want to send to the Update view...
Like so:
#Html.Action("Update me!", "Update", new { id = item.ID })
Then your Update action would look like this:
[HttpGet]
public ActionResult Update(int id)
{
var parameter = db/* connection string variable */.TableName.Find(id);
return View(parameter);
}
Then your link should work appropriately.
Hope this helps!

I have searched myself and the best way, aside from passing the ID, that I have found is to store any other variables that you might need into hidden input fields or HTML5 tags. Then you can script a way to handle any button/link click events. This way you can store vital object properties of each record and easily pass them back to a controller. Think Client-side here, Once the data ends up Client-Side, use Client-Side tools to handle and pass it back to server side/controller.
I do something similar with a type of library reservation system that allows users to reserve items on available dates. I pass all available records to the view. Each record has a few fields that I want to hold onto including the ID for users reference. When the user clicks the button, I collect the needed fields.
You could use HTML5 form input fields that are hidden or you could just use JavaScript to collect those values using GetElementByID. An example of this would be to store the ID in the div wrapper. Then have another div hold a sub parameter. You can use Javascript to find the record ID and then get the second div by it's id. Example would be get the id NameRecord from XRecord where X = the ID passed.
I then pass those values to the controller, instantiate a new class/object for the reservation. The new class object also has the item class/object as a property. For example consider the following;
var reservation = new Reservation
{
myKit = new ResourceKit()
};
After that, you can store it in a session if you need to build on it. In my case I am holding it in a session because I allow the user to check availability/dates. These items are a physical resources that gets checked out similar to a library and are transferred via office mail.
If you dont mind the data sitting client-side, you can store it using LocalStorage and JavaScript. This type of data isnt secure at all much like a cookie. One of the ways that I have used this is to set site preferences. Users can select a color scheme and those preferences are stored in LocalStorage. That way when the return to the site those preferences remain. This is a key attribute of LocalStorage and might not be applicable to your needs/circumstances.

Related

Efficiently and Eloquently Inserting/Deleting/Updating Child Records

I have page a that shows a header record along with a list of detail records. I'm struggling with making a clean and efficient way of inserting/deleting/updating detail records when the user clicks Save.
The detail records are shown in a jQuery DataTable, and the view model behind each detail record has an IsNew and IsRemoved property. When the user adds a detail record, its IsNew property is set to true. When the user removes a detail record, it is soft-deleted and its IsRemoved property is set to true.
When the user clicks Save and posts the page to my controller, my logic right now looks like this
[HttpPost]
public ActionResult EditData(ViewModel viewModel)
{
// Update the record's header details here
// ...
foreach (var childViewModel in viewModel.Children)
{
// Use AutoMapper to map the view model to a model
MyChildRecord childModel = this.mapper.Map<MyChildRecord>(childViewModel);
if (childViewModel.IsNew)
{
this.context.MyChildRecords.Add(childModel);
}
else if (childViewModel.IsRemoved)
{
this.context.MyChildRecords.Attach(childModel);
this.context.MyChildRecords.Remove(childModel);
}
else
{
this.context.Entry(childModel).State = EntityState.Modified;
}
}
this.context.SaveChanges();
return RedirectToAction("EditData", new { id = viewModel.Id } );
}
The thing I don't like about this code is that even if nothing about a child record is changed, I'm still updating it in the database. The only solutions I can come up with to prevent that are
Have each view model store a copy of its original values and then compare its current value to its original values when the user saves the page. I don't like this solution because I'll have to have a bunch of code to store the original values, and then I have to put the original values as hidden fields on my ASP page so that they get carried between web requests.
When the user saves the page, have the controller iterate through each child view model, load the original data from the database, and compare the view model's current values to see if the row needs to be updated or not. I don't like this method because it involves a lot of extra code for the field comparison, and I still have to make unncessary trips to the database.
This seems like a common scenario so there must be a commonly accepted way of doing this. How should I be going about this?
First, consider that while the extra updates do incur additional network traffic, the likelihood is that your actual database server is smart enough not to actually do anything to the database on disk if nothing changed.
Secondly, consider that your application might not be the only program working with your database table. Somebody else might have changed the record while you were looking at it. To be safe, you really need both solutions together: check whether the user changed your form, AND check whether the database row is the same as it was when you got the data to show to the user. If both have changed, usually it is considered an error and the user is notified.

MVC Model Binding Complex Type to Action Parameter

I'm binding a list of customers into a customer search results page using the model binding of MVC3, and using Razor to render all the customers in a foreach loop. My question is how then to send back the customer object to the action to save me having to fetch the details again.
Below is my action method signature:
public ActionResult BasketAddCustomer(Customer customer)
The Customer object is quite large, ie. lots of fields
Below is a cut down version of the view which renders each customer and has the button to select each one.
#model WebUI.Models.SearchModel
#foreach (var customer in Model.Customers)
{
<h5>#customer.FirstName #customer.LastName</h5>
<button onclick="window.location.href = '#Url.Action("BasketAddCustomer", "Cust", customer)';">Select customer</button>
}
The problem with this is that the customer that is passed into the action seems to come through as being full of nulls.
The html that is rendered by the #URL.Action is below and looks like a good start but only has some of the customer fields, not all. Is the Customer just too complex for being broken down this way? Is there a better way to do it?
<button onclick="window.location.href =
'/Test/Cust/BasketAddCustomer?BirthDate=01%2F01%2F0001%2000%3A00%3A00&PrimaryEmailFlag=False&PrimaryEmailDate=01%2F01%2F0001%2000%3A00%3A00&PrimaryEmailID=0&PrimaryPhoneFlag=False&PrimaryPhoneDate=01%2F01%2F0001%2000%3A00%3A00&PrimaryPhoneID=0&WifiConnected=False';" >Select customer</button>
As per my knowledge with #url.action we can only send route values and couldn't send the object itself.
Kindly refer this link: https://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.action(v=vs.118).aspx
So for your problem, you can send any unique value to the controller action as a route parameter in url.helper and get the entire object from the database using linq query in action .
Note : if your requirement says that , you should send the object only, then you can put all the controls inside the form in the view and send it as post method.
Hope above information was helpful.
Thanks
Karthik

How can I hold a collection of strings to post back?

From my controller I send a ViewModel with a collection of strings to be used in a <select> tag like so...
Controller:
var model = new InviteViewModel
{
SelectItems = new SelectViewModel
{
Companies = _companyRepository.GetCompanyNames()
}
};
Razor View:
<select class="form-control company_select" asp-for="Company" asp-items="#(new SelectList(Model.SelectItems.Companies))"></select>
This works perfectly and will display all the items in the drop down box. However when I go to submit the form the Companies object will be null and when the View is sent back I get a null reference exception. Normally I would create a hidden <input> tag to hold the value, but how can I do this with a collection?
There are so many ways to do this.
It seems like you may have a fundamental misunderstanding of the disconnect between Razor and html. Razor executes server side, and its result is simply a string that gets written to the response stream. Once written, razor's scope is gone and cannot hold data.
One option would be to store the collection in the application cache or session cache with a guid as the dictionary key, and then use a hidden input for the cache key. When the view is being recreated you would then have access to the server and could gather the collection.
This makes the assumption that the collection hasn't changed during the time the view was active, which given some user habits could have been a long time. There should also be some sort of metrics used when caching to invalidate old data, if this is the route you take.
Another option is to simply regenerate the collection from wherever (database?) it came from.
Lastly, you mention that the view is being returned with the empty collection, are you returning a view from a post method? That is bad practice. Look up the "post-redirect-get pattern" for why and how to avoid it.

MVC Http Post Action With Variable Parameters

I am having a bit of an issue with an MVC action that I am currently implementing. Simply put, I have a table of users with roles assigned to them. The number of roles per user can range from 1-3 and I am not sure how to reflect this in a POST action of MVC when editing them. To get the roles into the view I use a list of class AccountMapping that contains the role name and ID:
public ActionResult EditUser(User user, EditUserModel model)
{
// Omitted model assignments
model.AccountMappings = _reportUsersRepository.GetAccountMapping(user.UserId).ToList();
return View(model);
}
In the View (using Razor):
#foreach (var item in #Model.AccountMappings)
{
<div>
<p>#item.Target_Type.Replace('_', ' ')</p>
<input name="#item.Role_Name" value="#item.Target_Id" type="number" placeholder="Role Id" required />
</div>
}
How would I go about structuring the POST action to take into account these inputs? I am using the same model for the POST action and I am aware that the AccountMapping list would contain no results on postback. I am not sure about using the FormsCollection method because you need to know the name of the keys in order to retrieve the values. Am I missing something obvious?
Many Thanks.
Expanding on my comment, If you use indexing instead of foreach, then it can correctly name and map the individual items in a collection back to the posted input collection. e.g.:
#for (int i = 0; i < Model.AccountMappings.Count(); i++)
{
<div>
#Html.EditorFor(x=>Model.AccountMappings[i])
</div>
}
If you look at the generated HTML you will see the inputs are named uniquely based on their index position.
These will be automatically mapped back into a receiving collection (assuming your postback takes the same object type as your view model that was passed to the view).

How Can I Reconstruct URL/Route in ASP.NET MVC From Saved Data?

My unfamiliarity with the ASP.NET MVC framework and the plumbing thereof has brought me here, and I appreciate the patience it will take for anyone to read and consider my question!
Okay, here is the scenario: I have an application that has numerous pages with grids that display data based on searches, drilling down from other data, reports based on context-specific data (i.e. they are on a details page for Foo, then click on a link that shows a table of data related to Foo), etc.
From any and all of these pages, which are all over the app, the user can save the "report" or grid by giving it a name and a description. This doesn't really save the data, displayed in the grid, so much as saves the parameters that define what the grid looks like, saves the parameters that were used to get the data, and saves the parameters that define "where" in the app they are (the action, controller, route) - basically a bunch of metadata about the report/grid and how to construct it.
All of these saved reports are available in a single list, displaying the name and description, on a certain page in the app, with each linking to a generic URL, like "/Reports/Saved/248" (where 248 is an example of the report's ID).
Here is the part I need help on:
When I get to the action via the url "/Reports/Saved/248" and pull the metadata out of the database for that particular report, how can I redirect that data and the request to the same action, controller and route used to display the view that the report was originally saved from? Essentially, I want the user to view the report in the same view, with the same URL as it was saved from. If possible, it would be nice for me to be able to basically "call" that same action as though I am making a method call.
UPDATE: Unfortunately, our report pages (i.e. the pages these grids appear on) are NOT using RESTful URLs - for example, we have what we call an Advanced Search page, which takes a rather large number of potential parameters (nearly 30) that come from a form containing select lists, textboxes, etc. When the user submits that page, we do a POST to an action which accepts a complex type that the model binder builds for us - that same action is what I want to call when the user selects a saved Advanced Search from the database. That example epitomizes my problem.
Thanks
I think that you'll want to use RedirectToAction with the signature that takes a RouteValueDictionary. The method that you are redirecting to will need to be able to pull the values from the ValueProvider on the controller. It might look something like:
public ActionResult Saved( int id )
{
var reportParams = db.Reports.SingleOrDefault( r => r.ID == id );
if (reportParams == null)
...handle error...
var routeValues = ParamsToRouteValueDictionary( reportParams );
return RedirectToAction( reportParams.Action, reportParams.Controller, routeValues );
}
private RouteValueDictionary ParamsToRouteValueDictionary( object parameters )
{
var values = new RouteValueDictionary();
var properties = parameters.GetType().GetProperties()
.Where( p => p.Name != "Action" && p.Name != "Controller" );
foreach (var prop in properties)
{
values.Add( prop.Name, prop.GetValue(parameters,null) );
}
return values;
}
EDIT
Using a filter model as the parameter for your method actually may make it easier. You just need GET and POST versions of your action.
[ActionName("People")]
[AcceptVerbs( HttpVerbs.Get )]
public ActionResult PeopleDisplay( SearchModel filter )
{
return People( filter );
}
[AcceptVerbs( HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult People( SearchModel filter )
{
....
}
Then you would store in your db for the report the filter parameters (by name), the Action ("People"), and the Controller. The redirect result will use GET and be directed to the PeopleDisplay method, which in turns simply calls the People method with the correct parameter. Posting from the form calls the People method directly. Using two methods allows you to use the CSRF prevention mechanism. You might be able to use a flag in TempData to ensure that the GET action is only invoked via the redirection mechanism if you care to restrict access to it.
END EDIT
Another alternative, would be to simply store the View used as well and instead of doing a redirect, just render the appropriate view. One of the things that you'll want to consider is that doing the redirect will end up with a URL containing all the parameters, whereas rendering the View will leave the URL alone and just display the same view as the URL used when creating the report.
You can use the RedirectToAction method to issue a 301 redirect to a specific action method on any controller, along with route values:
ReportMeta meta = _reportDataAccess.Get(id);
return RedirectToAction(meta.Action, meta.Controller, meta.RouteData);
where those values are something like:
meta.Action = "Bar";
meta.Controller = "Foo";
meta.RouteData = new {
// possibly settings for the grid
start = DateTime.Min,
end = DateTime.Now,
sort = "Date"
// you get the idea
};
Of course, the immediate issue I can see with this is what happens when your controller/action methods change over time, the report data will be invalid. But then you probably thought of that already.

Categories

Resources