How to supply dynamic data set to a single view using ViewModels - c#

(first time posting and fairly new to MVC) I currently have a MVC/C# application where the user selects a list of ID's and then chooses a Report Type (8 possible different reports calling 8 different database views). I am passing all of these parameters to the controller. Based on the report type chosen, I want to fetch the data, send it back to the view and then display the data in a grid/table format.
I tried sending the data via ViewBag and ViewData but had trouble parsing the actual columns/data using #foreach. This is when I decided to try ViewModels.
//My ViewModel....
//I know that I am missing the {get;set;} code which is where I also
// need help with. I need to pass the selected ID's to each database view and
// perform additional query requests (i.e. Distinct, Order By).
// Sample query:
// var dd = (from p in _db.Report1 select new { sId = p.ReportID, UserName = p.Submitted_By,
// Balance = p.TotalDebt}).Distinct();
// dd = dd.Where(w => chosenUserIDs.Contains(w.sID));
// dd.OrderBy(p => p.sId).ThenBy(p => p.UserName).ThenBy(p => p.Balance);
public class UserReportsViewModel
{
public List<namespace.report1> Report1 = new List<namespace.report1>();
public List<namespace.report2> Report2 = new List<namespace.report2>();
public List<namespace.report3> Report3 = new List<namespace.report3>();
...
}
//My Controller
UserReportsViewModel UserReportVM = new UserReportsViewModel();
switch (reportType)
{
case "REPORT1":
//Pass the selected ID's and get the data back from Report1 db view
// not quite sure how to do this.
break;
case "REPORT2":
break;
case "REPORT3":
break;
default:
break;
}
return View(UserReportsVM);
Am I even on the right track? I also came across something about partial Views and having the View call/referrence the correct partial View (?). Older languages were a lot simpler to accomplish this but I am really liking MVC/.Net/C#.
As for my database, I am using CodeFirst Entity framework.

I highly suggest you have view, viewmodel and action for each report. Just call the correct endpoint based on the requested report in the client.
Or, if you want to do this with one endpoint (action) specify the view you want to return. If you don't specify the view name, it returns a view based on the action name, but you can specify the view to render.
return View("ViewToRender", viewModel);
If you want to get fancy, you could do this with one view. Include a layout object in the viewmodel with the data... this way you can use one view model. The layout would be a list of "column info fields". Column1 would have This header, this width, etc. It would be bound to GenericReportViewModel.Column1.
From there, you just need to project your data into that generic view model.

Each model will return unique data with unique set of columns.comments by the user above
If this is the case write the logic in the view like this
#if(model.Report1!=null && model.Report1.Count!=0){
// your logic for report1 goes here or you can also use partial view to render
}
else if(model.Report2!=null && model.Report2.Count!=0)
{
// your logic for report2 goes here or you can also use partial view to render
}
Same for all the other reports. Hope this will help

Thank you all for your help and guidance. I decided to to Partial Views. The solution to my question is as follows.
For each report, I get the data and immediately store the data into ViewData.
IQueryable<dbViewName> Report8 = _db.dbViewName;
ViewData["qryResults"] = Report8;
...
return View(ViewData["qryResults"]);
Then in the main view I call the partial view:
#Html.RenderPartial(rt, ViewData["qryResults"]);
In the partial view my model is strong type.
#model IQueryable<dbViewName>
This allowed me to list out the data in tables by using:
#foreach (var item in Model)
It is clean and easy to implement any additional reports that the client may request in the future.
Again, thank you all for your input.

Related

passing multiple models to controller to build ViewModel

So I'm consuming a RESTFul API to get the data I need. This API returns json and i converted this to C# models. The API returns some general info about a vendor and an array with products. The array of products also consists out of arrays for pricing information and product availability etc...
The problem im facing is that when a product is selected by a user to buy i have to gather specific information out of the different array's. Currently I've made a ViewModel for the data that needs to be send to process the order. This is done by binding the actual data using hidden fields and use a HttpPost from the view, resulting in +/- 30 hidden fields to set the proper data values.
I'm kinda new to MVC and this seems dirty. I thought i would be able to pass the models(for example the VendorModel,ProductModel,PricingModel,AvailabilityModel) from the view(with a POST) to the controller and create the ViewModel(based on the models send) so that i can send that to the API.
Is this actually how it should be done or is my design faulty and should i approach this differently?
A side note: One of the things i found is that most people suggest to use an identifier to get the data you need but the problem is that the API doesn't have the right calls to get Product, Pricing, Availability data based on Id, its just one big object with array's based on the search query.
Edit
So after some information i decided to try out nested models and created a viewmodel like this:
public class TestViewModel
{
public TestViewModel()
{
productInfo = new ProductInfo();
}
public ProductInfo productInfo { get; set; }
}
My view is like this(super simpel):
#using (Html.BeginForm("CreateOrder","Products"))
{
//Vender info etc...
//This is how i render partial view now:
{Html.RenderPartial("Info/ProductInfo", Product.ProductInformation.ProductInfo);}
<input type="submit" value="Order" class="btn btn-default" />
}
My controller(TestViewModel.ProductInfo is always null):
public ActionResult MakeReservation(TestViewModel testViewModel)
{
//Doesnt do anything just debugging TestViewModel
return View();
}
I'm not posting any inputs but just want to pass the modal with the data to the controller. How will MVC know which data to bind, because now it doesnt bind. Do i have to bind it myself somehow?
I had the similar situation and I have one suggestion, you can pass all the models to the view as save in a javascript object as JSON using the example code which I used like below-
<script type="text/javascript">
var pageBlockOptionsJSON = #(Html.Raw(Json.Encode(DesignOrderBlocks)));
var controlTypeJSON = #(Html.Raw(Json.Encode(controlList)));
</script>
Then you can do all the manipulations using jQuery to structure and create a javascript object and post that object to Controller Action. You can create the same or exact structure which is needed on the server. As specified in this url - http://www.nickriggs.com/posts/post-complex-javascript-objects-to-asp-net-mvc-controllers/
In this way you don't need to have use huge amount of hidden fields and you can do all the dirty work on the client side using jQuery.
I am sure this will help you out in doing the thing in the right way. Let me know if you need some details.
For people that somehow find this questions i solved this issue by saving the model to an database as an search result using entity framework. After getting the result i save it and create a viewmodel. After posting i send the id's that entity framework generated and search the right models in the database creating a new postviewmodel.

ASP.Net MVC Postback and Models

This is mostly a follow-up to a comment in this issu, but I don't have enough reputation to comment ...
ASP.Net MVC Postback a label value to your controller
Let's say I have a simple model:
public class SimpleClass
{
public String Label { get; set; }
public String FirstName { get; set; }
}
Label is changed based on user/client so it can't be a DataAttribute. If when posted back processing problems occur, we need to redraw the entire page. This is the crux of the problem of the previous post. The accepted solution is to do this:
#Html.DisplayTextFor(model => model.Label)
#Html.HiddenFor(model => model.Label)
#Html.EditorFor(model => model.FirstName)
That makes sense in that it works. But our models are much more complicated and extensive. This method will result in a ton of hidden fields which seems like a very dirty solution.
This brings me to JP's comment:
ASP.Net MVC Postback a label value to your controller
The solution there is to reload the model. But it's not just a reload, it's also a merge since you want to preserve any client-side data changes.
default: SimpleClass { Label="TheLabel", FirstName="Rob"}
postedback: SimpleClass { Label="", FirstName="Steve" }
we want: SimpleClass { Label="TheLabel", "FirstName="Steve" }
My question is does MVC have a good way to know what fields were postedback so it merges correctly? We would need to only merge postedback fields not blank properties.
Or is it better to just ajaxify the entire postback and not do a form submit? This avoids all model reload issues on submit.
Update
To give Pablo credit I accepted his solution. To see my simple example of his solution, check Robert Harvey's comment in the Answers below:
ASP.Net MVC Postback and Models
The main problem here is in trying to fit WebForms' PostBack concepts into MVC. There is no such thing as a stateful postback where things just automatically retain their state.
You only have ViewModels that are bound to the view, and ViewModels that are posted by the view to the Controller. They don't even necessarily need to be of the same Type. Meaning, the controller should only receive the data that the user indeed can change, not large objects with many properties that were part of the initial ViewModel but are read-only.
Labels commonly represent read-only texts and they are not editable form elements. Which is why you have to use hidden fields for that.
And yes, sometimes that implies that you have to reload the original data in the controller, and sync up with new data that you posted, which isn't necessarily a bad thing. If you bind read-only data to a view, which the user can't manually edit, you shouldn't really trust that data coming back in a post afterwards. Just because your html might try to make it read-only doesn't mean I can't manipulate the post and ultimately change your "read-only" data without you knowing.
I just read the second question you mentioned, and from the looks of it, his main problem was that he was trying to reuse the same ViewModel again, so all the data was missing and the model wasn't valid. The solution to that is indeed quite simple, ONLY post what you need, as a new ViewModel type, and have the controller take care of the rest.
[Moved from OP]
I think this is what Pablo is suggesting for those who are wondering. It seems to be a good pattern to resolve this problem.
Models:
public class SimpleClass : SimpleClassPostBack
{
public String Label { get; set; }
public SimpleClass()
{
// simulate default loading
Label = "My Label";
FirstName = "Rob";
}
}
// contains only editable by the user fields
public class SimpleClassPostBack
{
public String FirstName { get; set; }
}
Controller Actions:
[HttpGet]
public ActionResult SimpleClassExample3()
{
SimpleClass simpleClass = new SimpleClass();
return View(simpleClass);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SimpleClassExample3(SimpleClassPostBack postBackSimpleClass)
{
Boolean errorOccurred = true;
if (!errorOccurred)
{
// do whatever success action is necessary
}
// redraw the page, an error occurred
// reload the original model
SimpleClass simpleClass = new SimpleClass();
// move the posted back data into the model
// can use fancy reflection to automate this
simpleClass.FirstName = postBackSimpleClass.FirstName;
// bind the view
return View(simpleClass);
}
View:
#model SimpleClass
#{
ViewBag.Title = "Simple Class Example3";
}
<h2>Simple Class Example3</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<label for="FirstName">#Html.DisplayFor(m => m.Label)</label>
#Html.EditorFor(m => m.FirstName)
<br/>
<button>Submit</button>
}
You should only send data from the client to the server that the server can't "figure out" on its own. If the server knows what the labels were when the user first navigated to that view then if the user cannot modify them, the server will be able to know what the labels are when reloading the view.
Use hidden fields to identify the database objects. So your SimpleClass should probably have some sort of Id which you will use in the hidden input. Use the EditorFor for FirstName. Now when the form is posted, use the sent Id to find the correct SimpleClass from the database and modify its FirstName property with the value posted. The Label property will be null which is ok since you don't need to save it. Now if there's a problem in the post and you want to send the same view back like it was, you need to repopulate the Label the same way you did when the user arrived to the view for the first time. The values of Id and FirstName properties will be automatically sent back to the view with the model state.
In summary:
Only post data that is needed to identify something and what the user
can edit in that view.
Don't trust the client to send you anything valid. The user can change the values of the hidden field labels to anything.

DRYing MVC helper options

Imagine A MVC view that contains a grid:
#{
Html.Grid(
autoGenerateColumn: false,
Url : "/Grid/GetData",
columnBuilderAction: p =>
{
p.Add(c => c.Name, "100");
p.Add(c => c.Code, "100").AlignCenter();
}
);
}
The URL specify the action that returns a json array contains data for filling the grid.
Obviously "/Grid/GetData" should returns only Name and Code property of person, but person have more properties.
It is violation of DRY if we specified this properties in GetData action
It will take more traffic if we pass this properties from client (with Ajax request of grid) to action
It is not beautiful (in my opinion!) if this properties define in controller, pass with ViewData to grid and shared with GetData action
Is ther any best practices or suitable practices!
In the controller is where you define what view to show (or in your case the information to be passed to the view).
In this case... you have a controller Persons, and a Index action. In the Index view... you setup your grid.
So... for me is perfectly fine to provide a sub set of Person properties, just for the view. Remember that you are supposed to send VIEWMODELS to views, not domain models. So, in this case... if you just need Name and Code... you can create a QuickPerson class with those properties... and in you database query do:
.Select(x => new QuickPerson(){ Code = x.Code, Name = x.Name})
Not sure if the columnBuilderAction can detect the properies automatically to avoid this.
What happend if you don't set the columnBuilder? If it uses all the public properties of the model... then you just solved the issue, since your new QuickPerson model has just the needed ones.

How do I create a form in asp.net MVC 5

I'm used to web forms, but am switching to MVC 5 and have a question about creating a multi step application form.
This form is like a wizard then will display information entered in each step at the end, then submit.
Is it easier to write this using html form in the .cshtml or do it all in the controller?
THank you
MVC, as its name suggests, has a Model, a View, and Controller. To create a form, you set up a class that will act as your Model, containing the properties that need to be worked with in a particular view. This is a different thing than your entity, the class that corresponds to a table in your database. You can sometimes use the entity as the Model, but especially in the case of a multi-step form, you don't want to persist the data until the end, which means, they'll need to be separate.
This brings us to the topic of view models, which is actually from another different pattern called MVVM. Regardless, your Model for these views will be a series of view models that contain just the information that the particular step needs to collect. At the end, you will piece all of the collected data together by creating an instance of your entity and mapping the property values from each view model over to it. Then, you will save the entity.
Now, as far as persisting the collected data between requests goes, that's where your session comes in. You'll merely add each posted view model into your Session object, and then at the end, fetch all of them from the Session object to create your entity.
So each POST action will have something like the following:
[HttpPost]
public ActionResult Step1(Step1ViewModel model)
{
if (ModelState.IsValid)
{
Session["Step1"] = model;
return RedirectToAction("Step2");
}
// errors
return View(model);
}
Then, your final POST action:
[HttpPost]
public ActionResult StepFinal(StepFinalViewModel)
{
if (ModelState.IsValid)
{
var myEntity = new MyEntity();
var step1 = Session['Step1'] as Step1ViewModel;
myEntity.SomeField = step1.SomeField;
// ... repeat for field in view model, then for each step
db.MyEntities.Add(myEntity);
db.SaveChanges();
Session.Remove('Step1');
// repeat for each step in session
return RedirectToAction("Success");
}
// errors
return View(model);
}
All of your form information will be in the .cshtml file like this:
#using (Html.BeginForm("Controller Action Method", "Controller Name", FormMethod.Post, new { id = "Form Name" }))
{
// Form Elements here
}
Then you can simply add a submit button that submits the form to your Controller for processing.

Preparing models for ASP.NET MVC views

When returning strongly typed models for views such as Create and Edit (when validation of the object we are editing fails) I usually prepare the models like this:
//
// GET: /Invoice/Create
public virtual ActionResult Create()
{
// prepare the empty model
Invoice model = new Invoice();
model.Client = new Client();
model.Client.PostCode = new PostCode();
return View(model);
}
//
// POST: /Invoice/Create
[HttpPost]
public virtual ActionResult Create(Invoice document,
FormCollection collection)
{
// check for errors
if (!ViewData.ModelState.IsValid)
{
document.Client = new Client();
document.Client.PostCode = new PostCode();
return View(document);
}
Now I know that this is how others do it too, in fact you can see this same approach in MVC Music Store sample and others. However, this is very error prone because one might accidentally left out a referenced entity which is required in the view. It also requires too much thinking about view/model interaction. What I would want is some sort of automatism. Value typed properties in models usually aren't the problem because they default either to zero or empty strings. Reference types however should be initialized with new..but sooner or later we end up with code blocks that are being repeated, reference type properties being left out, etc..And I don't think it's good coding practice either.
What are other options we could take?
UPDATE:
Because replies kinda missed the point (they do not relief us of thinking about models in any way and require additional code in model classes), I was thinking if this option would work:
Use custom Action filter,
override OnActionExecuted()
use Reflection inside this method to take out the object from the Model and enumerate its public properties and try to initialize them.
I have steps 1, 2 and 3 partially implemented but I cannot figure out how to do "... = new Client();" programatically with Reflection.
Make the properties of your model return a new instance if it is null
private Client client;
public Client Client
{
get
{
if (client == null)
client = new Client();
return client;
}
}
I suggest that you use a Strongly typed view bound to a ViewModel that is distinct from the Domain Model you're trying to create, and put whatever necessary logic into the constructor of the ViewModel
I'm not sure I fully understand your question. You want what automated? ViewModels and Views? Are you creating strongly typed views?
I have created a T4 template that I point to a database and it generates a ViewModel for every table. Foreign keys become drop down lists, long strings get a TextArea instead of TextBox, etc. I then delete the ones I don't need and modify the ones I want to keep. It's not a totally automated process, but it does 80 to 90 percent of the work, depending upon the project.
Then I generate strongly typed Views from those ViewModels.
It also sounds like you might be interested in AutoMapper.

Categories

Resources