Tuesday, January 8, 2013

How to scroll a large text that doesn't scroll?

How to scroll a large text that doesn't scroll?

Just add this css element:

overflow: visible;

Monday, January 7, 2013

Need a Logout link on every page of your MVC 3 web site?

Need a Logout link on every page of your MVC 3 web site? This is fairly easy to do:

First thing to do is add a new Logout member of your Controller class as:


public PartialViewResult Logout(){  AdminData data = new AdminData( AccountController.OnlineUsers.Count );  return PartialView( data );}

What is AdminData? Just a small class that holds a number:

public class AdminData{
  public decimal LoggedInUsers { get; set; }
  public AdminData( decimal loggedInUsers )
  {
    LoggedInUsers = loggedInUsers;
  }
}

What should be in the view? Just this:

@model My_MSI.Net.Areas.Admin.Models.Entities.AdminData
 @using ( Ajax.BeginForm( "OnlineUsers", "BackOffice", new { area = "" }, new AjaxOptions { UpdateTargetId = "onlineUsers", HttpMethod = "Post" } ) )
{
  <table width="95%">
    <tr>
      <td style="text-align: right; width: 90%;">
       <span style="text-align: right;">We currently have
                       AjaxExtensions.ActionLink(  Ajax, @Model.LoggedInUsers.ToString),
                                                                      "OnlineUsers", "BackOffice",
                                                                       new { area = "" },
                                      new AjaxOptions { UpdateTargetId = "onlineUsers", HttpMethod = "Post" } )
                                      user(s) logged in.</span>
      </td>
      <td style="text-align: right; width: 10%;">
         @Html.ActionLink( Log Out", "LogOff", "BackOffice", new { area = "" }, null )
      </td>
    </tr>
  </table>}

This view may be more complicated than you want. Let me explain what it is doing:

The <td> with a <span ....>...</span> in it is actually an Unobtrusive AJAX link as the number of users currently logged into the BackOffice (password protected part of the site).

We put the LogOff member in a base class that all controllers derrive from and it looks like this:

public RedirectToRouteResult LogOff()
{
  if ( AccountController.OnlineUsers.Count >= 1 )
  {    LoggedInUsers member = AccountController.OnlineUsers
                                   .First( m => m.MemberName == User.Identity.Name );
       if ( member != null )
          AccountController.OnlineUsers.Remove( member );
  }
  FormsAuthentication.SignOut();
  return RedirectToAction( "Index", "Home",
                            new { MemberName = User.Identity.Name, area = "" } );
}

This processing takes place within the if (...) { } block:
We check the number of OnlineUsers in the first if( AcountController.OnlineUsers.Count >= 1 ) statement because if this list is empty we don't need to remove anyone.

Next we find the LoggedInUsers record for the current User.Identity.Name because this is the logged in member name.

Next we verify that the member is not null because that can happen sometimes.

If and only if the member is not null, we remove that member from the AccountController.OnlineUsers list.

Now we are outside the if() { } block:

The first thing that you'll want to do is do a: FormsAuthentication.SingOut(); This signs out the user and removes that authentication cookie from the HTTP stream.

Finally we need to RediredtToAction to the publicly accessable portion of the site.

That's almost everything that you need to do to get a Logout link on every page of your site:

The final step is:

In your _Layout.cshtml file,

Find this:
<div class="content">
@RenderBody()
</div>

You want to add this above the @RenderBody() method:

@{ Html.RenderAction( "Logout", "BackOffice" ); }

That's everything you need to do to get a Logout link on every page of your site.


Sunday, December 23, 2012

What happens if you need to test a user role where it isn't set up yet?

In my AccountController's LogIn method, since I've added Role based authentication to the site,
I wanted to use User.IsInRole( "Admin" ) to check for an Admin role.

Of course that doesn't work!

So instead, I had to do this:

decimal id = Identity.LookupId( details.MemberName, db );
Identity Id = Identity.Load( id, db );
if( Id.RoleIndex == 1 )
{
...

}

That's two hits to the database, but I do those two hits on almost every page so I know that the server can easily handle the task at hand.

Why was I interested in this:

Well we have two different controllers and I wanted the Admin role to be sent to the AdminController and normal Users to be sent to the BackOfficeContoller.

Wednesday, December 19, 2012

Ever want to create a list of logged in users for a site, maybe for the Admin view or all users?

Here's how:

In your AccountController add a simple class:

public class LoggedInUsers
{
  public string   MemberName   { get; set; }
  public DateTime LoggedInTime { get; set; }

  public LoggedInUsers( string memberName, DateTime loggedInTime )
  {
    MemberName   = memberName;
    LoggedInTime = loggedInTime;
  }
}


Then in your AccountController add a:
 
public static List<LoggedInUsers> OnlineUsers;

Then in your AccountController's [HtmlPost] Login(...) method add this line for authenticated users:
 
AccountController.OnlineUsers.Add( new LoggedInUsers( details.MemberName, DateTime.Now ) );

This adds a new user to the OnlineUsers list.

To remove a entry from the list:

In your Logout() method (you have one of these, right?):

Add this:
 
if ( AccountController.OnlineUsers.Count > 0 )

{
  If( AccountController.OnlineUsers.Count > 0 )
  {
    LoggedInUsers member = AccountController.OnlineUsers.First( m => m.MemberName == User.Identity.Name );
    AccountController.OnlineUsers.Remove( member );
  }
}

Just before this:

FormsAuthentication.SignOut();

There is one other place you'll need to add some code and that is in your global.asax Session_End method like this:

protected static void Session_End( object Sender, EventArgs e )
{ 
  if( AccountController.OnlineUsers.Count > 0 )
  {
    LoggedInUsers member    AccountController.OnlineUsers.Find( m => m.MemberName == HttpContext.Current.User.Identity.Name );
    if( member != null )
      AccountController.OnlineUsers.Remove( member );
  }

}

We need to check here if the member is null because if a user logged out he/she will already be removed from the OnlineUsers list.

Next you need to create a partial view method in your controller:

public PartialViewResult OnlineUsers()
{
  List<LoggedInUsers> data = AccountController.OnlineUsers;

  return PartialView( data );
}

Now you can create a partial view for logged in users to see:

@model List<My_MSI.Net.Controllers.LoggedInUsers>
@foreach ( My_MSI.Net.Controllers.LoggedInUsers user in Model )
{
   @Html.Label( @Html.Lable( String.Format( "{0} logged in at {1}", user.MemberName, user.LoggedInTime.Tostring() ) )<br />
}


Finally in your _Layout.cshtml file (You do have one of these, right?):
<div class="move-right">
  @{ Html.RenderAction( "OnlienUsers", "Your controllers name" ); }
</div>

Now you logged in users can see who else is logged in.

If you want to show logged in users you'll have to create this View in your Home controller:

public ActionResult OnlineUsers()
{
  List<LoggedInUsers> data = AccountController.OnlineUsers;
  return View( data );
}

And a View:

@model List<My_MSI.Net.Controllers.LoggedInUser{@ViewBagTitle = "OnlineUsers";Layout = "~/Views/Shared/_Layout.cshtml";}<h2>OnlineUsers</h2>

@foreach ( My_MSI.Net.Controllers.LoggedInUsers user in Model )
{
  @Html.Label( String.Format "{0} logged in at {1}", user.MemberName, user.LoggedInTime.ToString() )<br />
}

Finally add a new menu item for Online Users and link it back to your OnlineUsers() method.

That's it. You've successfully created a list of Online Users that Logged in members can see and a link were the general public can see who's logged in.

 
 



Sunday, December 16, 2012

How solve Unexpected Logout issues

When you set the Session TimeOut to 20, you would expect the Session to expire after 20 minutes of inactivity. However, you're using Session State Mode InProc (the default value), which means that the SessionState is stored in memory. When the Application Pool recycles. all Sessions stored in Memory will be lost. There can be many reasons why the Application Pool recycles.

http://blogs.msdn.com/b/johan/archive/2007/05/16/common-reasons-why-your-application-pool-may-unexpectedly-recycle.aspx

Also, in a shared hosted environment, Application Pools recycles frequently. To overcome both problems, you should consider to use another SessionState Mode:

http://msdn.microsoft.com/en-us/library/ms178586(v=vs.100).aspx

But this has nothing to do with authentication, as already stated! When you set the forms authentication to 20 minutes, it means that the user will be logged out anywhere between 10 to 20 minutes of inactivity. This is because the authentication ticket is only reset after more than half of the timeout has expired.

http://msdn.microsoft.com/en-us/library/system.web.configuration.formsauthenticationconfiguration.slidingexpiration.aspx

But sometimes the authentication ticket seems to expire unexpectedly also, forcing the user to the login page.. To understand why this happens, you need to understand how authentication works.

When you login, an authentication ticket is created in a cookie. By default, this authentication ticket encrypted using the machinekey section in web.config. When this section is not specified in web.config, ASP.NET will generate one for you. If the application pool recycles, sometimes ASP.NET will generate a new machinekey (although MSDN says different!) especially in shared hosted environment. But with this new key, the authentication ticket cannot be decrypted anymore, so the user is redirected to the login page. To overcome this, simply add a machinekey section in your web.config, so the same key is used on each and every request:

http://www.developmentnow.com/articles/machinekey_generator.aspx

Thursday, December 13, 2012

Ever had a View in MVC that was being reused by the Controller but the View doesn't display all the fields from the Model class?

What doesn't work is creating another view to display the data. So don't waste your time trying to create another View!

What does work is this:

ModelState.Remove( "yourModel'sDataName" );

You put the above statement (with your Model's Data Name) in the controller for every field that is not displaying even though you know that the Model class has data.



Monday, December 3, 2012

Ever wonder how to update a record with Entity Framework?

Well, I'm about to show you how to do that with the controller:

[HttpPost]
public ActionResult Manage( Home data, decimal Id1, decimal Id2 ){
  data.MemberId.Id = Id1;
  data.MemberDetails.Id = Id2;
 
  Identity record = db.Identity.FirstOrDefault( m => m.Id == Id1 );
  record.Copy( data.MemberId );
  Details det = db.Details.FirstOrDefault( m => m.Id == Id2 );
  det.Copy( data.MemberDetails );
  ( (EFDbContext) db ).SaveChanges();

  data.Status =
"Changes were sucessfully saved";
  return View( data );}

What we are accomplishing here is this:

The user has modified his/her Identity and/or Details records and we need to store them in the database.

These records come back to us in a Home record that is simply a holder of several types needed by the home page of the site.

The first thing we do is assign the record numbers to the two records because they are 0 when we get them in the controller.

The next thing we do is read those records from the database again (that connection to the database was lost someplace between the [HttpGet] method that passes a Home record to the View).

Then we call a copy method on the record to copy the updated info into the current record.

We do those last two steps twice once for each record type.

The next thing we do is call .SaveChanges() to write the updated info to the database.

The next thing we do is provide a little feedback to the user in a [NotMapped] field on the Home class.

Finally we return to the View passing the data we recieved.

This is much easier than calling .SqlQuery( "Update..." ); especailly when the detail record has way too many members!