a request level singleton object in asp.net - asp.net

I trying to write a kind of pseudo singleton implementation. I want it to work similar to how HttpContext does work, where I can get an instance to the context doing something as simple as:
var ctx = HttpContext.Current;
So my implementation goes something like this:
public class AppUser
{
public string Username { get; set; }
public string[] Roles { get; set; }
public AppUser()
{
var appuser = HttpContext.Session["AppUser"] as AppUser;
if(appuser == null)
throw new Exception("User session has expired");
Username = appuser.Username;
Roles = appuser.Roles;
}
}
public class WebAppContext
{
const string ContextKey = "WebAppContext";
WebAppContext() { } //empty constructor
public static WebAppContext Current
{
get
{
var ctx = HttpContext.Current.Items[ContextKey] as WebAppContext;
if(ctx == null)
{
try
{
ctx = new WebAppContext() { User = new AppUser() };
}
catch
{
//Redirect for login
}
HttpContext.Current.Items.Add(ContextKey, ctx);
}
return ctx;
}
}
public AppUser User { get; set; }
}
And I try to consume this object as follows:
var appuser = WebAppContext.Current.User;
Now does the above line guarantee I get the user associated with the correct request context; not some other user which is associated with another concurrent http request being processed?

Apart from the fact that I can't understand why would you need to barely copy the user information from the Session container to the Items container, the answer to your question should be - yes, if the Session data is correct then the same data will be available from your static property.
I wrote a blog entry on that once
http://netpl.blogspot.com/2010/12/container-based-pseudosingletons-in.html

Related

Asp.Net IdentityUser add custom List Property

I am fairly new to coding with asp.net so there might be an obvious answere to my question but I haven't found one yet.
So currently I am developing a site for project management and I want the users to get notified when an event happens, eg. they were added to a new project, a project has been updated etc.
For that I have expanded the IdentityUser Model with a new property List
public class CojectUser : IdentityUser
{
public List<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Message { get; set; }
public bool Seen { get; set; }
}
When an event happens I add them to the user's notification list and update the user via the userManager.
public class EventBroker<T> : IEventBroker<T>
{
private readonly UserManager<CojectUser> userManager;
public EventBroker(UserManager<CojectUser> userMgr, IUserValidator<CojectUser> userValid)
{
userManager = userMgr;
}
public async Task NotifyAsync(Message<T> message, List<UserRole> recipients)
{
foreach (var user in recipients)
{
var cojectUser = await userManager.FindByNameAsync(user.Name);
if (cojectUser != null)
{
if (cojectUser.Notifications == null)
{
cojectUser.Notifications = new List<Notification>();
}
cojectUser.Notifications.Add(new Notification
{
Message = message.Information,
Seen = false
});
IdentityResult result = await userManager.UpdateAsync(cojectUser);
if (!result.Succeeded)
{
throw new UserUpdateFailException();
}
}
}
}
}
}
I am able to save the custom data to the database, but I am unable to load it again from database.
When I want to display the user's notifications userManager retrieves an user object with null as notification list. Even though the data is stored in database.
public async Task<IActionResult> Index()
{
CojectUser user = await userManager.GetUserAsync(User);
if(user.Notifications == null)
{
user.Notifications = new List<Notification>();
}
return View(user);
}
Data in database:
Can anybody tell me what I am doing wrong?
UserManager don't eager load properties by default.
You should use DatabaseContext directly.
var user = _context.Users.Include(c => c.Notifications).Where(u => u.Id == user.Id).ToList();

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

The optimal way to decouple business logic from controller

The rule is that controllers shouldn't have business logic, instead they should delegate it to the services. But when we do that, we can't handle all possible cases and return appropriate HTTP response.
Let's look at an example. Let's say that we are building some kind of a social network, and we need to create an endpoint for rating (liking or disliking) a post.
First let's take a look at an example where we delegate the logic to the service, this is our controller action:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
Do you see a problem in this? What if there is no post with the given id, how would we return a not found response? What if user has no permissions to rate a post, how would we return a forbidden response?
PostsService.Rate can only return a new PostRating, but what about other cases? Well, we could throw an exception, we would need to create a lot of custom exception, so that we can map them to the appropriate HTTP responses. I don't like to use exceptions for this, I think there is a better way to do handle these cases instead of exceptions. Because I think that cases when post doesn't exist and when user has no permissions aren't exceptional at all, they're just normal cases just like rating a post successfully.
What I propose, is handling that logic in a controller instead. Because in my opinion, that should be a controllers responsibility anyway, to check all of the permissions before commiting an action. So this is how I would do it:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
var post = _postsRepository.GetByIdWithRatings(postId);
if (post == null)
return NotFound();
if (!_permissionService.CanRate(user, post))
return Forbidden();
PostRating newPostRating = new PostRating
{
Post = post,
Author = user,
Type = ratingType
};
_postRatingsRepository.Save(newPostRating);
return Created(newPostRating);
}
This is the way it should be done in my opinion but I bet that someone would say that this is too much logic for the controller, or that you shouldn't use a repository in it.
If you don't like using a repository in controller than where instead would you put a method that gets or saves posts? In service? So there would be PostsService.GetByIdWithRatings and PostsService.Save that would do nothing else but just call PostsRepository.GetByIdWithRatings and PostsRepository.Save. This so unnecessary and only causes boilerplate code.
Update:
Maybe someone will say to check the permissions using PostsService and then call PostsService.Rate. This is bad because it involves more unnecessary trips to database. For an example, it would probably be something like this:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
if(_postsService.Exists(postId))
return NotFound();
if(!_postsService.CanUserRate(user, postId))
return Forbidden();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
Do I even need to explain any further why this is bad?
There's a number of ways to handle this, but the closest thing to a "best practice" method is probably using a result class. For example, if your service method creates a rating and then returns that rating it created, you instead return an object that encapsulates the rating along with other relevant information, such as success status, error messages, if any etc.
public class RateResult
{
public bool Succeeded { get; internal set; }
public PostRating PostRating { get; internal set; }
public string[] Errors { get; internal set; }
}
Then, your controller code would become something like:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
var result = _postsService.Rate(postId, ratingType, user);
if (result.Succeeded)
{
return Created(result.PostRating);
}
else
{
// handle errors
}
}
What I did (just now) is created new class ApiResult
public class ApiResult
{
public int StatusCode { get; private set; } = 200;
public string RouteName { get; private set; }
public object RouteValues { get; private set; }
public object Content { get; private set; }
public void Ok(object content = null)
{
this.StatusCode = 200;
this.Content = content;
}
public void Created(string routeName, object routeValues, object content)
{
this.StatusCode = 201;
this.RouteName = routeName;
this.RouteValues = routeValues;
this.Content = content;
}
public void BadRequest(object content = null)
{
this.StatusCode = 400;
this.Content = content;
}
public void NotFound(object content = null)
{
this.StatusCode = 404;
this.Content = content;
}
public void InternalServerError(object content = null)
{
this.StatusCode = 500;
this.Content = content;
}
}
And a controller base class with a single method TranslateApiResult
public abstract class CommonControllerBase : ControllerBase
{
protected IActionResult TranslateApiResult(ApiResult result)
{
if (result.StatusCode == 201)
{
return CreatedAtAction(result.RouteName, result.RouteValues, result.Content);
}
else
{
return StatusCode(result.StatusCode, result.Content);
}
}
}
And now in controller I do:
[ApiController]
[Route("[controller]/[action]")]
public class MyController : CommonControllerBase
{
private readonly IMyApiServcie _service;
public MyController (
IMyApiServcie service)
{
_service = service;
}
[HttpGet]
public async Task<IActionResult> GetData()
{
return TranslateApiResult(await _service.GetData());
}
}
In your services you inject repositories and other dependencies:
public class MyApiServcie : IMyApiServcie
{
public async Task<ApiResult> GetData()
{
var result = new ApiResult();
// do something here
result.Ok("success");
return result;
}
}
Now, the reason for Api prefix before the Service is that this service is not meant to be the final service containing all logic.
At this point I would split the business logic into different domains so the services (or facades) would end up without Api prefix in them just to differentiate between i.e. CarService. Preferably these services will not know of anything related to API responses, statuses etc. It's up to you how implement it, though.

Unit test controller - membership error

I want to create a Unit test for the following controller but it got fail in the Membership class:
public class AccountController:BaseController
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if(FormsService == null) { FormsService = new FormsAuthenticationService(); }
if(MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult LogOn()
{
return View("LogOn");
}
[HttpPost]
public ActionResult LogOnFromUser(LappLogonModel model, string returnUrl)
{
if(ModelState.IsValid)
{
string UserName = Membership.GetUserNameByEmail(model.Email);
if(MembershipService.ValidateUser(model.Email, model.Password))
{
FormsService.SignIn(UserName, true);
var service = new AuthenticateServicePack();
service.Authenticate(model.Email, model.Password);
return RedirectToAction("Home");
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View("LogOn", model);
}
}
Unit test code:
[TestClass]
public class AccountControllerTest
{
[TestMethod]
public void LogOnPostTest()
{
var mockRequest = MockRepository.GenerateMock();
var target = new AccountController_Accessor();
target.Initialize(mockRequest);
var model = new LogonModel() { UserName = "test", Password = "1234" };
string returnUrl = string.Empty;
ActionResult expected = null;
ActionResult actual = target.LogOn(model, returnUrl);
if (actual == null)
Assert.Fail("should have redirected");
}
}
When I googled, I got the following code but I don't know how to pass the membership to the accountcontroller
var httpContext = MockRepository.GenerateMock();
var httpRequest = MockRepository.GenerateMock();
httpContext.Stub(x => x.Request).Return(httpRequest);
httpRequest.Stub(x => x.HttpMethod).Return("POST");
//create a mock MembershipProvider & set expectation
var membershipProvider = MockRepository.GenerateMock();
membershipProvider.Expect(x => x.ValidateUser(username, password)).Return(false);
//create a stub IFormsAuthentication
var formsAuth = MockRepository.GenerateStub();
/*But what to do here???{...............
........................................
........................................}*/
controller.LogOnFromUser(model, returnUrl);
Please help me to get this code working.
It appears as though you are using concrete instances of the IMembershipServive and IFormsAuthenticationService because you are using the Accessor to initialize them. When you use concrete classes you are not really testing this class in isolation, which explains the problems you are seeing.
What you really want to do is test the logic of the controller, not the functionalities of the other services.
Fortunately, it's an easy fix because the MembershipService and FormsService are public members of the controller and can be replaced with mock implementations.
// moq syntax:
var membershipMock = new Mock<IMembershipService>();
var formsMock = new Mock<IFormsAuthenticationService>();
target.FormsService = formsMock.Object;
target.MembershipService = membershipService.Object;
Now you can test several scenarios for your controller:
What happens when the MembershipService doesn't find the user?
The password is invalid?
The user and password is is valid?
Note that your AuthenticationServicePack is also going to cause problems if it has additional services or dependencies. You might want to consider moving that to a property of the controller or if it needs to be a single instance per authentication, consider using a factory or other service to encapsuate this logic.

Custom form authentication / Authorization scheme in ASP.net MVC

I am trying to create a custom authentication scheme in ASP.NET MVC using form authentication. The idea that I might have different areas on the site that will be managed - approver are and general user area, and these will use different login pages, and so forth. So this is what I want to happen.
User access restricted page (right now I have it protected with a customer AuthorizeAttribute)
User is redirected to a specific login page (not the one from Web.config).
User credentials are verified (via custom databse scheme) and user logs in.
Would really appreciate any help with this!!!
This is what I what I have so far, and it doesn't work:
public class AdministratorAccountController : Controller
{
public ActionResult Login()
{
return View("Login");
}
[HttpPost]
public ActionResult Login(AdministratorAccountModels.LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
if (model.UserName == "admin" && model.Password == "pass") // This will be pulled from DB etc
{
var ticket = new FormsAuthenticationTicket(1, // version
model.UserName, // user name
DateTime.Now, // create time
DateTime.Now.AddSeconds(30), // expire time
false, // persistent
""); // user data
var strEncryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strEncryptedTicket);
Response.Cookies.Add(cookie);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
[AdministratorAuthorize]
public ActionResult MainMenu()
{
return View();
}
public class AdministratorAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authenCookie = httpContext.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (authenCookie == null) return false;
var ticket = FormsAuthentication.Decrypt(authenCookie.Value);
var id = new FormsIdentity(ticket);
var astrRoles = ticket.UserData.Split(new[] { ',' });
var principal = new GenericPrincipal(id, astrRoles);
httpContext.User = principal;
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var model = new AdministratorAccountModels.LoginModel();
var viewData = new ViewDataDictionary(model);
filterContext.Result = new ViewResult { ViewName = "Login", ViewData = viewData };
}
}
}
I used a combination of code suggested by minus4 and my own code above to create this simplified scenario that might help someone else. I added some comments about things that confused me at first.
public class AdministratorAccountController : Controller
{
public ActionResult Login()
{
return View("Login");
}
[HttpPost]
public ActionResult Login(AdministratorAccountModels.LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
// Here you would call a service to process your authentication
if (model.UserName == "admin" && model.Password == "pass")
{
// * !!! *
// Creating a FromsAuthenticationTicket is what
// will set RequestContext.HttpContext.Request.IsAuthenticated to True
// in the AdminAuthorize attribute code below
// * !!! *
var ticket = new FormsAuthenticationTicket(1, // version
model.UserName, // user name
DateTime.Now, // create time
DateTime.Now.AddSeconds(30), // expire time
false, // persistent
""); // user data, such as roles
var strEncryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strEncryptedTicket);
Response.Cookies.Add(cookie);
// Redirect back to the page you were trying to access
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
[AdminAuthorize]
public ActionResult MainMenu()
{
return View();
}
public class AdminAuthorize : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.RequestContext.HttpContext.Request.IsAuthenticated)
{
// Redirect to the needed login page
// This can be pulled from config file or anything else
filterContext.HttpContext.Response.Redirect("/AdministratorAccount/Login?ReturnUrl="
+ HttpUtility.UrlEncode(filterContext.HttpContext.Request.RawUrl));
}
base.OnActionExecuting(filterContext);
}
}
}
okay here you go The Code
in there you have ActionFilters folder ( AuthAccess.cs)
Plugins Folder (security.cs (encrypt/decrypt cookie), SessionHandler.cs (all matters of login))
Controllers folder (BaseController.cs, and exampleController (show you how to use)
and the loginTable SQL file.
i use mysql so you may need to amend, also i use subsonic so my model would come from there
and would be in the empty models folder.
really simple to use will leave it up for a while for you, enjoy
nope cookie model is here sorry:
using System;
namespace TestApp.Models
{
public class CookieModel
{
public string CurrentGuid { get; set; }
public DateTime LoginTime { get; set; }
public Int32 UserLevel { get; set; }
public Int32 LoginID { get; set; }
public bool isValidLogin { get; set; }
public string realUserName { get; set; }
public string emailAddress { get; set; }
}
}
Isn't this what roles are for?
Have a look at asp.net mvc authorization using roles or have a look at roles in general
i tackled this one before i have a class i use for login
routines are login, read cookie, check cookie and they have a model that contains
name, email, id, userlevel
then you just have your own custom actionFilter
eg [CustomAuth(MinAllowedLevel=10)]
i use a baseclass for all my controllers so i can have an easier link to
all my session content and can then get info like so
var model = pictures.all().where(x => x.userid == users.ReadCookie.userID)
i will bob up the code tommorow if you want for you when im back on UK daytime
say 10 hrs i will let you have the class for all the session stuff and the
custom action filter that you can use, then all you need is a logins table with a userlevel field, best with levels of 10,20,30,40 incase you need a level between 1 and 2

Resources