Access Blob of Azure Storage Account via MSAL/OAuth - oauth

I have to access and upload files to an azure storage blob via msal. So I was following and configuring my environment according to the example from Microsoft https://github.com/Azure-Samples/storage-dotnet-azure-ad-msal. I even added the Service Principal of the App Registration to the IAM of the Storage Account to the Role "Storage Blob Data Owner" and "Storage Blob Delegator". When accessing the Blob I get the following Exception:
An unhandled exception occurred while processing the request.
RequestFailedException: This request is not authorized to perform this operation using this permission.
RequestId:c0de0782-701e-005b-69cd-a2c6ac000000
Time:2020-10-15T08:27:43.7229905Z
Status: 403 (This request is not authorized to perform this operation using this permission.)
ErrorCode: **AuthorizationPermissionMismatch**
Headers:
Server: Windows-Azure-Blob/1.0,Microsoft-HTTPAPI/2.0
x-ms-request-id: c0de0782-701e-005b-69cd-a2c6ac000000
x-ms-client-request-id: a18e57f6-b22e-48c8-990b-320529a4ef13
x-ms-version: 2019-12-12
x-ms-error-code: AuthorizationPermissionMismatch
Date: Thu, 15 Oct 2020 08:27:43 GMT
Content-Length: 279
Content-Type: application/xml
Azure.Storage.Blobs.BlobRestClient+BlockBlob.UploadAsync_CreateResponse(ClientDiagnostics clientDiagnostics, Response response)
Stack Query Cookies Headers Routing
RequestFailedException: This request is not authorized to perform this operation using this permission. RequestId:c0de0782-701e-005b-69cd-a2c6ac000000 Time:2020-10-15T08:27:43.7229905Z Status: 403 (This request is not authorized to perform this operation using this permission.) ErrorCode: AuthorizationPermissionMismatch Headers: Server: Windows-Azure-Blob/1.0,Microsoft-HTTPAPI/2.0 x-ms-request-id: c0de0782-701e-005b-69cd-a2c6ac000000 x-ms-client-request-id: a18e57f6-b22e-48c8-990b-320529a4ef13 x-ms-version: 2019-12-12 x-ms-error-code: AuthorizationPermissionMismatch Date: Thu, 15 Oct 2020 08:27:43 GMT Content-Length: 279 Content-Type: application/xml
Azure.Storage.Blobs.BlobRestClient+BlockBlob.UploadAsync_CreateResponse(ClientDiagnostics clientDiagnostics, Response response)
Azure.Storage.Blobs.BlobRestClient+BlockBlob.UploadAsync(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, Uri resourceUri, Stream body, long contentLength, string version, Nullable<int> timeout, byte[] transactionalContentHash, string blobContentType, string blobContentEncoding, string blobContentLanguage, byte[] blobContentHash, string blobCacheControl, IDictionary<string, string> metadata, string leaseId, string blobContentDisposition, string encryptionKey, string encryptionKeySha256, Nullable<EncryptionAlgorithmType> encryptionAlgorithm, string encryptionScope, Nullable<AccessTier> tier, Nullable<DateTimeOffset> ifModifiedSince, Nullable<DateTimeOffset> ifUnmodifiedSince, Nullable<ETag> ifMatch, Nullable<ETag> ifNoneMatch, string ifTags, string requestId, string blobTagsString, bool async, string operationName, CancellationToken cancellationToken)
System.Threading.Tasks.ValueTask<TResult>.get_Result()
System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable<TResult>+ConfiguredValueTaskAwaiter.GetResult()
Azure.Storage.Blobs.Specialized.BlockBlobClient.UploadInternal(Stream content, BlobHttpHeaders blobHttpHeaders, IDictionary<string, string> metadata, IDictionary<string, string> tags, BlobRequestConditions conditions, Nullable<AccessTier> accessTier, IProgress<long> progressHandler, string operationName, bool async, CancellationToken cancellationToken)
Azure.Storage.Blobs.Specialized.BlockBlobClient+<>c__DisplayClass48_0+<<GetPartitionedUploaderBehaviors>b__0>d.MoveNext()
Azure.Storage.PartitionedUploader<TServiceSpecificArgs, TCompleteUploadReturn>.UploadInternal(Stream content, TServiceSpecificArgs args, IProgress<long> progressHandler, bool async, CancellationToken cancellationToken)
Azure.Storage.Blobs.BlobClient.StagedUploadInternal(Stream content, BlobUploadOptions options, bool async, CancellationToken cancellationToken)
Azure.Storage.Blobs.BlobClient.UploadAsync(Stream content)
WebApp_OpenIDConnect_DotNet.Controllers.HomeController.CreateBlob(TokenAcquisitionTokenCredential tokenCredential) in HomeController.cs
await blobClient.UploadAsync(stream);
WebApp_OpenIDConnect_DotNet.Controllers.HomeController.Blob() in HomeController.cs
string message = await CreateBlob(new TokenAcquisitionTokenCredential(_tokenAcquisition));
Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
System.Threading.Tasks.ValueTask<TResult>.get_Result()
System.Runtime.CompilerServices.ValueTaskAwaiter<TResult>.GetResult()
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
What am I doing wrong? What do I miss?
UPDATE 1
API Permissions of the App Registration

Please make sure that you have granted the "Storage Blob Data Owner" role to the user account that you are using. When I granted this role to my SP only, I got the same error, after I granted this role to my login user account, everything works as excepted.
Result:
In my container:

Related

HealthCheck UI not able to pull the details when deployed using Azure Dev Spaces

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?

Resolving Depedency Injection in ASP.NET Core

I have three Repository classes, which are:
1. ITechniqueRepository
2. ITechniqueItemRepository
3. ITechniqueAssesstmentRepository
There is another Service class, which is:
ITechniqueService
In ITechniqueService constructor, I have the following code to resolve Dependency Injection.
private readonly ITechniqueRepository _techniqueRepository;
private readonly ITechniqueItemRepository _techniqueItemRepository;
private readonly ITechniqueAssesstmentRepository _techniqueAssesstmentRepository;
public TechniqueService(
ITechniqueRepository techniqueRepository,
ITechniqueItemRepository techniqueItemRepository,
ITechniqueAssesstmentRepository techniqueAssesstmentRepository
) : base(unitOfWork, settings, logger)
{
_techniqueRepository = techniqueRepository;
_techniqueItemRepository = techniqueItemRepository;
_techniqueAssesstmentRepository = techniqueAssesstmentRepository;
}
In my controller constructor, I have the following code to resolve the dependency.
private readonly ITechniqueService _techniqueService;
public TechniqueController(
ITechniqueService techniqueService
)
{
_techniqueService = techniqueService;
}
And in my Startup class, I have the bellow code:
services.AddScoped<ITechniqueRepository, TechniqueRepository>();
services.AddScoped<ITechniqueItemRepository, TechniqueItemRepository>();
services.AddScoped<ITechniqueAssessmentRepository, TechniqueAssessmentRepository>();
services.AddScoped<ITechniqueService, TechniqueService>();
The problem is when I execute my service, I get the below exception as response result.
{
"StatusCode": 3,
"Message": "Unable to resolve service for type 'Data.Repositories.ITechniqueAssesstmentRepository' while attempting to activate
'Services.TechniqueService'.",
"MessageDetail": "at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type
serviceType, Type implementationType, ISet1 callSiteChain,
ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type
serviceType, Type implementationType, ISet1 callSiteChain)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor
descriptor, Type serviceType, ISet1 callSiteChain)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type
serviceType, ISet1 callSiteChain)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type
serviceType, ISet1 callSiteChain)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type
serviceType, ServiceProvider serviceProvider)\r\n at
System.Collections.Concurrent.ConcurrentDictionaryExtensions.GetOrAdd[TKey,TValue,TArg](ConcurrentDictionary2
dictionary, TKey key, Func`3 valueFactory, TArg arg)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type
serviceType)\r\n at
Microsoft.Extensions.Internal.ActivatorUtilities.GetService(IServiceProvider
sp, Type type, Type requiredBy, Boolean
isDefaultParameterRequired)\r\n at lambda_method(Closure ,
IServiceProvider , Object[] )\r\n at
Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.g__CreateController|0(ControllerContext
controllerContext)\r\n at
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State&
next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14.MoveNext()\r\n---
End of stack trace from previous location where exception was thrown
---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task)\r\n at
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__23.MoveNext()",
"Errors": {} }
Where did I make a mistake?

webapi pagedresult failes when using $expand

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?

ArgumentException when result is empty

With the following query i get a Neo4jclient internal ArgumentException when the result is empty:
graphClient.Cypher
.Match("(a:Application {Id: {pId}})")
.WithParams(new { pId = request.Id })
.With("a")
.OptionalMatch("(a)-[:Uses]->(db:Database)")
.ReturnDistinct(db => db.As<DatabaseDto>());
The result is:
Additional information: Neo4j returned a valid response, however Neo4jClient was unable to deserialize into the object structure you supplied.
Include this raw JSON, with any sensitive values replaced with non-sensitive equivalents:
{
"columns" : [ "db" ],
"data" : [ [ null ] ]
}
Parameter name: content
Source=Neo4jClient
ParamName=content
StackTrace:
at Neo4jClient.Serialization.CypherJsonDeserializer`1.Deserialize(String content) in c:\TeamCity\buildAgent\work\5bae2aa9bce99f44\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 61
at Neo4jClient.GraphClient.<>c__DisplayClass1e`1.<Neo4jClient.IRawGraphClient.ExecuteGetCypherResultsAsync>b__1d(Task`1 responseTask) in c:\TeamCity\buildAgent\work\5bae2aa9bce99f44\Neo4jClient\GraphClient.cs:line 820
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
InnerException: System.InvalidOperationException
HResult=-2146233079
Message=While trying to map some JSON into an object of type ....DatabaseDto, we failed to find an expected property (Id) in the JSON at path data[0][0].
The JSON block for this token was:
Source=Neo4jClient
StackTrace:
at Neo4jClient.Serialization.CommonDeserializerMethods.Map(DeserializationContext context, Object targetObject, JToken parentJsonToken, IEnumerable`1 typeMappings, Int32 nestingLevel) in c:\TeamCity\buildAgent\work\5bae2aa9bce99f44\Neo4jClient\Serialization\CommonDeserializerMethods.cs:line 321
at Neo4jClient.Serialization.CommonDeserializerMethods.CreateAndMap(DeserializationContext context, Type type, JToken element, IEnumerable`1 typeMappings, Int32 nestingLevel) in c:\TeamCity\buildAgent\work\5bae2aa9bce99f44\Neo4jClient\Serialization\CommonDeserializerMethods.cs:line 274
at Neo4jClient.Serialization.CypherJsonDeserializer`1.<>c__DisplayClass1b.<ParseInSingleColumnMode>b__19(JToken row) in c:\TeamCity\buildAgent\work\5bae2aa9bce99f44\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 175
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at Neo4jClient.Serialization.CypherJsonDeserializer`1.Deserialize(String content) in c:\TeamCity\buildAgent\work\5bae2aa9bce99f44\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 34
InnerException: System.InvalidOperationException
HResult=-2146233079
Message=Cannot access child value on Newtonsoft.Json.Linq.JValue.
Source=Newtonsoft.Json
StackTrace:
at Newtonsoft.Json.Linq.JToken.get_Item(Object key)
at Neo4jClient.Serialization.CommonDeserializerMethods.Map(DeserializationContext context, Object targetObject, JToken parentJsonToken, IEnumerable`1 typeMappings, Int32 nestingLevel) in c:\TeamCity\buildAgent\work\5bae2aa9bce99f44\Neo4jClient\Serialization\CommonDeserializerMethods.cs:line 317
InnerException:
I couldn't recreate the exact exception you had here, but I've recreated the scenario and discovered a bug. That's fixed in 1.0.0.654.
https://github.com/Readify/Neo4jClient/commit/25b8d3701a0745fbb577e81005da8254c0d67f6f
Try upgrading. If it works, please mark this answer as accepted. If it doesn't, please do what the exception says and raise the issue at https://github.com/Readify/Neo4jClient/issues?state=open instead of here on StackOverflow.

Sending NServiceBus message inside TransactionScope

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.

Resources