3

In my database table I have a column in which the values are manipulated before saving into the database. The logic for the manipulation was added at a later stage of development, after a lot of values were inserted to the table. Now I want to edit the contents of the table to manipulate the values of the existing contents.

My approach

To call the edit function for all the items in the table as the manipulation logic is added in the EDIT action method as well.

When I call the edit function while looping through the contents in the database, I get a null reference exception which is not there when I use the edit function from the UI.

EDIT Action method

    public ActionResult Edit([Bind(Include = "SetValueID,Value,Status,TcSetID,OptionValueID,CreatedOn,CreatedBy,ModifiedOn,ModifiedBy")] SetValue setValue)
    {
       //Many lines removed for simplicity. all the variables used in the code are assigned.
            if (ModelState.IsValid)
            {
                // Valuestring from another function

                setValue.Value = valuestring;
                var original = db.SetValue.Find(setValue.SetValueID);
                bool modified = original.Value != setValue.Value;
                if (modified)
                {
                    var rev = new RevisionHistory();
                    rev.CreatedOn = original.ModifiedOn;
                    rev.ModifiedBy = User.Identity.Name; //If modified exception on this line
                    db.Entry(original).CurrentValues.SetValues(setValue);
                    db.RevisionHistory.Add(rev);
                }
                original.ModifiedOn = DateTime.Now;
                original.ModifiedBy = User.Identity.Name; //if not modified exception on this line
                db.Entry(original).State = EntityState.Modified; 
                db.SaveChanges();
            }
    }

The call to this function was made from the HomeController. I commented all the return statements in the EDIT method while calling it from the HomeController.

Exception

Object reference not set to an instance of an object.

Question

Why does the edit work while called from the UI without any Exception , but not from HomeController?

Why is the user null even when I call the function from a different controller? Using windows authentication. How do I get the user name if the user has already been authenticated?

EDIT - Added how the Edit function is called

    //Home controller (But I have also tried calling the function from a different controller where the call to the method is from the UI
    public ActionResult Index()
    {
        test();
        return View();
    }

    public void test()
    {
        foreach(var item in db.SetValue.ToList())
        {
            var setvalcon = new SetValuesController();
            setvalcon.Edit(item);

        }     
    }

Update - General Overview of the problem

The question can be generalized so as to find an answer how the User property is defined when using Windows Authentication.

Is it that the controller gets access to the Windows user property only when it is called from the UI?

Is it not possible to access the User Property in a function that is not called via UI?

And the best thing as far as I know about the issue is that, the User is defined in the Controller where the testmethod is called but not in the Edit in another controller.

brainless coder
  • 6,310
  • 1
  • 20
  • 36
Vini
  • 1,978
  • 8
  • 40
  • 82
  • 2
    Voting to close. Please ask a coworker to show you how to make a proper bug report. The error is clear - the error LINE is not stated. You expect us to build a test case? And all the code after the error can obviously be removed. – TomTom Feb 04 '16 at 09:19
  • @TomTom : I found the User is set to null. I have added that in the question. And the line where the exception is caught is also mentioned in the code. And moreover if I had a coworker who could help me out why would I ask the question here and wait hours for an answer? – Vini Feb 04 '16 at 09:26
  • Possible duplicate of [What is a NullReferenceException and how do I fix it?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Ondrej Tucny Feb 04 '16 at 09:29
  • Oh Come on.. Whenever the question is tagged nullrefernce this linked question does not always help. I have mentioned in my question clearly I am having a null value for `User`. Not for something that I have from my model. – Vini Feb 04 '16 at 09:30
  • You may want to show where that `User` property is defined and being assigned to, and how the non-UI flow differs from the UI flow... as well how you have tried to debug this issue so far. – Pieter Witvoet Feb 04 '16 at 09:46
  • In none of my methods i have not assigned the `User` property. I suppose it is populated when an user login to the website using windows authentication. This method is called only after atleast once the `User` property is used. The only difference in the control flow is that in Non UI the method is invoked from a fucntion call in another method. – Vini Feb 04 '16 at 09:58
  • Your question is still missing a lot of context. Which class contains this `Edit` method and which class(es) does it inherit from? Where is that `User` property defined? – Pieter Witvoet Feb 04 '16 at 10:04
  • The user property is not defined anywhere. It is the User attribute from the Windows authentication. In none of my action methods I have defined the User property, but have used it. Even in the given code snippet, the code works when the function is called from the UI. Even in that case the user poperty is not defined. It just takes the value from the currently logged in Window user. There is no inheritance. I have created an object of the controller which contains the EDIT method and called the method. it is evident in the code I suppose – Vini Feb 04 '16 at 10:08
  • Have you decorated the action method with the `[Authorize]` attribute? Also is Windows Auth enabled in IIS? – Chris Pickford Feb 04 '16 at 10:10
  • No. I have no `Authorize` attribute. But I have `HttpPost` and `ValidateAntiForgery`. Will check now by removing those as well. – Vini Feb 04 '16 at 10:14
  • Try adding `[Authorize]`. Are you running using IIS or through Visual Studio? – Chris Pickford Feb 04 '16 at 10:17
  • Through visual studio. I tried with authorize. It doenst help. – Vini Feb 04 '16 at 10:18
  • 1
    F4 properties on your project. Is `Windows Authentication` enabled and `Anonymous Authentication` disabled? – Chris Pickford Feb 04 '16 at 10:24
  • And now I figured out, if the method is called from the controller where the `Edit` method is, the User is not null. – Vini Feb 04 '16 at 10:24
  • @ChrisPickford: All those have already been checked. Nothing relating to that – Vini Feb 04 '16 at 10:25

2 Answers2

3

You are partially correct when you are saying you can only access the User property only accessing from the UI. The correct answer is -

When accessing the Edit method through the UI, it is actually going through the ASP.Net controller initializer pipeline and that environment takes care of the current browser session and assigns the User variable. HttpContext is available at this time.

But when you are creating the controller variable like this -

var setvalcon = new SetValuesController();
setvalcon.Edit(item);

You are bypassing all those initialization codes. No HttpContext and this is just another normal object and thus it does not have the User property populated.

ANSWER TO QUESTIONS:

  1. Is it that the controller gets access to the Windows user property only when it is called from the UI?

=> Yes, absolutely right, because you are going through the ASP.Net pipeline. But it is not only for Windows user, it is all those things that re in a HttpContext.

  1. Is it not possible to access the User Property in a function that is not called via UI?

=> Only if, you manually assign it otherwise NO.

MORE INSIGHT:

Basically, what you are trying to achieve is a very poor design. Nowhere, remember nowhere, you are supposed to call a controller from inside a controller unless it is a subclass to base method call. The only way you can call another controller from inside another controller is redirecting the execution by using "Redirect" methods. No one will stop you from calling controller like this, but this shows poor design principle..

The best way to solve your situation is like this -

public class ModelService {
    public void Edit(IPrincipal user, SetValue setValue){
        setValue.Value = valuestring;
            var original = db.SetValue.Find(setValue.SetValueID);
            bool modified = original.Value != setValue.Value;
            if (modified)
            {
                var rev = new RevisionHistory();
                rev.CreatedOn = original.ModifiedOn;
                rev.ModifiedBy = User.Identity.Name; //If modified exception on this line
                db.Entry(original).CurrentValues.SetValues(setValue);
                db.RevisionHistory.Add(rev);
            }
            original.ModifiedOn = DateTime.Now;
            original.ModifiedBy = User.Identity.Name; //if not modified exception on this line
            db.Entry(original).State = EntityState.Modified; 
            db.SaveChanges();
    }
}

Then in both the controllers constructors -

public class ControllerOne : Controller {
    private readonly ModelService _modelService
    //constructor
    public ControllerOne(){
         _modelService= new ModelService ();
    }

    public ActionResult Edit([Bind(Include = "SetValueID,Value,Status,TcSetID,OptionValueID,CreatedOn,CreatedBy,ModifiedOn,ModifiedBy")] SetValue setValue)
    {
        //Many lines removed for simplicity. all the variables used in the code are assigned.
        if (ModelState.IsValid)
        {
            _modelService.Edit(User, Setvalue);
        }
    }

//controller 2
public class HomeController: Controller {
    private readonly ModelService _modelService
    //constructor
    public ControllerOne(){
         _modelService= new ModelService ();
    }

    public ActionResult Index()
    {        
        foreach(var item in db.SetValue.ToList())
        {
            _modelService.Edit(User, item);
        }     
    }
}

You can take help from IoC Container for dependency injection, that is even better approach.

brainless coder
  • 6,310
  • 1
  • 20
  • 36
  • But in the controller just before the Edit function is called the user is not null. Only when the control is passed to the next controller it becomes null. In that case how do I assign the pass the value of the user? And isnt User a static global variable ? – Vini Feb 04 '16 at 13:15
  • Nope. It is controller property. You can't pass by default. Controllers are not meant to be used like this. You should use a third class as a service and pass in the User from both the controller as a parameter. Move the codes inside that service. – brainless coder Feb 04 '16 at 13:18
  • Right now i moved the test to the same controller in which the edit also resides and solved the issue. I thought user was not bound to any controller. – Vini Feb 04 '16 at 13:22
  • In that case how do I pass the user value, the only way is to pass the user as an argument to the Edit function as mentioned in the other answer? – Vini Feb 04 '16 at 13:55
  • Yeah.. more or less similar to the answer of issac. I have updated my answer a bit. – brainless coder Feb 04 '16 at 21:43
1

Invoking a Controller from within another Controller is probably getting some data (as the User) to be missing. I wouldn't be making much effort understanding why (unless curious), since doing it this way might be considered as bad design. (I bet some other info would be missing as well, maybe cookies and such). the better thing you can do is to separate the logic from your Controllers into some service layer and call the methods with the IPrincipal User as parameter. If you are forced to do it the way you described, then send the user as a parameter to the other controller.

public ActionResult Index()
{
    test(User);
    return View();
}
public void test(IPrincipal user)
{
    foreach(var item in db.SetValue.ToList())
    {
        var setvalcon = new SetValuesController();
        setvalcon.Edit(item, user);

    }     
}

And the oter controller

public ActionResult Edit([Bind(Include = "SetValueID,Value,Status,TcSetID,OptionValueID,CreatedOn,CreatedBy,ModifiedOn,ModifiedBy")] SetValue setValue, IPrincipal user = null)
{
   var currentUser = User == null? user : User;//or somthing like that
}

Didn't check this code. but it should give you something to work with.

Issac
  • 943
  • 1
  • 6
  • 13