I spent several hours reading through many different examples and documentation for setting up a portable area with ASP.NET MVC, with the intent of sharing a common login page with authentication for multiple applications. I got it all together and it works nicely so far, but one thing I'm having trouble with is the use of message bus. I see it is a way of communicating between the Host and Portable components, but I don't see a clear way of how to do this.
For instance; if my Portable login is successful, how do I tell the Host so it can do something (set a cookie, redirect to a specific page, etc.)? Also, if I want to send something to the Portable (like the title or Assembly Version of the Host application) how would I do that? I haven't tried anything yet because I cannot seem to find a complete example.
I got it all figured out. There was a MvcContrib source code archive that I was unable to download since Google Chrome was blocking the .zip file, but I was able to get it using Internet Explorer.
Here are the important bits after adapting it to my application. Hopefully this can help someone. I did my best to format my answer properly, this is my first time posting on StackOverflow:
In the Portable class library
Create a LoginResult class using the ICommandResult interface
public class LoginResult: MvcContrib.PortableAreas.ICommandResult
{
public bool Success { get; set; }
public string Message { get; set; }
public string Username { get; set; }
}
Create a LoginMessage class that also uses the ICommandResult interface with LoginResult. The LoginMessage class has a property for a LoginViewModel I use in my Login.cshtml view (it has Username, Password, and some other additional fields I needed for the view)
public class LoginMessage : ICommandMessage<LoginResult>
{
public LoginResult Result { get; set; }
public LoginViewModel Input { get; set; }
}
In the HttpPost action of the Login controller, create an instance of LoginMessage, passing in the LoginViewModel from the login view, and send it to the Host with MvcContrib.Bus.Send
[HttpPost]
public ActionResult Login(LoginViewModel mdl)
{
// TODO: Do basic auth here first, then send to Host for additional validation
// Create and send message to the Host
var message = new LoginMessage { Input = mdl, Result = new LoginResult() };
MvcContrib.Bus.Send(message);
if (message.Result.Success)
{
// Redirect to defaultUrl set in the Host's web.config
FormsAuthentication.RedirectFromLoginPage(mdl.Username, false);
}
return View("Login", "_Layout", mdl);
}
Note: LoginMessage sets a new empty LoginResult and then waits for Success. The Success is set by the Host (shown below). I do this because certain Host applications have specific additional logic that only apply to that application, so I let the Host do what it needs and return to the Portable to let it know if it passed or failed. Eventually, I will have the basic authentication logic in the Portable first and then let the Host do the extra work, but for the sake of this example I am keeping it simple.
In the Host web application (which has a reference to my Portable dll)
Create a handler for the Portable.LoginMessage so we can read it in the Host. Note that IsValidLogin is where I will eventually do my additional authentication logic to see if the user is valid
public class LoginHandler : MvcContrib.PortableAreas.MessageHandler<Portable.LoginMessage>
{
public override void Handle(Portable.LoginMessage message)
{
if (IsValidLogin(message.Input.Username, message.Input.Password))
{
message.Result.Success = true;
message.Result.Username = message.Input.Username;
}
else
{
message.Result.Message = "Username or Password was incorrect";
}
}
private bool IsValidLogin(string username, string password)
{
// TODO: Replace with actual authentication
return username.Equals("admin") && password.Equals("password");
}
}
In the web.config, set the defaultUrl that the Portable will redirect to in the controller I described earlier, when message.Result.Success is True. You do not need to be using Forms Authentication, the mode can be set to None, but you need to have the defaultUrl for this to work.
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Portable/Login" defaultUrl="~/Home/Index" />
</authentication>
</system.web>
That's it! This was a great exercise and learning experience for me. I am still figuring out the second part of my question where I need to send information to the Portable first (like the application title and assembly version) but I'm thinking I can do pretty much the same thing but in reverse, where I send an ICommandMessage to the Portable when my Host starts up (global.asax).