Getting 400 error when running basic auth test on apphost - servicestack

I have an apphost
public class LocalTestAppHost : AppSelfHostBase
{
public LocalTestAppHost() : base(nameof(LocalTestAppHost), typeof(MyServices).Assembly, typeof(LocalTestAppHost).Assembly) { }
public override void Configure(Container container)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
// .AddJsonFile("appSettings.json")
.AddEnvironmentVariables()
.AddUserSecrets(typeof(IntegrationTest).Assembly);
var configuration = builder.Build();
AppSettings = new NetCoreAppSettings(configuration);
container.AddSingleton<IAppSettings>(AppSettings);
SetConfig(new HostConfig
{
AddRedirectParamsToQueryString = true,
DebugMode = true
});
Plugins.Add(new CorsFeature(allowOriginWhitelist: new[] { IntegrationTestBase.BaseUriLocalDev },
allowedMethods: "GET, PATCH, POST, PUT, DELETE, OPTIONS",
allowCredentials: true,
allowedHeaders: "Content-Type, Allow, Authorization"));
JsConfig.DateHandler = DateHandler.ISO8601;
var connectionString = AppSettings.GetString("DefaultConnection");
OrmLiteConfig.StripUpperInLike = false;
container.AddSingleton<IDbConnectionFactory>(new OrmLiteConnectionFactory(connectionString, PostgreSqlDialect.Provider));
container.AddSingleton<IAuthRepository>(c =>
new OrmLiteAuthRepository<UserAuthCustom, UserAuthDetails>(c.Resolve<IDbConnectionFactory>())
{
UseDistinctRoleTables = true
});
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(AppSettings)
{
}, /* Sign In with Username / Password credentials */
}));
Plugins.Add(new AdminUsersFeature());
}
}
And I am trying to run simple test where I log in (using existing connection to my local db)
public class ReportTests : IntegrationTestBase
{
private string _adminUser;
private string _adminPass;
public ReportTests()
{
Licensing.RegisterLicense(Licence);
this.AppHost = new LocalTestAppHost()
.Init()
.Start(BaseUriLocalDev);
Settings = AppHost.Resolve<IAppSettings>();
Db = AppHost.Resolve<IDbConnectionFactory>().OpenDbConnection();
_adminUser = Settings.GetString("adminUser");
_adminPass = Settings.GetString("adminPass");
}
[Test]
public void TestStats()
{
var users = Db.Select<UserAuthCustom>();
var client = new JsonServiceClient(BaseUriLocalDev);
var authReq = new Authenticate()
{
UserName = _adminUser,
Password = _adminPass,
provider = CredentialsAuthProvider.Name
};
var resp = client.Post(authReq);
}
But it throws this exception:
System.Net.WebException: Received an invalid status line: '400'.
---> System.Net.Http.HttpRequestException: Received an invalid status line: '400'.
at System.Net.Http.HttpConnection.ParseStatusLine(Span`1 line, HttpResponseMessage response)
at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.DecompressionHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpMessageHandlerStage.Send(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.SocketsHttpHandler.Send(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClientHandler.Send(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpMessageInvoker.Send(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.Send(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at System.Net.HttpWebRequest.SendRequest(Boolean async)
at System.Net.HttpWebRequest.GetResponse()
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.GetResponse()
at ServiceStack.ServiceClientBase.Send[TResponse](String httpMethod, String relativeOrAbsoluteUrl, Object request) in C:\BuildAgent\work\3481147c480f4a2f\src\ServiceStack.Client\ServiceClientBase.cs:line 1416
at ServiceStack.ServiceClientBase.Post[TResponse](IReturn`1 requestDto) in C:\BuildAgent\work\3481147c480f4a2f\src\ServiceStack.Client\ServiceClientBase.cs:line 1581
at LeadInput.Tests.IntegrationTests.ReportTests.TestStats() in D:\Clients\LeadInput\LeadInput.Tests\IntegrationTests\ReportTests.cs:line 43
I am struggling to figure why it is not working and why it is not receiving a valid response from the apphost.
As the apphost only runs for scope of test I am finding it hard to debug. It seems like everything is correct but I am getting invalid response.
Stepping through code it is client.GetResponse(); that throws the exception, I guess it is getting unexpected format but I cannot see a way to get raw response in debugger.
Any ideas where I am going wrong?
EDIT
It happens also on hello world endpoint so I guess the AppHost is not running. I have tried changing the port but doesn't help. I upgraded to 5.13.3 recently, not sure if that's relevant.
EDIT
With debugger active this is logged:
DEBUG: CreateRequestAsync/requestParams:
WARN: Could not Set-Cookie 'ss-id': Could not load type 'Microsoft.Extensions.Primitives.InplaceStringBuilder' from assembly 'Microsoft.Extensions.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'., Exception: Could not load type 'Microsoft.Extensions.Primitives.InplaceStringBuilder' from assembly 'Microsoft.Extensions.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.

You can try enabling debug logging in your AppHost to see if logs any info:
LogManager.LogFactory = new ConsoleLogFactory(debugEnabled:true);
Don't forget your Integration test needs to dispose the AppHost after it's run or other integration tests will fail:
[OneTimeTearDown]
public void OneTimeTearDown() => AppHost.Dispose();
One way to view the response is run your AppHost for a long time then you can view the output by inspecting the HTTP Request externally, e.g. via Chrome's WebInspector or curl:
[Test]
public void Run_for_30secs()
{
Thread.Sleep(30000);
}
It sounds like the AppHost isn't properly running, so I'd comment/disable features until you can isolate the problem.
Note: CORS isn't relevant in an integration test, it's only relevant when called from a Web browser making cross-domain requests.

Related

Create folder if exists Sharepoint suddenly stopped working

I have the following code which was working nicely until recently
private async Task<DriveItem> CreateFolderIfNotExists(GraphServiceClient graphClient, string driveId, string folderName)
{
try
{
var driveItem = new DriveItem
{
Name = folderName,
Folder = new Folder(),
AdditionalData = new Dictionary<string, object>()
{
{ "#microsoft.graph.conflictBehavior", "fail" }
}
};
return await graphClient.Drives[driveId].Root.Children
.Request()
.AddAsync(driveItem);
}
catch (ServiceException exception)
{
if (exception.StatusCode != HttpStatusCode.Conflict)
{
throw;
}
return await this.GetFolderItem(graphClient, driveId, folderName);
}
}
All of a sudden we are getting this:
System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.Graph.HttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) at Microsoft.Graph.BaseRequest.SendRequestAsync(Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption) at Microsoft.Graph.BaseRequest.SendAsync[T](Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption) at xyz.CreateFolderIfNotExists(GraphServiceClient graphClient, String driveId, String folderName) in xyz\Client\SharePointClient.cs:line 63 at xyz.Client.SharePointClient.CopyLegacyFile(String fileId, String destination, String newFileName, String documentLibraryId) xyz\Client\SharePointClient.cs:line 222 at xyz.SharePointDocumentStorage.New() in xtz\SharePointDocumentStorage.cs:line 56 at zzzWorkflowAppService.NewRevision(NewRevisionInput input) at Abp.Authorization.AuthorizationInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation) at Abp.Domain.Uow.UnitOfWorkInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation) at Abp.EntityHistory.EntityHistoryInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation) at Abp.Auditing.AuditingInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation) at Abp.Runtime.Validation.Interception.ValidationInterceptor.InternalInterceptAsynchronou...
Any idea whats happening? There was no code push to cause this.
It is not a duplicate of What is a NullReferenceException, and how do I fix it?
It looks like my version of microsoft Graph which was version 1.21.0 needed to be be upgraded. There must have been a breaking change that was hosted with MS.
Its possible it is related to something like this issue https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/901

gRPC client cannot communicate with gRPC server after having the server hosted on Azure

The following code snippet shows how gRPC web is enabled in a gRPC AspNet core application:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseGrpcWeb();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<SubmissionService>().EnableGrpcWeb();
});
}
}
The client app's code looks like the following:
public static async Task SendSomethingAsync(string payload, ILoggerFactory loggerFactory)
{
const string url = "https://MASKED.azurewebsites.net/";
//const string url = "https://localhost:44308";
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var handler = new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler());
using var channel = GrpcChannel.ForAddress(url, new GrpcChannelOptions
{
HttpClient = new HttpClient(handler),
LoggerFactory = loggerFactory
});
var client = new TestgRPC.gRPC.PayloadSubmission.PayloadSubmissionClient(channel);
var submitted = await client.SubmitAsync(new SubmissionRequest
{
ClientKey = "678",
ConnectionId = "abcdef",
UserId = "3",
MyObjectPayload= payload,
});
Console.WriteLine($"Submitted: {submitted}");
}
The client code (above) receives the reply back from gRPC server when it's hosted on localhost (the development machine). The same client code fails to communicate with the server's instance hosted on Azure and it logs the following failure message:
dbug: Grpc.Net.Client.Internal.GrpcCall[18]
Sending message. fail: Grpc.Net.Client.Internal.GrpcCall[20]
Error sending message. System.Threading.Tasks.TaskCanceledException: A task was canceled.
at
System.Net.Http.TaskCompletionSourceWithCancellation1.WaitWithCancellationAsync(CancellationToken cancellationToken) at System.Net.Http.Http2Connection.Http2Stream.SendDataAsync(ReadOnlyMemory1
buffer, CancellationToken cancellationToken) at
Grpc.Net.Client.Web.Internal.Base64RequestStream.WriteAsync(ReadOnlyMemory1 data, CancellationToken cancellationToken) at Grpc.Net.Client.StreamExtensions.WriteMessageAsync[TMessage](Stream stream, ILogger logger, TMessage message, Action2 serializer, String
grpcEncoding, Nullable1 maximumMessageSize, Dictionary2
compressionProviders, CallOptions callOptions) fail:
Grpc.Net.Client.Internal.GrpcCall[6]
Error starting gRPC call. System.Threading.Tasks.TaskCanceledException: The operation was
canceled. at
System.Net.Http.CancellationHelper.ThrowOperationCanceledException(Exception
innerException, CancellationToken cancellationToken) at
System.Net.Http.CancellationHelper.ThrowIfCancellationRequested(CancellationToken
cancellationToken) at
System.Net.Http.Http2Connection.Http2Stream.GetCancelableWaiterTask(CancellationToken
cancellationToken) at
System.Net.Http.Http2Connection.Http2Stream.ReadResponseHeadersAsync(CancellationToken
cancellationToken) at
System.Net.Http.Http2Connection.SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) at
System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage
request, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at Grpc.Net.Client.Internal.GrpcCall2.RunCall(HttpRequestMessage
request, Nullable`1 timeout) info:
Grpc.Net.Client.Internal.GrpcCall[3]
Call failed with gRPC error status. Status code: 'Cancelled', Message: ''. dbug: Grpc.Net.Client.Internal.GrpcCall[4]
Finished gRPC call.
I could not find out what is hindering this communication to happen and what solution to implement. Any ideas why?
As a side note, I exhausted everything in this github ticket but no luck at all.
The issue is that we needed to deploy the application as a self-contained application. Apparently the default .net core 3.1 installed on Azure app services cannot accommodate a gRPC-web app.

AspNetCore.OData 7.1.0 "$search" Query Option not working

I have created a .Net Core 2 API using OData 4.0 (part of AspNetCore.OData 7.1.0).
Everything, except the "$search", seems to work.
The documentation says it should work.
The following requests I tested didn't work:
http://host/service/Products?$search=banana
http://host/service/Products?$search="banana"
http://host/service/$all?$search="banana"
error message:
{"message":"The query parameter 'Specified argument was out of the range of valid values.\r\nParameter name: $search' is not supported.","exceptionMessage":"Specified argument was out of the range of valid values.\r\nParameter name: $search","exceptionType":"System.ArgumentOutOfRangeException","stackTrace":" at Microsoft.AspNet.OData.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.<>c__DisplayClass1_0.<OnActionExecuted>b__3(ODataQueryContext queryContext)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, Func`2 modelFunction, IWebApiRequestMessage request, Func`2 createQueryOptionFunction)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.OnActionExecuted(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, IWebApiRequestMessage request, Func`2 modelFunction, Func`2 createQueryOptionFunction, Action`1 createResponseAction, Action`3 createErrorAction)"}
The following requests I tested worked:
http://host/service/$metadata/
http://host/service/Products?$filter=contains(name, "banana")
My code:
Configure app (defined in Startup.cs):
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// Cors
app.UseCors(builder => builder
.WithOrigins("*")
.AllowAnyHeader()
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowCredentials()
);
//app.UseHttpsRedirection();
app.UseMvc(routeBuilder =>
{
routeBuilder.MapODataServiceRoute("odata", $"service/", GetEdmModel());
routeBuilder.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
routeBuilder.EnableDependencyInjection();
});
}
EdmModel (defined in Startup.cs):
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Product");
builder.EntitySet<Producer>("Producer");
builder.EntitySet<Consumer>("Consumer");
return builder.GetEdmModel();
}
.Net Core 2.2 Api "Get" (ProductsController.cs):
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(IEnumerable<Product>))]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[EnableQuery]
public async Task<ActionResult<Product>> Get()
{
var dbResponse = _context.Products.AsQueryable();
return this.OK(dbresponse);
}

What is the AddService of JobHostingConfiguration with Azure WebJobs used for

I have the following WebJob Function...
public class Functions
{
[NoAutomaticTrigger]
public static void Emailer(IAppSettings appSettings, TextWriter log, CancellationToken cancellationToken)
{
// Start the emailer, it will stop on dispose
using (IEmailerEndpoint emailService = new EmailerEndpoint(appSettings))
{
// Check for a cancellation request every 3 seconds
while (!cancellationToken.IsCancellationRequested)
{
Thread.Sleep(3000);
}
log.WriteLine("Emailer: Canceled at " + DateTime.UtcNow);
}
}
}
I have been looking at how this gets instantiated which I can do with the simple call...
host.Call(typeof(Functions).GetMethod("MyMethod"), new { appSettings = settings })
However it's got me wondering how the TextWriter and CancellationToken are included in the instantiation. I have spotted that JobHostingConfiguration has methods for AddService and I have tried to inject my appSettings using this but it has failed with the error 'Exception binding parameter'.
So how does CancellationToken get included in the instantiation and what is JobHostingConfiguration AddService used for?
how does CancellationToken get included in the instantiation
You could use the WebJobsShutdownWatcher class because it has a Register function that is called when the cancellation token is canceled, in other words when the webjob is stopping.
static void Main()
{
var cancellationToken = new WebJobsShutdownWatcher().Token;
cancellationToken.Register(() =>
{
Console.Out.WriteLine("Do whatever you want before the webjob is stopped...");
});
var host = new JobHost();
// The following code ensures that the WebJob will be running continuously
host.RunAndBlock();
}
what is JobHostingConfiguration AddService used for?
Add Services: Override default services via calls to AddService<>. Common services to override are the ITypeLocator and IJobActivator.
Here is a custom IJobActivator allows you to use DI, you could refer to it to support instance methods.

Exception thrown when using Glimpse and Postal

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();
}
}

Resources