Cross area ViewComponents - c#

I have a ViewComponent stored in an area named "Dashboard" but now I want to use this ViewComponent in another area called "Appplications". Yes I could add it to the root views/shared folder but I'm striving to make a very modular application through the strong contained use of areas.
ASP.NET 5 RC1 MVC 6 doesn't seem to support cross area references to other components.
How do I add additional view locations? I need to add:
/Areas/Dashboard/Views/Shared/Components/DashboardMenu/Default.cshtml
as a search location to the view renderer
InvalidOperationException: The view 'Components/DashboardMenu/Default' was not found. The following locations were searched:
/Areas/Applications/Views/Application/Components/DashboardMenu/Default.cshtml
/Areas/Applications/Views/Shared/Components/DashboardMenu/Default.cshtml
/Views/Shared/Components/DashboardMenu/Default.cshtml.

Worked it out...
Startup.cs
// Add additional razor view engine configuration to facilitate:
// 1. Cross area view path searches
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new RazorViewLocationExpander());
});
Then create a class called RazorViewLocationExpander.cs
using Microsoft.AspNet.Mvc.Razor;
using System.Collections.Generic;
using System.Linq;
public class RazorViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context) { }
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
List<string> locations = viewLocations.ToList();
locations.Add("/Areas/dashboard/Views/Shared/{0}.cshtml");
return locations;
}
}
I would not recommend this generally. I am using this solution as a special case because I am using an area to isolate templating and core code for my other (members-only) areas to consume - so they need to know where to find this shared code. I am attempting to separate the public code from the admin code and this is the cleanest, most modular solution I can think of. The dashboard area will be present for all website that require members-only administration area. It is bending the rules of MVC ever so slightly.

Related

Customizing nopCommerce

We are developing an nopCommerce based application. Our login page needs to be minimalistic and would need only an email id, password entry fields and a Login button.
Could you point me to best practices for achieving the above objective ?
Do I modify the corresponding pages found in \Presentation\Nop.Web\Views\Customer\ & controllers in \Presentation\Nop.Web\Controllers\
Or
Is there a better way of doing this and organizing all the modified files in one place/folder so that upgrading to future versions of nopCommerce will not be difficult ?
The requirement is to ensure that all the changes made to the project(views/controllers etc) are in one folder so that they are not overwritten when we upgrade to a newer version of nopCommerce.
I read somewhere that you can copy stuff you need to change (Login.chtml, CustomerController) to Themes/DefaultClean and then make your changes in this folder. I dont remember where i read it.
I feel doing so will make it that much easier to maintain our codebase because all your custom code is in one place/folder/sub folders
Is this a best practise? And is there a disadvantage to this method of doing things?
The best way to modify your nopCommerce project without changing anything in the core code would be to use the plugin functionality which is described here (assuming you're using the newest version 4.40).
To change the login page you would then need to create your modified version as a .cshtml file in your plugin. You then need to set this file as Content and set the Copy to Output Directory property to Copy if Newer or Copy Always.
You also need to implement the IViewLocationExpander interface so that the Razor Engine knows that it should use your custom Login Page. The implementation should look something like this:
public class MyViewLocationExpander : IViewLocationExpander
{
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if(context.ViewName == "Login")
{
viewLocations = new[] { "PathToCustomLoginPage" }.Concat(viewLocations);
}
return viewLocations;
}
public void PopulateValues(ViewLocationExpanderContext context)
{
return;
}
}
After that you also need to register your ViewExpander by implementing the INopStartup interface. The implementation would look something like this:
public class MyStartup : INopStartup
{
public int Order => int.MaxValue;
public void Configure(IApplicationBuilder application)
{
}
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new MyViewLocationExpander());
});
}
}

.net core replacement for MvcCodeRouting. Namespace based routing

We have ASP.NET MVC 5 project, that we have plans to migrate to ASP.NET Core 3. Currently I gather dependency list and their equivalent replacements on the new new platform.
We are using MvcCodeRouting package to separate different workflows between different C# namespaces as was described in https://maxtoroq.github.io/2013/02/aspnet-mvc-workflow-per-controller.html.
Now with new platform in place, we need something similar. As last resort we could just specify all our namespaces in routing table, but I'd rather not to do so.
Any suggestions on how accomplish similar behavior?
EDIT:
I think with example it would be more understandable what I'm trying to accomplish.
We have following structure of controllers:
- Namespace1
-- Workflow1Controller.Index/Edit/Action
-- Workflow2Controller.Index/Edit/Action
-- Workflow3Controller.Index/Edit/Action
- Namespace2
-- Workflow4Controller.Index/Edit/Action
Workflow1Controller code:
namespace RootProjectNamespace.Controllers.Namespace1
{
class Workflow1Controller : Controller
{
public ActionResult Index() {}
// and so on
}
}
Appropriate Views are placed in similar manner.
And using MvcCodeRouting we able to create Action urls by following:
Url.Action("Namespace1.Workflow1Controller", "Index") // Creates -> ~/Namespace1/Workflow1Controller/Index
Url.Action("Namespace2.Workflow4Controller", "Action") // Creates -> ~/Namespace2/Workflow4Controller/Index
Is there possibility to achieve similar in .net core without explicit hardcoding routes in route table?
As an start point for your solution, you can create a custom IApplicationModelConvention and change the routing to use namespace in the routing.
There's an example in docs showing how you can do this. To learn more you can take a look at this great docs article: Work with the application model in ASP.NET Core.
To do so, first create the following NamespaceRoutingConvention class:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var hasAttributeRouteModels = controller.Selectors
.Any(selector => selector.AttributeRouteModel != null);
if (!hasAttributeRouteModels)
{
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = controller.ControllerType.Namespace.Replace('.', '/')
+ "/[controller]/[action]/{id?}"
};
}
}
}
}
Then in startup, register the convention like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.Conventions.Add(new NamespaceRoutingConvention());
});
}
Then you can browse:
http://localhost:xxxxx/SampleProject/Controllers/Home/Index
I have recently come across this issue.
An MVC .NET Framework project I had been working on needed migrating to .NET Core, however it was made up from lots of smaller assemblies (DLLs), each containing different parts of the overall codebase.
MvcCodeRouting was used to implement this, and provided a way for the assemblies to be utilised, routed, and also provided the ability for the central (core) project to utilise the relevant views, stored within the assemblies.
I have found that the .NET Core "Application Parts" feature seems to fulfill most of these points, with it having the ability for projects to be seperated between different assemblies, imported into a main project, and utilised.
The only real functional difference in using this method is that each "Application Part" generates two assemblies instead of one, with one containing the controllers and module logic, whilst the other contains the (Razor) views (if the specific module has any).
I'm still experimenting with this, however I thought I'd add this here for the sake of completeness.
Below are some useful links to documentation regarding this feature:
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-1.0#sample-generic-controller-feature
This is the main documentation for the feature within the ASP.NET Core documentation.
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-1.0&tabs=visual-studio
This tutorial demonstrated creating an assembly containing only Razor Views, if some of the modules require no logic.

Using Application.Controller MVC

Ok basically I am building a nuget package, and one of the things i would like the class to do is get the current applications controllers
So at the moment in the main project I do the following
using SolutionName.Website.MVC5.Controllers;
What I want to be able to do is something like
using this.Application.Controllers;
So it dynamicaly fills in the namespace for whatever solution the package is installed to.
I have sat here for an hour going through the possible permutations and have also googled a fair bit but not sure exactly what to google for
This is including API Controllers and I am Using MVC 5
Cheers
Martyn
The following little piece of reflection will give you all the namespaces for files that end in "Controller".
var namespaces = this.GetType().Assembly.GetTypes()
.Where(t => t.Name.EndsWith("Controller"))
.Select(x => x.Namespace).Distinct().ToList();
You would need to call this from within your code, preferably in the Global.asax.
Keep in mind that your controllers may be scattered across a number of namespaces, so additional logic will have to account for that. The number of namespaces is solely determined by how rigidly you structure your applications.
Alternatively, you can also pull types that inherit directly from 'System.Web.Mvc.Controller', as pointed out by Andrew Whitaker.
var namespaces = this.GetType().Assembly.GetTypes()
.Where(t => t => t.IsSubclassOf(typeof(Controller)))
.Select(x => x.Namespace).Distinct().ToList();
After using the information that #DavidL and #AndrewWhitaker gave me I thought I would post a little code snippet of how this can work in an MVC application as it maybe useful to go hand in hand with the question
I created the class below
public class GetControllerNameSpace
{
public static string NamespaceTag;
public void GetControllerNameSpaceMethod()
{
var NamespaceTagList = this.GetType().Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).Select(x => x.Namespace).Distinct().ToList();
NamespaceTag = NamespaceTagList.FirstOrDefault();
}
}
and then in the RouteConfig.cs
using Controllers = GetControllerNameSpace;
This then lets me pass the information to my method here
private static IEnumerable<Type> GetTypesWithFixedControllerRouteAttribute(RouteCollection routes)
{
//This is where i am passing the variables
foreach (Type type in Assembly.GetAssembly(typeof(Controllers.HomeController)).GetTypes())
{
// Register any fixed actions
RegisterFixedActionRoutes(type, routes);
if (type.GetCustomAttributes(typeof(FixedControllerRouteAttribute), true).Any())
{
yield return type;
}
}
}
Please note that this works in my use case and may require more work when using multiple controller namespaces as mentioned in #DavidL's post

MVC.NET HttpSessionStateWrapper - what is best usage practice?

We are developing large MVC project and we have an intension to use HttpSessionStateWrapper (as well as HttpRequestWrapper, HttpResponseWrapper, etc) to add extended functionalities do this objects. It would be adding session messages, additional collections, html metadata with response - stuff like that, managable from controllers and accessible in the views when needed.
I have done it in a smaller project and it gennerally worked well, except some casting issues here and there, but it can be worked around by not using wrappers outside controllers or eventually views. Every controller would be a custom controller with a code like that:
public class Controller : System.Web.Mvc.Controller
{
public new CustomHttpResponse Response
{
get
{
return (CustomHttpResponse)HttpContext.Response;
}
}
public new CustomHttpRequestRequest
{
get
{
return (CustomHttpRequestRequest)HttpContext.Request;
}
}
//etc...
}
ContextWrapper would be created in a custom MvcHandler. Response, request and session wrappers would be created and taken from ContextWrapper .
Is this a good policy to use wrappers to extend functionalities, or they where intended only for creating testing mocks?

MVC 3 solution with two views/controller/model. (Possible MVC, MVP hybrid?)

I am starting with MVC 3 and am planning in separating the model and the controllers into their own separate projects. I'll follow the suggestions made from this post for this:
asp.net mvc put controllers into a separate project
The purpose of separating them into separate projects is that there are chances that I may have to add a web service project to the solution and I’d like it to reuse the same functionality exposed by the controller project. So the solution will be formed of two view projects, WebServices and WebSite, the controller project and the model project.
I’d like to know if this is possible and if it’s a common scenario with MVC.
Update 1:
With your suggestions I agree and think it’s best to keep the view and the controllers together.
Would it be possible to have a hybrid of MVC and MVP? I have a feeling I am really overdoing things here so please let me know what you think.
So I would have:
1 – Web project with controllers.
2 – WebServices project
3 – Presenters/Interfaces.
4 – Model.
The controllers would then become the views in an MVP model. Also each web service would become a view in an MVP model.
For instance we could have the following, interface, presenter , controller.
public interface ICustomers {
string[] Customers{set;}
}
public class CustomerPresenter {
ICustomers view = null;
public CustomerPresenter(ICustomers view) {
this.view = view;
}
public void GetCustomers() {
view.Customers = new string[]{"Customer1","Customer2"};
}
}
public class CustomerController:ICustomers {
CustomerPresenter presenter = null;
public CustomerController() {
presenter = new CustomerPresenter(this);
}
private string[] customers = null;
public string[] Customers {
set { throw new NotImplementedException(); }
}
public void GetCustomers() {
presenter.GetCustomers();
//Return view.
}
}
The WebService would be a view in an MVP model.
public class CustomerWebService:ICustomers {
CustomerPresenter presenter = null;
public CustomerController() {
presenter = new CustomerPresenter(this);
}
[WebMethod]
public void GetCustomers() {
presenter.GetCustomers();
//Return response.
}
My projects are built specifically for the reason you stated, you want to implement a web service. I would not recommend separating the controllers because this is an actual part of the web project. What you actually want is around 3-4 different projects.
Repository/data layer (may contain your domain level models)
Domain Layer (optional)
Service layer. (Now this is where you can point your web service to very easily, all your re-usable logic should be here, not in the controller)
Web Layer. (Contains View Models, Views and Controllers)
I placed them in levels. Basically, the repository, domain and service layer are completely de-coupled, meaning you can use these libraries without a server or asp.net. A wpf application can just call to the service layer because the web layer is just for presentation purposes.
I am not sure it's common to separate the views and the controllers on their own projects. It might be but I haven't seen it personally.
The way I would split it initially is:
One project for views and controllers
One project for models
If and when you have the need to support different views you could update your controller to return something different depending on the type of request. It's common to have controllers that return different things for different clients (for example HTML vs JSON.)

Categories