I want to create mockable handler for HttpClient, so I define it like this:
type TestHandler() =
inherit HttpMessageHandler()
abstract SendAsync: HttpRequestMessage * CancellationToken -> Task<HttpResponseMessage>
override this.SendAsync(request, cancellationToken) = base.SendAsync(request, cancellationToken)
but I get the error:
Error FS0361
The override SendAsync : HttpRequestMessage * CancellationToken -> Task<HttpResponseMessage>
implements more than one abstract slot,
e.g. abstract member TestHandler.SendAsync : HttpRequestMessage * CancellationToken -> Task<HttpResponseMessage>
and
HttpMessageHandler.SendAsync(request: HttpRequestMessage, cancellationToken: CancellationToken) : Task<HttpResponseMessage>
My intent is to create public overridable method which can be mocked with Foq, and which is simply called in base SendAsync.
If I name abstract method differently, e.g. MockableSendAsync compiler does not complain, but is it possible to reuse the name? (Is it possible to make method sealed?)
If I understand the problem correctly, the issue is that the virtual SendAsync method in HttpMessageHandler is protected, but want to create a type where it is public so that it can be mocked easily.
I don't think there is a way to hide virtual method with another new (public) virutal method in F#, but I think your use case would work if you defined a virutal method of some other name and called it from the original SendAsync method:
type TestHandler() =
inherit HttpMessageHandler()
abstract MockableSendAsync:
HttpRequestMessage * CancellationToken -> Task<HttpResponseMessage>
default this.MockableSendAsync(request, cancellationToken) =
base.SendAsync(request, cancellationToken)
override this.SendAsync(request, cancellationToken) =
this.MockableSendAsync(request, cancellationToken)
Related
I have developed asp.net core 3.1 based web api and implemented the HealthCheck and HealthCheckUI based on the documentation available at : https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks
Before implementing it in the original project I have developed a POC and it worked fine as expected.
Now I tried to do the same in the original project which is leveraging Azure Kubernetes services and deployed in Azure Dev Spaces.
Here goes my code :
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCustomHealthChecks(Configuration);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseEndpoints(builder =>
{
builder.MapHealthChecks("/health", new HealthCheckOptions()
{AllowCachingResponses = false}).RequireCors(CorsPolicyName.AllowAny);
builder.MapHealthChecks("/healthcheck", new HealthCheckOptions()
{Predicate = _ => true, ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse}).RequireCors(CorsPolicyName.AllowAny);
}
app.UseHealthChecksUI();
}
public static IServiceCollection AddCustomHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
var cosmosDBServiceEndPoint = configuration.GetValue<string>("CosmosDBEndpoint");
var cosmosDBAuthKeyOrResourceToken = configuration.GetValue<string>("CosmosDBAccessKey");
var cosmosDBConnectionString = $"AccountEndpoint={cosmosDBServiceEndPoint};AccountKey={cosmosDBAuthKeyOrResourceToken};";
var hcBuilder = services.AddHealthChecks();
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()).AddCosmosDb(connectionString: cosmosDBConnectionString, name: "CosmosDB-check", failureStatus: HealthStatus.Degraded, tags: new string[]{"cosmosdb"});
services.AddHealthChecksUI();
return services;
}
On deploying the above code in Azure Dev Space I am getting the below error:
An exception occurred while iterating over the results of a query for context type 'HealthChecks.UI.Core.Data.HealthChecksDb'.
Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 1: 'near "(": syntax error'.
at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
at Microsoft.Data.Sqlite.SqliteCommand.PrepareAndEnumerateStatements(Stopwatch timer)+MoveNext()
at Microsoft.Data.Sqlite.SqliteCommand.GetStatements(Stopwatch timer)+MoveNext()
at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
Can anyone help me to know how to fix this issue?
I have a stateless service in Azure Service Fabric, and I'm using Microsoft.Extensions.DependencyInjection, although the same issue exists for any other DI frameworks. In my Program.cs, I create a ServiceCollection, add all (but one) of my registrations, create the service provider, and pass it to my service's constructor. Any service method with external entry will create a new service scope and call the main business logic class. The issue is that one of the classes I want to have scoped lifetime needs a value that is an input parameter on the request itself. Here's a code snippet of what I would like to achieve.
internal sealed class MyService : StatelessService, IMyService
{
private IServiceProvider _serviceProvider;
private IServiceScopeFactory _scopeFactory;
public MyService(StatelessServiceContext context, IServiceProvider serviceProvider)
: base(context)
{
_serviceProvider = serviceProvider;
_scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
}
public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken)
{
using (var scope = _scopeFactory.CreateScope())
{
var requestContext = new RequestContext(correlationId);
//IServiceCollection serviceCollection = ??;
//serviceCollection.AddScoped<RequestContext>(di => requestContext);
var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>();
return await businessLogic.ProcessAsync(request, cancellationToken);
}
}
}
The cancellation token is already passed around everywhere, including to classes that don't use it directly, just so it can be passed to dependencies that do use it, and I want to avoid doing the same with the request context.
The same issue exists in my MVC APIs. I can create middle-ware which will extract the correlation id from the HTTP headers, so the API controller doesn't need to deal with it like my service fabric service does. One way I can make it work is by giving RequestContext a default constructor, and have a mutable correlation id. However, it's absolutely critical that the correlation id doesn't get changed during a request, so I'd really like the safety of having get-only property on the context class.
My best idea at the moment is to have a scoped RequestContextFactory which has a SetCorrelationId method, and the RequestContext registration simply calls the factory to get an instance. The factory can throw an exception if a new instance is requested before the id is set, to ensure no id-less contexts are created, but it doesn't feel like a good solution.
How can I cleanly register read-only objects with a dependency injection framework, where the value depends on the incoming request?
I only had the idea for a RequestContextFactory as I was writing the original question, and I finally made time to test the idea out. It actually was less code than I expected, and worked well, so this will be my go-to solution now. But, the name factory is wrong. I'm not sure what to call it though.
First, define the context and factory classes. I even added some validation checks into the factory to ensure it worked the way I expect:
public class RequestContext
{
public RequestContext(string correlationId)
{
CorrelationId = correlationId;
}
public string CorrelationId { get; }
}
public class RequestContextFactory
{
private RequestContext _requestContext;
private bool _used = false;
public void SetContext(RequestContext requestContext)
{
if (_requestContext != null || requestContext == null)
{
throw new InvalidOperationException();
}
_requestContext = requestContext;
}
public RequestContext GetContext()
{
if (_used || _requestContext == null)
{
throw new InvalidOperationException();
}
_used = true;
return _requestContext;
}
}
Then, add registrations to your DI container:
services.AddScoped<RequestContextFactory>();
services.AddScoped<RequestContext>(di => di.GetRequiredService<RequestContextFactory>().GetContext());
Finally, the Service Fabric service method looks something like this
public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken)
{
using (var scope = _scopeFactory.CreateScope())
{
var requestContext = new RequestContext(correlationId);
var requestContextFactory = scope.ServiceProvider.GetRequiredService<RequestContextFactory>();
requestContextFactory.SetContext(requestContext);
var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>();
return await businessLogic.ProcessAsync(request, cancellationToken);
}
}
Kestrel middleware could look something like this
public async Task Invoke(HttpContext httpContext)
{
RequestContext requestContext = new RequestContext(Guid.NewGuid().ToString());
var factory = httpContext.RequestServices.GetRequiredService<RequestContextFactory>();
factory.SetContext(requestContext);
httpContext.Response.Headers["X-CorrelationId"] = requestContext.CorrelationId;
await _next(httpContext);
}
Then just do the normal thing and add a RequestContext parameter to the constructor of any class that needs to get the correlation id (or any other info you put in the request context)
To get the inline count in odata style from a webapi2 controller I read on this page:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options that I should return a PagedResult from my method. I made my method in my apicontroller like this:
public PageResult<Software> Get(ODataQueryOptions<Software> options)
{
ODataQuerySettings settings = new ODataQuerySettings()
{
};
IQueryable results = options.ApplyTo(db.Software, settings);
return new PageResult<Software>(
results as IEnumerable<Software>,
Request.ODataProperties().NextLink,
Request.ODataProperties().TotalCount
);
}
This works fine for request like this:
http://dummy.com/api/Softwareapi?$inlinecount=allpages&$filter=Deleted%20eq%20false&$orderby=SequenceNo&$top=7&$skip=0
But for a request like this:
http://dummy.com/api/Softwareapi?$inlinecount=allpages&$filter=Deleted%20eq%20false&$orderby=SequenceNo&$top=7&$skip=0&$expand=Supplier
I get:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>Value cannot be null. Parameter name: data</ExceptionMessage>
<ExceptionType>System.ArgumentNullException</ExceptionType>
<StackTrace>
at System.Web.Http.OData.PageResult`1..ctor(IEnumerable`1 items, Uri nextPageLink, Nullable`1 count) at DigiCampuz.Controllers.SoftwareApiController.Get(ODataQueryOptions`1 options) in c:\Users\bzs\Documents\Visual Studio 2012\Projects\DigiCampuz Webapp\DigiCampuz\Controllers\SoftwareApiController.cs:line 41 at lambda_method(Closure , Object , Object[] ) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
If I use quick-watch feature in the vs debugger I can see results has the correct amount of items and those items have suppliers, but I cannot seem to get pagedresult to see that.
Does anybody here want to help me with this?
I am trying to use NHibernate to save to a database in the same transaction as sending a message on the bus from inside an MVC application:
public void DoSomethingToEntity(Guid id)
{
var session = _sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
using (var transactionScope = new TransactionScope())
{
var myEntity = _session.Get(id);
myEntity.DoSomething();
_session.Save(myEntity);
_bus.Send(myMessage);
transactionScope.Complete();
}
session.Dispose();
}
In the configuration, .MsmqTransport() is set with .IsTransactional(true).
If I do this inside a message handler (which is wrapped in its own transaction so does not need the TransactionScope) Then it all works as expected, and if I include an exception, both fail.
However, if I do it inside my own transaction in an MVC application, I get the following error after transactionScope.Complete() when leaving the using block.:
'The operation is not valid for the current state of the enlistment.'
Stack Trace:
at System.Transactions.EnlistmentState.InternalIndoubt(InternalEnlistment enlistment)
at System.Transactions.VolatileDemultiplexer.BroadcastInDoubt(VolatileEnlistmentSet& volatiles)
at System.Transactions.TransactionStatePromotedIndoubt.EnterState(InternalTransaction tx)
at System.Transactions.TransactionStatePromotedBase.InDoubtFromEnlistment(InternalTransaction tx)
at System.Transactions.DurableEnlistmentDelegated.InDoubt(InternalEnlistment enlistment, Exception e)
at System.Transactions.SinglePhaseEnlistment.InDoubt(Exception e)
at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment)
at System.Transactions.TransactionStateDelegatedCommitting.EnterState(InternalTransaction tx)
at System.Transactions.TransactionStateDelegated.BeginCommit(InternalTransaction tx, Boolean asyncCommit, AsyncCallback asyncCallback, Object asyncState)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
at HumanResources.Application.Implementations.HolidayService.Book(BookHolidayRequest request) in C:\Users\paul.davies\Documents\GitHub\EdaCalendarExample\HumanResources.Application\Implementations\HolidayService.cs:line 76
at HumanResources.UI.Controllers.HolidayController.BookUpdate(BookHolidayViewModel viewModel) in C:\Users\paul.davies\Documents\GitHub\EdaCalendarExample\HumanResources.UI\Controllers\HolidayController.cs:line 82
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters)
at System.Web.Mvc.ControllerActionInvoker.<>c_DisplayClass15.b_12()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
Latest Edit:
This code works:
public void DoSomethingToEntity(Guid id)
{
var session = _sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
using (var transactionScope = new TransactionScope())
{
var myEntity = _session.Get(id);
_bus.Send(myMessage);
transactionScope.Complete();
}
session.Dispose();
}
This code creates the error:
public void DoSomethingToEntity(Guid id)
{
var session = _sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
using (var transactionScope = new TransactionScope())
{
var myEntity = _session.Get(id);
myEntity.AnyField = "a new value";
_bus.Send(myMessage);
transactionScope.Complete();
}
session.Dispose();
}
Note that I am not saving th entity in either example. The difference is in the second example, I am modifying the entity I have got from NHibernate. This is 100% reproducable.
This may not be related but you still have to call _session.Flush() before committing a TransactionScope even if the session flush mode is set to Commit - that only works for NH provided transactions.
As far as I can tell there is no way of being notified when a new System.Transactions.Transaction is created, and looking at the code in NHibernate it doesn't seem to have any code to deal with the situation where the TransactionScope is created AFTER creating the session.
When you create the session, it will try to enlist in the current Transaction, and if there isn't one then the session won't enlist in the transaction. I suspect that this is what's causing the transaction to fail on commit.
I would suggest creating the session INSIDE the TransactionScope - also check whether you are calling session.BeginTransaction somewhere before the TransactionScope.
I would like to throw an exception when an ASP.NET WebAPI function returns JSON where the value is an IEnumerable and the HTTP request method is GET - hopefully to stop any JSON being generated where the top level is an array.
I've tried to do this by creating a MediaTypeFormatter. Am I able to do this? Is there another way I can go about doing this? Thanks.
Something like:
public class CustomFormatter : MediaTypeFormatter
{
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
{
// Retrieve value for isGetRequest somehow...
if (value is IEnumerable && isGetRequest)
{
throw new InvalidOperationException();
}
...
}
}
It is possible as GetPerRequestFormatterInstance method has been added and can be overriden:
public class CustomFormatter : MediaTypeFormatter
{
private HttpRequestMessage _request;
private CustomFormatter(HttpRequestMessage request)
{
_request = request;
}
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
return new CustomFormatter(request);
}
..........
So if you do that, then at the time of WriteToStreamAsync, request will have a value.