I have a basic C# Web Api 2 controller that has a POST method to create an entity
public HttpResponseMessage Post(UserModel userModel){ ... }
And also a PUT method to update said model
public HttpResponseMessage Put(int id, UserModel userModel) { ... }
And here is the UserModel
public class UserModel
{
public virtual Name { get; set; }
public virtual Username { get; set; }
}
For my validator, I want to validate that the name is not taken on Post - easy enough. For PUT, I need to validate that the name is not taken, by another user, but of course this particular user would have the same username.
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator()
{
RuleFor(user => user.Username)
.Must(NotDuplicateName).WithMessage("The username is taken");
}
private bool NotDuplicateName(string username)
{
var isValid = false;
//Access repository and check to see if username is not in use
//If it is in use by this user, then it is ok - but user ID is
//in the route parameter and not in the model. How do I access?
return isValid;
}
}
I am using AutoFac, so maybe there is a way to inject the HttpRequest into the validator and get the route data that way.
Or possibly I could create a model binder that looks for the route data and adds it to the model?
Or is there an easy way?
I have found an other solution with inject the IActionContextAccessor into the Validator. With this I can access the ROUTE paramerter without the need of a special model binding.
Startup.cs
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
UserModelValidator.cs
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator(IActionContextAccessor actionContextAccessor)
{
RuleFor(item => item.Username)
.MustAsync(async (context, username, propertyValidatorContext, cancellationToken) =>
{
var userId = (string)actionContextAccessor.ActionContext.RouteData.Values
.Where(o => o.Key == "userId")
.Select(o => o.Value)
.FirstOrDefault();
return true;
});
}
}
Update 2022 / FluentValidation 11
Starting in FluentValidation 11.0, validators that contain asynchronous rules will now throw a AsyncValidatorInvokedSynchronouslyException
https://docs.fluentvalidation.net/en/latest/upgrading-to-11.html#sync-over-async-now-throws-an-exception
UserModelValidator.cs
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator(IActionContextAccessor actionContextAccessor)
{
RuleFor(item => item.Username)
.Must((context, username, propertyValidatorContext) =>
{
var userId = (string)actionContextAccessor.ActionContext.RouteData.Values
.GetValueOrDefault("userId");
return true;
});
}
}
The easiest way of course is to add the Id to the UserModel. You'd have to add some extra checking on the Post and Put operations though. The first should ignore the Id when a client provides it. The second could check whether the Id in the path is the same as the Id in the model. If not, then return a BadRequest.
Altered model:
public class UserModel
{
public virtual Id { get; set; }
public virtual Name { get; set; }
public virtual Username { get; set; }
}
Altered methods:
public HttpResponseMessage Post(UserModel userModel)
{
// ignore provided userModel.Id
}
public HttpResponseMessage Put(int id, UserModel userModel)
{
if(id != userModel.Id)
{
// return bad request response
}
}
Update
Having an Id in the route as well as in the model does indeed allow for a discrepancy between the two as you commented. A respectful API consumer will probably not post a request with misaligned Ids. But a malicious consumer (aka hacker) most probably will. Therefore you should return BadRequest when the Ids don't match.
You certainly do not want to update the UserModel with the Id as you mentioned otherwise you might end up with user 1 (the one in the url) overwritten by the details of user 2 (the one in the UserModel).
Related
I have an HTML form that is sent to a controller action (via POST) based on MVC.
The form contains different inputs. There are extra inputs if the user has certain claims.
For example, if the User is administrator, he/she sees an additional text area for comments.
public class MySubmit
{
public string Name { get; set; }
public string IsActive { get; set; }
// only an administrator should be able to set this field
// for all other users, this should be empty
public string Comment { get; set; }
}
public class MyController : Controller
{
public IActionResult MyActionResult(MySubmit submit)
{
}
}
What is the best and safest way to process the result on the action?
Theoretically it is possible that the a tries to submit values although he/she does not actually see the corresponding form controls, because he/she does not have the claim.
I would like to set default values used for field values instead, if the user does NOT have these claims - no matter what values he sends for these fields.
Is there anything built in?
Bryan Lewis gave the right hint: Fluent Validation.
Fluent Validation has the ability to use the HTTP context via Dependency Injection to receive the user and perform a claim comparison:
public class YourModelValidator: AbstractValidator<YourModel>
{
public YourModelValidator(IHttpContextAccessor httpContext)
{
RuleFor(x => x.YourProprty).Custom( (html, context) =>
{
var user = httpContext.User;
if (!user.HasClaim(c => c.Type.Equals(claim))
{
context.AddFailure("Claim is missing.");
}
});
}
}
You can validate the value, but you should not set the value.
Is there anything built in?
No. There's no built-in way to do that.
Design
You might want to achieve that with a custom model binder. But I believe that's not a good way. Because you'll have to process all kinds of input formatters at the same time. Think about somewhere your action expects a [FromForm]MySubmit mySubmit while another action expects a [FromBody] Submit mySubmit. The first action requires a payload of form, while the second action might expect a JSON. Even you take care of the two above scenarios, what about you want to enable XML payloads in future? In short, you can hardly write a general Model Binder for this.
Validation might help. But validation usually makes you repeat yourself if you have several models( Think about you have ten domain models, each one has several properties that requires some claims)
IMO, a better way is to use ActionFilter. Since ActionFilter takes place after the model binding, it would be possible to erase the field when the field requires a role.
To do that, create a custom attribute to mark which property requires some role:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
internal class RequireRolesForBindingAttribute : Attribute
{
public string[] Roles {get;}
public RequireRolesForBindingAttribute(params string[] roles)
{
this.Roles = roles;
}
}
Now when some roles are required, simply annotate the target property like below:
public class MySubmit
{
public string Name { get; set; }
public string IsActive { get; set; }
// only an root/admin can bind this field for all other users, this should be empty
[RequireRolesForBindingAttribute("root","admin")]
public string Comment { get; set; }
public Sub Sub{get;set;} // test it with a complex child
}
public class Sub{
public int Id {get;set;}
public string Name {get;set;}
[RequireRolesForBindingAttribute("root","admin")]
public string Note {get;set;}
}
The above data annotation represents that the two properties should be erased if the user has no rights:
Comment property of MySubmit
Note property of Sub
Finally, don't forget to enable an custom action filter. For example, add it on action method:
[TypeFilter(typeof(RequireRolesForBindingFilter))]
public IActionResult Test(MySubmit mySubmit)
{
return Ok(mySubmit);
}
An Implementation of RequireRolesForBindingFilter
I create an implementation of RequireRolesForBindingFilter for your reference:
public class RequireRolesForBindingFilter : IAsyncActionFilter
{
private readonly IAuthorizationService _authSvc;
public RequireRolesForBindingFilter(IAuthorizationService authSvc)
{
this._authSvc = authSvc;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// skip early when User ==null,
// if you don't want to allow anonymous access, use `[Authorize]`
if(context.HttpContext.User !=null) {
await this._checkUserRights(context.ActionArguments, context.HttpContext.User);
}
await next();
}
private async Task _checkUserRights(IDictionary<string, object> args, ClaimsPrincipal user){
// handle each argument
foreach(var kvp in args){
if(kvp.Value==null) { return; }
var valueType = kvp.Value.GetType();
if(await _shouldSetNullForType(valueType, user)) {
args[kvp.Key] = valueType.IsValueType? Activator.CreateInstance(valueType) : null;
}else{
// handle each property of this argument
foreach(var pi in valueType.GetProperties())
{
var pv = pi.GetValue(kvp.Value);
await _checkPropertiesRecursive( instanceValue: kvp.Value, propInfo: pi, user: user);
}
}
}
async Task<bool> _shouldSetNullForType(Type type, ClaimsPrincipal user)
{
// the `RequireRolesForBindingAttribute`
var attr= type
.GetCustomAttributes(typeof(RequireRolesForBindingAttribute), false)
.OfType<RequireRolesForBindingAttribute>()
.FirstOrDefault();
return await _shouldSetNullForAttribute(attr,user);
}
async Task<bool> _shouldSetNullForPropInfo(PropertyInfo pi, ClaimsPrincipal user)
{
// the `RequireRolesForBindingAttribute`
var attr= pi
.GetCustomAttributes(typeof(RequireRolesForBindingAttribute), false)
.OfType<RequireRolesForBindingAttribute>()
.FirstOrDefault();
return await _shouldSetNullForAttribute(attr,user);
}
async Task<bool> _shouldSetNullForAttribute(RequireRolesForBindingAttribute attr, ClaimsPrincipal user)
{
if(attr!=null) {
var policy = new AuthorizationPolicyBuilder().RequireRole(attr.Roles).Build();
// does the user have the rights?
var authResult = await this._authSvc.AuthorizeAsync(user, null, policy);
if(!authResult.Succeeded){
return true;
}
}
return false;
}
// check one property (propInfo) for instance `instanceValue`
async Task _checkPropertiesRecursive(object instanceValue, PropertyInfo propInfo, ClaimsPrincipal user){
if(instanceValue == null) return;
Type propType = propInfo.PropertyType;
object propValue = propInfo.GetValue(instanceValue);
if(await _shouldSetNullForPropInfo(propInfo, user))
{
propInfo.SetValue(instanceValue, propType.IsValueType? Activator.CreateInstance(propType) : null);
}
else if( !shouldSkipCheckChildren(propType) && propValue!=null ){
// process every sub property for this propType
foreach(var spi in propType.GetProperties())
{
await _checkPropertiesRecursive(instanceValue: propValue , spi, user );
}
}
bool shouldSkipCheckChildren(Type type) => (type == typeof(string) || type == typeof(DateTime));
}
}
}
Demo:
When some user, who has no rights to submit the comment and note filed, sends a payload as below:
POST https://localhost:5001/home/test
cookie: <my-cookie>
Content-Type: application/x-www-form-urlencoded
name=a&isActive=true&comment=abc&sub.Name=s1&sub.note=magic
The response will be:
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
{
"name": "a",
"isActive": "true",
"comment": null,
"sub": {
"id": 0,
"name": "s1",
"note": null
}
}
There are really two actions that you want to perform -- validation and conditional editing of the incoming model. For complex validation, you should consider using something like FluentValidation (https://fluentvalidation.net/), which is quite flexible, integrates with MVC's ModelState and will allow you to check all sorts of things based on conditions. It's not clear from your post if you referring to "claims" in the general sense or specifically to ASP.Net Identity Claims -- either way, you can pull identity information into the FluentValidation Validator and create conditional checks based on identity information. The validators (FV or otherwise) don't really handle resetting/editing the model. For your example, you can simply alter the model directly after the validation is complete.
// if Validation is successful
if (isAdmin) { // however you are checking their role
submit.Comment = null; // or whatever the default value should be
}
// Do something with the incoming model / dave to DB etc
Currently I'm trying to create a web api based on asp.net core 2.0 and I'd like to create a nested route. In case of a put request it sends a part of the information in the route and another part in the body.
Requirements
The desired url to call would be
https://localhost/api/v1/master/42/details
If we'd like to create a new detail below our master 42 I would expect to send the data of the details in the body while the id of the master comes out of the route.
curl -X POST --header 'Content-Type: application/json' \
--header 'Accept: application/json' \
-d '{ \
"name": "My detail name", \
"description": "Just some kind of information" \
}' 'https://localhost/api/v1/master/42/details'
The outcoming response of the request would be
{
"name": "My detail name",
"description": "Just some kind of information",
"masterId": 42,
"id": 47
}
and a location url within the response header like
{
"location": "https://localhost/api/v1/master/42/details/47
}
Work done so far
To get this to work I created this controller:
[Produces("application/json")]
[Route("api/v1/master/{masterId:int}/details")]
public class MasterController : Controller
{
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var response = await Do.Process(request);
return CreatedAtAction(nameof(Get), new { id = response.Id }, response);
}
}
Which uses these classes:
public class DetailCreateRequest
{
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class DetailResponse
{
public int Id { get; set; }
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The problem
So far most of the stuff works as expected. The only thing that really doesn't work is merging the MasterId from the route into the DetailCreateRequest that comes from the body.
First try: Use two attributes on the parameter
I tried to combine these two things by this action call:
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
But the incoming object only had a MasterId of zero. If I change the order of the two attributes, then only the id from the route will be taken and all values within the body are ignored (so seems to be first attribute wins).
Second try: Use two different parameters in action
Another approach that I tried was this action call:
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request)
In the first spot this looks okay, cause now I have both values within the controller action. But my big problem with this approach is the model validation. As you can see in the above code I check ModelState.IsValid which was filled through some checks from FluentValidation, but these checks can't be really done, cause the object wasn't build up correctly due to the missing master id.
(Not-working) Idea: Create own attribute with merge parameters
Tried to implement something like this:
public async Task<IActionResult> Post([FromMultiple(Merge.FromBody, Merge.FromRoute)]DetailCreateRequest request)
If we already would have something like this, that would be great. The order of the arguments within the attribute would give out the order in which the merge (and possible overwrites) would happen.
I already started with implementing this attribute and creating the skeleton for the needed IValueProvider and IValueProviderFactory. But it seems to be a quite lot of work. Especially finding all the nifty details to make this work seamlessly with the whole pipeline of asp.net core and other libraries I'm using (like swagger through swashbuckle).
So my question would be, if there already exists some mechanism within asp.net core to achieve such a merge or if anybody is aware about an already existing solution or about a good example on how to implement such a beast.
Solution so far: Custom ModelBinder
After getting the answer from Merchezatter I look into how to create a custom model binder and came up with this implementation:
public class MergeBodyAndValueProviderBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var body = bindingContext.HttpContext.Request.Body;
var type = bindingContext.ModelMetadata.ModelType;
var instance = TryCreateInstanceFromBody(body, type, out bool instanceChanged);
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var setters = type.GetProperties(bindingFlags).Where(property => property.CanWrite);
foreach (var setter in setters)
{
var result = bindingContext.ValueProvider.GetValue(setter.Name);
if (result != ValueProviderResult.None)
{
try
{
var value = Convert.ChangeType(result.FirstValue, setter.PropertyType);
setter.SetMethod.Invoke(instance, new[] { value });
instanceChanged = true;
}
catch
{ }
}
}
if (instanceChanged)
bindingContext.Result = ModelBindingResult.Success(instance);
return Task.CompletedTask;
}
private static object TryCreateInstanceFromBody(Stream body, Type type, out bool instanceChanged)
{
try
{
using (var reader = new StreamReader(body, Encoding.UTF8, false, 1024, true))
{
var data = reader.ReadToEnd();
var instance = JsonConvert.DeserializeObject(data, type);
instanceChanged = true;
return instance;
}
}
catch
{
instanceChanged = false;
return Activator.CreateInstance(type);
}
}
}
It tries to deserialize the body into the desired object type and afterwards tries to apply further values from the available value providers. To get this model binder to work I had to decorate the destination class with the ModelBinderAttribute and made the MasterId internal, so that swagger doesn't announce it and JsonConvert doesn't deserialize it:
[ModelBinder(BinderType = typeof(MergeBodyAndValueProviderBinder))]
public class DetailCreateRequest
{
internal int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Within my controller the action method parameters are still containing the [FromBody] flag, cause it is used by swagger to announce on how the method can be called, but it never will be called, cause my model binder has a higher priority.
public async Task<IActionResult> Post([FromBody]DetailCreateRequest request)
So it is not perfect, but works good so far.
That is looks like a right choice:
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request) {
//...
}
But if you have some problems with domain model validation, create custom Dto object without master Id.
Otherwise you can use custom model binder, and then work with arguments from action and binding contexts.
I'm not sure if this works in Asp.Net-Core 2.0, but we use the following in 3.1 to have a single request object which gets its properties from multiple locations:
// Annotate the action parameter with all relevant attributes
public async Task<IActionResult> Post([FromBody][FromRoute][FromQuery]DetailCreateRequest request) { ... }
// Annotate each property separately, so the binder(s) don't overwrite
public class DetailCreateRequest
{
[FromRoute]
public int MasterId { get; set; }
[FromBody]
public string Name { get; set; }
[FromQuery]
public string Description { get; set; }
}
It works with .Net 6:
[HttpPost]
[Route("{id}")]
public async Task<ActionResult<CustomerResponse>> Post([FromRoute, FromBody] CustomerPostRequest request)
{
return Ok();
}
public class CustomerPostRequest
{
[FromRoute(Name = "id")]
public int Id { get; set; }
[FromBody]
public string Name { get; set; }
}
Set the your required "source" attributes on the single request object parameter, and inside this object add each property the relevant "source" attribute.
Make sure the FromBody is the last one (it didn't work when I switched them).
I have some entities which have data that must only be accessible for some users.
public class Foo
{
public virtual Bar { get; set; }
...
}
public class Bar
{
public string Secret { get; set; }
...
}
For example Bar.Secret must only be accessible by UserA but not by UserB.
I could so something like this:
public class BarsController : ODataController
{
[EnableQuery]
public IHttpActionResult Get()
{
if (User.Identity.Name != "UserA")
return Unauthorized();
return _db.Bars();
}
}
Besides that being a bad implementation. It doesn't cover this controller:
public class FoosController : ODataController
{
[EnableQuery]
public IHttpActionResult Get()
{
return _db.Foos();
}
}
Which could be called with /odata/Foos?$expand=Bars and then I could view Bar.Secret. I can't just disable $expand on Foo because that query is totally legit for UserA and also needed.
Is there a way to make OData validate the queries against some predicate that involves the requested entities.
Something like
public class SecureEnableQueryAttribute : EnableQueryAttribute
{
public bool ValidateResult(IEnumerable<T> entities)
{
return entities.All(x => x.Secret == UserA.Secret);
}
}
You can validate the query options before the query is executed and fail if the user is not authorized to retrieve the requested data. To do this, derive from EnableQueryAttribute and override ValidateQuery.
public class SecureEnableQueryAttribute : EnableQueryAttribute
{
public virtual void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
base.ValidateQuery(request, queryOptions);
// Insert custom logic, possibly looking at queryOptions.SelectExpand
// or queryOptions.RawValues.
}
}
I've found that I have a lot of repeated code in all of my actions, and want to know the best way to avoid this. Say for example that each logged on user belongs to a school and I need to access this SchoolId in almost every action.
They way I have it now almost every action will have a repeated database hit and need to reference my userService class...something like:
public ActionResult Index()
{
var schoolId = userService.GetSchoolId(User.Identity.GetUserId());
var textBooksForSchool = textBookService.GetBooks(schoolId);
...
}
public ActionResult Delete()
{
var schoolId = userService.GetSchoolId(User.Identity.GetUserId());//all over the place
var textBooksForSchool = textBookService.DeleteBooks(schoolId);
...
}
I know that I can add the SchoolId to the claims but the syntax for returning it in every method is quite verbose (as far as I understand this avoids the db hit each time the claim is accessed?):
In GenerateIdentityAsync:
var claims = new Collection<Claim>
{
new Claim("SchoolId", User.SchoolId.ToString())
};
userIdentity.AddClaims(claims);
In Action:
var SchoolId = Convert.ToInt32((User as ClaimsPrincipal).Claims.First(x => x.Type == "SchoolId").Value);
Is there some kind of best practice here? Possibly storing the claim in a global variable on logon?
This is how I am doing...
Base Controller
public class BaseController : Controller
{
public AppUser CurrentUser
{
get
{
return new AppUser(this.User as ClaimsPrincipal);
}
}
}
Claims Principal
public class AppUser : ClaimsPrincipal
{
public AppUser(ClaimsPrincipal principal)
: base(principal)
{
}
public string Name
{
get
{
return this.FindFirst(ClaimTypes.Name).Value;
}
}
public string Email
{
get
{
return this.FindFirst(ClaimTypes.Email).Value;
}
}
}
In the other controller you can access the claim type just by doing
CurrentUser.Email
What about creating your own base controller that all your controllers inherit from that has SchoolId as a property and then creating an ActionFilter that casts each controller as that base controller and sets that value on every request? Then it will be available on all your actions but you only have to write the code once.
It will fire each request, so you might consider other techniques for minimizing the number of times you have to look up the value, but this mechanism is a way to solve your code duplication issue.
I really like the extension method approach:
public static int SchoolId(this IPrincipal principal)
{
return Convert.ToInt32((principal as ClaimsPrincipal).Claims.First(x => x.Type == "SchoolId").Value);
}
Action:
var textBooksForSchool = textBookService.GetBooks(User.SchoolId());
I'm working with ASP.NET MVC application which is based on Identity sample available via NuGet. Because of this I already have some classes to work with the database e.g. ApplicationDbContext.
Say, I decided to let users leave requests for the administrator. I've added the Request class to the models:
public class Request
{
public int Id { get; set; }
public string Message { get; set; }
public ApplicationUser User { get; set; }
}
Since the sample uses different managers to work with users, roles, etc, I've decided to create another one called ApplicationRequestManager inside the Identity.config file (though I'm not sure it's a good practice).
public class ApplicationRequestManager : IRequestManager
{
private ApplicationDbContext db = new ApplicationDbContext();
public void Add(Request request)
{
db.Requests.Add(request);
db.SaveChanges();
}
...
}
This class uses the ApplicationDbContext to work with the database and has some methods to create a request, find it and so on.
I've created a method responsible for sending request inside the Manage controller:
public ActionResult SendRequest(IndexViewModel model)
{
Request request = new Request { Message = model.Message, User = UserManager.FindById(User.Identity.GetUserId()) };
requestManager.Add(request);
return View();
}
When this method is invoked, I get the following exception:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
If I understood correctly, the reason of exception is that I use one ApplicationDbContext to get User - via UserManager and I use another ApplicationDbContext to add the request - via RequestManager, so my request is attached to two contexts. As far as I know, such mistake can be avoided by passing the same context to both UserManager and RequestManager. However, UserManager gets its context via the OwinContext together with other managers:
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
How can I make my own manager follow that pattern as well? I've tried to use the CreatePerOwinContext method like
app.CreatePerOwinContext<ApplicationRequestManager>(ApplicationRequestManager.Create);
And I've also tried to implement the Create method following the RoleManager example
public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
{
return new ApplicationRoleManager(new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
}
But I don't have any Store for my requests so I don't know what I should do with the 'new RoleStore' part. How could I solve that problem?
Updated:
I've tried Gert's solution and it worked:
public class Request
{
public int Id { get; set; }
public string Message { get; set; }
[ForeignKey("User")]
public int ApplicationUserId { get; set; }
public ApplicationUser User { get; set; }
}
var userId = User.Identity.GetUserId();
Request request = new Request
{
Message = model.Message,
ApplicationUserId = userId
};
I've also tired another way using HttpConext.Current.GetOwinContext().Get method. I've added the following line to my ApplicationRequestMananger:
public ApplicationRequestManager()
{
this.db = HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>();
}
And it worked fine with the original Request class.
The question is, what advantages and disadvantages does each way have? I've read about foreign keys and I understand the general idea quite well; but I don't really understand what problems can 'HttpContext.Current.GetOwinContext().Get()' cause. Should I use it since it's simpler than adding foreign keys?
The trouble with your design is that each manager has its own context. Seeing this example, I think each manager should call...
db = context.Get<ApplicationDbContext>();
...or receive the request-bounded context in their constructor.
Apart from that, you could make this much simpler by exposing the foreign field to ApplicationUser (ApplicationUserId?) as a primitive property in Request:
public class Request
{
public int Id { get; set; }
public string Message { get; set; }
[ForeignKey("User")]
public int ApplicationUserId { get; set; }
public ApplicationUser User { get; set; }
}
And then create Request like so:
var userId = User.Identity.GetUserId();
Request request = new Request
{
Message = model.Message,
ApplicationUserId = userId
};
This is refered to as foreign key associations, as opposed to independent associations that only have a reference navigation property.