Autofac multitenancy not resolving type per tenant - autofac

I have an ASP.NET MVC/WebApi2 application where I use Autofac.Multitenant 3.1.1. I've setup a TenantIdentificationStrategy that identifies the tenant. I've also registered a type as InstancePerTenant. I have a tenant id for each customer and a special id for a background job where no context is present
The TenantIdentificationStrategy is invoked correctly and the id is found from the context, but the InstancePerTenant is only instatiated twice on boot: Once for the default lifetimescope (tenant is null) and once for the first tenant. If I log out and in with another tenant, the same type is reused and not a new one. I can see in the container, that a tenantlifetime scope is created per tenantid, but not 4 independent InstancePerTenant types.
My tenant id code is:
public class TenantIdentificationStrategy : ITenantIdentificationStrategy
{
public bool TryIdentifyTenant(out object tenantId)
{
tenantId = null;
try
{
var context = HttpContext.Current;
if (context == null)
{
tenantId = "jobservice";
}
else
{
if (context.User?.Identity != null && context.User.Identity.IsAuthenticated)
{
var claims = context.User as ClaimsPrincipal;
tenantId = claims.FindAll(c => c.Type == "cID").FirstOrDefault()?.Value;
}
}
}
catch (HttpException)
{
// Happens at app startup in IIS 7.0
}
return tenantId != null;
}
}
In Startup.cs - Configuration() I have (snippet):
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules(AppDomain.CurrentDomain.GetAssemblies());
var container = builder.Build();
var tenantIdentifier = new TenantIdentificationStrategy();
mtContainer = new MultitenantContainer(tenantIdentifier, container);
And a registration module in a seperate assembly:
public class RegistrationModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
......
builder.RegisterType<Office365ClientService>().As<IOffice365ClientService>().InstancePerDependency();
builder.RegisterType<Office365Service>().As<IOffice365Service>().InstancePerDependency();
builder.RegisterType<Office365ClientHttpProvider>().As<IHttpProvider>().InstancePerTenant();
......
}
}
The Office365ClientService has a dependency on IHttpProvider
Did I miss something?

Related

What should my repository return on a http post when the posted id (foreign key) is wrong

Given the user sends a valid token to an api endpoint via fiddler/postman, he could post a resource (pupil) for a related resource (schoolclass).
When the schoolclass id
does not exist yet in the database
does exist already in the database but this schoolclass Id belongs to another user.
does exist in the database and belongs to the passed userId
Then
What would you change in the Controller and Repository class to make it work for all 3 cases using a REST api + repository pattern.
Controller:
[HttpPost("~/api/pupils")]
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
return Ok(pupil.Id);
}
Repository:
public async Task CreatePupil(Pupil pupil, int schoolclassCodeId, string userId)
{
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode != null)
{
schoolclassCode.Pupils.Add(pupil);
await context.SaveChangesAsync();
}
}
NOTE
At the moment the last of the 3 use cases is implemented!
From REST prospective you need to return 400 or 404 depending on your design.
If your route need to be like /classes/{id}/users/{id}/pupil I thing you need to use 404 in case user or class is wrong.
In case of separate route (as I can see in your question) I think this should be 400 code as request URL is pointing to valid resource but payload is invalid.
In both cases I think the batter error handling strategy here is to write some set of custom exceptions (like EntityNotFondException, EntityInvalidException, BusinessLogicException) and throw them from repository in case something is wrong. Then you can create some global action filter or OWIN middleware to catch those exceptions and translate them to correct response status codes with appropriate messages
Example:
public class NotFoundException : Exception
{
public NotFoundException(Type entityType)
: base($"Entity {entityType.Name} was not found")
{
}
}
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
public ApiExceptionFilterAttribute()
{
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var exception = actionExecutedContext.Exception;
if (exception == null)
return;
if (exception is HttpResponseException)
return;
var entityNotFoundException = exception as NotFoundException;
if (entityNotFoundException != null)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, entityNotFoundException.Message);
return;
}
}
}
Usage:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if(schoolclassCode == null)
throw new NotFoundException(typeof(Schoolclass));
You can throw validation exceptions in the same way. E.g:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId);
if(schoolclassCode == null)
throw new InvalidModelStateException("Schoolclass was not found.")
if(schoolclassCode.UserId != userId)
throw new InvalidModelStateException("Schoolclass is owned by different user.")
... etc.
I always use Result classes for returning state from a service class (wouldn't implement that in Repository as it shouldn't contain business logic):
public class QueryResult
{
private static readonly QueryResult success = new QueryResult { Succeeded = true };
private readonly List<QueryError> errors = new List<QueryError>();
public static QueryResult Success { get { return success; } }
public bool Succeeded { get; protected set; }
public IEnumerable<QueryError> Errors { get { return errors; } }
public static QueryResult Failed(params QueryError[] errors)
{
var result = new QueryResult { Succeeded = false };
if (errors != null)
{
result.errors.AddRange(errors);
}
return result;
}
}
public class QueryResult<T> : QueryResult where T : class
{
public T Result { get; protected set; }
public static QueryResult<T> Suceeded(T result)
{
if (result == null)
throw new ArgumentNullException(nameof(result));
var queryResult = new QueryResult<T>
{
Succeeded = true,
Result = result
};
return queryResult;
}
}
public class QueryError
{
public string ErrorId { get; set; }
public string ErrorMessage { get; set; }
}
And use it like
var schoolclassCode = await context.Schoolclasses
.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode == null)
return QueryResult.Failed(new QueryError
{
ErrorId = 1,
ErrorMessage = "Invalid User Id"
});
Edit:
Just as an addition and rule of thumb
Services which operate on one or multiple entities and perform user input validation should return Result classes
Domain Models (which you don't seem to use, since you use a repository and Repository + Rich Domains doesn't work out well in real life applications) should throw exception (i.e. InvalidOperationException or ArgumentException, ArgumentNullException). Doing Result-types her will pollute the model and mix the separation of responsibility (Domain Model will suddenly also do validation instead only guarding against invalid state)
Using XxxResult type classes gives you an easy way to transport one or multiple errors back to the user, where an exception should act as an guard against your domain model getting into invalid state.
Edit 2
In response to the comments:
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
var result = await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok(pupil.Id);
}
Edit 3
Example with 3 parameters for let's say /api/schoolclasses/1/students/2/lessons/2 (Update an existing lesson to the student with the id 2 for the school class with id 1).
// on SchoolClasses Controller
[HttpPost("{schoolClassId:int}/students/{studentId:int}/lessons/{lessonId:int}")]
public async Task<IActionResult> Post([FromBody]Lessons lessonDto)
{
// rough input validation, do first to avoid db hits
if(!ModelState.IsValid)
return BadRequest(ModelState);
// best put logic into service classes i.e. SchoolClassService
var result = schoolClassService.UpdateLessonFor(schoolClassId, studentId, lessonDto)
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok();
}
Content of UpdateLessonsFor
List<ErrorMessage> errors = new List<ErrorMessage>();
// with .Include to include both student and all of his lessons
// does student exist?
// Hits db once and gets both, student and all lessons in a single query
var student = _context.SchoolClasses
.Include(sc => sc.Students)
.ThenInclude(s => s.Lessons)
.Where(sc => sc.SchoolClassId == schoolClassId)
.SelectMany(sc => sc.Students)
FirstOrDefault(s => s.StudentId == studentId);
if(student==null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 1, ErrorMessage = "Student or School Class not found" } );
// Doesn't hit the database, since lessons have been loaded with the above call
var lesson = student.Lessons.Any(l => l.LessonId = lessonId))
if(lesson == null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 2, ErrorMessage = "Lesson not found. " } );
// modify it
lesson.SomeValue = dto.SomeValue;
try
{
} catch(Exception ex) {
return QueryResult.Failed(new ErrorMessage { ErrorId = 3, ErrorMessage = "Couldn't update the lesson. Try again and if the error appears again, contact the administrator." } );
} finally {
return QueryResult.Suceeded;
// or if you also want to return a result
return QueryResult.Suceeded(lesson);
}
Also from the comments of the other answer: Don't put logic into your repository, that's what services are for when you use anemic domain (models have no logic, all in services) or have thin service layer and put most logic into domain service. But that's out of the scope.

Is it possible to instruct ServicePartitionClient to talk to a specific node in service fabric?

I have
public class HttpCommunicationClient : HttpClient, ICommunicationClient
{
public HttpCommunicationClient()
: base(new HttpClientHandler() { AllowAutoRedirect = false, UseCookies = false })
{
}
public HttpCommunicationClient(HttpMessageHandler handler)
: base(handler)
{
}
public HttpCommunicationClient(HttpMessageHandler handler, bool disposeHandler)
: base(handler, disposeHandler)
{
}
#region ICommunicationClient
string ICommunicationClient.ListenerName { get; set; }
ResolvedServiceEndpoint ICommunicationClient.Endpoint { get; set; }
ResolvedServicePartition ICommunicationClient.ResolvedServicePartition { get; set; }
#endregion ICommunicationClient
}
and
public class HttpCommunicationClientFactory : CommunicationClientFactoryBase<HttpCommunicationClient>
{
private readonly Func<HttpCommunicationClient> _innerDispatcherProvider;
public HttpCommunicationClientFactory(IServicePartitionResolver servicePartitionResolver = null, IEnumerable<IExceptionHandler> exceptionHandlers = null, string traceId = null)
: this(() => new HttpCommunicationClient(), servicePartitionResolver, exceptionHandlers, traceId)
{
}
public HttpCommunicationClientFactory(Func<HttpCommunicationClient> innerDispatcherProvider, IServicePartitionResolver servicePartitionResolver = null, IEnumerable<IExceptionHandler> exceptionHandlers = null, string traceId = null)
: base(servicePartitionResolver, exceptionHandlers, traceId)
{
if (innerDispatcherProvider == null)
{
throw new ArgumentNullException(nameof(innerDispatcherProvider));
}
_innerDispatcherProvider = innerDispatcherProvider;
}
protected override void AbortClient(HttpCommunicationClient dispatcher)
{
if (dispatcher != null)
{
dispatcher.Dispose();
}
}
protected override Task<HttpCommunicationClient> CreateClientAsync(string endpoint, CancellationToken cancellationToken)
{
var dispatcher = _innerDispatcherProvider.Invoke();
dispatcher.BaseAddress = new Uri(endpoint, UriKind.Absolute);
return Task.FromResult(dispatcher);
}
protected override bool ValidateClient(HttpCommunicationClient dispatcher)
{
return dispatcher != null && dispatcher.BaseAddress != null;
}
protected override bool ValidateClient(string endpoint, HttpCommunicationClient dispatcher)
{
return dispatcher != null && dispatcher.BaseAddress == new Uri(endpoint, UriKind.Absolute);
}
}
and is using it like below
var servicePartitionClient = new ServicePartitionClient<HttpCommunicationClient>(_httpClientFactory,
_options.ServiceUri,
_options.GetServicePartitionKey?.Invoke(context),
_options.TargetReplicaSelector,
_options.ListenerName,
_options.OperationRetrySettings);
using (var responseMessage = await servicePartitionClient.InvokeWithRetryAsync(httpClient => ExecuteServiceCallAsync(httpClient, context)))
{
await responseMessage.CopyToCurrentContext(context);
}
The question is now, if I know at the time of using ServicePartitionClient that I would like it to connect to a specific node, is there any way to do so?
The case is that its a gateway application that forward requests to other services and I would like it to behave like with sticky sessions.
It makes more sense to think in terms of services than nodes. So rather than connecting to a specific node, you're actually connecting to a specific instance of a service.
When you're connecting to a service, if it's stateless, it shouldn't matter which instance you connect to, by definition of it being stateless. If you find that a user is tied to a specific instance of a service, that service is stateful (it's keeping track of some user state), and that's exactly the type of scenario that stateful services are meant to handle.
I found a solution, where I in the ExecuteServiceCallAsync call below reads a cookie from request with the information about which node it was connected to if its a sticky session, and if no cookie is present i set one with the information from the request. If the node dont exist any more the cookie is updated to new node.
using (var responseMessage = await servicePartitionClient.InvokeWithRetryAsync(httpClient => ExecuteServiceCallAsync(httpClient, context)))
{
await responseMessage.CopyToCurrentContext(context);
}

RoleStore and Role management in Asp.NET Identity Without Entity Framework

I am writing WCF Services, I authenticate User who will have access to my WCF Services through ASP.NET Identity without Entity Framework. Now I got an issue on Role Authorization. I am using custom way without Entity Framework so for it to achieve authentication I created User class and UserStore Class. And how could I authorize the role?
[Note:I have Role in Database table (ASPNetRoles and ASPNetUserRoles) that can only access WCF Services and I know I have to decorate the method with principalpermission.]
namespace CalculatorService
{
public class IdentityValidator : UserNamePasswordValidator
{
public override void Validate(string UserName, string Password)
{
using (var userManager = new UserManager<User>(new UserStore("data=source=pcb-sql01;initial catalog=InsitePCB;integrated security=True;MultipleActiveResultSets=True")))
{
var user = userManager.Find(UserName, Password);
if (user == null)
{
var msg = string.Format("Unknown Username {0} or incorrect password {1}", UserName, Password);
Trace.TraceWarning(msg);
throw new FaultException(msg);
// //the client actually will receive MessageSecurityException. But if I throw MessageSecurityException, the runtime will give FaultException to client without clear message.
}
}
}
}
public class RoleAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
using (var userStore = new UserStore("data source=pcb-sql01;initial catalog=InsitePCB;integrated security=True;MultipleActiveResultSets=True"))
{
using (var userManager = new UserManager<User>(userStore))
{
var identity = operationContext.ServiceSecurityContext.PrimaryIdentity;
var user = userManager.FindByName(identity.Name);
if (user == null)
{
var msg = string.Format("Unknown Username {0} .", user.UserName);
Trace.TraceWarning(msg);
throw new FaultException(msg);
}
//Assign roles to the Principal property for runtime to match with PrincipalPermissionAttributes decorated on the service operation.
var roleNames = userManager.GetRoles(user.Id).ToArray();//users without any role assigned should then call operations not decorated by PrincipalPermissionAttributes
operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = new GenericPrincipal(operationContext.ServiceSecurityContext.PrimaryIdentity, roleNames);
return true;
}
}
}
}
}

MEF and WEB API 2.2

I am trying to inject dependencies into a Web Api Controller.
I created an own IHttpControllerActivator class and replaced the default one in lobalConfiguration.
public class SimpleASPWebAPIContainer : IHttpControllerActivator
{
private readonly CompositionContainer container;
public SimpleASPWebAPIContainer(CompositionContainer compositionContainer)
{
container = compositionContainer;
}
public IHttpController Create(System.Net.Http.HttpRequestMessage request, System.Web.Http.Controllers.HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
if (controllerType != null)
{
var export = container.GetExports(controllerType, null, null).FirstOrDefault();
IHttpController result = null;
if (null != export)
{
result = export.Value as IHttpController;
}
else
{
//result = base.GetControllerInstance(requestContext, controllerType);
//container.ComposeParts(result);
}
return result;
}
else
{
return null;
}
}
public void Dispose()
{
if (container != null)
container.Dispose();
}
}
var apiSimpleContainer = new SimpleASPWebAPIContainer(container);
System.Web.Http.GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), apiSimpleContainer);
But when the client app is calling a controller method the IHttpControllerActivation Create method is not invoked.
Anybody can help me?
It was a very silly mistake.
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
MefConfig.RegisterMef(config);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
AutoMapperConfig.InitAutoMapper();
}
I should have to used the new HttoConfiguration instance to replace default IHttpControllerActivator instead of System.Web.Http.GlobalConfiguration.Configuration.

MembershipCreateUserException: The username supplied is invalid

Im trying to use Facebook registration on my MVC 4 application, but I keep getting this cryptic error System.Web.Security.MembershipCreateUserException: The username supplied is invalid.
when OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName); is run
provider = "facebook"
providerUserId = "token-key"
model.Username = "bobsaget"
Everything is basically set back to a default MVC 4 application at this point. Here is the code I am running.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl)
{
string provider = null;
string providerUserId = null;
if (User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId(model.ExternalLoginData, out provider, out providerUserId))
{
return RedirectToAction("Manage");
}
if (ModelState.IsValid)
{
// Insert a new user into the database
using (UsersContext db = new UsersContext())
{
UserProfile user = db.UserProfiles.FirstOrDefault(u => u.UserName.ToLower() == model.UserName.ToLower());
// Check if user already exists
if (user == null)
{
// Insert name into the profile table
db.UserProfiles.Add(new UserProfile { UserName = model.UserName});
db.SaveChanges();
db.SaveChanges();
OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
}
}
}
ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(provider).DisplayName;
ViewBag.ReturnUrl = returnUrl;
return View(model);
}
The MembershipAttribute have always been default, except its getting information from web.config.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
var i = Config.ConnectionStringName;
WebSecurity.InitializeDatabaseConnection(
Config.ConnectionStringName,
Config.UserTableName,
Config.UsersPrimaryKeyColumnName,
Config.UsersUserNameColumnName,
autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
I have browsed the web, Stackoverflow and even tried to decompile the dll without getting to the root of the problem.
Thanks in advance for your input.
I am not sure but i think you need to make to sure that an entry of user is properly inserted into userprofile table and also the other tables which are required for membership.