How do I handle page flow in MVC (particularly asp.net) - asp.net-mvc

If you had to provide a wizard like form entry experience in mvc how would you abstract the page flow?

Investigate the post-redirect-get pattern.
http://weblogs.asp.net/mhawley/archive/tags/MVC/default.aspx
http://devlicio.us/blogs/tim_barcz/archive/2008/08/22/prg-pattern-in-the-asp-net-mvc-framework.aspx
Use that along with a robust domain model (for tracking steps or form completion state or whatever you call it) and you're golden.

In order to keep the steps you could implement a page flow action filters, which provide an experience like this one:
[RequiredStep(FlowStart = true)]
public ActionResult Confirm()
{
return View();
}
[RequiredStep (PreviousStep = "Confirm")]
public ActionResult ExecuteOrder()
{
return RedirectToAction("ThankYou");
}
[RequiredStep(PreviousStep = "ExecuteOrder")]
public ActionResult ThankYou()
{
return View();
}

I left the page flow up to the view, where I believe it belongs, so different views could have different page flows (e.g. for desktop browser clients or mobile phone clients etc.) I wrote it up on my blog: A RESTful Wizard Using ASP.Net MVC… Perhaps?

public class CreateAccountWizardController : Controller
{
public ActionRresult Step1()
{
}
public ActionResult Step2()
{
}
}

There are a couple ways, create an action for each step of the wizard process, or create a parameter that is passed in to the action method. Like step that will allow you to know what the state of the wizard is in.

Related

ASP.NET-MVC 3 force log for every page

I'm new to ASP.NET and not the most experienced of programmers.
I have recently been introduced to ASP.NET-MVC 3 for an application I'd like to build.
I have the basic functionality down but am not to familiar with the login.
The built-in login works for what I want (just something simple), but I want to ensure that a login must be used before any of the actual functionality appears.
What would be the best way of doing this?
Any help would be greatly appreciated.
in your controller you should make use of the Authorize attribute, this forces the authorization before doing the decorated action.
For instance, in your home controller add the following [Authorize] as such
[Authorize]
public ViewResult Index()
{
return View();
}
Also you can decorate an entire controller which will force ALL methods to be authroized prior to being used, as such:
[Authorize]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}

A smart way to handle Return URLs in an MVC environment

A problem I come up against again and again is handling redirection to the previous page after a user runs some action such as clicking a 'Back To ...' link or saving the record they are editing.
Previously whenever I have needed to know what page to return to, I would provide a returnURL parameter to my current view.
http://blah.com/account/edit/1?returnURL="account/index"
This isn't a very clean way of handling this situation, as sometimes the return URL contains parameters such as search strings, etc, which have to be included in the URL.
http://blah.com/account/edit/1?returnURL="account/index?search="searchTerm""
Also, this creates an issue when a user can go another page forward before coming back to the page with the returnURL because you have to pass the returnURL through all visited pages.
Simply calling the browser's Back functionality isn't really sufficient either, because you might want the page to refresh, e.g. to show the edits you just saved in the previous page.
So my question is, has anyone found a smart way to handle this kind of situation, specifically in an MVC environment?
Note: I am using ASP .NET MVC so if possible I'd like answers to pertain to that, however any ideas are welcome.
What's wrong with setting a cookie, or using a session variable? The only reason you wouldn't is if you don't control the page that calls into you, in which case your only options are query strings, post values, or referrer.
I thought I might add my answer to the question to see if others think it's a good idea.
I'm simply passing it through to the controller using TempViewData:
#{
TempData["returnURL"] = Request.Url.AbsoluteUri;
}
and then accessing it in a similar way to this (in my real version I check that the key is in TempData and that the returnURL is a real URL):
return Redirect(TempData["returnURL"].ToString());
If it needs to continue on past the first page change (i.e. Search page -> Edit page -> Edit Section page) I'm adding it again
TempData["returnURL"] = TempData["returnURL"];
Check my blog post on it: Using cookies to control return page after login on asp.net mvc 3
Just like #Mystere Man mentioned, you can just use a cookie or session for it. I went for cookies back when I had a similar situation a while ago.
Try register a new route of which the url is /{controller}/{action}/{id}/returnurl/{*url} and then use a RedirectToAction in the action that accepts url as a parameter
Request.UrlReferrer.AbsoluteUri
though i'd still argue that you shouldn't be creating your own "back" button.
Use an interceptor or an aspect:
Intercept each request in some fashion (e.g., a #Before aspect) and save the requested URL to the session, overwriting it each time
In your view layer, access that Session object as needed, in your case for the back link.
This kind of design allows you to always have the most recent request available if you want to use it. Here's an example to write an aspect / interceptor in .NET. Additionaly, PostSharp is a .NET aspect project.
At present, a quick and dirty method has eluded me... so I'm using a practical method.
On a conceptual level, the 'back-ability' of a page should be determined by the page that you're currently on. The View can infer this (in most cases) if the parameters captured in the Controller are passed to it via the ViewModel.
Example:
Having visited Foo, I'm going to Bar to view some stuff, and the back button should return to Foo.
Controller
public ActionResult Foo(string fooId) // using a string for your Id, good idea; Encryption, even better.
{
FooModel model = new FooModel() { fooId = fooId }; // property is passed to the Model - important.
model.Fill();
return View("FooView", model);
}
public ActionResult Bar(string fooId, string barId)
{
BarModel model = new BarModel() { fooId = fooId; barId = barId };
model.Fill()
return View("BarView", model)
}
ViewModels
public class FooModel
{
public string fooId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
public class BarModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
View (Partial) // No pun intended... or maybe it was. :)
Your BarView can now interpret from its model where it needs to go back to (using fooId).
On your BarView (using MVC2 syntax):
Back
You can use Html.ActionLink as well.
Alternatively:
You can inherit your ViewModels from a BaseViewModel, which can have a protected property returnURL. Set this where necessary.
Example:
On your ViewModel:
public class BarModel : BaseViewModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
returnURL = string.Format("/Foo?fooId={0}", fooId)
// Get info from Repository.
}
}
On View:
Back
Would this be better handled by partial actions that display without leaving the page and using JQuery to make a dialog/wizard workflow?
Then you only need to react to the 'Finish' button on the dialog to refresh the original view.
For the part of your question regarding "saving the record they are editing" I would think the post-redirect-get (PGR) pattern would apply to you.
This might be a good place to read about it if you are not familiar with it.
http://en.wikipedia.org/wiki/Post/Redirect/Get
http://blog.andreloker.de/post/2008/06/Post-Redirect-Get.aspx
Encode the returnUrl using Url.Encode(returnUrl) for inclusion in the URL.
When ready to redirect, use Url.Decode(returnUrl) and use the value for the actual redirect.

.NET MVC: Map Route Using Attribute On Action

I'm pretty new to MVC and can't find an answer one way or another to this question. Is there a built in architecture in MVC 1 (or 2, I suppose) that allows you to specify a route mapping via an attribute on a specific action method, rather than in the Global.asax? I can see its use being limited to a degree as multiple methods can be tied to the same action thusly requiring routes to be unnecessarily duplicated, but my question still remains.
Also, does anyone see any gotcha's in implementing something like this, aside from the one I just mentioned about the same action on multiple methods?
Note: I'm not asking HOW to implement this. Only checking if something like this exists, and if not, if it's more trouble than it's worth.
You can also try AttributeRouting, which is available via NuGet. Disclosure -- I am the project author. I've used this in personal and professional projects with great success and would not go back to the default routing mechanism of ASP.NET MVC unless I had to. Take a look at the github wiki. There's extensive documentation of the many features there.
Simple usage looks like this:
public class RestfulTestController : Controller
{
[GET("Resources")]
public ActionResult Index()
{
return Content("");
}
[POST("Resources")]
public ActionResult Create()
{
return Content("");
}
[PUT("Resources/{id}")]
public ActionResult Update(int id)
{
return Content("");
}
[DELETE("Resources/{id}")]
public ActionResult Destroy(int id)
{
return Content("");
}
}
AttributeRouting is highly configurable and has a few extension points. Check it out.
I would recommend ASP.NET MVC Attribute Based Route Mapper for this. This is third party library and does not come with ASP.NET MVC 1 or 2. Usage is like the following:
public SiteController : Controller
{
[Url("")]
public ActionResult Home()
{
return View();
}
[Url("about")]
public ActionResult AboutUs()
{
return View();
}
[Url("store/{category?}")]
public ActionResult Products(string category)
{
return View();
}
}
Then in your global.asax, you just call routes.MapRoutes() to register your action routes.
It's dead simple to implement this.

ASP.NET MVC security: how to check if a controller method is allowed to execute under current user's perrmissions

Given an ASP.NET MVC Controller class declaration:
public class ItemController : Controller
{
public ActionResult Index()
{
// ...
}
public ActionResult Details()
{
// ...
}
[Authorize(Roles="Admin, Editor")]
public ActionResult Edit()
{
// ...
}
[Authorize(Roles="Admin")]
public ActionResult Delete()
{
// ..
}
}
I need to reflect a list of methods in this class which may be invoked with the current user's permissions.
Please share some ideas of what could be done in this case.
Well for the new question think something along the lines of:
new ReflectedControllerDescriptor(typeof(ItemController)).GetCanonicalActions()
could be used to return the list of all available actions. I don't have ASP.NET MVC available to me at work, so I can't really check to see if the ActionDescriptor's returned by that will contain some parameter which says which members are allowed to execute them.
http://msdn.microsoft.com/en-us/library/system.web.mvc.actiondescriptor_members%28v=VS.90%29.aspx
That is the members of the ActionDescriptor, you might be able to find something in there. I'll see tonight if I can figure it out, this has gotten me kind of intrigued.
There's no universal user login/authentication system for all applications, thus this really isn't possible to create a 'universal solution'. You could create your own user login and authorization classes which you then add your own annotations to methods to do, but its going to have the same restrictions that the asp.net mvc system has, its only for your login/authorization system (or whoever extends that system).

ASP.NET MVC Account Controller usage guidelines?

I'm looking at the MVC account controller, and it seems to be from ASP.NET webforms. Is there any good background information on how to use it?
Can you map it to a user database table or is it better to roll your own user management?
How do you make use of it in MVC to restrict what pages a logged in user can view? Do you have to roll all of that on your own?
What resources on the web can help with understanding the ASP.NET Membership?
I'm looking at the MVC account
controller.... it seems to be from
asp.net?
Scott Guthrie explains this quite well in his blog entry about ASP.NET MVC Preview 4. He basically says that the Account Controller from the MVC sample uses the ASP.NET membership provider, so you can use any of those. (I think you can find out more about ASP.NET membership providers on the internet.) If you do not want to implement/use one of those, modifying the application to use your own user management would probably be the best option.
How do you make use of it in MVC to
restrict what pages a logged in user
can view? Do you have to roll all of
that on your own?
You can add the Authorize attribute to the controller class or action method. (Same source as above.)
// Only logged in users can access this controller.
[Authorize]
public class SomeController : Controller
{
#region Not really important for this example. :]
// Maybe rather use a BLL service here instead of the repository from the DAL, but this example is already more verbose than required.
private IStuffRepository stuffRepository;
public SomeController(IStuffRepository stuffRepository)
{
if (null == stuffRepository)
{
throw new ArgumentNullException("stuffRepository");
}
this.stuffRepository = stuffRepository;
}
#endregion
// The authorize attribute is inherited - only logged in users can use the index action.
public ActionResult Index()
{
return View();
}
// Moderators can flag stuff.
[Authorize(Roles="Moderator")]
public ActionResult Flag(int id)
{
this.stuffRepository.Flag(id);
return RedirectToAction("Index");
}
// Admins ans SysOps can delete stuff.
[Authorize(Roles="Admin,SysOp")]
public ActionResult Delete(int id)
{
this.stuffRepository.Delete(id);
return RedirectToAction("Index");
}
// Only joed can change the objects stuff. ;)
// (This is probably bullshit, of course, but I could not make any better example. I blame the fact it is late at night. :))
[Authorize(Users="COMPANY\\joed")]
public ActionResult ChangeId(int oldId, int newId)
{
this.stuffRepository.ChangeId(oldId, newId);
return RedirectToAction("Index");
}
}

Resources