Same Code different behavior Client vs Server 10.1.500.8

So I had the need to un-release a Job using custom code in a BPM, I wrote the code and it threw an error “Update not allowed, Engineered and Prevent Changes.” the issue is that the same EXACT code works fine in a customization the only difference being how the BO is instanciated.

See the code below,


/****
Customization Code triggered from a button Click, works fine
Result: Job is Un-Released, no Errors
*/
JobEntryImpl svc = WCFServiceSupport.CreateImpl<JobEntryImpl>((Ice.Core.Session)oTrans.Session, Epicor.ServiceModel.Channels.ImplBase<Erp.Contracts.JobEntrySvcContract>.UriPath); //Instanciated Client Side
var ds = svc.GetByID("CAN000076");
ds.JobHead[0].JobReleased = false;
ds.JobHead[0].RowMod="U";
svc.ChangeJobHeadJobReleased(ds);
svc.Update(ds);
/***
BPM Code triggered from ABCCode-> GetNew Preprocessing for testing purposes
Result: Error: Update not allowed,  Engineered and Prevent Changes
*/
var svc = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.JobEntrySvcContract>(); //Instanciated Server Side
var ds = svc.GetByID("CAN000076");
ds.JobHead[0].JobReleased=false;
ds.JobHead[0].RowMod="U";
svc.ChangeJobHeadJobReleased(ref ds);
svc.Update(ref ds);

Theoretically speaking both pieces of code should run the same exact result… Anyone have any ideas? I’ve replicated this in 2 different instances of 500.8 with the same results so I know it is not just my DB.

@Bart_Elia, @Edge

1 Like

Check this company setting.

Hi Ted,
Yes that is flagged but that is not the issue, that flag is supposed to be checked but you are still allowed to un-release / un-engineer a job. Furthermore it works fine in the customization, just not on the BPM… strange behavior

I’ve had this issue to in the past with BPMs, just wasn’t sure if it was that checkbox causing problems also.

Maybe try changing the RowMod again after the ChangeJobHeadJobReleased method?

Tried that… no change.

Try to wrap it in this using statement?

using (System.Transactions.TransactionScope txScope = IceDataContext.CreateDefaultTransactionScope())
{

}

I am invoking the business which carries its own transaction scope when it reaches back into the server.

From what I understand when it comes to ServiceRenderer. I am sure Bart will correct me, might be off a bit.

When you use Ice.Assemblies.ServiceRenderer.GetService() you are creating a Assembly Instance without any args so i assume it will create its own context instance / transaction.

When you use Ice.Assemblies.ServiceRenderer.GetService(Db) you are calling GetService(IceDataContext context, bool ignoreFacade = false) which does an CreateInstance and passes the Db Context as an argument to the Assembly. Which is returned to you.

When you use Ice.Assemblies.ServiceRenderer.GetService(Db, true) you are also setting the ignoreFacade to true… What is a Facade in Epicor? Well if you use the ICE SDK and you create your own Business Objects you always create a SvcFacade.cs file which contains the required code for interacting with BPM… So by setting ignoreFacade to true you are basically saying… Do Not Trigger the “Update” BPM… perhaps you want it, but if you are in the Update already you would cause a loop.

I am wondering if it helps to pass through the Db Context. Perhaps there is a issue in how ServiceRenderer instantiates the Transaction, perhaps it does not pass the Client Context.

2 Likes

Thanks @hkeric.wci good idea, however I just tried it with passing Db …same result :frowning:

Seems a bit odd.
Can you include the detail from the error so we can see the stack trace.

Also can you recreate this in Training DB?
It is as if you are getting back changed data from the ChangeJobHeadJobReleased method.

Have you attached a debugger and compared what is being passed into update, or tried without the ChangeJob method in the chain?

@Edge
Full stack trace below, BPM attached E10Test.bpm (11.8 KB) runs on ABC Code -> Get New only fails if Prevent Changes is Checked in Company Config -> Production -> Change Engineered Jobs

I haven’t tried it in the Training DB, but I will

Business Layer Exception

Update not allowed, Engineered and Prevent Changes.

Exception caught in: Epicor.ServiceModel

Error Detail 
============
Description:  Update not allowed, Engineered and Prevent Changes.
Program:  Erp.Services.BO.JobEntry.dll
Method:  valPreventChange
Line Number:  5954
Column Number:  29
Table:  JobAsmbl
Server Trace Stack:     at Erp.Services.BO.JobEntrySvc.valPreventChange() in C:\_Releases\ERP\UD10.1.500.8\Source\Server\Services\BO\JobEntry\JobEntry.cs:line 5954
   at Erp.Services.BO.JobEntrySvc.BeforeUpdate() in C:\_Releases\ERP\UD10.1.500.8\Source\Server\Services\BO\JobEntry\JobEntry.cs:line 5965
   at Erp.Services.BO.JobEntrySvc.OnTablesetEvent(DatasetEventType type) in C:\_Releases\ERP\UD10.1.500.8\Source\Server\Services\BO\JobEntry\JobEntry.Designer.cs:line 619
   at Ice.Services.Trace.TablesetProfilingCollector.DoTablesetEventTrace(String tablesetName, String methodName, Action action) in C:\_Releases\ICE\3.1.500.8\Source\Framework\Epicor.Ice\Services\TablesetProfilingCollector.cs:line 200
   at Ice.TablesetBound`3.InnerUpdate(IceDataContext dataContext, TFullTableset tableset) in C:\_Releases\ICE\3.1.500.8\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 794
   at Erp.Services.BO.JobEntrySvc.Update(JobEntryTableset& ds) in C:\_Releases\ERP\UD10.1.500.8\Source\Server\Services\BO\JobEntry\JobEntry.Designer.cs:line 7392
   at Erp.Services.BO.JobEntrySvcFacade.Update(JobEntryTableset& ds) in C:\_Releases\ERP\UD10.1.500.8\Source\Server\Services\BO\JobEntry\JobEntrySvcFacade.cs:line 4043
   at Epicor.Customization.Bpm.BO1A649256125F45B2A3A14D0B240477F1.GetNewABCCodePreProcessingDirective_TEST_E93C55B5ED684B728BF4D2BC44B8F28A.A001_CustomCodeAction()
   at Epicor.Customization.Bpm.BO1A649256125F45B2A3A14D0B240477F1.GetNewABCCodePreProcessingDirective_TEST_E93C55B5ED684B728BF4D2BC44B8F28A.ExecuteCore()
   at Epicor.Customization.Bpm.DirectiveBase`3.Execute(TParam parameters) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\DirectiveBase.Generic.cs:line 160
   at Epicor.Customization.Bpm.MethodCustomizationBase2`3.<>c__DisplayClass20_0.<RunDirectives>b__3(MethodDirectiveBase`3 dir) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\MethodCustomizationBase2.cs:line 151
   at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
   at Epicor.Customization.Bpm.MethodCustomizationBase2`3.RunDirectives(TParam parameters) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\MethodCustomizationBase2.cs:line 153
   at Epicor.Customization.Bpm.CustomizationBase2`3.Execute(TParam parameters) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\CustomizationBase2.cs:line 77
   at Epicor.Customization.Bpm.BO1A649256125F45B2A3A14D0B240477F1.ABCCodeSvcCustomization.GetNewABCCode(ABCCodeTableset& ds)
   at Erp.Services.BO.ABCCodeSvcFacade.GetNewABCCode(ABCCodeTableset& ds) in c:\_Releases\ERP\RL10.1.500\Source\Server\Services\BO\ABCCode\ABCCodeSvcFacade.cs:line 60
   at SyncInvokeGetNewABCCode(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   at Epicor.Hosting.OperationBoundInvoker.InnerInvoke(Object instance, Func`2 func) in C:\_Releases\ICE\3.1.500.8\Source\Framework\Epicor.System\Hosting\OperationBoundInvoker.cs:line 59
   at Epicor.Hosting.OperationBoundInvoker.Invoke(Object instance, Func`2 func) in C:\_Releases\ICE\3.1.500.8\Source\Framework\Epicor.System\Hosting\OperationBoundInvoker.cs:line 28
   at Epicor.Hosting.Wcf.EpiOperationInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs) in C:\_Releases\ICE\3.1.500.8\Source\Framework\Epicor.System\Hosting\Wcf\EpiOperationInvoker.cs:line 23
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
   at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
   at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
   at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
   at System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(IAsyncResult result)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveItemAndVerifySecurityAsyncResult`2.InnerTryReceiveCompletedCallback(IAsyncResult result)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceiveAsyncResult.OnReceive(IAsyncResult result)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.SynchronizedMessageSource.ReceiveAsyncResult.OnReceiveComplete(Object state)
   at System.ServiceModel.Channels.SessionConnectionReader.OnAsyncReadComplete(Object state)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
   at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
   at System.Net.Security._SslStream.ProcessFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security._SslStream.ReadFrameCallback(AsyncProtocolRequest asyncRequest)
   at System.Net.AsyncProtocolRequest.CompleteRequest(Int32 result)
   at System.Net.FixedSizeReader.CheckCompletionBeforeNextRead(Int32 bytes)
   at System.Net.FixedSizeReader.ReadCallback(IAsyncResult transportResult)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.ConnectionStream.IOAsyncResult.OnAsyncIOComplete(Object state)
   at System.Net.Sockets.SocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs e)
   at System.Net.Sockets.SocketAsyncEventArgs.FinishOperationSuccess(SocketError socketError, Int32 bytesTransferred, SocketFlags flags)
   at System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)



Client Stack Trace 
==================
   at Epicor.ServiceModel.Channels.ImplBase`1.ShouldRethrowNonRetryableException(Exception ex, DataSet[] dataSets)
   at Erp.Proxy.BO.ABCCodeImpl.GetNewABCCode(ABCCodeDataSet ds)
   at Erp.Adapters.ABCCodeAdapter.GetNewAbcCode()
   at Erp.UI.App.AbcCodeEntry.Transaction.GetNew()
   at Ice.Lib.Framework.EpiSingleViewTransaction.Ice.Lib.Framework.IEpiAdapterLink.GetNew()
   at Ice.Lib.Framework.EpiViewUtils.OnGetNew(EpiTransaction trans, EpiDataView view, IEpiAdapterLink link, Boolean displayExceptions)

BTW I tried without the ChangeMethod just now, same result.

Tried it in Education DB
Company “Epicor Education”
Job: 2031

BPM Attached / Modified for Job 2031 in Training DB, Same Result
E10TestEducation.bpm (11.3 KB)

You need to set the Prevent Changes in the Company Configuration (below)

Full Stack Trace for Training DB Below (though it should be the same as above)


Business Layer Exception

Update not allowed, Engineered and Prevent Changes.

Exception caught in: Epicor.ServiceModel

Error Detail 
============
Description:  Update not allowed, Engineered and Prevent Changes.
Program:  Erp.Services.BO.JobEntry.dll
Method:  valPreventChange
Line Number:  5954
Column Number:  29
Table:  JobAsmbl
Server Trace Stack:     at Erp.Services.BO.JobEntrySvc.valPreventChange() in C:\_Releases\ERP\UD10.1.500.7\Source\Server\Services\BO\JobEntry\JobEntry.cs:line 5954
   at Erp.Services.BO.JobEntrySvc.BeforeUpdate() in C:\_Releases\ERP\UD10.1.500.7\Source\Server\Services\BO\JobEntry\JobEntry.cs:line 5963
   at Erp.Services.BO.JobEntrySvc.OnTablesetEvent(DatasetEventType type) in C:\_Releases\ERP\UD10.1.500.7\Source\Server\Services\BO\JobEntry\JobEntry.Designer.cs:line 619
   at Ice.Services.Trace.TablesetProfilingCollector.DoTablesetEventTrace(String tablesetName, String methodName, Action action) in C:\_Releases\ICE\3.1.500.7\Source\Framework\Epicor.Ice\Services\TablesetProfilingCollector.cs:line 200
   at Ice.TablesetBound`3.InnerUpdate(IceDataContext dataContext, TFullTableset tableset) in C:\_Releases\ICE\3.1.500.7\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 794
   at Erp.Services.BO.JobEntrySvc.Update(JobEntryTableset& ds) in C:\_Releases\ERP\UD10.1.500.7\Source\Server\Services\BO\JobEntry\JobEntry.Designer.cs:line 7392
   at Erp.Services.BO.JobEntrySvcFacade.Update(JobEntryTableset& ds) in C:\_Releases\ERP\UD10.1.500.7\Source\Server\Services\BO\JobEntry\JobEntrySvcFacade.cs:line 4043
   at Epicor.Customization.Bpm.BO9AF352B197AD49158C3BF8C381E20873.GetNewABCCodePreProcessingDirective_TEST_BAD_BPM_E93C55B5ED684B728BF4D2BC44B8F28A.A001_CustomCodeAction()
   at Epicor.Customization.Bpm.BO9AF352B197AD49158C3BF8C381E20873.GetNewABCCodePreProcessingDirective_TEST_BAD_BPM_E93C55B5ED684B728BF4D2BC44B8F28A.ExecuteCore()
   at Epicor.Customization.Bpm.DirectiveBase`3.Execute(TParam parameters) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\DirectiveBase.Generic.cs:line 160
   at Epicor.Customization.Bpm.MethodCustomizationBase2`3.<>c__DisplayClass20_0.<RunDirectives>b__3(MethodDirectiveBase`3 dir) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\MethodCustomizationBase2.cs:line 151
   at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
   at Epicor.Customization.Bpm.MethodCustomizationBase2`3.RunDirectives(TParam parameters) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\MethodCustomizationBase2.cs:line 153
   at Epicor.Customization.Bpm.CustomizationBase2`3.Execute(TParam parameters) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\CustomizationBase2.cs:line 77
   at Epicor.Customization.Bpm.BO9AF352B197AD49158C3BF8C381E20873.ABCCodeSvcCustomization.GetNewABCCode(ABCCodeTableset& ds)
   at Erp.Services.BO.ABCCodeSvcFacade.GetNewABCCode(ABCCodeTableset& ds) in c:\_Releases\ERP\RL10.1.500\Source\Server\Services\BO\ABCCode\ABCCodeSvcFacade.cs:line 60
   at SyncInvokeGetNewABCCode(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   at Epicor.Hosting.OperationBoundInvoker.InnerInvoke(Object instance, Func`2 func) in C:\_Releases\ICE\3.1.500.7\Source\Framework\Epicor.System\Hosting\OperationBoundInvoker.cs:line 59
   at Epicor.Hosting.OperationBoundInvoker.Invoke(Object instance, Func`2 func) in C:\_Releases\ICE\3.1.500.7\Source\Framework\Epicor.System\Hosting\OperationBoundInvoker.cs:line 28
   at Epicor.Hosting.Wcf.EpiOperationInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs) in C:\_Releases\ICE\3.1.500.7\Source\Framework\Epicor.System\Hosting\Wcf\EpiOperationInvoker.cs:line 23
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
   at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
   at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
   at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
   at System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(IAsyncResult result)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveItemAndVerifySecurityAsyncResult`2.InnerTryReceiveCompletedCallback(IAsyncResult result)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.TransportDuplexSessionChannel.TryReceiveAsyncResult.OnReceive(IAsyncResult result)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.SynchronizedMessageSource.ReceiveAsyncResult.OnReceiveComplete(Object state)
   at System.ServiceModel.Channels.SessionConnectionReader.OnAsyncReadComplete(Object state)
   at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
   at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
   at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
   at System.Net.Security.NegotiateStream.ProcessFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.NegotiateStream.ReadCallback(AsyncProtocolRequest asyncRequest)
   at System.Net.AsyncProtocolRequest.CompleteRequest(Int32 result)
   at System.Net.FixedSizeReader.CheckCompletionBeforeNextRead(Int32 bytes)
   at System.Net.FixedSizeReader.ReadCallback(IAsyncResult transportResult)
   at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.Channels.ConnectionStream.IOAsyncResult.OnAsyncIOComplete(Object state)
   at System.Net.Sockets.SocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs e)
   at System.Net.Sockets.SocketAsyncEventArgs.FinishOperationSuccess(SocketError socketError, Int32 bytesTransferred, SocketFlags flags)
   at System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)



Client Stack Trace 
==================
   at Epicor.ServiceModel.Channels.ImplBase`1.ShouldRethrowNonRetryableException(Exception ex, DataSet[] dataSets)
   at Erp.Proxy.BO.ABCCodeImpl.GetNewABCCode(ABCCodeDataSet ds)
   at Erp.Adapters.ABCCodeAdapter.GetNewAbcCode()
   at Erp.UI.App.AbcCodeEntry.Transaction.GetNew()
   at Ice.Lib.Framework.EpiSingleViewTransaction.Ice.Lib.Framework.IEpiAdapterLink.GetNew()
   at Ice.Lib.Framework.EpiViewUtils.OnGetNew(EpiTransaction trans, EpiDataView view, IEpiAdapterLink link, Boolean displayExceptions)

Thanks @Edge

1 Like

I think that is your delta in the usage.

public static TService GetService(bool ignoreFacade = false) where TService : class

ignoreFacade bypasses BPM firing. By default that is false so no BPM will be fired. (This defaults false so we don’t end up in an endless loop accidentally with a BPM firing itself recursively).

1 Like

I think you meant the opposite of this, it defaults to ignore=false meaning it will not ignore the facade. I just tried this and by default BPM does fire, if ingorefacade = true then no BPM fires…

Regardless though this isn’t related to the initial issue (I don’t think) of why the code behaves differently from the client side vs the bpm. Any ideas on that?

2 Likes

No clue if both are firing the bpm. I wonder if that logic is doing something with cross services as well. It would take a bit to unravel. Queue up a call if you need it looked into.
I’ve been a little buried in 10.2 efforts in a new role. Don’t have as much free time as previous with the holidays :wink:

2 Likes

No I did them independently, no worries I worked around it for now
Thanks!

PS: New Role? DO TELL

1 Like

I am running into the same problem. Do we know the cause of the problem?

How did you work around this? I can’t seem to get it at all.

I used BeforeImage.