Is it possible to render View without create Action in ASP.NET MVC? - asp.net-mvc-4

Is it possible to get/render View without creating Action in Controller? I have many Views where I dont need pass any model or viewbag variables and I thing, its useless to create only empty Actions with names of my Views.

You could create a custom route, and handle it in a generic controller:
In your RouteConfig.cs:
routes.MapRoute(
"GenericRoute", // Route name
"Generic/{viewName}", // URL with parameters
new { controller = "Generic", action = "RenderView", }
);
And then implement a controller like this
public GenericContoller : ...
{
public ActionResult RenderView(string viewName)
{
// depending on where you store your routes perhaps you need
// to use the controller name to choose the rigth view
return View(viewName);
}
}
Then when a url like this is requested:
http://..../Generic/ViewName
The View, with the provided name will be rendered.
Of course, you can make variations of this idea to adapt it to your case. For example:
routes.MapRoute(
"GenericRoute", // Route name
"{controller}/{viewName}", // URL with parameters
new { action = "RenderView", }
);
In this case, all your controllers need to implement a RenderView, and the url is http://.../ControllerName/ViewName.

In my opinion it's not possible, the least you can create is
public ActionResult yourView()
{
return View();
}

If these views are partial views that are part of a view that corresponds to an action, you can use #Html.Partial in your main view to render the partial without an action.
For example:
#Html.Partial("MyPartialName")

Related

Calling a Controller Action from a TagHelper

I'm using a simple CMS to build a website and have a requirement to inject 'components' into my pages. At present, I am rendering out components in a fairly simple way such as follows:
<latest-blog-posts limit="3" order-by="date asc"/>
I then want to use TagHelpers to fill in the real content. Is there a way to call a controller action in ASP.NET Core and replace the invoking tag with the resulting view. My feeling is that I need to be looking at using output.WriteTo(TextWriter writer, HtmlEncoder encoder), but I'm stuck on how to feed the response from an Action into the writer.
You're looking for view components. They're similar in function to the way child actions used to work in MVC 5 and previous.
public class LatestBlogPostsViewComponent : ViewComponent
{
private readonly BlogDbContext db;
public LatestBlogPostsViewComponent(BlogDbContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(int limit, string orderBy)
{
var posts = // get latest posts;
return View(posts);
}
}
Next, create the view Views/Shared/Components/LatestBlogPosts.cshtml and add the code you'd like to render your list of latest blog posts. Your view's model will be the type of posts returned by the view component.
Finally, in the view or layout where you want the latest blog posts to be rendered call the component using:
#await Component.InvokeAsync("LatestBlogPosts", new { limit = 3, orderBy = "date asc" })
Or, if you prefer the TagHelper syntax:
<vc:latest-blog-posts limit="3" orderBy="date asc"></vc:latest-blog-posts>
The latter method requires that you register the view component(s).

Voyager - laravel admin panel

My question is related to controller in Voyager admin panel. For example I created a table with migration . It's name was "groups" and then I created BREAD and added it to menu in Voyager.
I created a folder that it's name is "groups" in \resources\views\vendor\voyager andthen I created two file to override the view.
But I do not know where the controller is . I created controller with php artisan make:controller GroupsController. I guess this controller is not related to voyager controllers.
I want to change the index or create method and pass some data to views in controller but I do not know where it is.
I created a controller in \vendor\tcg\voyager\src\Http\Controllers that it's name is VoyagerGroupsController.php but when I create class and index method in it , it does not work.
How can I create controller for "groups" and pass the data to the view?
Whenever we create a table in voyager, Voyager calls it datatype. And for all tables / datatypes created by us, Voyager users only one controller VoyagerBreadController.php located at **vendor\tcg\voyager\src\Http\Controllers**.
For example, if I create a table named brands. Laravel will use controller VoyagerBreadController.
But where are the routes which use or point to this controller. Routes are located in file vendor\tcg\voyager\routes\voyager.php. In this file, find the following lines:
try {
foreach (\TCG\Voyager\Models\DataType::all() as $dataTypes) {
Route::resource($dataTypes->slug, $namespacePrefix.'VoyagerBreadController');
}
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException("Custom routes hasn't been configured because: ".$e->getMessage(), 1);
} catch (\Exception $e) {
// do nothing, might just be because table not yet migrated.
}
In my version, these lines are between line No. 29 to 37.
As you can see, above code is fetching all our datatypes and creating a resouce route for our tables / datatypes.
Now, if I want to override this route and create a route to use my own controller for a particular action. For example, if I want to create a route for brands/create url. I can do this by simply adding following line (my route) below above code (i.e. after line 37):
Route::get('brands/create', function(){return 'abc';})->name('brands.create');
or you can do the same by adding following line in routes\web.php after Voyager::routes();
Route::get('brands/create', function(){return 'abc';})->name(**'voyager.brands.create'**);
Because it's now it's using your App controller not a Voyager controller so you have to override your full controller
like
In config/voyager.php add
'controllers' => [
'namespace' => 'App\\Http\\Controllers',
],
Create new controller like MyBreadController.php into App/controller
<?php
namespace App\Http\Controllers;
class MyBreadController extends \TCG\Voyager\Http\Controllers\Controller
{
//code here
}
app/Providers/AppServiceProvider.php
use TCG\Voyager\Http\Controllers\VoyagerBreadController;
use App\Http\Controllers\MyBreadController;
public function register()
{
$this->app->bind(VoyagerBreadController::class, MyBreadController::class);
//
}
I added Route::get('groups', 'GroupsController#index') as you said in routes/web.php
like this
Route::group(['prefix' => 'admin'], function () {
Voyager::routes();
Route::get('groups', 'GroupsController#index');
});
and added these lines in index method
public function index(Request $request){
// GET THE SLUG, ex. 'posts', 'pages', etc.
$slug = $this->getSlug($request);
// GET THE DataType based on the slug
$dataType = DataType::where('slug', '=', $slug)->first();
// Check permission
Voyager::can('browse_'.$dataType->name);
// Next Get the actual content from the MODEL that corresponds to the slug DataType
$dataTypeContent = (strlen($dataType->model_name) != 0)
? app($dataType->model_name)->latest()->get()
: DB::table($dataType->name)->get(); // If Model doest exist, get data from table name
$view = 'voyager::bread.browse';
if (view()->exists("voyager::$slug.browse")) {
$view = "voyager::$slug.browse";
}
return view($view, compact('dataType', 'dataTypeContent'));
}
But getSlug method does not work. This error will be shown
ErrorException in GroupsController.php line 23:
Trying to get property of non-object
I guess after overriding Controlles getSlug() does not work and I have to set the slug manually
$slug = 'groups';

ASP.NET MVC two of 'the same' controller actions returning different views for different purposes

I have two controller actions, where one is used to return the view in a standard way and another returns a large chunk of the same view for use in, for example, a javascript modal somewhere else in my application. My question is what would be the best practise way to do this, or if the way I have done it is ok. Maybe I should move the duplicated code out to a helper method?
(Note the Create View has the _Create partial inside it)
Right now I have:
public ActionResult Create(int someParamater)
{
//Lots of code
return View(model);
}
public PartialViewResult GetCreatePartial(int someParameter)
{
//All of the same code as in Create
return PartialView("_Create", model);
}
You can check on some condition and return PartialView or View on its basis, instead of creating a separate action:
public ActionResult Create(int someParamater)
{
if(Request.IsAjaxRequest()) // check here if ajax call return partial
return PartialView("_Create", model);
else
return View(model); // otherwise return Full View
}
If indeed your GetCreatePartial method can be called standalone, and the someParameter argument is available in the View as well, you can call it within parent view using Html.Action().
For example (Create.cshtml):
<div>
<span>some parent view stuff</span>
</div>
<div class="partial-wrapper">
#Html.Action("GetCreatePartial", new { someParameter = Model.someParameter })
</div>
See Html.Action

Change routing of the controller to include it under another controller

I have a controller named DummyController when i call the controller it is like DummyController/Index i want this controller to be called as maincontroller/dummycontroller/index where mainController is a different controller altogether.
Code for DummyController:
public ActionResult Index()
{
return View("~/Views/main/dummy/index.cshtml",db.Users.ToList());
}
the location of index file of Dummy Controller is main/dummy
Now the problem is when I call the dummy controller's index page i get the url as dummy/index i want the url to be displayed as main/dummy/index.
Is there any way to create child controllers? o change the url only for the specific controller
This was relatively straightforward, once I got past a simple issue.
By using a combination of [RoutePrefix("")] & [Route("")] on my controller, I was able to make this work. Attribute Routing requires a call to routes.MapMvcAttributeRoutes() in your Global.asax.cs RegisterRoutes() method.
[RoutePrefix("main/dummy")]
[Route("{action=index}/{id:long?}")]
{action=index} defines the action handling for the route, and specifies /index as the default action, if it's not supplied (ie, ~/main/dummy/ will render ~/main/dummy/index
{id:long?} specifies the id route attribute
:long is the syntax for constraining the param to the long datatype
? denotes this param is optional. (more on this here)
This could also be done using the standard MapRoute() technique:
routes.MapRoute(
name: "DummyControllerRoute",
url: "main/dummy/{action}/{id}",
defaults: new { controller = "Dummy", action = "Index", id = UrlParameter.Optional });
I forgot to add what my simple issue was..
I had 2 actions in the controller: Index() and Index(id), which would always result in the AmbiguousRouteException, and was leading me to believe my Route was defined incorrectly. Changing that to View(id) solved it. (I guess technically the route was wrong, but I didn't need to spend any more time trying to make it work that way)

Using RedirectToAction to break out of a controller/action

In every action in every controller, I would like to have a check that, in certain cases, would return the app to another controller/action. I would like the check to be as simple as possible, something like TestForExit( );
Here's my problem: all my actions return ActionResult, and here is a sample:
public ActionResult Partial()
{
TestForExit( );
...
return PartialView( "ViewPartial", data );
}
If TextForExit returns RedirectToAction( "Index", "Home" ) I have to have something like this:
public ActionResult Partial()
{
var result = TestForExit( );
if( result == null )
{
...
result = PartialView( "ViewPartial", data );
}
return result;
}
But, as I am going to have this everywhere, I'd really like to have TestForExit( ) itself be able to send me to Home/Index rather than return an ActionResult that my Action has to return.
In other words, how can I have TestForExit ACTUALLY go to Home/Index, instead of just returning an ActionResult the the original Action must return?
You will want to use an custom ActionFilter. You can apply this action filter globally. Then in the OnActionExecuting, you can perform the TestForExit check, and redirect if needed.
For example.
public void TestForExitActionFilterAttribute : ActionFilterAttribute, IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(TextForExit())
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {{ "Controller", "ExitController" },
{ "Action", "ExitAction" } });
}
base.OnActionExecuting(filterContext);
}
}
Now apply your [TestForExitActionFilter] attribute to your controllers, or individual actions. Or, to add it everywhere, add the following line to FilterConfig.RegisterGlobalFilters filters.Add(new TextForExitActionFilterAttribute()).
Here are some related links.
Redirecting to specified controller and action in asp.net mvc action filter
http://www.asp.net/mvc/tutorials/hands-on-labs/aspnet-mvc-4-custom-action-filters
Alternatively, you can just override the OnActionExecuting method directly in your controller class and add the logic there. This would make more sense than a custom attribute if you only need this exit logic for one particular controller.
Well your controller action method has to return eventually, so you still have to return an ActionResult no matter what if the action is executed.
If you want to avoid adding that code to every action, you could think about creating a custom Action Filter and then marking your actions with that filter, or applying a global filter if you need it for EVERY action.
Then in your action filter, you check your exit condition and redirect if necessary.