Modify GetQueryNameValuePairs() for UrlHelper.Link in ASP.NET WebApi - c#

With ASP.NET WebApi, when I send GET api/question?page=0&name=qwerty&other=params and API should give result within pagination envelop.
For that, I'd like to put result and change given page querystring to other values.
I tried as below code but I got a bad feeling about this.
protected HttpResponseMessage CreateResponse(HttpStatusCode httpStatusCode, IEnumerable<Question> entityToEmbed)
// get QueryString and modify page property
var dic = new HttpRouteValueDictionary(Request.GetQueryNameValuePairs());
if (dic.ContainsKey("page"))
dic["page"] = (page + 1).ToString();
else
dic.Add("page", (page + 1).ToString());
var urlHelper = new UrlHelper(Request);
var nextLink= page > 0 ? urlHelper.Link("DefaultApi", dic) : null;
// put it in the envelope
var pageEnvelope = new PageEnvelope<Question>
{
NextPageLink = nextLink,
Results = entityToEmbed
};
HttpResponseMessage response = Request.CreateResponse<PageEnvelope<Question>>(httpStatusCode, pageEnvelope, this.Configuration.Formatters.JsonFormatter);
return response;
}
The NextPageLink gives a lot complex result.:
http://localhost/api/Question?Length=1&LongLength=1&Rank=1&SyncRoot=System.Collections.Generic.KeyValuePair%602%5BSystem.String%2CSystem.String%5D%5B%5D&IsReadOnly=False&IsFixedSize=True&IsSynchronized=False&page=1
My question is,
My page handling with Dictionary approach seems dirty and wrong. Is there better way to address my problem?
I don't know why urlHelper.Link(routeName, dic) gives such a verbose ToString result. How to get rid of unusable Dictionary-related properties?

The key issue probably in your code is the conversion to the HttpRouteValueDictionary. New it up instead and add in a loop all key value pairs.
The approach can be simplified quite a lot, and you should also probably want to consider using an HttpActionResult (so that you can more easily test your actions.
You should also avoid using the httproutevaluedictionary and instead write your UrlHelper like
urlHelper.Link("DefaultApi", new { page = pageNo }) // assuming you just want the page no, or go with the copying approach otherwise.
Where just pre calculate your page no (and avoid ToString);
Write it all in an IHttpActionResult that exposes an int property with the page No. so you can easily test the action result independently of how you figure out the pagination.
So something like:
public class QuestionsResult : IHttpActionResult
{
public QuestionResult(IEnumerable<Question> questions>, int? nextPage)
{
/// set the properties here
}
public IEnumerable<Question> Questions { get; private set; }
public int? NextPage { get; private set; }
/// ... execution goes here
}
To just get the page no, do something like:
Source - http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21
string page = request.Uri.ParseQueryString()["page"];
or
you can use this extension method below (from Rick Strahl)
public static string GetQueryString(this HttpRequestMessage request, string key)
{
// IEnumerable<KeyValuePair<string,string>> - right!
var queryStrings = request.GetQueryNameValuePairs();
if (queryStrings == null)
return null;
var match = queryStrings.FirstOrDefault(kv => string.Compare(kv.Key, key, true) == 0);
if (string.IsNullOrEmpty(match.Value))
return null;
return match.Value;
}

Related

Microsoft graph SingleValueLegacyExtendedProperty request returns empty GUID

I made a request to get a specific single value property from all events in a calendar with the Graph-SDK. To achieve this i used a filter according to the Graph APIs documentation (https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/singlevaluelegacyextendedproperty_get). The filter i used was " id eq 'Boolean {00062002-0000-0000-C000-000000000046} Id 0x8223' ". Below is the code i used for this request.
public static async Task<Tuple<List<Event>, List<ICalendarEventsCollectionRequest>>> GetEventsSingleValuePropertyAsync(GraphServiceClient graphClient, String userId, String calendarId, String filterQuery, int top, String select)
{
List<ICalendarEventsCollectionRequest> requestList = new List<ICalendarEventsCollectionRequest>();
// filterQuery = "id eq 'Boolean {00062002-0000-0000-C000-000000000046} Id 0x8223'"
String filterSingleVP = "singleValueExtendedProperties($filter=" + filterQuery + ")";
List<Event> eventList = new List<Event>();
ICalendarEventsCollectionPage result = null;
ICalendarEventsCollectionRequest request = null;
if (calendarId == "")
request = graphClient.Users[userId].Calendar.Events.Request().Expand(filterSingleVP).Top(top).Select(select);
else
request = graphClient.Users[userId].Calendars[calendarId].Events.Request().Expand(filterSingleVP).Top(top).Select(select);
try
{
if (request != null)
{
result = await request.GetAsync();
requestList.Add(request);
eventList.AddRange(result);
}
if (result != null)
{
var nextPage = result;
while (nextPage.NextPageRequest != null)
{
var nextPageRequest = nextPage.NextPageRequest;
nextPage = await nextPageRequest.GetAsync();
if (nextPage != null)
{
requestList.Add(nextPageRequest);
eventList.AddRange(nextPage);
}
}
}
}
catch
{
throw;
}
return new Tuple<List<Event>, List<ICalendarEventsCollectionRequest>>(eventList, requestList);
}
I get all events and every event that matched the query gets expanded with the SingleValueLegacyExtendedProperty. The only thing that bothers me is that it looks like this:
"singleValueExtendedProperties":
[
{
"value":"true",
"id":"Boolean {00000000-0000-0000-0000-000000000000} Id 0x8223"
}
],
As you can see the value is present but the id now has an empty GUID.
I tested some other properties but i always had the same result.
I thought my filter compares the given "filterQuery" with the "id" in the answer.
Did i misunderstand something or is my request implementation just wrong?
That just looks like a bug on our side. Seems like the Guid prop might not be getting set or serialized correctly. I will bring it up to the team - thanks for the report.
-edit-
Yep, in fact we already have a fix for this that should be checked in in a few days.
-edit, edit-
Just for education's sake, the reason that it behaves this way is that the GUID that you are using is one of the "well known guids". In that case, our code is setting the well-known GUID field internally instead of the normal propertySetId guid field and REST always uses the propertySetId when rendering responses. The fix of course on our side is to use the well known guid field if the propertySetId is Guid.Empty.
-edit,edit,edit-
A fix was checked in for this and will begin normal rollout. It should reach WW saturation in a few weeks.

Visibility of individual items in MvcSiteMapProvider?

I want to hide a certain page from menu, if the current session IP is in Israel. Here's what I've tried, but in fact the menu-item doesn't appear anywhere.
I tested the GeoIP provider and it seems to be working, what am I doing wrong?
Here's how I the menu is created and how I try to skip the items I don't want in the menu:
public class PagesDynamicNodeProvider
: DynamicNodeProviderBase
{
private static readonly Guid KeyGuid = Guid.NewGuid();
private const string IsraelOnlyItemsPageKey = "publications-in-hebrew";
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode siteMapNode)
{
using (var context = new Context())
{
var pages = context.Pages
.Include(p => p.Language)
.Where(p => p.IsPublished)
.OrderBy(p => p.SortOrder)
.ThenByDescending(p => p.PublishDate)
.ToArray();
foreach (var page in pages)
{
//*********************************************************
//Is it the right way to 'hide' the page in current session
if (page.MenuKey == IsraelOnlyItemsPageKey && !Constants.IsIsraeliIp)
continue;
var node = new DynamicNode(
key: page.MenuKey,
parentKey: page.MenuParentKey,
title: page.MenuTitle,
description: page.Title,
controller: "Home",
action: "Page");
node.RouteValues.Add("id", page.PageId);
node.RouteValues.Add("pagetitle", page.MenuKey);
yield return node;
}
}
}
}
Here's how I determine and cache whether the IP is from Israel:
private const string IsIsraeliIpCacheKey = "5522EDE1-0E22-4FDE-A664-7A5A594D3992";
private static bool? _IsIsraeliIp;
/// <summary>
/// Gets a value indicating wheather the current request IP is from Israel
/// </summary>
public static bool IsIsraeliIp
{
get
{
if (!_IsIsraeliIp.HasValue)
{
var value = HttpContext.Current.Session[IsIsraeliIpCacheKey];
if (value != null)
_IsIsraeliIp = (bool)value;
else
HttpContext.Current.Session[IsIsraeliIpCacheKey] = _IsIsraeliIp = GetIsIsraelIpFromServer() == true;
}
return _IsIsraeliIp.Value;
}
}
private static readonly Func<string, string> FormatIpWithGeoIpServerAddress = (ip) => #"http://www.telize.com/geoip/" + ip;
private static bool? GetIsIsraelIpFromServer()
{
var ip = HttpContext.Current.Request.UserHostAddress;
var address = FormatIpWithGeoIpServerAddress(ip);
string jsonResult = null;
using (var client = new WebClient())
{
try
{
jsonResult = client.DownloadString(address);
}
catch
{
return null;
}
}
if (jsonResult != null)
{
var obj = JObject.Parse(jsonResult);
var countryCode = obj["country_code"];
if (countryCode != null)
return string.Equals(countryCode.Value<string>(), "IL", StringComparison.OrdinalIgnoreCase);
}
return null;
}
Is the DynamicNodeProvider cached? If yes, maybe this is what's causing the issue? How can I make it cache per session, so each sessions gets its specific menu?
Is it right to cache the IP per session?
Any other hints on tracking down the issue?
The reason why your link doesn't appear anywhere is because the SiteMap is cached and shared between all if its users. Whatever the state of the user request that builds the cache is what all of your users will see.
However without caching the performance of looking up the node hierarchy would be really expensive for each request. In general, the approach of using a session per SiteMap is supported (with external DI), but not recommended for performance and scalability reasons.
The recommended approach is to always load all of your anticipated nodes for every user into the SiteMap's cache (or to fake it by forcing a match). Then use one of the following approaches to show and/or hide the nodes as appropriate.
Security Trimming
Built-in or custom visibility providers
Customized HTML helper templates (in the /Views/Shared/DisplayTemplates/ folder)
A custom HTML helper
It is best to think of the SiteMap as a hierarchical database. You do little more than set up the data structure, and that data structure applies to every user of the application. Then you make per-request queries against that shared data (the SiteMap object) that can be filtered as desired.
Of course, if none of the above options cover your use case, please answer my open question as to why anyone would want to cache per user, as it pretty much defeats the purpose of making a site map.
Here is how you might set up a visibility provider to do your filtering in this case.
public class IsrealVisibilityProvider : SiteMapNodeVisibilityProviderBase
{
public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
{
return Constants.IsIsraeliIp;
}
}
Then remove the conditional logic from your DynamicNodeProvider and add the visibility provider to each node where it applies.
public class PagesDynamicNodeProvider
: DynamicNodeProviderBase
{
private const string IsraelOnlyItemsPageKey = "publications-in-hebrew";
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode siteMapNode)
{
using (var context = new Context())
{
var pages = context.Pages
.Include(p => p.Language)
.Where(p => p.IsPublished)
.OrderBy(p => p.SortOrder)
.ThenByDescending(p => p.PublishDate)
.ToArray();
foreach (var page in pages)
{
var node = new DynamicNode(
key: page.MenuKey,
parentKey: page.MenuParentKey,
title: page.MenuTitle,
description: page.Title,
controller: "Home",
action: "Page");
// Add the visibility provider to each node that has the condition you want to check
if (page.MenuKey == IsraelOnlyItemsPageKey)
{
node.VisibilityProvider = typeof(IsraelVisibilityProvider).AssemblyQualifiedName;
}
node.RouteValues.Add("id", page.PageId);
node.RouteValues.Add("pagetitle", page.MenuKey);
yield return node;
}
}
}
}
For a more complex visibility scheme, you might want to make a parent visibility provider that calls child visibility providers based on your own custom logic and then set the parent visibility provider as the default in web.config.
<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="MyNamespace.ParentVisibilityProvider, MyAssembly"/>
Or, using external DI, you would set the default value in the constructor of SiteMapNodeVisibilityProviderStrategy.
// Visibility Providers
this.For<ISiteMapNodeVisibilityProviderStrategy>().Use<SiteMapNodeVisibilityProviderStrategy>()
.Ctor<string>("defaultProviderName").Is("MyNamespace.ParentVisibilityProvider, MyAssembly");
I am not sure which version of MVCSiteMapProvider you are using, but the latest version is very extensible as it allows using internal/external DI(depenedency injection).
In your case it is easy to configure cache per session, by using sliding cache expiration set to session time out.
Link
// Setup cache
SmartInstance<CacheDetails> cacheDetails;
this.For<System.Runtime.Caching.ObjectCache>()
.Use(s => System.Runtime.Caching.MemoryCache.Default);
this.For<ICacheProvider<ISiteMap>>().Use<RuntimeCacheProvider<ISiteMap>>();
var cacheDependency =
this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
.Ctor<string>("fileName").Is(absoluteFileName);
cacheDetails =
this.For<ICacheDetails>().Use<CacheDetails>()
.Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
.Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
.Ctor<ICacheDependency>().Is(cacheDependency);
If you are using Older Version, you can try to implement GetCacheDescription method in IDynamicNodeProvider
public interface IDynamicNodeProvider
{
IEnumerable<DynamicNode> GetDynamicNodeCollection();
CacheDescription GetCacheDescription();
}
Here are the details of CacheDescription structure.
Link

How can I reuse a controller for multiple partial views?

I want to dynamically choose a partial view depending on what is sent to the controller, and have managed it but I feel that I am a) over complicating it and b) am unsure how to then easily get all page links. Each will be addressed in turn. Please ignore if I make a mistake below, it's from memory! At home it all works, I am after clarification on if there's a better way!
Firstly, what I've done:
I have a controller:
private Dictionary<int, string> pagesForFolder = new Dictionary<int, string>() {
... here I have my list of pages (index and pagename pairs)
}
public ActionResult Test(int id = -1)
{
try {
string pageName = "";
var result = pagesForFolder.TryGetValue(id, out pageName);
if(result)
string directiory = "~Views/folderA/_" + pageName +".cshtml";
return PartialView(directory);
}
catch(Exception ex) { ... }
return View();
}
Then in folderA I have all my partial views for that section of my website. I have many sections and 1 controller per section. However, I am thinking this won't scale out that great as I'll have to keep rebuilding every time I add a page. Would it be better to store the pages in the DB?
I think your current approach is quite reasonable. I think your problem as you mentioned is you have to rebuild your application whenever you add or remove new entry to the dictionary.
As a solution for this you can store your page names in db and retrieve when you needed.
Instead of having a hard coded dictionary try something like this...
In your controller..
public ActionResult Test(int id = -1)
{
try {
string pageName = GetYourPageFromDB(id);
if(!string.IsNullOrEmpty(pageName))
string directiory = "~Views/folderA/_" + pageName +".cshtml";
return PartialView(directory);
}
catch(Exception ex) { ... }
return View();
}
This may not be the exact answer try something like this...

POST method for RESTful API

I'm still a beginner for .NET, C#, RESTful API. Now, I'm learning the POST method in RESTful API. Here is the sample coding of POST method, but I still can't get the meaning of the coding. Can anyone provide me an explanation for each line of the coding in a clearly understand way? Or if you don't mind, can you explain it to me by using comment style? For example:
public string message; //to declare message for what use...`
public class MessageController : ApiController
{
// GET api/values
public class MessageToShow
{
public string message;
public string from;
}
public List<MessageToShow> Get()
{
var x = new cheeAdDCClf3rfFREntities();
var y=x.messages.Take(100);
List<MessageToShow> messageToShow = new List<MessageToShow>();
foreach (var xx in y)
{
MessageToShow m = new MessageToShow();
member me = x.members.FirstOrDefault(j => j.ID == xx.from);
if (me != null)
{
m.from = me.username;
m.message = xx.message1;
messageToShow.Add(m);
}
}
return messageToShow;
}
// POST api/values
public void Post(int memberid, dynamic value)
{
var x = new cheeAdDCClf3rfFREntities();
message m = new message();
m.ID = x.messages.Max(record => record.ID) + 1;
m.from = memberid;
m.message1 = value.value;
x.messages.Add(m);
x.SaveChanges();
}
}
}
I would be very appreciate if anyone would like to share me your knowledge on programming. Thank you so much!!! ^_^
It looks like you posted code for both a GET and POST method.
When I make a GET to your API:
public List<MessageToShow> Get()
{
// Looks like EntityFramework? Represents the items already in database
var x = new cheeAdDCClf3rfFREntities();
// Take to top 100 item already in the database
var y=x.messages.Take(100);
// Create a new list to hold the messages we will return
List<MessageToShow> messageToShow = new List<MessageToShow>();
// For each message in the 100 we just took
foreach (var xx in y)
{
MessageToShow m = new MessageToShow();
// Get the details of the member that send this message
member me = x.members.FirstOrDefault(j => j.ID == xx.from);
// If we found the member, create a message to show
// (populating the message and the username of the member
// who sent it)
if (me != null)
{
m.from = me.username;
m.message = xx.message1;
messageToShow.Add(m);
}
}
// Return the list of messages we just created to the caller of the API
return messageToShow;
}
When I POST to your API this is what happens:
public void Post(int memberid, dynamic value)
{
// Gets the items already in the database
var x = new cheeAdDCClf3rfFREntities();
// Create a new message object
message m = new message();
// Find the highest ID already in the database, then add 1. This is the
// ID for our new item
m.ID = x.messages.Max(record => record.ID) + 1;
// The 'from' property is set to the memberId that the user passed in the POST
m.from = memberid;
// The 'message' property is set to whatever dynamic value is passed in the POST
m.message1 = value.value;
// Add the message to the database
x.messages.Add(m);
x.SaveChanges();
}
To understand more about REST, you can read this:
A beginners Guide To HTTP and REST
The code you posted actually looks more like EntityFramework, which is a way of interacting with a database. It's not specific to APIs.
You can find our more about EF here:
EntityFramework

Web-API Get with object

I created a Web-API and i would like to get all routes with parameters BeginAddress (string), EndAddress(string), BegineDate (Datetime). I created a new Class SearchRoute with these properties.
I can do a normal Getwith an id or a string but how to do a Get by giving an object? Is this possible?
Would it be possible to do a post/put with an object and than ask for a return?
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(url + userid);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
List<Route> list = await SerializeService.Deserialize<List<Route>>(content);
return list;
}
return null;
}
Web API Function
public List<Route> GetAllByCity(SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}
Update:
If i do this, the Post doesn't work but if i create a new controller it works.
[HttpPost]
// POST api/route
public void Post([FromBody]Route route)
{
RouteDAO.Create(route);
}
// POST api/route
[HttpPost]
public List<Route> Post([FromBody]SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}
I prefer sticking with GET even when using a complex object as a parameter. If you are concerned about the length of the URI then remember that:
Prefixing the property names for simple like complex objects is not necessary because the Web API object binding can auto resolve based on property names alone.
The maximum allowed URL length is 2083 characters which is more than sufficient in most cases.
If you we take your example
public class SearchRoute {
public string BeginAddress {get;set;}
public string EndAddress {get;set;}
public DateTime BeginDate {get;set;}
}
[HttpGet]
public List<Route> Get([FromUri]SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}
Uri when searching on
BeginAddress = "Some beginning";
EndAddress = "Some ending"
BeginDate = "2016-01-01T16:40:00"
Resulting query string:
?BeginAddress=Some beginning&EndAddress=Some ending&BeginDate=2016-01-01T16:40:00
Again, the properties will auto resolve even without the object prefix/qualifier and populate the object instance.
Add a domain info to the URL maybe another 50 or so characters
Add a controller name maybe another 30 or so characters
Add the query string = 82 characters
Note that I am not taking into account resolving the special characters like spaces to Url escaped character sequence
Total ≈ 162 characters give or take
Not bad considering that the maximum allowed URL length is 2083 characters, so you have used up only 7% of what is possible in this simple example.
This would probably be the preferred way of doing it because it conforms to the RESTful API standard where GET calls/verbs do not alter data and POST calls/verbs do.
You can pass an object by using a complex type in the URI. You need to help Web API by using the correctly formatted Query String. This would be an example:
?SearchRoute.BeginAddress=TheAddressValue&SearchRoute.EndAddress=TheAddressValue
However, if your Query String starts to become too big, you might be modeling the interaction incorrectly.
Then, in the server you should let Web API know that it should look in the URI for the values:
public List<Route> GetAllByCity([FromUri]SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}

Categories