0

Currently in every one of my model classes I am doing the following in the constructor.

    public Product()
    {
        CreatedBy = System.Threading.Thread.CurrentPrincipal.Identity.Name;
        ModifiedBy = System.Threading.Thread.CurrentPrincipal.Identity.Name;
    }

This way when saving/updating records these values will always be set.

However, I want to switch my application from using the logged in user's name which the above gets me, to using the logged in user's Id.

With forms authentication what is the best way to retrieve additional data about the current logged in user without using Session? I noticed you can pass additional data when creating the forms auth cookie as soon as the user logs in, but I would have no idea how to retrieve it.

Here is the solution I came up with. I would appreciate any comments or suggestions. It feels kind of strange that everytime I create one of these model objects on the UI I need to pass the userId in the constructor:

public abstract class AuditableEntity : BaseEntity
{
    public DateTime CreatedDate { get; set; }
    public int? CreatedBy { get; set; }
    public DateTime ModifiedDate { get; set; }
    public int? ModifiedBy { get; set; }
}

public class Product : AuditableEntity
{
    public User(int userId)
    {
        this.CreatedBy = userId;
        this.ModifiedBy = userId;
    }

    public string ProductName { get; set; }
    public string SerialNumber { get; set; }
}
William Venice
  • 329
  • 2
  • 7
  • 16
  • Don't do that. Instead, have the constructor take parameters for that information, and have the calling code inject that information in. – mason Sep 23 '15 at 16:05
  • Alright, so lets say I make this part of the constructor for all of my business entities that have these properties. Isn't that kind of crazy that everytime I create or get an existing object I have to pass this information to it so these properties always get set? Just seems like my UI code-behind will be ugly. Is there any way to make it so they are just always set, similar to what you told me not to do automatically in the constructor? I am not using entity framework so I can't add it to a dbcontext, I am using dapper/sql strings directly in my repo. – William Venice Sep 24 '15 at 05:11
  • I updated my post, please see the bottom and tell me if this is how you were telling me to do it. Should the CreatedBy and ModifiedBy Ids be nullable? – William Venice Sep 24 '15 at 05:54
  • @mason Additionally I am a bit confused of what happens when I retrieve a list of Products from the database, all of these Auditing properties will be set. But what happens if I make updates to this list of products and need to update these properties. I am assuming this wouldn't be through the constructor, but just setting the properties directly. – William Venice Sep 24 '15 at 06:02
  • You don't need to set the properties in the UI layer. You should have a separate layer for application logic. And that layer should also handle making updates to the entities. – mason Sep 24 '15 at 12:38
  • I have a typical UI layer > BLL layer > DAL layer, and Model layer that everything references. So are you saying when I call ProductBLL.SaveProduct(product), the ProductBLL.SaveProduct function will handle it? Then the DAL will have it and be able to save it? It doesn't make sense to me that I would have to reference web from my BLL and retrieve the HttpContext.Current info – William Venice Sep 24 '15 at 13:11
  • No, I didn't say that. When you call `ProductBLL.SaveProduct`, that's where you pass in relevant info needed to *save the product*. That is not your UI layer, it's hopefully a controller in an MVC application, or some centralized location in a Web Forms project. – mason Sep 24 '15 at 13:16
  • Alright, now I think I understand. So I am still handling the setting of those properties in my UI layer, either setting them directly or passing them to the constructor when creating new. In my case I am assuming it will be in the Code-Behind since my app is Webforms. Still don't really understand what you mean by centralized location. I imagine myself having to pass the info in all over the place whenever necessary. – William Venice Sep 24 '15 at 13:18
  • In that case, I'd create a helper class that sits between your code behind and BLL that centralizes your SaveProduct calls. In that helper class, add the info you want. That will keep your code behind from having extraneous repetitive calls. – mason Sep 24 '15 at 13:21
  • I am wondering if I will be able to create a generic helper class that can work with all of my enities that are auditable. So these values are just always set. I still don't really see what your saying though. Could you give me a small example by posting in here of what the helper class could look like and how I would use it from my code-behind. If not it's alright. – William Venice Sep 24 '15 at 13:23

2 Answers2

1

When authenticating an user, userYou can use FormsAuthenticationTicket to pass all cookie information and additional userData (string) to it, here is an example, then retrieve it by:

       FormsIdentity id = (FormsIdentity)User.Identity;
       FormsAuthenticationTicket ticket = id.Ticket;

       //those are label ids, that why they have Text property 
       cookiePath.Text = ticket.CookiePath;
       expireDate.Text = ticket.Expiration.ToString();
       expired.Text = ticket.Expired.ToString();
       isPersistent.Text = ticket.IsPersistent.ToString();
       issueDate.Text = ticket.IssueDate.ToString();
       name.Text = ticket.Name;
       userData.Text = ticket.UserData; // string userData you passed is HERE
       version.Text = ticket.Version.ToString();

More information

By the way, you should use HttpContext.Current.User.Identity.Name, take a look at this and this maybe this question.

Community
  • 1
  • 1
Ronaldinho Learn Coding
  • 13,254
  • 24
  • 83
  • 110
  • This is exactly what I needed. However, will I be able to use this retrieval technique from a class library such as my DAL or BLL? – William Venice Sep 23 '15 at 18:13
  • Yes I believe so, use it anywhere you want, make sure the user is logged otherwise there will be an exception when casting, just put them in a `try catch block`. – Ronaldinho Learn Coding Sep 23 '15 at 18:41
1

You mentioned you have a lot of classes that have these CreatedBy and ModifiedBy properties.

Have them implement an interface:

interface IAuditableEntity
{
    int CreatedBy {get; set;}
    int ModifiedBy {get; set;}
}

public class Product : IAuditableEntity
{
    public string ProductName { get; set; }
    public string SerialNumber { get; set; }
    public int CreatedBy {get; set;}
    public int ModifiedBy {get; set;}
}
public class Vendor : IAuditableEntity
{
    public string VendorName{ get; set; }
    public string VendorNumber { get; set; }
    public int CreatedBy {get; set;}
    public int ModifiedBy {get; set;}
}

Create a centralized location in your web application responsible for communicating to the BLL that sets these properties for you.

public class BllLink
{
    public static void SaveEntity(IAuditableEntity entity)
    {
        entity.ModifiedBy = HttpContext.Current.User.Identity.Name;
        if(String.IsNullOrEmpty(entity.CreatedBy)) //assume new
        {
            entity.CreatedBy = HttpContext.Current.User.Identity.Name;
        }
        Bll.SaveEntity(entity); //you might need to use generics
    }
}

When you see repetitive code, it's a good candidate for simplification. Learning how to effectively utilize generics and interfaces will go a long ways towards that.

mason
  • 31,774
  • 10
  • 77
  • 121
  • Thank you so much for showing me this. It looks similar to one of the things a UnitOfWork class would take care of. This example is using MVC but it can at least show me how to set up my Model with interfaces: http://techbrij.com/generic-repository-unit-of-work-entity-framework-unit-testing-asp-net-mvc – William Venice Sep 24 '15 at 14:23
  • It seems like I will definitely have to use generics to implement this. As of now all of my entities have their own bll / repo functions, i.e. SaveProduct, SaveVendor, etc. I am not using entity framework, I am using dapper in my repo (old school sql strings). So unless I decide to use different technologies I am assuming I can't implement this and I do have to pass it directly in. It seems like the furthest I can go is create a common method that accepts IAuditableEntity and sets the values. So during my save in the code-behind I would just call SetAuditValues(Product), then call ProductSave. – William Venice Sep 24 '15 at 14:33
  • @WilliamVenice That's a good interim solution. I thought about suggesting it in my answer, but didn't want to make it overly complex. I wrote a repository last night and implemented separate `SaveProduct` and `SaveCustomer` methods, but after I went to bed I realized how repetitive all of that code was, since the code for saving didn't very between the different entity types other than the type names. – mason Sep 24 '15 at 14:40