Get the parent controller name from partialview action - asp.net-mvc

I have a partialview called search. I want to put this partial view in many view.
The target is to take the search string input from the search controller and send it to the parent controller where the search view is used.
In this way I want to make the search partial view generic so that I can re-use it.
SearchController:
[HttpPost]
public ActionResult Index(string searchString)
{
var controller = RouteData.Values.First().Value.ToString(); // this gives me "Search", which i dont want.
//here i want to take the parent controller name and redirect to that controller
return RedirectToAction("action", "controller", new { searchString = searchString });
}
can anyone help me to find the parent controller name ??

Instead of having a SearchController, you can make a base class for your controllers and write code that will be shared between them. It makes sense if your feature is needed in multiple controllers.
Let's say your have a base class for your controllers :
public class BaseController : Controller
{
[HttpPost]
public ActionResult Search(string searchString)
{
// ... some process
return RedirectToAction("SomeAction", new { searchString = searchString });
}
public virtual ActionResult SomeAction(string searchString)
{
// ... some other process
}
}
Then your specific controller :
public class MyController : BaseController
{
public override ActionResult SomeAction(string searchString)
{
// ... again some process
}
// .. some other actions
}
Your partialview "Search" will target current controller instead of "SearchController" (by not specifying controller name in your view), so your RedirectToAction will also redirect to an action of that controller, without having to get his name (that's why there's no controller name in the code snipper above).
Instead of having a virtual method, you can also pass a string variable as the action name, if you need to name it differently according to current controller (it can become another parameter, along the searchString parameter) :
public class BaseController : Controller
{
[HttpPost]
public ActionResult Search(string searchString, string targetAction)
{
// ... some process
return RedirectToAction(targetAction, new { searchString = searchString });
}
}
If you don't want to go with a base class, you can always get current controller name in your view, before triggering your search feature :
#HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString()
In your controller, it becomes :
[HttpPost]
public ActionResult Search(string searchString, string controllerName)
{
// ... some process
return RedirectToAction("action", controllerName, new { searchString = searchString });
}
But going for a base class is a good way to make this kind of feature generic and reusable.

Related

MVC Routes data not available

I have a controller called BaseController. In the BaseController, I have an Action method called Index which has some logic that involves querying the routes and building the URLs. Something on the lines of:
var link = Url.RouteUrl("myroute", new { id = 5 });
All this is well and fine until I create a controller NewController that extends the BaseController. In the constructor of NewController, I pass BaseController as a dependency.
public class NewController
{
private BaseController _baseController;
public NewController(BaseController baseController)
{
_baseController = baseController;
}
public ActionResult Index()
{
return _baseController.Index();
}
}
Reason why this was needed was because I need to override the view (some HTML and CSS changes). I didn't want to recreate the models and services and rewrite the business logic, so thought this would be the best and most time-effective approach.
Only issue is when the BaseController's Index Action is called, the Url is null obviously. Routes data is not available because the request was generated outside the base controller.
What is the best way to get around this?
Make BaseController.Index() virtual:
public class BaseController : Controller
{
public virtual ActionResult Index()
{
return View();
}
}
Then use inheritance:
public class NewController : BaseController
{
public override ActionResult Index()
{
var index = base.Index();
//do whatever
return index;
}
}
You are trying to call action method from another controller. Propably your constructor method gets baseController as a null. can you try to implement it like following
public ActionResult Index()
{
return new BaseController().Index(); // assume you call index action
}
Or you can call BaseController action from another controller like following
public ActionResult Index()
{
return RedirectToAction("Index", "Base"); // assume you call index action
}
You can also change Route url like following.
#Url.RouteUrl("myroute", new { controller = "Base", action = "Index", id = 5 })
I have another solution that requires a little bit of code design efforts.
Why don't you Abstract your business logic away from the two Controllers?
For example: RouteBuilder.cs a class that have the functions that contains the logic of building the routes.
And BaseClass.cs is a class that contains the Logic shared between the two Controllers.
Then:
public class BaseController
{
public ActionResult Index()
{``
//Instantiase BaseClass.cs and call the needed functions. Then RouteBuilder.cs and call functions.
return View();
}
}
public class NewController
{
public ActionResult Index()
{``
//Instantiase BaseClass.cs and call the needed functions.
return View();
}
}
Viola. Problem solved and clean code produced.

ASP.NET MVC Clean way to inject partial view from action

I have an app with many widgets and their content depends on the user requesting specific route. Simply put: if widget action is requested, its content must be rendered, otherwise it's empty. Consider routes/actions like this:
~/MyApp/Index -> without model; app HTML, without any widgets
~/MyApp/Foo/{id} -> uses FooModel; if ModelState is valid, returns
Index HTML with injected partial view of Foo's widget to div#foo;
otherwise redirects to Index.
~/MyApp/Bar/{id} -> same as Foo, but different model and widget
My foo action :
public ActionResult Foo(string id) {
if (ModelState.IsValid) {
var response = FooService.GetData(id);
// Inject Foo widget to Index
}
return RedirectToAction("Index");
}
I know that it is possible to use ViewBag or other means to send variables and using the condition to decide whether to render partial view or not. But... there should be a better way to do this, right?
I use MVC's Html.RenderActionResult when I want to build shared views with non-trivial binding logic (calling the database, composing complex objects, etc). The binding logic for each widget is contained in a PartialViewResult method, which is called from the *.cshtml file using Html.RenderAction().
ContentController:
public ActionResult Index(int id)
{
var indexViewModel = new IndexViewModel
{
Id = id,
Title = "My Title",
SubHeader = "Wow its 2016"
};
return View(indexViewModel);
}
public PartialViewResult PopularContent(int id)
{
var popularContentViewModel = new List<PopularContentViewModel>();
// query by id to get popular content items
return PartialView("_PopularContent", popularContentViewModel);
}
public PartialViewResult Widget2(int id)
{
return PartialView("_Widget2Partial");
}
Index.cshtml:
#model StackOverflow.RenderAction.ViewModels.IndexViewModel
<h1>#Model.Title</h1>
<h2>#Model.SubHeader</h2>
--RenderAction will call out to the specified route.
--Note the use of the Id parameter from the viewmodel.
#{Html.RenderAction("PopularContent", "Content", new {Model.Id});}
ASP.NET MVC Attribute Routing could a be a nice solution for this:
In your controller:
public class WidgetController : Controller
{
[Route("myapp/foowidget", Name = "FooWidget")]
public ActionResult FooWidget()
{
//create any model and return any view or partial or redirect
}
[Route("myapp/boowidget/{id:int}", Name = "BooWidget")]
public ActionResult BooWidget(int id)
{
//create any model and return any view or partial or redirect
}
}
And then in a View, you can call the Route by name:
#Url.RouteUrl("FooWidget")
or
#Url.RouteUrl("BooWidget")
or
#Html.RenderPartial("FooWidget")
#Url.RouteUrl("BooWidget") will render or concatenate the id that is in current url, if url is /myapp/something/id, because of your Route attribute definition: "myapp/boowidget/{id:int}". In fact #Url.RouteUrl("BooWidget") might extract the id from any current url of the format /controllerName/action/id, though you will have to test for sure.
And notice how you can have a separation of concerns with your WidgetController and your url Routes are not dependent on that controller's name in any way. That is a nice feature of Attribute Routing, you can declare custom routes as well as organize your controllers and break from nameing convention dependency of a controllerName being part of the url controllerName/action a user sees in their browser.
In regards to Html.RenderPartial, I am not sure if RenderPartial "connects" or will be able to route to your RouteName like "FooWidget". If it does great.
If not your solution is this:
public class WidgetController : Controller
{
public ActionResult FooWidget()
{
//model, you choose, return a partial
}
public ActionResult RedirectUser()
{
//do a redirect
}
public ActionResult BooWidget()
{
//any model, any partial
}
public ActionResult BooWidget(int id)
{
//any model, any partial
}
}
Each method in your controller is single purpose, has a distinct signature and does one thing, no conditions to pass in and no decisions required.

Default controller and default action in MVC5

I have a website developed in MVC 5, I'm using route attributes for routing.
I've set the default controller and the default action for each controller using the following code
public class CompanyController : MainController
{
[Route("~/", Name = "default")]
[Route("Company/Index")]
public ActionResult Index(string filter = null)
{
//My code here
}
[Route("Company/Edit")]
public ActionResult Edit(int id)
{
//My code here
}
}
I've another controller with a default action :
[RoutePrefix("Analyst")]
[Route("{action=Index}")]
public class AnalystController : MainController
{
[Route("Analyst/Index")]
public ActionResult Index(string filter = null)
{
//My code here
}
[Route("Analyst/Edit")]
public ActionResult Edit(int id)
{
//My code here
}
}
The default controller worked perfectly, but when I navigate to the analyst controller without specifying the name of the action I get the following error:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
SurveyWebsite.Controllers.AnalystController
SurveyWebsite.Controllers.CompanyController
How can I correct navigate to http://localhost:61534/analyst and reach the default action ( index) ? The action also should remain accessible by http://localhost:61534/analyst/Index
Thanks for your help.
Give an empty string as the route value for index action so that it works for Analyst, which is your controller route prefix. You can decorate with a second Route attribute for it to work with "Analyst/Index" url where you will pass "Index" to it.
[RoutePrefix("Analyst")]
public class AnalystController : MainController
{
[Route("")]
[Route("Index")]
public ActionResult Index(string filter = null)
{
//My code here
}
[Route("Edit/{id}")]
public ActionResult Edit(int id)
{
//My code here
}
}
This will work for both /Analyst and /Analyst/Index

Asp.Net MVC 5 How to send ViewBag to Partial View

I have a _LoginPartial View and want to send data to it by ViewBag, but the Controller that I'am sending data from, doesn't have a View.
public PartialViewResult Index()
{
ViewBag.sth = // some data
return PartialView("~/Views/Shared/_LoginPartial.cshtml");
}
This code didn't work for me.
It seems you're expecting this Index action to be called when you do: #Html.Partial('_LoginPartial'). That will never happen. Partial just runs the partial view through Razor with the current view's context and spits out the generated HTML.
If you need additional information for your partial, you can specify a custom ViewDataDictionary:
#Html.Partial("_LoginPartial", new ViewDataDictionary { Foo = "Bar" });
Which you can then access inside the partial via:
ViewData["Foo"]
You can also use child actions, which is generally preferable if working with a partial view that doesn't need the context of the main view. _LoginPartial seems like a good candidate, although I'm not sure how exactly you're using it. Ironically, though, the _LoginPartial view that comes with a default MVC project with individual auth uses child actions.
Basically, the code you have would already work, you would just need to change how you reference it by using Html.Action instead of Html.Partial:
#Html.Action("Index")
Notice that you're calling the action here and now the view.
You can always pass data directly to the partial view.
public PartialViewResult Index()
{
var data = // some data
return PartialView("~/Views/Shared/_LoginPartial.cshtml", data);
}
Pass multiple pieces of data
public class MyModel
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
}
public PartialViewResult Index()
{
var data = new MyModel(){ Prop1 = 5, Prop2 = 10 };
return PartialView("~/Views/Shared/_LoginPartial.cshtml", data);
}
I passed viewBag data to my partial view like below, and I converted that viewBag data object to JSON in my partial view by using #Html.Raw(Json.Encode(ViewBag.Part));
my code sample is given below.
public async Task<ActionResult> GetJobCreationPartialView(int id)
{
try
{
var client = new ApiClient<ServiceRepairInspectionViewModel>("ServiceRepairInspection/GetById");
var resultdata = await client.Find(id);
var client2 = new ApiClient<PartViewModel>("Part/GetActive");
var partData = await client2.FindAll();
var list = partData as List<PartViewModel> ?? partData.ToList();
ViewBag.Part = list.Select(x => new SelectListItem() {Text = x.PartName, Value = x.Id.ToString()});
return PartialView("_CreateJobCardView" ,resultdata);
}
catch (Exception)
{
throw;
}
}
Here i have passed both model and viewBag .
First off, the code in your question does not run. When you do #Html.Partial("_SomeView") the Index() method you have there does not run. All #Html.Partial("_SomeView") does is render _SomeView.cshtml in your current view using the current view's ViewContext.
In order to get this to work you need a bit of functionality that's common to all the controllers in your project. You have two options: extension method for ControllerBase or a BaseController that all the controllers in your project inherit from.
Extension method:
Helper:
public static class ControllerExtensions
{
public static string GetCommonStuff(this ControllerBase ctrl)
{
// do stuff you need here
}
}
View:
#ViewContext.Controller.GetCommonStuff()
BaseController
Controller:
public class BaseController : Controller
{
public string GetCommonStuff()
{
// do stuff you need here
}
}
Other controllers:
public class SomeController : BaseController
...
...
View:
#((ViewContext.Controller as BaseController).GetCommonStuff())

How To Use an Attribute (maybe) to point at Layout Page I want

I've got an ASP.NET MVC4 project with standard controllers and views. I have to different master pages I use, depending on a global variable I can reach out and get based on the Request.Url.Host. I've written the code below but it is getting kind of bulky to put in every controller. I've gotten it pretty short but was hoping for a suggestion to make it much cleaner.
private ActionResult IndexBase(string year)
{
var data = null; // real data here for model
var localConfig = LocalConfig.GetLocalValues(Request.Url.Host, null, year);
ViewResult view = localConfig.EventType == "svcc"
? View("Index", "~/Views/Shared/_Layout.cshtml", data)
: View("Index", "~/Views/Shared/_LayoutConf.cshtml", data);
return view;
}
I don't know if this solution works for you, but I would solve it with ViewModel's and a common base controller.
One of the nice things with Layouts is you can pass a base ViewModel with the properties common to all your pages (the users name, for example). In your case, you could store the path to the Layout.
First, the base class every ViewModel derives from:
public class MasterViewModel
{
public string Name { get; set; }
public string Layout { get; set; }
}
I prefer to use a 1:1 mapping of ViewModels to Views. That is, each action gets it's own ViewModel. For example: HomeIndexViewModel for /Home/Index, ProfileEditViewModel for /Profile/Edit, etc.
public class HomeIndexViewModel : MasterViewModel
{
// properties you need for /Home/Index
}
To simplify creating the ViewModels, I add a generic method on a base controller that handles setting all these the common properties:
public class BaseController : Controller
{
protected T CreateViewModel<T>() where T : MasterViewModel, new()
{
User user = db.GetUser(User.Identity.Name);
var localConfig = LocalConfig.GetLocalValues(Request.Url.Host, null, year);
return new T()
{
Name = user.Name,
Layout = localConfig.EventType == "svcc" ? "~/Views/Shared/_Layout.cshtml"
: "~/Views/Shared/_LayoutConf.cshtml"
}
}
}
And finally, just use CreateViewModel() in each of your Actions and things should work:
public class HomeController : BaseController
{
public ActionResult Index()
{
HomeIndexViewModel viewModel = CreateViewModel<HomeIndexViewModel>();
return View(viewModel);
}
}
Inside the Views, you can just set
#model HomeIndexViewModel
#{
Layout = Model.Layout;
}
There's no need to duplicate the path anywhere, and changing the logic on which Layout to show requires you only change it in one place.

Resources