ActionResult Index(LoginPage currentPage){} - you can also have an empty method without parameters and get the currentPage object like this instead: var currentPage = PageContext.Page as LoginPage; (cleaner imho).
When it comes to posting a form to your controller, how does your view look? (basically your Html.BeginForm method).
Frederik
Thanks, didn't know that - it looks cleaner to me as well. Now my login controller have the following action methods defined:
[HttpGet]
public ActionResult Index()
{
var model = new LoginPageModel(PageContext.Page as LoginPage);
return View(model);
}
[HttpPost]
public ActionResult Foo(LoginPageModel model)
{
// Authentication logic
}
My view looks like following (I have removed irrelevant html markup):
@using (Html.BeginForm("Foo", "Login", FormMethod.Post))
{
<div class="control-group">
@Html.TextBoxFor(m => m.UserName)
@Html.ValidationMessageFor(m => m.UserName)
</div>
<div class="control-group">
@Html.PasswordFor(m => m.Password)
@Html.ValidationMessageFor(m => m.Password)
</div>
<input type="submit" value="Login">
}
When submitting, I see two "requests" happening:
Foo (/Login) POST - 301 Moved Permanently
Foo/ (/Login) GET - 404 Not Found
What is the value of your forms action attribute (when rendered)? And what's the URL if the login page?
Frederik
When rendered, the form looks like this: <form action="/Login/Foo" method="post">
The URL of the login page is http://localhost/login
I'm wondering what is making that redirect...
That looks correct to me, try and turn on logging to see what's causing the redirect.
Frederik
Yes, looks correct to me as well :)
Hm, stupid question, but do we have any built-in logging for EPiServer/MVC that covers this?
Yes, EPiServer is using log4net. There should be a EPiServerLog.config file in your web's root folder (if not copy from the demo site). By default almost at the bottom you have a commented out appender for errorFileLogAppender which logs to a text file under APp_Data\EPiServerErrors.log. You can also change the logging level (default is error, but you also have All, Debug and Info).
Frederik
Another thing you could try is rename your action method to Index and make sure it posts the form to /login/.
Right, now I did setup the logger to level All for errorFileLogAppender, and it produces some logs (search/indexing warnings etc). I renamed the action method to Index and Html.BeginForm("Index", "Login", FormMethod.Post), which renders as <form action="/Login/Index" method="post">. The same thing happens now:
Index (/Login) POST - 301 Moved permanently
Index/ (/Login) GET - 200 OK
Nothing is catched in the logs regarding this. Gah.
If you have Fiddler or similar, what happens if you submit the form to /Login/ (notice the ending slash, without Index) instead?
Interesting - I manually rendered the markup for the form tag to "/Login/" and run the debugger. I didn't get as far as into the correct action method, but an another exception occured:
[MissingMethodException: No parameterless constructor defined for this object.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +113
System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +232
System.Activator.CreateInstance(Type type, Boolean nonPublic) +83
System.Activator.CreateInstance(Type type) +6
System.Web.Mvc.DefaultModelBinder.CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) +183
System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +564
System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +416
System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) +317
Do you happen to know what might be causing this? Obviously it fails somewhere when it tries to create my complex parameter (LoginViewModel).
UPDATE:
The reason for above exception is obviously that my ViewModel has the following and only constructor:
public LoginPageModel(LoginPage currentPage) : base(currentPage)
{
}
The problem is how do I change this so that I can add a parameterless ctor, but yet is able to get hold of the currentPage instance? (It is needed in the base class as well as in accessing properties in the view).
Also, why is this not trigger when it renders the action as /Login/Index ? Big thanks for helping out!
Usually I have a separate DTO object for posting data back, but you should be able to add a parameterless constructor to your ViewModel base class and then later set it in the method instead using PageContext.Page as LoginPage. You could also add a custom model binder for this.
Frederik
Hm, yes separate DTOs for incoming data might be the right way to go. I'm just worried that I'm building to much "infrastructure" - first the PageType model, then the ViewModel for modelling the view and then an additional DTO model for posting back data. But it might be worth it and perhaps there is some performance gain also (less overhead compared to creating a fully populated ViewModel)?
PageContext.Page - is that only available in the context of a controller, or is there a way to fetch the current page from within the ViewModel class?
I will have a look at how to implement a custom model binder or overriding OnActionExecuting(). Thank you!
You can also use EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<PageRouteHelper>().Page as LoginPage.
Frederik
Aha, thank you, that's good to know! I have not been able to find such "tips and tricks" or best practices for EPiServer 7/MVC yet.
I'm leaning towards separate DTOs though, prefer not to have logic in the models!
Aha, thank you, that's good to know! I have not been able to find such "tips and tricks" or best practices for EPiServer 7/MVC yet.
I'm leaning towards separate DTOs though, prefer not to have logic in the models!
Yes, EPiServer and MVC is still a little new so not that much information out there. I have Google Docs document with notes that I've taken while developing with EPiServer and MVC that I hope to post soon as a blog post (need to work a little more with EPiServer and MVC first).
Joel Abrahamasson just started writing a book on EPiServer Development with MVC that I recommend checking out: EPiServer 7 CMS Development.
Frederik
Great to hear!
Btw - do you know why my Html.BeginForm("Index", "Login", FormMethod.Post) renders as "/Login/Index"? If I manually add the trailing slash it works, otherwise it doesn't. Also I didn't expect it would add "Index" to the URL at all. Is this related to how EPiServer sets up the default routes?
Also remember, if you are using multilingual site (more than one language) use:
@using (Html.BeginForm(null, null, new { language = ContentLanguage.PreferredCulture.Name }))
This will make sure that action Url is "/{language}/{controller}/{action}/"
Hi,
I recently started to develop for EPiServer 7 MVC. I have some knowledge about Asp.Net MVC since a few other non-episerver project - it's a great framework. But now I'm confused how this integrates with EPiServer.
Adopting the code from Alloy MVC template, I'm now trying to build a simple login page. I have created a LoginPage (inheriting SitePageData), I have created a view model LoginPageModel (inheriting PageViewModel<LoginPage>) that contains a UserName and Password property, and lastly I have built a view (that uses the LoginPageModel) that renders a simple login form that should POST data back to my LoginPageController.
Now, how would would the LoginPageController look like in terms of action methods? In a "normal" MVC project, I would have setup two actions:
But in EPiServer, to start with, I just have a method "public ActionResult Index(LoginPage currentPage)". When I add another action method, e.g. "[HttpPost] public ActionResultIndex Foo(LoginPageModel model)", and try to POST to that it responds with a redirect (301 Moved Permanently) and tries to go to the corresponding GET action, which is not found since there I only allow POST (as indicated [HttpPost]).
Why does redirect occur and how do I setup up the controller actions? :o
Thank you!