Access Views and Controllers which are under Sub folders for specific scenarios? - asp.net-core

On my all actions methods I use this route attribute to use - sign.
[HttpPost]
[Route("lower-case-converter")]
public IActionResult Index(BassModel model)
Since I have many case converters I want to put them inside a folder called "CaseConverters". I made a folder in views folder called ""CaseConverters". Note that there are other tools too.
So I changed the route attribute like this
[Route("~/CaseConverters/lower-case-converter")]
Above not worked. So I changed it to
[Route("/CaseConverters/lower-case-converter")]
Still its not working. Note that i want to add this folder to Controllers folder too. How to acehve this?

To specify a route for lower-case-converter action inside your CaseConverters controller you can specify the route as follows, (I'm assuming CaseConverters is your controller. If yes It's better to use the correct naming convention. Ex: CaseConvertersController)
[Route("CaseConverters/lower-case-converter")]
You can also specify a route template for your controller class. as follows,
[Route("CaseConverters")]
public class CaseConvertersController : Controller
{
[Route("lower-case-converter")]
public IActionResult LowerCaseConverter()
{
//you implementation
}
}
For the above code route template for the action is CaseConverters/lower-case-converter. And also make sure use have used app.UseMvc() and MapRoute() methods inside Configure() method inside the startup.cs

Related

Inject httpcontext into custom attribute outside controller in .net core

I need to inject httpcontext into custom attribute that is used outside the controller. I found several solutions how to do it in controller, but my case is little tricky. Now I have following code in my PermissionController
[PermissionFilter(PermissionEnum.Permission, AccessLevelEnum.Create)] <-- it works perfectly
[HttpPost("users/{userId}")]
public async Task<IActionResult>
AssignPermissionToUser([FromBody] List<PermissionToVM> permissions, int userId)
{
await _permissionService.Assign(permissions); <-- .Assign() extension
//code goes here
}
In the method above there is a call of extension method .Assign. This method code is available below.
//[SecondPermissionFilter(PermissionEnum.Permission,
AccessLevelEnum.Create)] <-- here I check permissions but don't
know how to inject the httpcontext
public async Task Assign(List<PermissionToVM> permissions)
{
//code goes here
}
As mentioned in many websites I visited f.e. here https://dotnetcoretutorials.com/2017/01/05/accessing-httpcontext-asp-net-core/ injecting of httpcontext outside the controller can be done using IHttpContextAccessor. The problem is that I don't know how to use it without passing it into constructor. My custom attribute should be called as decorator [SecondPermissionFilter(PermissionEnum.Permission, AccessLevelEnum.Create)] when only permission settings should be passed, so there is no any reference to httpcontextaccessor.
Is this even possible? If not, there is maybe another way to do this?
EDIT: Here is the code of SecondPermissionFilter class:
public sealed class SecondPermissionFilterAttribute : Attribute
{
private readonly PermissionEnum _requestedPermission;
private readonly IEnumerable<AccessLevelEnum> _accessLevelCollection;
private readonly IHttpContextAccessor _contextAccessor; //<-- how to inject?
public PermissionFilterAttribute(PermissionEnum requestedPermission, params AccessLevelEnum[] accessLevelCollection)
{
_requestedPermission = requestedPermission;
_accessLevelCollection = accessLevelCollection;
}
}
What you are after is something called Property Injection. As per the official docs this is not something that is supported out of the box by the .NET Core DI Container.
You can however use a third party library such as Ninject or Autofac - both of which are available via NuGet.
In my opinion the Ninject syntax is nicer, however as noted in this answer, and this answer property injection itself is considered bad practice. So if possible I would try to avoid it.
So you should instead use one of the three methods specified by the filter documentation, this answer breaks things down a bit more.
Edit
This answer deals specificically with Attribute injection, the second answer looks to achieve this without external dependencies.

How are Razor Page View Component paths searched? I fail to get them working, .NET Core tries to include irrelevant model

I cannot get ViewComponent to be included in a page.
I have a folder structure:
- Pages
- Components
- ExternalSystems
- Default.cshtml
- Views
- Shared
- Components
- ExternalSystems
- Default.cshtml
Class file
public class Default : ViewComponent
{
private readonly Models.PermissionRegisterContext _context;
public Default(Models.PermissionRegisterContext context)
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var externalSystems = await _context.ExternalSystem.ToListAsync();
return View("Default", externalSystems);
}
}
within Pages/Index.cshtml i'v tried to include this component by trial & error (i'm learning this stuff after all):
#await Component.InvokeAsync("ExternalSystems");
#await Component.InvokeAsync("/Pages/Components/ExternalSystems");
#await Component.InvokeAsync("/Pages/Components/ExternalSystems/Default");
#await Component.InvokeAsync("/Pages/Components/ExternalSystems/Default.cshtml");
#await Component.InvokeAsync("/Views/Shared/ExternalSystems");
I expected that exception will show places searched like it does for #Html.Partial:
InvalidOperationException: The partial view 'none' was not found. The following locations were searched: /Pages/none.cshtml /Views/Shared/none.cshtml
However for every single call to #await Component.InvokeAsync it spits this exception:
InvalidOperationException: Could not find an 'Invoke' or 'InvokeAsync' method for the view component '.Pages.Identities.IndexModel'.
First of all, it doesn't show me paths searched. Second, wait what? Why are you telling me something about Pages.Identities? Yes, I have that model, but it is nowhere referenced in neither Pages/Index.cshtml or View Component i'm trying to include.
Could someone please provide me with a guidance to determine: How view component paths are searched? For Razor Pages it is not documented, only in 3rd party site.
Anyway it doesn't work that way for me - what would be the debugging steps? Console debug doesn't show anything useful.
dotnet 2.0.7
Let's say you have created a view component as follows :
public class ExternalSystems : ViewComponent
{
public ExternalSystems()
{
//constructor can have dependencies injected.
}
public IViewComponentResult Invoke()
{
//View is a helper method available in classes inherited
//from ViewComponent that returns an instance of
//ViewViewComponentResult. It has multiple overloads as described
//later.
return View(viewName,viewModel);
}
}
View method has multiple overrides :
View() - use default view for view component without any viewmodel
View(viewModel) - use default view for view component with specified viewmodel
View(viewName) - use specified view for view component without any viewmodel
View(viewName,viewModel) - use specified view for view component with specified viewmodel
When you try to render this view component from a Controller, view will be looked up at following locations :
"/Views/{ControllerName}/Components/ExternalSystems/{ViewName}.cshtml" .
So, if you are using HomeController and have specified viewName as ExternalSystemsView in the View(viewName,viewModel) call , your path becomes
/Views/Home/Components/ExternalSystems/ExternalSystemsView.cshtml . This allows each controller to have its own custom view for the view returned by view component.
If the ExternalSystemsView.cshtml is not located at above path , it will be looked up at /Views/Shared/Components/ExternalSystems/ExternalSystemsView.cshtml
You can override the lookup position by passing the complete path of view - View("Views/Shared/Components/Common/YourView.cshtml") while calling View(viewName,viewModel) from your ViewComponent's Invoke method.
Note : If you don't specify a viewName, it defaults to Default.cshtml which is different from Index.html used for controllers
For your case, #await Component.InvokeAsync("ExternalSystems") is the correct call as it expects the viewcomponent name as parameter. ViewName will be picked up from what you have passed as the viewName parameter value to View(viewName,viewModel) call in your ViewComponent's Invoke method and will default to Default.cshtml if no viewname has been specified.
Alright, my mistake was that I didn't see I have decorated ...Pages.Identities.IndexModel with [ViewComponent]. It was unintentional and was just trying to make ViewComponents work by trial & error and failed to see this mistake.
Basically dotnet discovered type that didn't have either Invoke/InvokeAsync function and thus was stuck on that exception.
I validated that these paths are searched when providing "HelloWorld" as view name:
/Pages/Components/HelloWorld/HelloWorld.cshtml
/Views/Shared/Components/HelloWorld/HelloWorld.cshtml

Using attribute routing on a controller is forcing me to manage all routes

I am just getting to grips with Asp.net Core and I'm trying to set up a basic site.
I want to build an admin panel that is under a subdirectory.
I have a simple controller which was scaffolded by the EF crud feature.
So it seems that from the examples I should just be able to add a [Route()] attribute to the controller and it will prefix everything. Something like this:
[Route("Admin/Subfolder/[controller]")]
public class EventsController : Controller
{
}
But when I do that I just get an error page saying "multiple actions matched" and it lists index, details, create, etc.
I can get it working if I then go through every method and put a [Route()] attribute on it but this doesn't seem to be in line with the documentation.
It feels like I should be able to just add a prefix to the controller route without having to take over management of every route within the controller. Case in point, the POSTS are not working now and I'm not sure what the format of the route attribute should be for them.
What am I doing wrong?
You are doing it correctly. Default route attribute can be applied at the controller level. “Placing a route attribute on the controller makes all actions in the controller use attribute routing.”
Can you post complete code of your controller? There must be something else going on in there. Make sure you use HttpPost/HttpGet attribute for actions with the same name, like so:
[Route("Admin/Subfolder/[controller]")]
public class EventsController : Controller
{
[HttpGet]
public IActionResult NewEvent()
{ }
[HttpPost]
public IActionResult NewEvent()
{ }
}
Good explanation on routing can be found here

How to set up layout for all controllers in Yii module?

in my Yii app I have one module called admin, so the module class is AdminModule, it extends the CWebModule class and is located in the AdminModule.php file. According the documentation the CWebModule has a layout property which is shared among all module controllers in case the controllers itself do not have any layout defined.
My controller does not have any layout defined and in AdminModule.php i put this:
$this->layout='webroot.themes.bootstrap.views.layouts.column2';
$this->layoutPath = Yii::getPathOfAlias('webroot.themes.bootstrap.views.layouts');
However, my controllers in admin module are still using some other layout, i think it is the one defined in the Controller.php in components directory. Why is that? How do I setup shared layout for a particular module?
The solution is to slightly change my code, like this:
$this->layoutPath = Yii::getPathOfAlias('webroot.themes.bootstrap.views.layouts');
$this->layout = 'column2';
as with the path specified I do not need to specify whole path alias for a layout. I have these to lines in init() function of my AdminModule.php and it works fine.
try setting the layout path as shown below
$this->layout="webroot/themes/bootstrap/views/layouts/column2";
The solution is set the layout on beforeControllerAction in your module. It should work.
I've answer the similar question, please refer to it
Yii module layout
if you use Module and Theme(like bootstrap) at the same time,it means Yii will find view file(include layout file, a special view file) in Theme folder firstly,there is an important function named resolveViewFile in CController, you can add debug point on this to watch out how it works, and below is my solution:
1. I have a module named "admin".
2. In AdminModule's init function, add:
$this->layout = 'column2';
3. remove module's all view files to theme folder
you can put the following code in your controller.
public function init()
{
Yii::$app->setLayoutPath($this->module->getBasePath().'/views/layout');
}
or you can put the following code in your module bootstrap class
public function init()
{
parent::init();
Yii::$app->setLayoutPath($this->getBasePath().'/views/layout');
}

initialize simple membership in MVC 4

I have a problem with my MVC 4 application which used to work fine, but stopped for some reason, and I cannot find out why. I use simple memebrship provider and code first approach. This is my Index action method in the home controller
[Authorize]
public class HomeController : Controller
{
private IActivityRepository repo;
public HomeController(IActivityRepository activityRepository)
{
repo = activityRepository;
}
//Allow anonymous to allow to create database if there isn't one yet
[AllowAnonymous]
public ActionResult Index()
{
repo.InitializeDatabase(); //!!!!!!!!!!!!!!!!!!!!!
return RedirectToAction("ManageActivities");
}
The whole concept of mine is that if database doesn't exist it gets created in InitializeDatabase Method. Then user is redirected to ManageActivities action method which is decorated with [Authorize] attribute, what in effect takes user to login action method in AccountCotroller (out of the box in MVC4). This controller is decorated with [InitializeSimpleMembership], what fires InitializeSimpleMembershipAttribute filter.
This logic worked fine for me a while ago. Today I wanted to create a new database for testing purposes. When I create data context I call the base class with a custom name for the database like so:
public class ActivityLogContext : DbContext
{
public ActivityLogContext() : base("ActivitiesConnection")
{
}
So I've changed details for my connection string and run the application. Unfortunatelly, for some reason the code hits InitializeSimpleMemebership filter before running Index method from the home controller (even though its decorated with [AllowAnonymous]). In effect simple membership is initialized but database does not yet exist, what runs me into error.
My question is, why InitializeSimpleMemebership filter is getting released on application start if Index method doesn't require authorization?
I would eliminate the use of the InitializeSimpleMembership as discussed in this article. Move initialization to the Global.asax Application_Start method and do your initialization there also, so that it happens in the correct sequence.