Mvc3 Wizard with separate controller actions - c#

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.

Related

How to use MapDynamicControllerRoute to select a specific controller without the conventional controller routing in ASP.NET core?

I want to decide where to send each url at runtime.
I don't want to use attributes like [Route("/hello")] and I don't want to use the conventional [controller=hello]/[action=Index]. What I want is a way to execute a specific controller after deciding with my own logic like this
if(context.Request.Path.Value == "/hello"){
SomehowCallTheActionOnController("MyController", "MyAction", anyUrlValuesLikeId);
}
Using the DynamicRouteValueTransformer, I can get the dictionary of values such as values["controller"]="home"; But since I want to catch all, in my case I only get "values["all"]="hello/this/is/my/url", even if I set the controller name manually, it won't work because I don't want to use the conventional mapping of controllers and actions.
I guess what I'm trying to do is the equivalent of Route::get('/user', [UserController::class, 'index']); in laravel for example; use my own routing but let asp pass dependency injections to controllers and instantiate ActionContext for me. Is this possible at all in ASP.NET?
I can just use
app.MapGet("myroute", async context => {
var actionContext = new ActionContext(context, context.getRouteData(), new ActionRequest());
await new ContentResult(){ Content = "hi" }.ExecuteResultAsync(actionContext);
})
and manage dependency injections on my own by GetService... but I'm not sure this is how it's supposed to be done because the actionRequest for example won't have all necessary properties set up, and asp might be using it for something??
Anyway, thanks in advance :)
I found the issue. When you specify the controller name for the route, you must not include the word "Controller"!
Like this:
app.MapControllerRoute(
"About",
"/About/{something}",
defaults: new { controller = "App", action = "Index" }
);
and the controller name is "AppController" with a method "Index" like this
public class AppController : Controller {
public IActionResult Index(string something) {
return /* put your result here */;
}
}
I still don't know how to specify a method (GET, POST, etc) with "MapControllerRoute" but at least this way I can specify routes from an array with templates and names...

ASP.NET MVC5 - append querystring to all URLs

My team is building a simple MVC site for very low end "feature" phones. One problem we have is that certain phone aggressively cache HTML, so what the user actually gets to see is not what we intend. An extreme example is, a user registers and gets a "thank you page". Another user on the same device then tries to register. The phone simply serves the cached page without creating a new account.
In the past I've dealt with this by adding a "cache buster" querystring to things I don't want cached, eg all pages will be served in the format
http://domain.com/controller/route?cb=somerandomstringofnumbers
In this case we'd need to do this for all URLs in the site - this includes the URLs auto-generated by Controller actions such as RedirectToAction or Redirect and also the Razor Url.Action, Html.BeginForm, Html.ActionLink etc.
Now obviously I could decorate the Razor HTML helpers (or extend them) and add the argument to an controller action, but it seems to me that because the actual URLs generated by these built in methods are auto-generated from the Controller/Action params passed in, there should be a way to hijack that process.
Unfortunately the MS classes are protected - I'm mostly looking in System.Web.Routing.Routes.
I've tried a few things I've found online but they are not MVC5 (dating back to 2008) and it seems the framework has changed significantly.
eg, from
http://forums.asp.net/t/1216840.aspx?Append+value+to+all+urls+built+by+RouteCollection+GetUrl
public class SessionAppendingRouteHandler : IRouteHandler
{
public IHttpHandler GetHandler(RequestContext context)
{
SessionAppendingHttpHandler handler = new SessionAppendingHttpHandler();
handler.RequestContext = context;
return handler;
}
}
public class SessionAppendingHttpHandler : MvcHandler
{
public override ProcessRequest(RequestContext context)
{
//append your sid here
}
}
// and in the route setup
RouteTable.Routes.Add( new Route
{
Url = "/[controller].mvc/[action]/",
Defaults = new { action = "index" },
RouteHandler = typeof(SessionAppendingRouteHandler)
});
This I cant get to work as the framework has changed too much, but it looks very close to what I would like to achieve.
I feel like I'm in the right area, but I've hit a brick wall.
Any suggestions?
This is quite old, but let me answer based on how I solved a similar problem:
Instead of having it as query string, have the cb as a route value just as action and controller are route values. You can do this by registering a route; for instance:
routes.MapRoute(
name: "CB",
url: "{cb}/{controller}/{action}/{id}",
defaults: new { cb = "3600", area = "", id = UrlParameter.Optional }
);
If the value for cb is not a constant, then you can find a convenient point to set the cb for each user session. A good place will be after a successful login. With this, you'll now need to provide just two custom methods for RedirectToAction and ActionLink. Your implementation will simply package a RouteValueDictionary and then pass it in to MVC's own implementation using the appropriate overloads.

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" });
}
}

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

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.

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