I'll attempt to comment on each of the bullet points you mentioned:
Your master pages still exist in MVC and are used to provide a consistent layout to the site. not much new there.
Your content pages will become views in the MVC world. They still provide the same content areas to your master pages.
The eventhandling of webforms should not be used in MVC, instead your Controller classes and their action methods will handle loading your data into a "model" that gets passed to the view.
Although webform style databinding is possible in MVC, I find that it is not the optimal solution. Better to place your data in a model class and strongly type your view so that you have direct access to that model. Then its simply a matter of using the <%= ViewData.Model.SomeProperty %>
syntax to access your data and display it in the desired locations. As for viewstate, my recommendation is to forget that it even exists.
Remember that one of the advantages of using MVC is that you have control over the HTML you send to the client. Embrace that power and try to find solutions that allow you to maintain that control. Webform controls attempt to hide the the html from you and as such make it more difficult to customize the html when you need to.
I would highly recommend JQuery or one of the other similarly powerful javascript libraries. But learn to use them to access the HTML DOM directly and avoid the id mangling issues of webform controls.
You can use jquery to hook into the dropdown selection on the client side and submit standard or ajax style requests. Those request can return new pages, redirects, html fragments or even JSON data that can be used to update the existing page.
The asp.net Session can be used as needed.
Here's how I do it.
I decided to use IPrincipal instead of IIdentity because it means I don't have to implement both IIdentity and IPrincipal.
Create the interface
interface ICustomPrincipal : IPrincipal
{
int Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}
CustomPrincipal
public class CustomPrincipal : ICustomPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public CustomPrincipal(string email)
{
this.Identity = new GenericIdentity(email);
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
CustomPrincipalSerializeModel - for serializing custom information into userdata field in FormsAuthenticationTicket object.
public class CustomPrincipalSerializeModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
LogIn method - setting up a cookie with custom information
if (Membership.ValidateUser(viewModel.Email, viewModel.Password))
{
var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.Id = user.Id;
serializeModel.FirstName = user.FirstName;
serializeModel.LastName = user.LastName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
viewModel.Email,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
return RedirectToAction("Index", "Home");
}
Global.asax.cs - Reading cookie and replacing HttpContext.User object, this is done by overriding PostAuthenticateRequest
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.Id = serializeModel.Id;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
HttpContext.Current.User = newUser;
}
}
Access in Razor views
@((User as CustomPrincipal).Id)
@((User as CustomPrincipal).FirstName)
@((User as CustomPrincipal).LastName)
and in code:
(User as CustomPrincipal).Id
(User as CustomPrincipal).FirstName
(User as CustomPrincipal).LastName
I think the code is self-explanatory. If it isn't, let me know.
Additionally to make the access even easier you can create a base controller and override the returned User object (HttpContext.User):
public class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
and then, for each controller:
public class AccountController : BaseController
{
// ...
}
which will allow you to access custom fields in code like this:
User.Id
User.FirstName
User.LastName
But this will not work inside views. For that you would need to create a custom WebViewPage implementation:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
Make it a default page type in Views/web.config:
<pages pageBaseType="Your.Namespace.BaseViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
and in views, you can access it like this:
@User.FirstName
@User.LastName
Best Answer
The convention is already available without jumping through too many hoops. The trick is to wire up the TextBox values based off of the model you pass into the view.
What happens next is the ViewEngine takes the formCollection and matches the keys within the collection with the ID names/values you have in your view, using the Html helpers. For example:
Notice the textbox and textarea has the IDs of Title and Body? Now, notice how I am setting the values from the View's Model object? Since you passed in a FormCollection (and you should set the view to be strongly typed with a FormCollection), you can now access it. Or, without strongly-typing, you can simply use ViewData["Title"] (I think).
POOF Your magical ViewState. This concept is called convention over configuration.
Now, the above code is in its simplest, rawest form using FormCollection. Things get interesting when you start using ViewModels, instead of the FormCollection. You can start to add your own validation of your Models/ViewModels and have the controller bubble up the custom validation errors automatically. That's an answer for another day though.
I would suggest using a PostFormViewModel instead of the Post object, but to each-his-own. Either way, by requiring an object on the action method, you now get an IsValid() method you can call.
And your Strongly-Typed view would need to be tweaked:
You can take it a step further and display the errors as well in the view, directly from the ModelState that you set in the controller.
What is interesting with this approach is that you will notice I am not setting the validation summary, nor the individual validation messages in the View. I like to practice DDD concepts, which means my validation messages (and summaries) are controlled in my domain and get passed up in the form of a collection. Then, I loop throught he collection (if any errors exist) and add them to the current ModelState.AddErrors collection. The rest is automatic when you return View(post).
Lots of lots of convention is out. A few books I highly recommend that cover these patterns in much more detail are:
And in that order the first covers the raw nuts and bolts of the entire MVC framework. The latter covers advanced techniques outside of the Microsoft official relm, with several external tools to make your life much easier (Castle Windsor, Moq, etc).