Passing username to Web Api methods in ASP.NET MVC

2019-06-26 00:13发布

I am using ASP.NET MVC 4 with Web Api

I have the following ApiController.

public class ProductsController : ApiController
{
    public List<Product> GetProducts()
    {
        return _productService.GetAllProducts();
    }

    public List<Product> GetProductsFromId(string username)
    {
        return _productService.GetProductsFromUsername(username);
    }
}

Now if you see the second Action GetProductsFromId(string username) here I need to pass username which earlier (i.e before upgrading from MVC 3) I was using User.Identity.Username to get the username.

How should I handle this in such a scenario.

How do I pass the username ? Is there something I could do.

Also the I dont want to use User.Identity.Username inside the GetProductFromId(string username) method as it will defeat the entire purpose of using web api and will not be testable too.

Please guide me help me on this. Thanks

2条回答
疯言疯语
2楼-- · 2019-06-26 00:17

Here is the basic outline of what I'm doing.

I use the "username" to get the IPrincipal.

I use the MemoryCache/ObjectCache to I'm only hitting the database, every 60 minutes. If you need it "per login" and not "per user".. (if your principal definition changes often or you need to code for the possibility, just change the cache-key to something that is username AND session based.

Note, I cannot stand using "IsInRole" in any app that isn't your kid's soccer club. (I don't have any kids, its a metaphor).

namespace MyProduct.MyApplication.WebServices.Controllers
{
    /// <summary>
    /// Performs operations with "Item" list
    /// </summary>
    public class MyItemController : BaseApiController
    {

        [Authorize] 
        public IEnumerable<Item> Get()
        {

            string username = User.Identity.Name;

            IPrincipalCache cache = new PrincipalCache(); /* use injection - Unity, this hard coding for demo purposes */
            MyCustomPrincipal princ = cache.GetMyCustomPrincipal(username);

            if ( ! princ.HasRight("USER_CAN_GET_ITEMS"))  /* Note my MyCustomPrincipal has this method...out of the "box" you'll have "IsInRole('MyRoleName') */
            {
                return null;
            }

            return itemsService.GetItems(); }

}




using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Web;

using MyProduct.MyApplication.Infrastructure.Security;

namespace MyProduct.MyApplication.WebServices.Security.Caching
{
    public interface IPrincipalCache
    {
        MyCustomPrincipal GetMyCustomPrincipal(string userName);
    }

    public class PrincipalCache : IPrincipalCache
    {
        public MyCustomPrincipal GetMyCustomPrincipal(string userName)
        {
            string cacheKey = "MyCustomPrincipalCacheKey" + userName;
            MyCustomPrincipal cachedOrFreshPrincipal = GetFromCache<MyCustomPrincipal>(cacheKey, () =>
            {
                return new MyCustomPrincipal(); /* Go to the method/datalayer/ and hydrate a MyCustomPrincipal */
            });
            return cachedOrFreshPrincipal;
        }

        private TEntity GetFromCache<TEntity>(string key, Func<TEntity> valueFactory) where TEntity : class
        {
            ObjectCache cache = MemoryCache.Default;
            var newValue = new Lazy<TEntity>(valueFactory);
            CacheItemPolicy policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(60) }; /* Most people will do stuff for less than one hour I guess */
            //The line below returns existing item or adds the new value if it doesn't exist
            var value = cache.AddOrGetExisting(key, newValue, policy) as Lazy<TEntity>;
            return (value ?? newValue).Value; // Lazy<T> handles the locking itself
        }
    }
}
查看更多
Summer. ? 凉城
3楼-- · 2019-06-26 00:36

Also the I dont want to use User.Identity.Username inside the GetProductFromId(string username) method as it will defeat the entire purpose of using web api and will not be testable too.

That's exactly what you should use:

[Authorize]
public List<Product> GetProductsFromId()
{
    string username = User.Identity.Name;
    return _productService.GetProductsFromUsername(username);
}

Notice the [Authorize] attribute. Depending on the authorization scheme you are using the User.Identity will be populated differently. For example if you have enabled forms authentication then the username will obviously come from the forms authentication cookie that the client need to pass when invoking the action. You could also write a custom handler for example if you are using basic authentication instead of forms authentication. I wrote an example here.

This doesn't defeat any purpose of unit testing. This method is perfectly fine unit testable. The User property of the ApiController is an IPrincipal which could be trivially mocked in a unit test. For example with Moq:

// arrange
var sut = new ProductsController();
var user = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
user.Setup(x => x.Identity).Returns(identity.Object);
identity.Setup(x => x.Name).Returns("john");
Thread.CurrentPrincipal = user.Object;

// act
var actual = sut.GetProductsFromId();

// assert
...
查看更多
登录 后发表回答