Caching a Linq Result for Reuse MVC 3 - linq

I am trying to learn how to reuse my Linqtotwitter result for reuse in my application.
Everything works fine except I am going to hit the ratelimit very quickly if I dont do a form of caching. I have 3 queries in my application that hits the twitter feed.
I tried to use the respository pattern at http://ardalis.com/introducing-the-cachedrepository-pattern but its way over my head and lets just say i didnt get far.
A sample controller from my code
[HttpGet]
public ActionResult PublicShouts()
{
var twitterCtx = new TwitterContext(Auth);
List<TweetViewModel> friendstweets = (from tweet in twitterCtx.Status
where tweet.Type == StatusType.User &&
tweet.ScreenName == "BattleShouts" &&
// tweet.InReplyToScreenName == "Battleshouts" &&
tweet.IncludeEntities == true &&
tweet.IncludeRetweets == true &&
tweet.IncludeContributorDetails == true &&
tweet.CreatedAt < DateTime.Now.AddDays(-30).Date
select new TweetViewModel
{
ImageUrl = tweet.User.ProfileImageUrl,
ScreenName = tweet.User.Identifier.ScreenName,
Tweet = tweet.Text
})
.ToList();
return PartialView(friendstweets);
I have also looked at this post How to cache data in a MVC application
How can I go about caching my results for later reuse so I dont hit the twitter limit?
Thank you

How about OutputCacheAttribute?
http://msdn.microsoft.com/en-us/library/system.web.mvc.outputcacheattribute.aspx
Look at VaryByCustom to have separate cache entry for each user.

Related

ASP.NET Web API - Swagger , create multiple views

I am using Swagger with ASP.NET Web API application. If I visit URL http://localhost:5000/swagger
Swagger list all the controllers and actions defined in these controllers. Lets say I have five controllers and each controller has one action. I want to create multiple views such that when
user says http://localhost:5000/swagger/v1 he gets to see all controllers
when user says http://localhost:5000/swagger/v2 he gets to see only one controller
when user says http://localhost:5000/swagger/v3 he gets to see only two controller
Basically I am trying to restrict access to controller via swagger. Based on user requirement, I will share specific URL with them.
Is it possible to achieve this with Swagger?
Yes, you can do exactly what you want.
You should do the following steps:
Create a class that inherits from IDocumentFilter and register it in SwaggerConfig.cs as follows c.DocumentFilter<HideSwaggerEndpointsDocumentFilter>();
Example:
public class HideSwaggerEndpointsDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
//enter code here
}
}
This filter is loaded once you load the swagger page. Inside it, you have control over each and every controller action. You can delete some actions based on any criteria decided by you.
Deleting them is a bit tricky, I do it as follows:
foreach (var apiDescription in apiExplorer.ApiDescriptions)
{
var route = "/" + apiDescription.RelativePath.Substring(0, (apiDescription.RelativePath.IndexOf('?') != -1) ? apiDescription.RelativePath.IndexOf('?') : apiDescription.RelativePath.Length).TrimEnd('/');
var path = swaggerDoc.paths[route];
switch (apiDescription.HttpMethod.Method)
{
case "DELETE": path.delete = null; break;
case "GET": path.get = null; break;
case "HEAD": path.head = null; break;
case "OPTIONS": path.options = null; break;
case "PATCH": path.patch = null; break;
case "POST": path.post = null; break;
case "PUT": path.put = null; break;
default: throw new ArgumentOutOfRangeException("Method name not mapped to operation");
}
if (path.delete == null && path.get == null &&
path.head == null && path.options == null &&
path.patch == null && path.post == null && path.put == null)
{
swaggerDoc.paths.Remove(route);
}
}
Disclaimer:
If you put the above code in your DocumentFilter class it will delete all actions regardless of the given URL.
So we are in the final step, where you basically do your desired logic.
Inside the (foreach (var apiDescription in apiExplorer.ApiDescriptions)) you can play and do your custom logic. You have access to HttpContext.Current, so you can get the current URL.
If you don't want to delete the current action have something like this, before the swaggerDoc.paths.Remove(route);.
bool forDelete = false; // your custom logic when it should be deleted
if (!forDelete)
{
return;
}
Hope this helps you.

How to search exact record by asp.net web api with help of lamda expresion

I am facing a problem in searching a exact record by LINQ query method in ASP.NET Web API my controller. This is my code:
[HttpGet]
[Route("api/tblProducts/AllProductbySearch/{SearchText}")]
[ResponseType(typeof(IEnumerable<tblProduct>))]
public IHttpActionResult AllProductbySearch(string SearchText)
{
IEnumerable<tblProduct> tblProduct = db.tblProducts.Where(x=>x.PrdKeyword.Contains(SearchText)).AsEnumerable();
if (tblProduct == null)
{
return NotFound();
}
return Ok(tblProduct);
}
In this I am searching the record with value have keyword column and getting the result but problem is that it is not giving exact result for example if in database two record have keyword column value like shirt and another have Tshirt
Then if I pass shirt in SearchText or pass tshirt in SearchText it is giving both record while I want one record which exact match with SearchText. Please help me
My updated action method code is:
[HttpGet]
[Route("api/tblProducts/AllProductbySearch/{SearchText}")]
[ResponseType(typeof(IEnumerable<tblProduct>))]
public IHttpActionResult AllProductbySearch(string SearchText)
{
IEnumerable<tblProduct> tblProduct = db.tblProducts.Where(x => CheckWord(x.PrdKeyword, SearchText)).AsEnumerable();
if (tblProduct == null)
{
return NotFound();
}
return Ok(tblProduct);
}
private bool CheckWord(string source, string searchWord)
{
var punctuation = source.Where(Char.IsPunctuation).Distinct().ToArray();
var words = source.Split().Select(x => x.Trim(punctuation));
return words.Contains(searchWord, StringComparer.OrdinalIgnoreCase);
}
But is throwing the same error - http 500
EDITED 2
Added ToList() - db.tblProducts.ToList().... In this case we retrieve all data from Data Base and filter them in memory. If we don't retrieve all data before filtering .Net tries to create request to SQL with filtration and can't because there are .Net methods as CheckWord().
I think we can get required data without retrieving all table into memory, but don't know how. As variant we should write specific Stored Procedure and use it. Get all into memory is a simplest way (but not faster)
Please, look at this post Get only Whole Words from a .Contains() statement
Actually, for your case solution can be:
IEnumerable<tblProduct> tblProduct = db.tblProducts.ToList()
.Where(x => Regex.Match(x.PrdKeyword, $#"\b{SearchText}\b", RegexOptions.IgnoreCase).Success)
.AsEnumerable();
Option 2. Without regexp:
public static bool CheckWord(string source, string searchWord)
{
if (source == null)
return false;
var punctuation = source.Where(Char.IsPunctuation).Distinct().ToArray();
var words = source.Split().Select(x => x.Trim(punctuation));
return words.Contains(searchWord, StringComparer.OrdinalIgnoreCase);
}
[HttpGet]
[Route("api/tblProducts/AllProductbySearch/{SearchText}")]
[ResponseType(typeof(IEnumerable<tblProduct>))]
public IHttpActionResult AllProductbySearch(string SearchText)
{
IEnumerable<tblProduct> tblProduct = db.tblProducts.ToList()
.Where(x => CheckWord(x.PrdKeyword, SearchText)).AsEnumerable();
if (tblProduct == null)
{
return NotFound();
}
return Ok(tblProduct);
}
Sorry, I'm from phone now, there can be mistakes here. Will try it in 3-4 hour
You are making a simple mistake. You just need to use .Equals instead of .Contains.
When you use Contains .Net will check if the input string is part of the main string. Whereas Equals will check for exact match.
var mainStr = “long string with Hello World”;
var inputStr = “Hello”;
var status = mainStr.Contains(inputStr);
// Value of status is `true`
status = mainStr.Equals(inputStr);
// Value of status is `false`
So your code should look like this:
IEnumerable<tblProduct> tblProduct = db.tblProducts.Where(x=>x.PrdKeyword.Equals(SearchText)).AsEnumerable();
.Equals can also help you find exact match with or without having case-sensitive check in force. The single-parameterised method does a Case-Sensitive check whereas the other overridden methods of .Equals gives you an opportunity to ignore it.
Hope this helps!

Enumeration yielded no result?

I am trying to filter some objects using linq to enitites and I get an error telling me "Enumeration yielded no results".
on the client side I get a message like this:
The operation cannot be completed because the DbContext has been
disposed
I know that these filter values should return some results but it just doesnt work, so Im guessing my query is wrong, can you help please.
var mediaChannels =
NeptuneUnitOfWork.MediaChannels
.FindWhere(m => m.CountryID == CountryID &&
m.SonarMediaTypeID == MediaTypeID &&
m.SonarMediaTypes.SonarMediaGroupID == MediaGroupID &&
m.Name.Contains(search))
.Select(m => new MediaChannelModel() {
ID = m.ID,
Name = m.Name,
MediaType = m.MediaType.Name,
Country = m.Countries.Name,
SubRegion = m.Countries.Lookup_SubRegions.Name,
Region = m.Countries.Lookup_SubRegions.Lookup_Regions.Name
});
My guess is that this runs just fine, then you dispose you context, then you try to access mediaChannels. The problem is that Linq uses deferred execution. Therefore, you query doesn't really execute until you enumerate mediaChannels, which is after you context is disposed.
If you don't want to use deferred execution, then add a .ToList() to the end of your query to force it to load right there.
If you want to use deferred execution, then you can't dispose of your context until a later point.
The operation cannot be completed because the DbContext has been disposed is often seen if you send data to the client without saving the data to memory. This can be easily fixed by .ToList()-ing your query before sending it to the page
var mediaChannels = NeptuneUnitOfWork.MediaChannels
.Where(m => m.CountryID == CountryID
&& m.SonarMediaTypeID == MediaTypeID &&
&& m.SonarMediaTypes.SonarMediaGroupID == MediaGroupID
&& m.Name.Contains(search))
.Select(m => new MediaChannelModel() {
ID = m.ID,
Name = m.Name,
MediaType = m.MediaType.Name,
Country = m.Countries.Name,
SubRegion = m.Countries.Lookup_SubRegions.Name,
Region = m.Countries.Lookup_SubRegions.Lookup_Regions.Name
}).ToList(); // <<-- NOTE this additional method

Add Linq statement to a Web API Get with a filter, i'm trying to add $select

I trying to apply some linq statements to all my Get Web api commands. I figured I could do this using an ActionFilterAttribute.
I'm basically adding $select support in web api since its currently not supported. I'm not sure where to get the IQueryable results. I believe I need it before sql execution happens but after Get function has returned the IQueryable result. Any help would be great. I'm trying something similiar to this post, but his idea will not work because HttpResponseMessage response = actionExecutedContext.Result; is no longer in RC.
Thanks
Nick
solution
public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
HttpRequestMessage request = actionExecutedContext.Request;
HttpResponseMessage response = actionExecutedContext.Response;
IQueryable obj;
if (response != null && response.TryGetContentValue(out obj) && request.RequestUri.ParseQueryString()["$select"] != null)
{
System.Collections.Specialized.NameValueCollection QueryItems = request.RequestUri.ParseQueryString();
string select = QueryItems["$select"];
if (!string.IsNullOrWhiteSpace(select))
{
obj = obj.Select(string.Format("new ({0})", select));
}
//
//this should be generic not hard coded for Json
//
string json = JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse();
actionExecutedContext.Response.Content = new StringContent(json);
actionExecutedContext.Response.Content.Headers.Clear();
actionExecutedContext.Response.Content.Headers.Add("Content-Type", "application/json");
actionExecutedContext.Response.StatusCode = System.Net.HttpStatusCode.OK;
}
}
see the original post above. I added the solution to the bottom.

Query DB loads one of my object's properties and it shouldn't

In my Edit Controller Action, I post the object to update.
[HttpPost]
public virtual ActionResult Edit(Case myCase){
var currentDocuments = db.CaseDocuments.Where(p => p.idCase == myCase.idCase);
foreach (CaseDocument docInDB in currentDocuments )
{
var deleteDoc = true;
foreach (CaseDocument docNew in myCase.CaseDocuments )
{
if (docNew.idDocument == docInDB.idDocument)
deleteDoc = false;
}
if (deleteDoc )
db.CaseDocuments.Remove(docInDB);
}
foreach (CaseDocument pc in myCase.CaseDocuments)
{
if (pc.idDocument == 0)
db.CaseDocuments.Add(pc);
else
db.Entry(pc).State = EntityState.Modified;
}
*** **db.Entry(myCase).State = EntityState.Modified;** //THIS LINE
db.SaveChanges();
}
The Case model has a collection of Documents, and they are posted along with the Case Model.
As soon I enter the action, I can count the number of documents in the collection, and lets say there are 3.
Then, in order to see if I need to delete documents from database (as the user deleted one from UI), I need to get the Documents for that case from database in this way:
var currentDocuments = db.CaseDocuments.Where(p => p.idCase == myCase.idCase);
And here starts the weird thing: as soon I executa that statement, the myCase.Documents is loaded with what it is in database (lets say there are 4)!! So, I'm not able to compare the 2 collections (to detect if a document was deleted and remove it from db).
What I need is during the Edit Action of my Case model, I need to create/update/modify its documents. Do I need to see this from other angle? What I'm doing is wrong?
EDIT:
After the comments, I realized that the line where I marked myCase as Modified, was at the begining, and I suppose that this was the reason for that behaviour.
Now, moving that line to just before the db.SaveChanges(), fixed that problem, but at the db.Entry(myCase).State = EntityState.Modified; says "There is already an object with the same key in ObjectStateManager. "
What am I doing wrong here? This code looks bad!
Try it this way:
[HttpPost]
public virtual ActionResult Edit(Case myCase){
var currentDocumentIds = db.CaseDocuments
.Where(p => p.idCase == myCase.idCase)
.Select(p => p.idDocument);
foreach (int idInDb in currentDocumentsIds.Where(i => !myCase.CaseDocuments
.Any(ci => ci.idDocumnet == i))
{
var docToDelete = new CaseDocument { idDocument = idInDb };
db.CaseDocuments.Remove(docToDelete);
}
foreach (CaseDocument pc in myCase.CaseDocuments)
{
if (pc.idDocument == 0)
db.CaseDocuments.Add(pc);
else
db.Entry(pc).State = EntityState.Modified;
}
db.Entry(myCase).State = EntityState.Modified;
db.SaveChanges();
}
Edit: The difference between this code and your code is the way how it works with existing documents. It doesn't load them - it loads just their ids. This way you will save some data transfer from database but it should also help you avoiding that exception. When you load the document from the database you have it already attached in the context but if you try to call this:
db.Entry(pc).State = EntityState.Modified;
you will try to attach another instance of the document with the same key to the context. That is not allowed - context can have attached only single instance with unique key.

Resources