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.
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.
=> 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.
=> Only if, you manually assign it otherwise NO.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With