I am really struggling with changing my mindset from the #html.Action way of doing things to this new Component.Invoke way of life.
So to outline my structure. I have a Controller called "Contract" with the standard Index IActionResult.
In there I have a partial view that is a form, which I would have normally have just loaded from the index.cshtml using the following:
if(Something = my value){
#Html.Action("p_NewContract", new { id = x })
}
Which always worked.
For what I can read, I now need to make a new folder outside my nicely structured view and partial view folders under each Controller. The folder I HAVE to create is "Shared/Components/p_NewContract.cshtml"
And then in that folder, I need to create a .cs file with the postfix of ViewComponent (p_NewContractViewComponent.cs) And somehow the runtime will know those two things belong together?
In that CS file I then add [ViewComponent(Name = "p_NewContract")] before the public class and then after all that I can add to my Index.cshtml
#await Component.InvokeAsync("p_NewContract", new { id= 2})
I have done all of this, no sign of it working. Am I missing something fundamental, is there a way I can get my old Html.Action back? It was so much easier.
Any assistance to help my frustration would be great!
Caz
There's two parts to a view component: your class that derives from ViewComponent and a view that component should return. By convention, your view component classes actually go in /ViewComponents, while again by convention, your view goes into Views/Shared/Components/{component name}/Default.cshtml.
That's just convention, though. The same as how Razor automatically finds views based on controller/action name. In truth, the classes can go wherever you like. They are brought in via reflection, so as long as it's somewhere in the assembly or a referenced assembly, it will be found. The views likewise can be moved wherever you like, you just have to tell it where to find them, since it won't be able to do it automatically based on convention anymore. See: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components#view-search-path
Related
I decided to try avoid view models and pass dynamic data to views, here is the test code I wrote in my controller:
dynamic model = new ExpandoObject();
var user = unitOfWork.UserRepository.Get().First();
model.Title = "Dynamic test";
model.User = user;
return View(model);
And in View:
#Model.Title
#Model.User.UserName
I like this method more because I don't have to write ViewModel class for every controller action, and also don't have to change ViewModel every time I decide to pass a new property to View.
The only downside I see is lack of intellisense in Views, because they are not strongly typed any more, but that doesn't seem as a big problem.
My question is, is there any other downsides of using this approach instead of ViewModels, because I am just starting a new project in my company, and I don't want to regret later if I use this approach...
I believe that the main advantage of creating view models is readability and resuseability of the code.
If I go to your piece of controller which you pasted, technically I can understand it, but I don't know what the model is about from business perspective. Of course you can name it more descriptive, but I don't believe it resolve the issue.
Second thing is reuseability, so I can imagine a lot of situation when multiple views can use the same model or some part of models and in this case you need to copy & paste code which creates you dynamic model or create some helper function which do this for you.
Basically I believe that it will be very painful when those dynamic models need to be changed e.g you removed some property in controller, but you forgot to do the same in some of the views. You won't be informed by compilator that something is wrong.
Additionally without strongly typed view model I believe there is no way to create attribute based model validation (which is very common business case)
I have a separate project in my solution that contains some Controllers and compiled views.
I use those controllers for base classes to other controllers in my MVC application and the views are compiled using RazorGenerator.
Lets say B is Base Controller with non abstract action method SomeAction that returns View("_MyView"). _MyView.cshtml is compiled using RazorGenerator.
Lets say controller A inherits B but doesn't override SomeAction.
I've tried to make another view "~/Views/A/_MyView.cshtml" to override the default one, but it doesn't work. My question is how can I accomplish this?
ADDITIONAL INFO
1) I know that the views by default are searched in that order in those paths
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
2) Putting "~/Views/Shared/_MyView.cshtml" does override the view, but not only for controller A, but also for every other controller that inherits
B
3) Overriding SomeAction to return base.SomeAction() doesn't work
UPDATE
I have found similar question here, but doing the suggestion nothing happened
RazorGenerator Issues
I have posted my own issue here
Thank you in advance!
So far my only workaround is to install RazorGenerator on the consumer app and to also set the view _MyView.cshtml as being RazorGenerated. RazorGenator then picks up the correct view.
Another note for other visitors is not to compound the wrong view confusion with the route going to the base controller instead of the the consumer controller. In solving this issue earlier to being able to figure out the actual wrong view was being served by the right controller as the OP and I have an issue with. I have code in my base application_start that removes route duplicates.
Anyone else hitting this issue you need to update the RazorGeneratorMvcStart.cs to set PreemptPhysicalFiles = false in the master project. By default this is not the case and the views in the master project with take priority:
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal,
PreemptPhysicalFiles = false
};
ViewEngines.Engines.Add(engine);
This file is App_Start\RazorGeneratorMvcStart.cs. It is also important to Add the engine rather than Insert it. The default is to insert at position 0.
Note: I hit this issue when updating NuGet packages, it seems that the file gets overritten, resetting this back to the default behaviour.
I have a controller ItemsController that has an instance method GetRecent() which returns IQueryable<Item>
In the index page of my application I want to render a partial view _Recent.cshtml using the results from ItemsController.GetRecent()
To do this, I've written the following in my Index.cshtml
I've added namespace references to the ~/Views/web.config so I don't need to write out the controllers full namespace
#{
ViewBag.ItemsController = new ItemsController();
}
...
#Html.Partial("~/Views/Items/_Recent.cshtml",
((ItemsController)ViewBag.ItemsController).GetRecentItems())
I thought of passing new ItemsController().GetRecentItems() directly or turning GetRecentItems() into a static method however I'm not sure what direction this should take.
I want to know if this is an accepted way of building a partial view from a controller and if not, how can this be accomplished more efficiently?
RenderAction
The first option is Html.Action or Html.RenderAction(What's the difference?). These are part of the ASP.Net MVC framework, and provide a more polished means to implement something similar to your code sample.
With this approach, your view calls the appropriate controller, which fetches any needed data, renders the partial view, and returns it.
I tend to shy away from using this approach as it seems backwards, i.e. the view "knows" quite a bit about controllers and controller structure and is actively executing rather than just consuming.
Still, it's a legitimate option.
Remodeling
You could pass the "recent items" along with the view model for the main view. This is more effort to model, but "pure" in that your view is less coupled to the controllers. Your partial view would not need to change.
Async Rendering
You could render the recent items using an AJAX call to the appropriate controller, and have that controller return the partial view as its result. This is probably the most effort, but elegant in that some of the work is delayed until the rest of the page is rendered (possibly allowing lazy loading, and/or improved page load times).
Done correctly, this approach also allows decent separation of concerns.
Related Question (not identical, as it doesn't focus on passing data to the partial view)
After I found this in our code, I realized a few things:
"get it done now, get it right later" has a limit
I don't actually know where this goes with MVC3
#model int
#using Company.Core.Logic.Financial
#using Company.Core.Repositories
#{
var values = from AccountPlan e in new AccountPlanRepository().GetItemList()
where String.IsNullOrEmpty(e.PromoCode) // filter out promotional plans
select new { id = e.AccountPlanId, name = e.Description };
var items = new SelectList(values, "id", "name", Model);
}
#Html.DropDownListFor(m => m, items)
This specifically is an editor template (#Html.EditorFor(m => m.AccountPlan)), but it made me realize that I don't know where this kind of code goes for common things like menu-builders. If you're using Layouts for MVC3 (and who isn't), where is the code for building a menu across the top based on the user's roles? I'd imagine the view code would be iterating through pre-built menu items and HTML-ifying them, but since the Model is strong-typed, does that mean that all Models need to have knowledge of the menu items?
For once, this is where Webforms made more sense to me, since this would go in the CodeBehind, but I really want to get away from that.
edit: even though I started asking about Layout code, I'm under the assumption it works for EditorTemplates and DisplayTemplates as well. If this is an incorrect assumption, please let me know where these are supposed to go.
edit2: What I ultimately want is to have a clean, possibly even dependency-injectable place to run code that's called from an EditorTemplate. Perhaps this is a case where the EditorTemplate calls immediately into a RenderAction?
Looks like this solves the problem pretty well (see the marked answer, not the original question):
http://forums.asp.net/t/1566328.aspx/1?Building+ASP+NET+MVC+Master+Page+Menu+Dynamically+Based+on+the+current+User+s+Role+s+quot+
Basically, call RenderAction(...) and it will build the model it needs, rather than forcing you to have every model require a list of menu items.
For me personally, I do a lot of menu filtering based off of active directory groups so I need to know what their access levels are across the entire application.
I create a new controller that I call ControllerBase
public class ControllerBase : Controller
{
//authorization group setting an menu creation here.
//set properties and objects to ViewBag items to access from the front end.
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
and then on all the other controllers in my project I just extend from ControllerBase
public class HomeController : ControllerBase
{}
This keeps all my server logic in one file for managing permissions and gives all my pages access to these variables when I need to hide or show different ui elements based on permissions.
Child Actions are perfect for this scenario. The logic required to generate the view is wrapped up in a controller action, like normal, and the view that wants to make use of the child action simply renders the action..
You can also cache these partial views, which would make sense for something like a main menu - as presumably the users permissions won't change that often.
e.g.
[OutputCache(Duration = 300)]
[ChildActionOnly]
public ViewResult MainMenu()
{
var model = GetMenuModel();
return View(model);
}
The view that wants to render the child action does so like this.
#{ Html.RenderAction("MainMenu", "Account"); }
And thus the view calling the ChildAction has no need to know what model the view requires.
There are also overloads on RenderAction, should your child action require you to pass parameters to it.
You shouldn't (have to) access the Repository inside the View. That belongs in the Controller.
And the Menu is implemented in the Masterpage, you don't give much details on specifics.
This question already has answers here:
How do you name your ViewModel classes?
(6 answers)
Closed 6 years ago.
I'm writing an asp.net mvc2 project with a lot of views/partialviews. Reading on good MVC practices, I've been encourage to create ViewModels. Its really nice and makes sense to do so but I'm noticing recently that I can no longer easily remember what model is for what view. I'm hoping there is a nice naming convention for ViewModels that can handle large projects.
My question is can this situation be improved?
I like to keep it simple, for instance, if you have an object named Folder:
View = "FolderView.xaml"
ViewModel = "FolderViewModel.cs"
Make it simple:
ModelName = ViewName + "Model";
If you have model hierarchies, you sometimes need to pass the model from the view to partial views. In that case the above rule can be waived.
If you are keeping your view models in the same assembly as your views, one trick I have used to help organize views with view models in addition to a simple naming scheme like {View Name}ViewModel for the view model class is to do the following:
Create your view class (Shell.xaml)
In the same location, add your view
model class (ShellViewModel.cs)
Rename the view model class file to
be the same name as the view, but
append .model to the file path
(Shell.model.cs)
Edit the project file (either
manually or using the power command
extensions in VS)
Locate the Compile element for
the view model class in the project
XML document
Add a child DependentUpon element
whose content is the name of the
view class
Save changes and reload the project
This will cause the view model class to be visually grouped along side the code-behind file for the view.
For ASP.NET MVC I keep my view models in a ViewModels.{Controller} namespace and the class name is just {Action}. I don't use the ViewModel suffix just like how the view files don't have a View suffix.
I've started going one step further and specifying if a ViewModel is used to generate a Form via EditorFor by ending the name with Form. Helps keep forms vs. simple display models easily identifiable.