Is there any way to create special controller for my _Layout? - c#

I am developing a website that has a View called _MainPage in View\Shared folder that other views use that for basic layout of my website, so I have a section in this view that populates data from database in fact this section is latest news of the site and I should show latest news in this section ,in simple way I should make section for example called latestNews :
<ul class="news-list">
#RenderSection("latestNews",required:false)
</ul>
and in each views I should fill this section by razor that data came from relevant controller :
#foreach (var item in Model.News)
{
<div>
<p>#Html.Raw(item.Body)<br />ادامه مطلب</p>
</div>
}
in fact I have latest news in every views in footer of my pages.
now my question is : How can define a custom controller for my View (_MainPage) that haven't do these routine in each view. Is there any generic way to accomplish that?

Any public method in a controller class is an action method. Every action method in a controller class can be invoked via an URL from web browser or a view page in application.
Your scenario is some dynamic information (data) need to displayed on couple pages in your application. Generally we end up in adding that data to model passed to views by action methods. We duplicate same data in multiple models where we brake DRY (Don’t Repeat Yourself) principle.
To handle this scenario ASP.NET MVC provides child action methods. Any action method can be child an action method but child actions are action methods invoked from within a view, you can not invoke child action method via user request (URL).
We can annotate an action method with [ChildActionOnly] attribute for creating a child action. Normally we use child action methods with partial views, but not every time.
[ChildActionOnly] represents an attribute that is used to indicate that an action method should be called only as a child action.
[ChildActionOnly]
public ActionResult GetNews(string category)
{
var newsProvider = new NewsProvider();
var news = newsProvider.GetNews(category);
return View(news);
}
Above child action method can be invoked inside any view in the application using Html.RenderAction() or Html.Action() method.
#Html.Action("GetNews", "Home", new { category = "Sports"})
(or)
#{ Html.RenderAction("GetNews", "Home", new { category = "finance"}); }

Instead of a section, you can use Html.RenderAction to specify a controller and an action. The action should return a partial view which is then integrated within the calling site.

Related

POST data using RenderMvcController in Umbraco

I have set up a document type in Umbraco, and have created a custom controller & model for this document type.
The custom controller inherits from : RenderMvcController class and the views inherit the model through #inherits Umbraco.Web.Mvc.UmbracoViewPage<com.something.model>
This all works fine for any HttpGet requests. However as soon as I want to do a form post back to the controller using #using (Html.BeginUmbracoForm("SomeAction", "SomeController", null, new { #class = "some-class" }))
I get the following error message: Could not find a Surface controller route in the RouteTable for controller name SomeController
From all the documentation that I was able to find it always refers to SurfaceControllers when it comes to form posts. Is there a way to change the routing so that it would post to my custom controller, rather then another controller that inherits from the SurfaceController class?
If you are going to post the form in this way, you need two controllers. One for the Document Type that inherits from MvcRenderController (as you already have) and a second which inherits from the SurfaceController.
The surface controller just needs a single POST action that does one of the following things:
// e.g. if modelstate is invalid
return CurrentUmbracoPage();
// redirecting back to original page after a successful post
return RedirectToCurrentUmbracoPage();
// Redirecting to another page after a successful post
return RedirectToUmbracoPage(12345)
This has been taken from the documentation here: http://our.umbraco.org/documentation/Reference/Templating/Mvc/forms
Strictly speaking the initial document type controller is not necessary in this scenario as it does not play a part in the post of the form.
If you want to post directly to the custom controller then you should not use Html.BeginUmbracoForm, you should just post directly to the current URL. The complication here is that it is then a little tricky to bind your model as a parameter of the post action. Your view model will have to inherit from RenderModel in the following way:
public class BaseModel : RenderModel
{
public BaseModel() :
base(UmbracoContext.Current.PublishedContentRequest.PublishedContent) { }
}
According to http://our.umbraco.org/forum/developers/api-questions/38662-(v6)-Could-not-find-a-Surface-controller-route-error?p=0,
SomeController Needs to inherit from SurfaceController, not RenderMvcController.
Alternatively, you could create a dedicate route and point it directly to your controller:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("someController", "some/someAction",
new { controller = "Some", action = "SomeAction" });
}
}

MVC 4 master page _Layout pass data

I have a masterpage called _Layout.cshtml and placed in the folder Views/Shared/_Layout.cshtml.
I created a controller: SharedController.cs to pass data to _Layout.cshtml
but does not enter into the controller.
How can I pass data to each load masterpage _Layout.cshtml?
This is the controller for example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace HAnnoZero.Controllers
{
public class SharedController : Controller
{
public ActionResult _Layout()
{
ViewBag.help = "ciao";
return View();
}
}
}
First, don't call it a master page. That's from Web Forms. In MVC, _Layout.cshtml is a "layout". That may seem like semantics, but it's important to differentiate because in Web Forms, the master page is a true page in it's own right, with it's own code behind. In MVC, a layout is only an HTML template with some placeholders. The controller, and specifically the action of the controller that was requested, is solely responsible for the page context. That also means you can't have a _Layout action that adds context to the layout because that action is not being called, and even if you did (by going to a URL like /Shared/_Layout in your browser, it would fail when loading _Layout.cshtml as a view, because it needs a superficial view to fill in the call to #RenderBody().
If you want to render something with its own context in your layout, then you must use child actions:
Controller
[ChildActionOnly]
public ActionResult Something()
{
// retrieve a model, either by instantiating a class, querying a database, etc.
return PartialView(model);
}
Something.cshtml
#model Namespace.To.ModelClass
<!-- HTML that utilizes data from the model -->
_Layout.cshtml
#Html.Action("Something", "ControllerSomethingActionIsIn")
Another way is creating a common base controller that other controllers inherit from:
public abstract class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.Controller.ViewBag.Title = "Hello world";
}
}
public class SharedController : BaseController
{
...
}
Then in _layout you just use:
<title>#ViewBag.Title</title>
Layout pages do not work this way. The view returned by your action is rendered within a _Layout page when using a default MVC project.
See here for more of an introduction to _Layout pages.
One way to display information in a _Layout page is to use a child action method which is called from the _Layout page.
For example, the _Layout.cshtml may contain a call similar to:
#Html.Action("GetNews", "Home", new { category = "Sports"})
And then you would have a controller action similar to:
[ChildActionOnly]
public ActionResult GetNews(string category)
{
....
}

Mvc3 Wizard with separate controller actions

I'm putting together a wizard in mvc3/c#. I have a model setup roughly
public interface IStepView {}
public class Step1View : IStepView {}
public class Step2View : IStepView {}
I have a parent view which displays 1 of 2 partial views for these steps.
I would like the form submission for Step 2 to use a custom action on the same controller. Reading similar posts it seems what I need to do is add a custom route like so
RouteTable.Routes.MapRoute("Step2Route", "", new { controller = "Demo", action = "MyAction" });
which I wire together on the Main.cshtml
#using (Html.BeginForm())
{
#Html.BeginRouteForm("Step2Route", new { controller = "RolloverController", action = "Stuff" })
// and so on for each Step I want to use a custom action
}
Is this the way to do it?
If you didn't know the number of steps in the wizard, a custom route might make sense to allow for tracking of progress in the wizard.
i.e. Wizard/{WizardName}Step/{StepNumber}/
But since it looks like you do know the steps, your actions on the controller can correspond to them without custom routes:
i.e RegistrationWizard/EnterInfo, RegistrationWizard/Confirm, RegistrationWizard/Success
Create a get and post method for each action on the controller. Take the same model and pass it along using RedirectToAction or store it in session, so that you keep track of the changes that the user is making to the data before committing the data on the final step.

how does asp.net mvc relate a view to a controller action?

I have opened a sample ASP.NET MVC project.
In HomeController I have created a method (action) named MethodA
public ActionResult MethodA()
{
return View();
}
I have right clicked on MethodA and created a new view called MethodA1
Re-did it and created a new view called MethodA2.
How is this magical relationship done? I looked for the config to tell the compiler that views MethodAX are related to action MethodA, but found none.
What view will the controller return when MethodA is called?
The convention is that if you don't specify a view name, the corresponding view will be the name of the action. So:
public ActionResult MethodA()
{
return View();
}
will render ~/Views/ControllerName/MethodA.cshtml.
But you could also specify a view name:
public ActionResult MethodA()
{
return View("FooBar");
}
and now the ~/Views/ControllerName/FooBar.cshtml view will be rendered.
Or you could even specify a fully qualified view name which is not inside the views folder of the current controller:
public ActionResult MethodA()
{
return View("~/Views/Foo/Baz.cshtml");
}
Now obviously all this assumes Razor as view engine. If you are using WebForms, replace .cshtml with .aspx or .ascx (if you are working with partials).
For example if there is no view it will even tell you where and in what order is looking for views:
Remember: ASP.NET MVC is all about convention over configuration.
The MVC framework use convention over configuration. The framework calls the ExecuteResult on the ViewResult object (created by the return View();). The framework by convention then looks in a number of locations to find a view
If you are using areas the framework will look in the following locations for a view.
/Areas//Views/ControllerName/ViewName.aspx
/Areas//Views/ControllerName/ViewName.ascx
/Areas//Views/Shared/ViewName.aspx
/Areas//Views/Shared/ViewName.ascx
/Areas//Views/ControllerName/ViewName.cshtml
/Areas//Views/ControllerName/ViewName.vbhtml
/Areas//Views/Shared/ViewName.cshtml
/Areas//Views/Shared/ViewName.vbhtml
Without areas (or if you are using areas and no view has been found) the framework will look at the following locations
/Views/ControllerName/ViewName.aspx
/Views/ControllerName/ViewName.ascx
/Views/Shared/ViewName.aspx
/Views/Shared/ViewName.ascx
/Views/ControllerName/ViewName.cshtml
/Views/ControllerName/ViewName.vbhtml
/Views/Shared/ViewName.cshtml
/Views/Shared/ViewName.vbhtml
As soon as the Framework tests a location and finds a file, then the search stops,
and the view that has been found is used to render the response to the client.
There are a number of overriden versions of the View method. The most common one is to render a specific view, outside of the framework convention, by calling it by name. For example
return View("~/Views/AnotherIndex.cshtml");
As an interesting footnote, the framework looks for legacy ASP, C# and VB Razor views (aspx, ascx, cshtml and vbhtml) even though you have a specific view engine.
In MVC controller action is not bound to view.
It uses delegate mechanism to pickup the view.
Model Binding(Mapping)
I was looking for the same and I just made a couple of tests and figured out.
It doesn't save anywhere.
To understand how it works; just do these steps:
In your controller, right click, Add View
Then enter a different View Name
and Ctrl F5
you will get Server error in application.
For example if you right click , Add View in following Index action method and type "Index2" in View name, you will get the error.
public class TestController : Controller
{
// GET: Test
public ActionResult Index()
{
return View();
}
}
So basically there is a 1-1 matching between action name and View name. And you cannot add view for the same method so there is no need to save in a config file.
Now change the view file name in Visual Studio from Index2.cshtml to Index.cshtml then Ctrl+F5. You should see it is working.

Make HttpPost request to an action method in an ASP.NET MVC controller

I am trying to build a functionality where I need to a create a candidate's profile in our application. There are two steps/UI's to create a candidate's profile:
1 - Create template...where the user enters candidate's information.
2 - Preview template...where the user will be shown a preview of how their profile would look like once they add the profile to our system.
I have already created the views to support these UI's via a controller called "CandidateController" which contains few action methods:
1- [HttpGet] "Create" that returns a Create template.
[HttpGet]
public ViewResult Create()
2- [HttpPost] "Preview" that returns a Preview template.
[HttpPost]
public ActionResult Preview(ProfileViewModel viewModel)
Now what I need to implement is to have a button/link in the Create template that would call the action method [HttpPost] Preview in the controller.
Challenge
I am also wondering if there is a way that the model binder would load the ViewModel object for me if am able to call the HttpPost Preview action method from the first create template.
I am looking for a suggestion/help to how to best achieve this kind a functionality.
Any help will be deeply appreciated.
Challenge I am also wondering if there is a way that the model binder
would load the ViewModel object for me if am able to call the HttpPost
Preview action method from the first create template.
You could use either a standard form or an AJAX call to invoke the Preview POST action and pass all the property values of the view model then. All the values you pass in this request will be the values that will be bound by the default model binder. Here's an article explaining how the default model binder expects the parameters to be named for more complex structure such as lists and dictionaries.
Example with AJAX:
$.ajax({
url: '#Url.Action("Preview")',
type: 'POST',
data: { Prop1: 'value 1', Prop2: 'value 2' },
success: function(result) {
// TODO: do something with the result returned from the POST action
}
});
If you don't want to use AJAX you could use a standard form with hidden fields:
#using (Html.BeginForm())
{
#Html.Hidden("Prop1", "value 1")
#Html.Hidden("Prop2", "value 2")
...
<button type="submit">Preview</button>
}
OK so here are the options that I had to get around:
As Darin suggested you may go with the unobtrusive way by using $.ajax(options), however the thing is you might want to go this way only if you want to do a partial page update or if you want to work on updating/dumping new html in the same view.
And if you don't want to use the Ajax, instead of using Hidden fields, you can simply use the TempData property in MVC, this is how I implemented my targeted functionality using TempData. p.s.below...
[HttpPost]
public ActionResult Create(ViewModel viewModel)
{
this.TempData["profile"] = viewModel;
return RedirectToAction("Preview");
}
public ActionResult Preview()
{
if (TempData["profile"] != null)
{
return View((ViewModel)TempData["profile"]);
}
// Handle invalid request...
return null;
}
So, this solution worked pretty well for me, where I did not write any JavaScript or unnecessary HTML. AND thanks Darin for directing me to a starting point.

Categories