DRYing MVC helper options - c#

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.

Related

Does a model need to be explicitly passed to a view if the model is strongly bound to the view?

I have an example from a project I'm working on in which a particular model is passed on to the view from the controller, like this:
result = View(stu) //stu is an object of type Student
//
//
return result;
In that particular view, at the top I have #model Project.Models.Student and I have textboxes which bind the information entered to the model. However, in another controller I have another method that has the following code:
//
//
result = View();
//
and the view for this has #model.Project.Models.Login, but I can still bind things to the model from this view just like in the other view (eg, I have something like:
#Html.TextBoxFor(model => model.login_name, new { maxlength = "30" })
I didn't pass a model to the view like I did in the first method, and yet it seems to have the same functionality. If I am using #model at the top of the view, does that mean that it is not necessary to explicitly pass a model object as a parameter from the controller method?
If you declare #model at the top of your cshtml page, then your page expect to get model of the type you have declared. Of course you can choose not to use a model in your page, and in this situation your declare is useless.
In addition you can declare a model, but don't send any object to the view.
If you send an object, it must be the same type as you declare in #model.

How to supply dynamic data set to a single view using ViewModels

(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.

ASP.NET MVC Html.Editor pass model to Editor Template

I have an Editor Template called "Address.cshtml" that has a model defined as:
#model Acme.Models.Address
In a View I want call the Editor Template and pass a local variable of the same type, and define the name it will use for the variables, I've tried a number of things including:
#Html.Editor("address", "Address", new { Model = address })
How do I pass the model?
Note, I cannot use #Html.EditorFor() because the view uses a different model.
The only purpose of EditorFor is to work with your view's model. If you need to work with a completely different class instance that's not your view's model or accessible through you're view's model. Then just use Html.Partial. They're functionally the same. If you're worried about using a specific editor template, you can always pass the full path to the view to Html.Partial.
In a view (in its header or somewhere else, but before the row you are calling your Html.Editor) you can add the model in the ViewData with the key equals to your Html.Editor's expression and it will be used as a model in your called editor. For example:
#{
var address = new Acme.Models.Address();
ViewData["address] = address;
}
#Html.Editor("address", "Address")

VS 2012 MVC - How does file structure of View files affect access to relevant Model / Controller files?

To explain, my initial setup was as follows:
Contollers:
Library => Controllers => ParametersController.cs
Library => Controllers => ReaderLevelController.cs
Library => Controllers => ResourceTypeController.cs
Views:
Library => Views => Parameters => Index.cshtml
Library => Views => ReaderLevel => Index.cshtml
Library => Views => ResourceType => Index.cshtml
Each view referenced its appropriate Model like so:
#model IEnumerable<Library.DAL.PrmTbl_Level>
and each controller contained an ActionResult for Index(), some FormCollections and not much else. The screens displayed fine, and pulled, edited and updated the db no problem.
I wanted to change my views to a more descriptive hierarchy so moved my View files like so:
Views:
Library => Views => Parameters => Index.cshtml
Library => Views => Parameters => ReaderLevel => Index.cshtml
Library => Views => Parameters => ResourceType => Index.cshtml
I then updated the ParametersController.cs file to reflect the new ActionResults that would now be directed toward it, it being the 'parent' file:
public ActionResult ResourceType() { return View("ResourceType/Index"); }
Now however the new 'sub' screens (ReaderLevel & ResourceType, in this example) do not display, as an error is thrown when a foreach loop tries to run through their relevant Model - which now returns as null. I am confused as to how changing the location of the View file can alter the viability of the data access (as, to my mind, the Model is filled via the ab path #model IEnumerable<Library.DAL.PrmTbl_Level> which does not change).
Can someone explain how changing the View's location affects its accessing of its Controller and Model?
EDIT
My current setup (file structure as above):
ParametersController
public ActionResult Index() {
ViewBag.Title = "Parameters";
return View();
}
public ActionResult ResourceType() {
return RedirectToAction("ResourceType");
}
This gives me the appropriate url, but 'Firefox has detected that the server is redirecting the request for this address in a way that will never complete.' Using the RedirectToAction "ResourceType","Index" resolves to the url '/Index/ResourceType' and the resource cannot be found.
I'm not clear on this line of code:
public ActionResult ResourceType() { return View("ResourceType/Index"); }
Does this mean that you are loading the resource type view from the Parameters controller?
If that's the case, it's not what you want to do. You want to use RedirectToAction.
Edit: you could also supply the model in your existing code using a different View() overload. I assume this isn't what you want, though, since you do have a ResourceTypeController.
Edit #2
Based on your comment,
I then figured that once those sub views were loaded, their own
individual controllers would take over?
No, it actually works the other way around. The controller loads the view (and passes the model, if one is required). It looks like you are trying to load the view, and expecting it to load its own controller. Redirect to the proper URL, and the controller will take over.
So a few things I'm noticing here:
In the action method you showed above you are not passing a Model to the View, you are simply calling the view. To pass the View a Model you need to supply it as the second argument to the View method, i.e. return View("ResourceType/Index", model);. Also I believe if you are passing a path, instead of just the name of the View, to the View method you need to specify the full path and file extension, i.e. ~/Views/Parameters/ResourceType/Index.cshtml. #Phil Sandler made a good point about using RedirectToAction as well since you are attempting to return a View associated with another Controller.
If you want to change the default View hierarchy than you should consider extending the RazorViewEngine to supplement its search locations with your new directories. Have a look at this question for an example (it uses the WebFormViewEngine but you do the same thing for Razor): Can I specify a custom location to "search for views" in ASP.NET MVC?
Lastly, I don't really understand your new hierarchy. Why are the ReaderLevel and ResourceType Views being placed under the Parameters subdirectory if they don't belong to that Controller? The new hierarchy actually seems more confusing which may make it harder to understand later. Maybe we don't have all the information to understand your decision but it seems odd.

TextBoxFor<DIFFERENT VIEW MODEL>() for AJAX operation?

I have a view model ViewModel1 that has all of the proerties for my view.
In my view I need 2 text boxes that will be used in a AJAX call to another action that has different parameters then the view I'm currently on.
Here's what I'd like the code to look like:
#using(Ajax.BeginForm("AjaxAction", "Home", new { TestId = Model.TestId }, new BOHAjaxOptions(), new { id = "newform", name = "newform" }))
{
#Html.TextBoxFor<DIFFERENT VIEW MODEL>(model => model.FIELD1)
#Html.TextBoxFor<DIFFERENT VIEW MODEL>(model => model.FIELD2)
Submit
}
I figured this would make sense since I want to be able to use the "AjaxAction" action on different views from different controllers since this action is going to be called from multiple parts on the site.
Or am I just not seeing the right picture here? Should I just include FIELD1 and FIELD2 in all of the ViewModels that need to call this AJAX action?
You should include all the data needed for the view in the view model, including data that might be posted back to another action as a separate model. Note that generally there isn't a one-to-one correspondence between a entity model and a view model. In nearly all cases my view models include either more or less data that the primary entity that the view is focused on.

Categories

Resources