Server side caching and client side caching in webapi - asp.net

I have to implement caching in asp.net web api methods because i am accessing data from third party datasource and Calling the third party data source is costly and data only gets updated every 24 hours.So with the help of Strathweb.I have implemented caching like this
/// <summary>
/// Returns the sorted list of movies
/// </summary>
/// <returns>Collection of Movies</returns>
[CacheOutput(ClientTimeSpan = 86400, ServerTimeSpan =86400)]
public IEnumerable<Movie> Get()
{
return repository.GetMovies().OrderBy(c => c.MovieId);
}
/// <summary>
/// Returns a movie
/// </summary>
/// <param name="movie">movieId</param>
/// <returns>Movie</returns>
[CacheOutput(ClientTimeSpan = 86400, ServerTimeSpan = 86400)]
public Movie Get(int movieId)
{
var movie = repository.GetMovieById(movieId);
if (movie == null)
{
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No movie with ID = {0}", movieId)),
ReasonPhrase = "Movie ID Not Found"
};
throw new HttpResponseException(httpResponseMessage);
}
return movie;
}
but in Strathweb,i have seen two attributes one is ClientTimeSpan and Other is ServerTimeSpan.I am not sure when to use ClientTimeSpan and when to use ServerTimeSpan.In the simplest term i want to understand when to use serverside caching and when to use clientside caching and what are the diffrences between both.

As the definition says
ClientTimeSpan (corresponds to CacheControl MaxAge HTTP header)
ServerTimeSpan (time how long the response should be cached on the server side)
Code sample , with explanation.
//Cache for 100s on the server, inform the client that response is valid for 100s
[CacheOutput(ClientTimeSpan = 100, ServerTimeSpan = 100)]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
//Inform the client that response is valid for 50s. Force client to revalidate.
[CacheOutput(ClientTimeSpan = 50, MustRevalidate = true)]
public string Get(int id)
{
return "value";
}
SOURCE

ClientTimeSpan
Use client side caching if you want to allow clients (usually browsers) to cache your data locally on the users computer. The benefits are that the clients may not requests your API until the cache expires. On the other side you can't invalidate this cache because it is stored on the client side. Use this cache for non-dynamic/not frequently changing data.
ServerTimeSpan
Stores data on your server. You can easily invalidate this cache but it needs some resources (memory)

Related

Google OAuth2 from .NET Web API

First of all I would like to say that there is a chance that this has already been answered some place else, but please read my question carefully before coming to that conclusion, because I have seen so many samples now that do things in a different way or is in another language using components I can't easily interpret into something I know.
What I'm trying to accomplish is to make an ASP.NET Web API, that can authorize with Google aka get an access_token for further communication with Google REST APIs.
That is all I want it to be able to do - the later comminucation with the Google APIs is out of the scope of my Web API - it's just the access_token (either through user consent or by refresh token if user has already previously consented).
I have tried using several different approaches including using the Google API Client Library for .NET.
I'm not going to post code at this point since I'm pretty much confused by now as to which approach should be used in this scenario.
My latest read was this: https://developers.google.com/identity/protocols/OAuth2WebServer and I really wish they had added some C# samples here and not only PHP, Python and Ruby.
For what I'm trying to do, I'm not sure which type of credentials I should be using Oauth or Service Account and if OAuth should it then be for application type Web Application or Other?
It seems to make a great difference which I pick, but I haven't been able to find a guide that explains this so that there isn't any doubt which to use.
What I do know though is, that some of the things I have tried worked from my local computer, but didn't as soon as it was published to an IIS.
So if you can explain to me which of the many approaches is the right one for my scenario, it would be much appreciated - any code samples are also much welcome.
Summing Up:
I want to make an ASP.NET Web API, that can get an access token from Google (an access token to communicate with Google APIs) nothing else.
It has to me able to get an access token via the refresh token, so that the owner of the Google account, only has to grant access once.
Any further communication with Google APIs will happen through normal REST calls and is outside scope of my Web API.
I had the same problem and went down most of the same roads you went down, but having some experience writing OAuth implementations it wasn't too terribly difficult. Maybe you can use what I did, which seems to work for me. You will need to install RestSharp or use some other HttpClient.
First I wrote a GoogleAuthService that handles a few basic issues. Gets the authorization url, exchanges an authorization_code for an access_token and will refresh an access_token with a refresh_token.
GoogleAuthService
public class GoogleAuthService : IGoogleAuthService
{
/// <summary>
/// Step 1 in authorization process
/// </summary>
/// <param name="appId"></param>
/// <returns></returns>
public dynamic AuthorizationUrl(string appId)
{
var qs = HttpUtility.ParseQueryString("");
qs.Add("client_id", CloudConfigurationManager.GetSetting("ga:clientId"));
qs.Add("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"));
qs.Add("scope", CloudConfigurationManager.GetSetting("ga:scopes"));
qs.Add("access_type", "offline");
qs.Add("state", $"appid={appId}");
qs.Add("response_type", "code");
return new { Url = $"{CloudConfigurationManager.GetSetting("ga:authUrl")}?{qs.ToString()}" };
}
/// <summary>
/// Take the code that came back from Google and exchange it for an access_token
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<GoogleAccessTokenResponse> AccessToken(string code)
{
var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
var request = new RestRequest();
request.AddParameter("code", code, ParameterType.GetOrPost);
request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
request.AddParameter("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"), ParameterType.GetOrPost);
request.AddParameter("grant_type", "authorization_code", ParameterType.GetOrPost);
var response = await client.ExecuteTaskAsync<GoogleAccessTokenResponse>(request, Method.POST);
return response.Data;
}
/// <summary>
/// Take an offline refresh_token and get a new acceses_token
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
public async Task<GoogleRefreshTokenResponse> RefreshToken(string refreshToken)
{
var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
var request = new RestRequest();
request.AddParameter("refresh_token", refreshToken, ParameterType.GetOrPost);
request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
request.AddParameter("grant_type", "refresh_token", ParameterType.GetOrPost);
var response = await client.ExecuteTaskAsync<GoogleRefreshTokenResponse>(request, Method.POST);
return response.Data;
}
}
GoogleAccessTokenResponse
public class GoogleAccessTokenResponse
{
/// <summary>
/// Initial token used to gain access
/// </summary>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
/// <summary>
/// Use to get new token
/// </summary>
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
/// <summary>
/// Measured in seconds
/// </summary>
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
/// <summary>
/// Should always be "Bearer"
/// </summary>
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
GoogleRefreshTokenResponse
public class GoogleRefreshTokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
Lastly you will need a Callback handler to accept the authorization_code.
GoogleOAuthController
public class GoogleOAuthController : Controller
{
private readonly ITenantGoogleAuthenticationService service;
private readonly ITenantService tenantService;
private readonly IGoogleAuthService googleAuthService;
public GoogleOAuthController(ITenantGoogleAuthenticationService service, ITenantService tenantService, IGoogleAuthService googleAuthService)
{
this.service = service;
this.tenantService = tenantService;
this.googleAuthService = googleAuthService;
}
public async Task<ActionResult> Callback(GoogleAuthResponse model)
{
try
{
var response = await this.googleAuthService.AccessToken(model.Code);
var qs = HttpUtility.ParseQueryString(model.State);
var appid = qs["appid"];
var tenant = await this.tenantService.TenantByAppId(appid);
var webTenant = await this.tenantService.GetWebTenant(appid);
var result = await this.service.GoogleAuthenticationSave(new TenantGoogleAuthenticationViewModel
{
AccessToken = response.AccessToken,
Expires = DateTime.Now.AddSeconds(response.ExpiresIn),
RefreshToken = response.RefreshToken,
TenantId = tenant.Id
}, webTenant);
return new RedirectResult("/");
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
}
Model for what is sent to you in the Callback
GoogleAuthResponse
public class GoogleAuthResponse
{
public string State { get; set; }
public string Code { get; set; }
public string Scope { get; set; }
}
Don't worry about the Tenant code as that is specific to my system and shouldn't have any bearing on this implementation. I use the "appid" to identify the users of my application and google is nice enough to allow me to pass that to them in the AuthorizationUrl and they nicely pass it back to me.
So basically you make a call to the GoogleAuthService.AuthorizationUrl() to get the URL. Redirect the User to that URL. Make sure you setup a ga:scopes in your Web.config. When the user agrees to all your security requests they will be forwarded back to the GoogleOAuthController and hit the Callback action, where you will take the code and exchange it for an access_token. At this point you can do like I do and just save it to your database so you can use it later. Looks like by default it expires in like an hour, so you will most likely be calling a RefreshToken before every use but that is up to you.

"The read session is not available for the input session token." exception

I'm having a problem on Azure DocumentDB with a single partion collection.
Whenever I try to programmatically insert or query any document, I get an exception with the message saying
"The read session is not available for the input session token."
As this collection was newly created, I thought this was a generic error and I tried to recreate the collection on another database, but then when trying to create the collection I can't submit the deploy because I get asked of the partition key.
error
Standing on what the documentation says,
"You do not have to specify a partition key for these collections."
Can someone help? Am I doing something wrong?
The region is West Europe (in case it helps)
For the error you're getting about the input session token, can you add your code here?
For the issue in the portal where you're trying to create a collection, do the following:
In the partition key box, enter space and then press delete, you should get a green check mark in the box.
This will be fixed in the portal shortly.
I assume from your code that you are trying to create a generic pagination logic. From my experience with DocDB, pagination needs to be achieved by using the Continuation Token.
I generally have an extension that obtains said token and then I use it on subsequent requests like so:
/// <summary>
/// Paged results with continuation token
/// </summary>
/// <typeparam name="T"></typeparam>
public class PagedResults<T>
{
public PagedResults()
{
Results = new List<T>();
}
/// <summary>
/// Continuation Token for DocumentDB
/// </summary>
public string ContinuationToken { get; set; }
/// <summary>
/// Results
/// </summary>
public List<T> Results { get; set; }
}
/// <summary>
/// Creates a pagination wrapper with Continuation Token support
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static async Task<PagedResults<T>> ToPagedResults<T>(this IQueryable<T> source)
{
var documentQuery = source.AsDocumentQuery();
var results = new PagedResults<T>();
try
{
var queryResult = await documentQuery.ExecuteNextAsync<T>();
if (!queryResult.Any())
{
return results;
}
results.ContinuationToken = queryResult.ResponseContinuation;
results.Results.AddRange(queryResult);
}
catch
{
//documentQuery.ExecuteNextAsync might throw an Exception if there are no results
return results;
}
return results;
}
You can use this helper in your code along with the FeedOptions:
var feedOptions = new FeedOptions() { MaxItemCount = sizeOfPage };
var collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);
PagedResults<T> results = await client.CreateDocumentQuery<T>(collectionUri,feedOptions).Where(predicate).ToPagedResults();
//You can check of the ContinuationToken and use it on another query
if(!string.IsNullOrEmpty(results.ContinuationToken)){
feedOptions.RequestContinuation = results.ContinuationToken;
PagedResults<T> moreResults = await client.CreateDocumentQuery<T>( collectionUri,feedOptions ).Where(predicate).ToPagedResults();
}
Also, I maintain a repo on Github that contains helpers and providers for DocDB that you are free to use if you want, most are based on the Performance guidelines article and personal experience.
Another word of advice, try to update your SDK to the latest version, either the .Net Full framework or the .Net Core version (depending on your project).

MVC6 and Web Api Authentication

Well, this was a mistake.
I decided to migrate my MVC5 application to MVC6 and things were going fine until I needed to migrate my authentication.
My MVC application was logging in using an external Web Api 2 application which returns a token.
I built a filter to handle that very simply like this:
/// <summary>
/// Uses the session to authorize a user
/// </summary>
public class SimpleAuthorize : AuthorizeAttribute
{
/// <summary>
/// Authorizes the user
/// </summary>
/// <param name="httpContext">The HTTP Context</param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var accessToken = httpContext.Session["AccessToken"];
if (accessToken == null)
return false;
return true;
}
}
which was applied to all controllers.
Now, it appears that you can't do that anymore as mentioned here.
So, how can I get my application to work with the API?
I have tried searching and found nothing that helps me with my situation. Does anyone know how I can solve this or could point me in the direction of some decent documentation?
You'd approach it by writing Authorization middleware which creates an identity out of the access token. Having a valid identity is enough for authorization to succeed, and then you can delve into policy should you need something more detailed. Something like
public class SessionAuthenticationHandler :
AuthenticationHandler<SessionAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var sessionToken = Request.HttpContext.Session.GetString("Token");
if (sessionToken == null)
{
return AuthenticateResult.Failed("No session token");
}
// Construct principal here
var principal =
new ClaimsPrincipal(new ClaimsIdentity(new[] {
new Claim("SessionToken", sessionToken) }, Options.AuthenticationScheme));
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
Options.AuthenticationScheme);
return AuthenticateResult.Success(ticket);
}
}
Having said that the reason ASP.NET has never used session to hold authentication details is due to session fixation attacks.

Items count in OData v4 WebAPI response

How to return number of items in OData v4 HTTP response?
I need this number to pagination, so it should be number of items after filtering, but before 'skip' and 'top'.
I already tried passing '$inlinecount=allpages' and '$count=true' parameters in query options in url (https://damienbod.wordpress.com/2014/06/13/web-api-and-odata-v4-queries-functions-and-attribute-routing-part-2/ - "Example of $count"), but my responses from WebAPI always have only query results (collection) - whole response looks like:
[
{
"Name":"name1",
"age":5
},
{
"Name":"name2",
"age":15
}
]
There is nothing like "odata.count" in the response.
I also tried returning PageResult instead of IQueryable in my WebAPI controller action (like described here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#server-paging), but Request.GetInlineCount() is deprecated and its value is always null.
Any ideas?
[Update] I just found the same problem here: WebApi with Odata NextPage and Count not appearing in the JSON response and I removed [EnableQuery] attribute and now my response looks like:
{
"Items":
[
{
"Name":"name1",
"age":5
},
{
"Name":"name2",
"age":15
}
],
"NextPageLink":null,
"Count":null
}
But still "Count" is always null. :(
Edit: After debugging and searching for count value in Request properties in my controller, I found out that correct Count value is in property named "System.Web.OData.TotalCount". So right now I exctract this value from that request property and my controller looks like that:
public PageResult<People> Get(ODataQueryOptions<People> queryOptions)
{
var query = _context.People.OrderBy(x => x.SomeProperty);
var queryResults = (IQueryable<People>)queryOptions.ApplyTo(query);
long cnt = 0;
if (queryOptions.Count != null)
cnt = long.Parse(Request.Properties["System.Web.OData.TotalCount"].ToString());
return new PageResult<People>(queryResults, null, cnt);
}
And it works fine, but I still don't know why I have to use workarounds like that.
For future reference (OData v4):
First of all $inlinecount it's not supported in OData v4 so you should use $count=true instead.
Second, if you have a normal ApiController and you return a type like IQueryable<T> this is the way you can attach a count property to the returned result:
using System.Web.OData;
using System.Web.OData.Query;
using System.Web.OData.Extensions;
//[EnableQuery] // -> If you enable globally queries does not require this decorator!
public IHttpActionResult Get(ODataQueryOptions<People> queryOptions)
{
var query = _peopleService.GetAllAsQueryable(); //Abstracted from the implementation of db access. Just returns IQueryable<People>
var queryResults = (IQueryable<People>)queryOptions.ApplyTo(query);
return Ok(new PageResult<People>(queryResults, Request.ODataProperties().NextLink, Request.ODataProperties().TotalCount));
}
Note:
OData functionality does not supported by ApiControllers so you
cannot have things like count or $metadata. If you choose to
use simple ApiController the way above is the one you should use
to return a count property.
For a full support of OData functionality you should implement a ODataController the following way:
PeopleController.cs
using System.Web.OData;
using System.Web.OData.Query;
public class PeopleController : ODataController
{
[EnableQuery(PageSize = 10, AllowedQueryOptions = AllowedQueryOptions.All)]
public IHttpActionResult Get()
{
var res = _peopleService.GetAllAsQueryable();
return Ok(res);
}
}
App_Start \ WebApiConfig.cs
public static void ConfigureOData(HttpConfiguration config)
{
//OData Models
config.MapODataServiceRoute(routeName: "odata", routePrefix: null, model: GetEdmModel(), batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder
{
Namespace = "Api",
ContainerName = "DefaultContainer"
};
builder.EntitySet<People>("People").EntityType.HasKey(item => item.Id); //I suppose the returning list have a primary key property(feel free to replace the Id key with your key like email or whatever)
var edmModel = builder.GetEdmModel();
return edmModel;
}
Then you access your OData Api this way (example):
encoded uri:
http://localhost:<portnumber>/People/?%24count=true&%24skip=1&%24top=3
decoded:
http://localhost:<portnumber>/People/?$count=true&$skip=1&$top=3
References:
How to Use Web API OData to Build an OData V4 Service without Entity Framework
Web API OData V4 Pitfalls
Create an OData v4 Endpoint Using ASP.NET Web API 2.2
This can also be achieved by an action filter:
/// <summary>
/// Use this attribute whenever total number of records needs to be returned in the response in order to perform paging related operations at client side.
/// </summary>
public class PagedResultAttribute: ActionFilterAttribute
{
/// <summary>
///
/// </summary>
/// <param name="actionExecutedContext"></param>
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
if (actionExecutedContext.Response != null)
{
dynamic responseContent=null;
if (actionExecutedContext.Response.Content != null)
responseContent = actionExecutedContext.Response.Content.ReadAsAsync<dynamic>().Result;
var count = actionExecutedContext.Response.RequestMessage.ODataProperties().TotalCount;
var res = new PageResult<dynamic>() {TotalCount=count,Items= responseContent };
HttpResponseMessage message = new HttpResponseMessage();
message.StatusCode = actionExecutedContext.Response.StatusCode;
var strMessage = new StringContent(JsonConvert.SerializeObject(res), Encoding.UTF8, "application/json");
message.Content = strMessage;
actionExecutedContext.Response = message;
}
}
}
And the custom PageResult class is:
public class PageResult<T>
{
public long? TotalCount { get; set; }
public T Items { get; set; }
}
Usage:
[PagedResult]
[EnableQuery()]
Will you please take a look at the sample service TripPin web api implementation at https://github.com/OData/ODataSamples/blob/master/Scenarios/TripPin. You can follow the code in Airports controller and the service with the code http://services.odata.org/TripPinWebApiService/Airports?$count=true can return the count correctly.
That's what I am using with oData v4:
Request.ODataProperties().NextLink,
Request.ODataProperties().TotalCount
If you are using OData conventional routing, $odata.count is not returned when your routes are not known to odata. Add 'app.UseODataRouteDebug();' to your ConfigureServices-method and then invoke 'https://localhost:5001/$odata'. If your route is not in the OData-route table, your route is not known to OData and you are not using correct naming conventions for your controller and EDM-type to be included in OData conventional routing.

protecting against CSRF in asp.net web application when using ajax

I have an asp web application I wanted to update to prevent cross site request forgery attacks.
I have used the Microsoft auto-generated code from VS 2012, and added it to the master page as described here. It is working well, but one page posts JSON via an AJAX request to a webmethod
I would like to check this ajax request as well.
The forseeable problems are:
var responseCookie = new HttpCookie(AntiXsrfTokenKey)
{
//Set the HttpOnly property to prevent the cookie from
//being accessed by client side script
HttpOnly = true,
this can obviously be changed, but this would then seem to increase site vulnerability. Is this a significant issue?
I can send the value of the viewstate hidden input with the ajax request, but this will then need to be decoded back into key value pairs to do the equivalent of:
(string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
Is there an easy way to use existing asp.net methods to do this?
Thank you for any help.
Here is what i have discovered. I ended up using the LosFormatter, as described by geedubb, by adding the following code to the MasterPage, and assigning the value to a hidden input which is posted back with the ajax request. I did not realise when I posted the question that HttpCookie.HttpOnly property still posts back the cookie on an ajax request, and so can be left set to false.
internal string GetToken()
{
// call the static method to guarantee LosFormatter remains threadsafe
return GetToken(_antiXsrfTokenValue);
}
private static string GetCurrentUserName()
{
var currentUser = HttpContext.Current.User.Identity;
return (currentUser == null) ? string.Empty : currentUser.Name;
}
private static string GetToken(string token)
{
var los = new System.Web.UI.LosFormatter(true, token);
var writer = new System.IO.StringWriter();
var data = new Dictionary<string,string>();
data.Add("TokenValue",token);
data.Add("UserNameKey", GetCurrentUserName());
los.Serialize(writer, data);
return writer.ToString();
}
internal static void Validate(string token)
{
var request = HttpContext.Current.Request;
var requestCookie = request.Cookies[AntiXsrfTokenKey];
var antiXsrfTokenValue = requestCookie.Value;
var los = new System.Web.UI.LosFormatter(true, antiXsrfTokenValue);
var xsrfData = (Dictionary<string,string>)los.Deserialize(token);
if (xsrfData["TokenValue"] != antiXsrfTokenValue || xsrfData["UserNameKey"] != GetCurrentUserName())
{
throw new System.Security.Authentication.AuthenticationException("Validation of Anti-XSRF token failed.");
}
}
Initially, I had tried sending the value of the _VIEWSTATE hidden input, using the same code
var los = new System.Web.UI.LosFormatter(true, antiXsrfTokenValue);
var ajaxViewState = los.Deserialize(token)
but this threw an error stating the supplied key could not deserialze the string. obviously setting
Page.ViewStateUserKey = _antiXsrfTokenValue;
has a more complex key than the supplied key alone. I would be interested if anyone knew how to deserialize a viewstate string with a userKey.
The only problem with the method I have provided is the size of the string posted back - 1976 characters long for a GUID + 6 character username!!!!
If approaching this problem again, I would reference the System.Web.WebPages.dll (used in an mvc project), and use the same methods which create the Html.AntiForgeryToken in MVC
namespace System.Web.Helpers
{
/// <summary>
/// Provides access to the anti-forgery system, which provides protection against
/// Cross-site Request Forgery (XSRF, also called CSRF) attacks.
/// </summary>
public static class AntiForgery
{
public static void GetTokens(string oldCookieToken, out string newCookieToken, out string formToken)
public static void Validate()

Resources