I am trying to insert a new record into a custom table in the database. This is being performed through a graph extension onto the SO graph. The code is as follows:
public PXAction<PX.Objects.SO.SOOrder> addToDatabase;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Send to Manufacturing")]
protected void AddToDatabase()
{
try
{
Flow2 graphWO = PXGraph.CreateInstance<Flow2>();
EMPWorkOrder wo = null;
foreach (PXResult<SOLine, InventoryItem> line in PXSelectJoin<SOLine, LeftJoinSingleTable<InventoryItem, On<InventoryItem.inventoryID, Equal<SOLine.inventoryID>>>,
Where<SOLine.orderNbr, Equal<Current<SOOrder.orderNbr>>>>.Select(Base, Base.Document.Current.OrderNbr))
{
PXCache sender = Base.Transactions.Cache;
SOLine soLine = (SOLine)line;
InventoryItem item = (InventoryItem)line;
SOLineExt lineExt = sender.GetExtension<SOLineExt>(soLine);
if (lineExt.UsrisSentToManufacturing != true)
{
wo = new EMPWorkOrder();
wo.Soid = Base.Document.Current.OrderNbr;
wo.ItemCD = item.InventoryCD;
wo.LineNbr = soLine.LineNbr;
wo.QtyReceived = 0;
wo.DateReceived = null;
wo.QtyComplete = 0;
wo.QtySentToInventory = 0;
wo.RouteId = "0";
wo.KitId = -1;
wo.IsStarted = false;
wo.NoteID = Guid.NewGuid();
graphWO.Document.Insert(wo);
graphWO.Actions.PressSave();
graphWO.Clear();
}
}
throw new PXException("Successfully moved to Manufacturing");
}
}
When executing the code, the error I receive is the following:
Error#14: Inserting 'EMPWorkOrder' record raised one or more errors. Please review.
When I go into the trace, this is the information it gives me:
12/16/2016 1:44:23 PM Error:
Error #14: Inserting 'EMPWorkOrder' record raised one or more errors. Please review.
at PX.Objects.SO.SOOrderEntry_Extension.AddToDatabase()
at PX.Data.PXAction`1.<>c__DisplayClass3_0.<.ctor>b__0(PXAdapter adapter)
at PX.Data.PXAction`1.a(PXAdapter A_0)
at PX.Data.PXAction`1.d__31.MoveNext()
at PX.Data.PXAction`1.d__31.MoveNext()
at PX.Web.UI.PXBaseDataSource.tryExecutePendingCommand(String viewName, >String[] sortcolumns, Boolean[] descendings, Object[] searches, Object[] >parameters, PXFilterRow[] filters, DataSourceSelectArguments arguments, >Boolean& closeWindowRequired, Int32& adapterStartRow, Int32& adapterTotalRows)
at PX.Web.UI.PXBaseDataSource.ExecuteSelect(String viewName, DataSourceSelectArguments arguments, PXDSSelectArguments pxarguments)
Is there any reason that my record wouldn't be inserting into the custom table I made? If you need any other information to develop a resolution to this issue, please feel free to ask.
Eric, you most likely get this error because of some empty field or fields (with no value or null value assigned) decorated with the PXDefaultAttribute. While running the code under debugger, you should get access to a more detailed exception through the InnerException property of the originally thrown exception.
On a side note, let me also advise you to add SOLine.orderType field in Where clause of your BQL query in the beginning of action delegate, as there are 2 key fields defined for the SOOrder DAC has: OrderType and OrderNbr
Related
I am trying to add a condition to a PXSelect defined on the SOShipmentEntry graph. I had read that I can just redefine the PXSelect and it would overwrite the original value with mine, but that doesn't seem to be happening.
I created the graph extension as usual, then defined the PXSelect with the extra "And" I need like this:
public PXSelectJoin<SOShipmentPlan,
InnerJoin<SOLineSplit, On<SOLineSplit.planID, Equal<SOShipmentPlan.planID>>,
InnerJoin<SOLine, On<SOLine.orderType, Equal<SOLineSplit.orderType>, And<SOLine.orderNbr, Equal<SOLineSplit.orderNbr>, And<SOLine.lineNbr, Equal<SOLineSplit.lineNbr>>>>,
InnerJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<SOShipmentPlan.inventoryID>>,
LeftJoin<INLotSerClass,
On<InventoryItem.FK.LotSerClass>,
LeftJoin<INSite,
On<SOLine.FK.Site>,
LeftJoin<SOShipLine,
On<SOShipLine.origOrderType, Equal<SOLineSplit.orderType>,
And<SOShipLine.origOrderNbr, Equal<SOLineSplit.orderNbr>,
And<SOShipLine.origLineNbr, Equal<SOLineSplit.lineNbr>,
And<SOShipLine.origSplitLineNbr, Equal<SOLineSplit.splitLineNbr>,
And<SOShipLine.confirmed, Equal<boolFalse>,
And<SOShipLine.shipmentNbr, NotEqual<Current<SOShipment.shipmentNbr>>>>>>>>>>>>>>,
Where<SOShipmentPlan.siteID, Equal<Optional<SOOrderFilter.siteID>>,
And<SOShipmentPlan.planDate, LessEqual<Optional<SOOrderFilter.endDate>>,
And<SOShipmentPlan.orderType, Equal<Required<SOOrder.orderType>>,
And<SOShipmentPlan.orderNbr, Equal<Required<SOOrder.orderNbr>>,
And<SOLine.operation, Equal<Required<SOLine.operation>>,
And<SOShipLine.origOrderNbr, IsNull,
And<SOLineExt.usrShipmentHold, NotEqual<True>>
>>>>>>> ShipmentScheduleSelect;
Edit: After reviewing the code posted by Samvel, I found some additional information online and have created a delegate method below. However, when this runs, I am getting the message that the SiteID is not found, and the Site ID appears to be blank. When I step through the code, PXView seems to contain all of the parameters passed in as expected. It's almost like the parameters are being wiped out. I tried copying the parameters to a new array as well, but that also didn't work. Any ideas?
public IEnumerable shipmentScheduleSelect()
{
PXView select = new PXView(Base, true, Base.ShipmentScheduleSelect.View.BqlSelect);
select.BqlSelect.WhereAnd(typeof(Where<SOLineExt.usrShipmentHold, NotEqual<True>>));
Int32 totalrow = 0;
Int32 startrow = PXView.StartRow;
List<object> result = select.Select(PXView.Currents, PXView.Parameters,
PXView.Searches, PXView.SortColumns, PXView.Descendings,
PXView.Filters, ref startrow, PXView.MaximumRows, ref totalrow);
PXView.StartRow = 0;
return result;
}
Please see below an example of how you can add the data delegate for this specific case:
using System.Collections;
using System.Collections.Generic;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.IN;
namespace PX.Objects.SO
{
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
#region Event Handlers
public IEnumerable shipmentScheduleSelect()
{
throw new PXException("TEST EXCEPTION");
/*
Replace the PXException with the override of the select as you need. Below is just an example how you can do that but make sure to provide all the parameters to the select
var cmd = new PXSelectJoin<SOShipmentPlan, InnerJoin<SOLineSplit, On<SOLineSplit.planID, Equal<SOShipmentPlan.planID>>,
InnerJoin<SOLine, On<SOLine.orderType, Equal<SOLineSplit.orderType>, And<SOLine.orderNbr, Equal<SOLineSplit.orderNbr>, And<SOLine.lineNbr, Equal<SOLineSplit.lineNbr>>>>,
InnerJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<SOShipmentPlan.inventoryID>>,
LeftJoin<INLotSerClass, On<InventoryItem.FK.LotSerialClass>, LeftJoin<INSite, On<SOLine.FK.Site>,
LeftJoin<SOShipLine, On<SOShipLine.origOrderType, Equal<SOLineSplit.orderType>,
And<SOShipLine.origOrderNbr, Equal<SOLineSplit.orderNbr>,
And<SOShipLine.origLineNbr, Equal<SOLineSplit.lineNbr>,
And<SOShipLine.origSplitLineNbr, Equal<SOLineSplit.splitLineNbr>,
And<SOShipLine.confirmed, Equal<boolFalse>,
And<SOShipLine.shipmentNbr, NotEqual<Current<SOShipment.shipmentNbr>>>>>>>>>>>>>>,
Where<SOShipmentPlan.siteID, Equal<Optional<SOOrderFilter.siteID>>,
And<SOShipmentPlan.planDate, LessEqual<Optional<SOOrderFilter.endDate>>,
And<SOShipmentPlan.orderType, Equal<Required<SOOrder.orderType>>,
And<SOShipmentPlan.orderNbr, Equal<Required<SOOrder.orderNbr>>,
And<SOLine.operation, Equal<Required<SOLine.operation>>,
And<SOShipLine.origOrderNbr, IsNull>>>>>>>(this.Base);
List<object> pars = new List<object>(){
siteId,
endDate,
orderType,
orderNbr,
operation
};
bool condition = true;
if(condition)
{
cmd.WhereAnd(typeof(Where<InventoryItem.itemClassID, Equal<Required<InventoryItem.itemClassID>>>));
pars.Add("TEST_ITEM_CLASS");
}
return cmd.Select(pars.ToArray());
*/
}
#endregion
}
}
How can a particular sales order be cancelled via code? Perhaps I can call the ProcessOrders graph, loop through the select orders, and execute the Cancel Order method. Unfortunately I see no such method. The drop-down action is driven by the automation menu. I do not find a cancel order action in the standard sales order entry graph. So what is the best way to accomplish the goal?
Via code, I can manually set the cancelled flag and status. This seems to work, but I'm not sure that is recommended. Seems like I'm skipping something, and the automation menu should be the way.
I'm copying the answer from this Acumatica blog post since it does exactly what you need, call the 'Cancel Order' automation step from code: Running Automation Step from Code
To call automation step you have to:
Define a new custom PXView that will return record that we want to
process
Create an adapter, that will provide data for button handler. Adapter
will get data from custom PXview.
Create a separate instance of graph, that will handle action.
Code:
public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
{
//Lets define additional button than will call automation button.
public PXAction<SOOrder> ButtonExample;
[PXButton()]
[PXUIField(DisplayName = "Button Example")]
public virtual IEnumerable buttonExample(PXAdapter adapter)
{
SOOrder order = Base.Document.Current;
//creating a graph that will process Internal command
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = graph.Document.Search<SOOrder.orderNbr>(order.OrderNbr, order.OrderType);
//Searching for correct button from that is defined in Automatin steps.
//All sub menues are adden under action button, so we can get them and iterate.
foreach (var action in (graph.action.GetState(null) as PXButtonState).Menus)
{
if (action.Command == "Cancel Order")
{
//Constructing dummy view that will always return only one record.
adapter = new PXAdapter(new DummyView(graph, graph.Document.View.BqlSelect, new List<object> { order }));
//defining a button command
adapter.Menu = action.Command;
//running button
return graph.action.Press(adapter);
}
}
return adapter.Get();
}
//Defining a dummy view that is inherited from PXView
internal class DummyView : PXView
{
//Storing list of records
List<object> _Records;
internal DummyView(PXGraph graph, BqlCommand command, List<object> records)
: base(graph, true, command)
{
_Records = records;
}
//Everytime when system calls select for the view, retun saved records.
public override List<object> Select(object[] currents, object[] parameters, object[] searches, string[] sortcolumns, bool[] descendings, PXFilterRow[] filters, ref int startRow, int maximumRows, ref int totalRows)
{
return _Records;
}
}
}
I would think it's preferable to call the Automation Step instead of manually setting the Cancelled field because if a user changes the Automation Step you would pick up the changes by calling it.
Note that when possible you should always use existing Graph when you need to manually handle data because this will trigger validations.
If you were to manually change the Cancelled field using SOOrderEntry instead of calling the automation steps, the following validation in SOOrderEntry would still apply:
protected virtual void SOOrder_Cancelled_FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
SOOrder row = (SOOrder) e.Row;
PXResultset<CCProcTran> trans = PXSelect<CCProcTran, Where<CCProcTran.origRefNbr, Equal<Current<SOOrder.orderNbr>>,
And<CCProcTran.origDocType, Equal<Current<SOOrder.orderType>>,
And<CCProcTran.refNbr, IsNull,
And<CCProcTran.docType, IsNull>>>>>
.Select(this);
CCProcTranHelper.UpdateCCPaymentState(row, trans);
if (row != null && (row.IsCCAuthorized == true || row.IsCCCaptured == true))
{
bool authIsValid = true;
if (row.IsCCAuthorized == true)
{
if (row.CCAuthTranNbr != null)
{
CCProcTran authTran = PXSelect<CCProcTran, Where<CCProcTran.tranNbr, Equal<Required<CCProcTran.tranNbr>>>>.Select(this, row.CCAuthTranNbr);
if (String.IsNullOrEmpty(authTran.DocType) == false && String.IsNullOrEmpty(authTran.RefNbr) == false)
{
authIsValid = false;
}
}
else
{
CCProcTran authTran = this.ccAuthTrans.Select(); //Double-checking for valid auth tran
if (authTran == null)
authIsValid = false;
}
if (authIsValid && row.CCAuthExpirationDate.HasValue)
{
authIsValid = row.CCAuthExpirationDate.Value > PXTimeZoneInfo.Now;
}
}
if (authIsValid)
{
sender.RaiseExceptionHandling<SOOrder.cCPaymentStateDescr>(row, row.CCPaymentStateDescr, new PXSetPropertyException(Messages.CannotCancelCCProcessed, PXErrorLevel.Error));
}
}
}
I'm trying to process return orders in Acumatica, for which I will have to create a Sales Order of type RC, invoke the Create Receipt Action on the sales order which creates a shipment of type Receipt and then invoke Confirm Shipment on the shipment. However in the list of Actions defined on the Sales Order, I do not find an action corresponding to Create Receipt. Can anyone guide me on how to invoke the Create Receipt action programatically as we do through the screen?
For e.g. this is how I invoke Confirm Shipment on a shipment and looking for something similar to Create Receipt:
var adapter = new PXAdapter(graph.CurrentDocument);
adapter.Arguments.Add("actionID", SOShipmentEntryActionsAttribute.ConfirmShipment);
PXLongOperation.StartOperation(graph, delegate ()
{
foreach (SOShipment soShipment in graph.action.Press(adapter)) ;
});
This is a solution previously provided by Acumatica, I tried this method in few screens and it was working. In the following code try to change the graph to your SOOrderEntry and Command to "Create Receipt"
docgraph.Document.Current = docgraph.Document.Search<SOShipment.shipmentNbr>("mYsHIPMENTnUMBER");
docgraph.Cancel.Press();
foreach (var action in (docgraph.action.GetState(null) as PXButtonState).Menus)
{
if (action.Command == "Confirm Shipment")
{
PXAdapter adapter2 = new PXAdapter(new DummyView(docgraph, docgraph.Document.View.BqlSelect, new List<object> { docgraph.Document.Current }));
adapter2.Menu = action.Command;
docgraph.action.PressButton(adapter2);
}
}
internal class DummyView : PXView
{
List<object> _Records;
internal DummyView(PXGraph graph, BqlCommand command, List<object> records)
: base(graph, true, command)
{
_Records = records;
}
public override List<object> Select(object[] currents, object[] parameters, object[] searches, string[] sortcolumns, bool[] descendings, PXFilterRow[] filters, ref int startRow, int maximumRows, ref int totalRows)
{
return _Records;
}
}
I've updated our test environment to the latest version of Acumatica 5.10.0752. After updating our sync process has started to fail when inserting an item into a sales order. Note that no code changes were implemented to the sync process and the only change was to apply the latest update to Acumatica. I've been struggling with this and can't seem to solve adequately and could use a bit of help..
Here is the stack trace:
System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> PX.Data.PXException: Error #112: An error occurred while processing the field InventoryID : Object reference not set to an instance of an object.. ---> System.NullReferenceException: Object reference not set to an instance of an object. at PX.Objects.SO.SOOrderEntry.SOLine_CuryUnitCost_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e) at PX.Data.PXCache.OnFieldDefaulting(String name, Object row, Object& newValue) at PX.Data.PXCache1.SetDefaultExt(Object data, String fieldName) at PX.Objects.SO.SOOrderEntry.SOLine_UOM_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e) at PX.Data.PXFieldUpdated.Invoke(PXCache sender, PXFieldUpdatedEventArgs args) at PX.Data.PXCache.OnFieldUpdated(String name, Object row, Object oldValue, Boolean externalCall) at PX.Data.PXCache1.SetDefaultExt(Object data, String fieldName) at PX.Objects.SO.SOOrderEntry.SOLine_InventoryID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e) at PX.Data.PXFieldUpdated.Invoke(PXCache sender, PXFieldUpdatedEventArgs args) at PX.Data.PXCache.OnFieldUpdated(String name, Object row, Object oldValue, Boolean externalCall) at PX.Data.PXCache1.a(TNode A_0, TNode A_1, IDictionary A_2, PXCacheOperation A_3, Boolean A_4) --- End of inner exception stack trace --- at PX.Data.PXCache1.a(TNode A_0, TNode A_1, IDictionary A_2, PXCacheOperation A_3, Boolean A_4) at PX.Data.PXCache1.a(TNode A_0, TNode A_1, IDictionary A_2, PXCacheOperation A_3) at PX.Data.PXCache1.Insert(IDictionary values) at PX.Data.PXCache1.Update(IDictionary keys, IDictionary values) at PX.Data.PXGraph.ExecuteUpdate(String viewName, IDictionary keys, IDictionary values, Object[] parameters) at PX.Data.PXGraph.CopyPasteCommitChanges(String viewName, OrderedDictionary keys, OrderedDictionary vals) at PX.Api.SyImportProcessor.SyStep.CommitChanges(Object itemToBypass, PXFilterRow[] targetConditions) at PX.Api.SyImportProcessor.ExportTableHelper.ExportTable() at PX.Api.ScreenUtils.Submit(String screenId, Command[] commands, SchemaMode schemaMode, PXGraph& graph, String& redirectContainerView, String& redirectScreen, Boolean mobile) at PX.Api.Services.ScreenService.Submit(String id, IEnumerable1 commands, SchemaMode schemaMode, Boolean mobile, PXGraph& forceGraph, String& redirectContainerView, String& redirectScreen) at PX.Api.Services.ScreenService.Submit(String id, IEnumerable`1 commands, SchemaMode schemaMode) at PX.Api.Soap.Screen.ScreenGate.Submit(Command[] commands) --- End of inner exception stack trace ---
Here is a code snippet that worked fine before the update:
cmds.Add( SO301000.DocumentDetails.ServiceCommands.NewRow );
cmds.Add( new AcumaticaApiWS.Value { Value = item.InventoryId, LinkedCommand = SO301000.DocumentDetails.InventoryID } );
cmds.Add( new AcumaticaApiWS.Value { Value = item.Quantity, LinkedCommand = SO301000.DocumentDetails.Quantity } );
cmds.Add( new AcumaticaApiWS.Value { Value = item.UnitPrice, LinkedCommand = SO301000.DocumentDetails.UnitPrice } );
cmds.Add( new AcumaticaApiWS.Value { Value = item.WebCode, LinkedCommand = SO301000.DocumentDetails.WebCode } );
cmds.Add( new AcumaticaApiWS.Value { Value = item.ExtPrice, LinkedCommand = SO301000.DocumentDetails.ExtPrice} );
cmds.Add( new AcumaticaApiWS.Value { Value = item.ReasonCode, LinkedCommand = SO301000.DocumentDetails.ReasonCode } );
cmds.Add( new AcumaticaApiWS.Value { Value = item.LineDescription, LinkedCommand = SO301000.DocumentDetails.LineDescription } );
cmds.Add( SO301000.Actions.Save );
try
{
this.context.SO301000Submit( cmds.ToArray() );
}
catch( Exception ex )
{
...
}
It seems to be related to UOM but I'm not sure what the issue is. Thinking that there might be a some configuration somewhere that needs to be setup. But not seeing what it could be.
If I add this line of code right after the new row command, it works fine.
cmds.Add( new AcumaticaApiWS.Value { Value = "EA", LinkedCommand = SO301000.DocumentDetails.UOM } );
However, I would prefer it to get the UOM from the item's inventory default as it was before the update.
Any help would be greatly appreciated.
** Another thought we had was that when we skipped updates by installing just the latest version without installing each of the versions in between we inadvertently caused this issue. - Could that cause weird behavior - skipping updates to just install the latest? Tx
Regarding stack trace you need to set UOM
I'm just starting to use Glimpse with my MVC5 project and have run into an issue when I use Postal to send an email without disabling Glimpse. I've been able to narrow it down to an issue with both packages - it doesn't occur if the Glimpse cookie has not been turned on.
In Fiddler, I checked the difference between the two. When it threw the exception, the cookie was
glimpsePolicy=On
when it worked (Glimpse was off) there were two cookies
glimpseId=FBar; glimpsePolicy=
The exception I get is
System.ArgumentNullException: Value cannot be null.
Parameter name: controllerContext
at System.Web.Mvc.ChildActionValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
at Castle.Proxies.Invocations.ValueProviderFactory_GetValueProvider.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Glimpse.Core.Extensibility.ExecutionTimer.Time(Action action)
at Glimpse.Core.Extensibility.AlternateMethod.NewImplementation(IAlternateMethodContext context)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.ValueProviderFactoryProxy.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ControllerBase.get_ValueProvider()
at Glimpse.Mvc.Message.ActionMessageExtension.AsActionMessage[T](T message, ControllerBase controller)
at Glimpse.Mvc.AlternateType.ViewEngine.FindViews.PostImplementation(IAlternateMethodContext context, TimerResult timerResult)
at Glimpse.Core.Extensibility.AlternateMethod.NewImplementation(IAlternateMethodContext context)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.IViewEngineProxy.FindView(ControllerContext controllerContext, String viewName, String masterName, Boolean useCache)
at System.Web.Mvc.ViewEngineCollection.<>c__DisplayClass6.<FindView>b__4(IViewEngine e)
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 lookup, Boolean trackSearchedPaths)
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 cacheLocator, Func`2 locator)
at Postal.EmailViewRenderer.Render(Email email, String viewName)
at Postal.EmailService.Send(Email email)
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
at System.Web.Mvc.ActionMethodDispatcher.<>c__DisplayClass1.<WrapVoidAction>b__0(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__36(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult)
at Castle.Proxies.Invocations.AsyncControllerActionInvoker_EndInvokeActionMethod.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Glimpse.Mvc.AlternateType.AsyncActionInvoker.EndInvokeActionMethod.NewImplementation(IAlternateMethodContext context)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.AsyncControllerActionInvokerProxy.EndInvokeActionMethod(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3c()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass45.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3e()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass30.<BeginInvokeActionMethodWithFilters>b__2f(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass1e.<>c__DisplayClass28.<BeginInvokeAction>b__19()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass1e.<BeginInvokeAction>b__1b(IAsyncResult asyncResult)
I created a quick action to test it. The controller code is:
public void TestEmailExt()
{
var confirmationToken = "ConfirmationToken";
var Phone1 = "**********";
dynamic email = new Email("RegEmail");
email.To = "**#gmail.com";
email.UserName = "UserName";
email.ConfirmationToken = confirmationToken;
email.Phone = Extensions.Right(Phone1, 4);
if (email.To.Contains("#mydomain"))
email.From = INTERNAL_EMAIL_FROM;
else
email.From = EXTERNAL_EMAIL_FROM;
email.Send();
}
The reason this fails is because the Postal library creates its own HttpContext instance while rendering the email view as the decompiled CreateControllerContext method inside Postal's EmailViewRenderer class shows:
private ControllerContext CreateControllerContext()
{
HttpContextWrapper httpContextWrapper = new HttpContextWrapper(new HttpContext(new HttpRequest("", this.UrlRoot(), ""), new HttpResponse(TextWriter.Null)));
RouteData routeData = new RouteData();
routeData.Values["controller"] = (object) this.EmailViewDirectoryName;
return new ControllerContext(new RequestContext((HttpContextBase) httpContextWrapper, routeData), (ControllerBase) new EmailViewRenderer.StubController());
}
This means that the setup that Glimpse does at BeginRequest is completely removed, while the hooks are still in place to intercept MVC related calls.
We've had a similar issue where I gave a similar response to why this is not working.
UPDATE :
I mentioned above that a similar issue had been reported previously, but while I was trying to find a more appropriate solution, it seemed that this case is slightly different in that respect that the other similar issue actually executes a controller with the freshly created context resulting in a NullReferenceException in Glimpse specific code, while here we get a NullReferenceException inside MVC specific code, albeit triggered by Glimpse.
System.ArgumentNullException: Value cannot be null.
Parameter name: controllerContext
at System.Web.Mvc.ChildActionValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
And the exception we get here is because the ControllerContext property on the StubController instance (created inline) is null, which would normally be set when executing the controller (which is not the case here).
So the workaround that I proposed below still applies, but can be avoided if the code of the CreateControllerContext() above is slightly modified:
private ControllerContext CreateControllerContext()
{
HttpContextWrapper httpContextWrapper = new HttpContextWrapper(new HttpContext(new HttpRequest("", this.UrlRoot(), ""), new HttpResponse(TextWriter.Null)));
RouteData routeData = new RouteData();
routeData.Values["controller"] = (object) this.EmailViewDirectoryName;
// MODIFIED
var stubController = new EmailViewRenderer.StubController();
var controllerContext = new ControllerContext(new RequestContext(httpContextWrapper, routeData), stubController);
stubController.ControllerContext = controllerContext;
return controllerContext;
}
I've created an issue for this on the Postal issue tracker
END OF UPDATE
I think the best solution, for now, is to disable Glimpse while calling into Postal and restore normal Glimpse behavior back again afterwards. We might include this one way or the other into the Glimpse Core library in one of the upcoming releases as it seems that disabling Glimpse during a specific part of the request processing logic doesn't seem to be that uncommon, but for now the following snippet might help you (beware it makes use of a Glimpse internal key which is not guaranteed to be there in an upcoming release)
public class GlimpseSuppressionScope : IDisposable
{
private const string GlimpseRequestRuntimePermissionsKey = "__GlimpseRequestRuntimePermissions";
private readonly HttpContext currentHttpContext;
private readonly RuntimePolicy? currentRuntimePolicy;
private bool disposed;
public GlimpseSuppressionScope(HttpContext currentHttpContext)
{
if (currentHttpContext == null)
{
throw new ArgumentNullException("currentHttpContext");
}
this.currentHttpContext = currentHttpContext;
this.currentRuntimePolicy = this.currentHttpContext.Items[GlimpseRequestRuntimePermissionsKey] as RuntimePolicy?;
this.currentHttpContext.Items[GlimpseRequestRuntimePermissionsKey] = RuntimePolicy.Off;
}
~GlimpseSuppressionScope()
{
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.currentHttpContext != null)
{
this.currentHttpContext.Items.Remove(GlimpseRequestRuntimePermissionsKey);
if (this.currentRuntimePolicy.HasValue)
{
this.currentHttpContext.Items[GlimpseRequestRuntimePermissionsKey] = this.currentRuntimePolicy.Value;
}
}
}
this.disposed = true;
}
}
}
which you can then use in your controller action method as shown below:
public void TestEmailExt()
{
using (new GlimpseSuppressionScope(System.Web.HttpContext.Current))
{
var confirmationToken = "ConfirmationToken";
var Phone1 = "**********";
dynamic email = new Email("RegEmail");
email.To = "**#gmail.com";
email.UserName = "UserName";
email.ConfirmationToken = confirmationToken;
email.Phone = Extensions.Right(Phone1, 4);
if (email.To.Contains("#mydomain"))
email.From = INTERNAL_EMAIL_FROM;
else
email.From = EXTERNAL_EMAIL_FROM;
email.Send();
}
}