Web API httpget with many parameters - asp.net

I am trying to create my first REST service using WEB API to replace some of my postbacks in a web forms asp.net project. In the web forms project, when I browse to a new web page, I always get an ASP.net Application variable and a querystring value that helps me determine which database to connect to. In this old app, it connects to several different databases that all have the same schema and database objects but the data is different in each database
I am not sure the best way to pass these variables to a REST Service or if they should be part of the route or some other method.
So in a REST method like the one below
// GET api/<controller>/5
public string GetCategoryByID(int id)
{
return "value";
}
I can get the category id and pass that to my database layer, but I also need the two variables mentioned above. I will need to obtain these variables in every call to my REST api in order to access the appropriate database. Should I use something like the following:
// GET api/<controller>/5
public string GetCategoryByID(int id, string applicationEnvironment, string organization)
{
return "value";
}
Or should they be part of the route with something like this:
api/{appEnvironment}/{organization}/{controller}/{id}
This seems like a simple problem, but I am having trouble figuring out a solution.

I ended up passing extra parameters with my httpget call. I will probably follow this pattern unless I get some additional feedback.
[HttpGet]
public Company[] GetProgramCompanies(int id, [FromUri] string org, [FromUri] string appEnvir)
{
DataLayer dataAccess = new DataLayer(Utilities.GetConnectionString(org, appEnvir));
IEnumerable<BudgetProgramCompanyListing> companies = dataAccess.GetProgramCompaniesListing(id).OrderBy(o => o.Company_Name);
Company[] returnComps = new Company[companies.Count()];
int count = 0;
foreach (BudgetProgramCompanyListing bpc in companies)
{
returnComps[count] = new Company
{
id = bpc.Company_ID,
name = bpc.Company_Name
};
count++;
}
return returnComps;
}
Calling the above service with this url:
api/programcompanies/6?org=SDSRT&appEnvir=GGGQWRT

In .Net core 1.1 you can specify more parameters in HttGet attribute like this:
[HttpGet("{appEnvironment}/{organization}/{controller}/{id}")]
It may work in other .Net versions too.

I used to follow the below two method to pass multiple parameter in HttpGet
public HttpResponseMessage Get(int id,[FromUri]int DeptID)
{
EmpEntity = new EmpDBEntities();
var entity = EmpEntity.USP_GET_EMPINFO(id, DeptID).ToList();
if(entity.Count()!=0)
{
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Employee With ID=" + id.ToString() + " Notfound");
}
}
and the webapi url will be http://localhost:1384/api/emps?id=1&DeptID=1
in the above methode USP_GET_EMPINFO is the stored procedure with two parameters.
in second method we can use the class with [FromUri] to pass multiple parameter.
the code snippet is as below
public HttpResponseMessage Get(int id,[FromUri]Employee emp)
{
EmpEntity = new EmpDBEntities();
var entity = EmpEntity.USP_GET_EMPINFO(id,emp.DEPTID).ToList();
if(entity.Count()!=0)
{
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Employee With ID=" + id.ToString() + " Notfound");
}
}
and the webapi url will be http://localhost:1384/api/emps?id=1&DEPTID=1
here the DEPTID is one of the property of the class. we can add multiple parameters separated with & in the url

You could also define a model and send that with the request and bind it to a variable in your api function using [FromBody].
Something like:
[HttpGet]
public Company[] GetProgramCompanies([FromBody] YourModel model) { ... }
As explained here Model binding in Asp.Net Core

Related

Can I disable model binding and use the raw request body in an action in dotnet core?

I want to setup an endpoint for testing webhooks from third parties. Their documentation is uniformly poor and there is no way ahead of time to tell exactly what I will be getting. What I've done is setup an ApiController that will just take a request and add a row to a table with what they are sending. This lets me at least verify they are calling the webhook, and to see the data so I can program to it.
// ANY api/webook/*
[Route("{*path}")]
public ActionResult Any(string path)
{
string method = Request.Method;
string name = "path";
string apiUrl = Request.Path;
string apiQuery = Request.QueryString.ToString();
string apiHeaders = JsonConvert.SerializeObject(Request.Headers);
string apiBody = null;
using (StreamReader reader = new StreamReader(Request.Body))
{
apiBody = reader.ReadToEnd();
}
Add(method, name, apiUrl, apiQuery, apiHeaders, apiBody);
return new JsonResult(new { }, JsonSettings.Default);
}
This works great, except for this new webhook I am usign that posts as form data so some middleware is reading the body and it ends up null in my code. Is there any way to disable the model processing so I can get at the request body?
You could actually use model binding to your advantage and skip all that stream reading, using the FromBody attribute. Try this:
[Route("{*path}")]
[HttpPost]
public ActionResult Any(string path, [FromBody] string apiBody)

Consuming Asp.Net Core api locally

I don't know if my google skills are diminishing or what but I can't seem to figure out how to consume a local api. This may be best explained with sample code...
So I have a simple api
public class FooApiController : Controller
{
public IActionResult GetFoo(int id)
{
if (id == 0)
return BadRequest();
var data = ... do db access
return Ok(data);
}
}
and a view controller
public class FooController : Controller
{
public IActionResult Foo()
{
var api = new FooApiController();
var data = api.GetFoo(1);
ViewBag.Data = data;
return View();
}
}
So in the above view controller I call the api to get the data needed. However, being that the api controller returns an IActionResult, ViewBad.Data ends up being an IActionResult object. So how do I change the above to check the StatusCode of the api call, handle errors if need be, and if not... put just the data into the ViewBag, instead of the entire result object.
Every sample I have found seems to have the view controller return a view that then uses an ajax call to get the data. While I understand and could easily do that, I don't like the idea of making 2 round trips to the server when I don't need to.
You are doing it wrong.
If you want to reuse the code among multiple controllers, then it is better to move it from the GetFoo method and put it into a shared class and access it from everywhere else.
If you want to call it from a view through REST, then call it using $.ajax
ex:
$.ajax('FooApi/GetFoo/5',function(data){alert(data);});
If you want to access it from another C# client, then use the HttpClient class, ex:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("api/FooApi/GetFoo", 3);
response.EnsureSuccessStatusCode();

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.

asp.net mvc global variables without cookies and session[""]

Is there any method for storing global variables without using cookies or session[""] in asp.net mvc ?
I know that cookies and session[""] have some disadvantages and I want to use the best method if exit.
If they are indeed global variables, you should implement the singleton pattern and have an Instance globally accessible that holds your variables.
Here is a simple example:
public sealed class Settings
{
private static Settings instance = null;
static readonly object padlock = new object();
// initialize your variables here. You can read from database for example
Settings()
{
this.prop1 = "prop1";
this.prop2 = "prop2";
}
public static Settings Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new Settings();
}
return instance;
}
}
}
// declare your global variables here
public string prop1 { get; set; }
public string prop2 { get; set; }
}
The you can use them in your code like this:
var globalvar1 = Settings.Instance.prop1;
This class with its variables will be initialized only once (when the application starts) and it will be available in your application globally.
Basically you have following options:
Cookies - valid as long as you set, must be allowed by client's browser, can be deleted by user, stored on user's PC.
Session - valid for all requests, not for a single redirect, stored on server.
ViewData - after redirect it's cleared (lives only during single request).
TempData - it's useful for passing short messages to view, after reading a value it's deleted.
ViewBag - is available only during the current request, if redirection occurs then it’s value becomes null, is dynamic so you don't have intellisense and errors may occur only in runtime.
Here - http://www.dotnet-tricks.com/Tutorial/mvc/9KHW190712-ViewData-vs-ViewBag-vs-TempData-vs-Session.html - you can find fantastic article which describes them.
Sure: HttpContextBase.Application (no expiration) or HttpContextBase.Cache (with expiration). You can access the HttpContextBase instance through the HttpContext property of the Controller class.
So... HACK ALERT... There is no good way to do an MVC 5 or 6 web app using session variables that I have found (yet). MVC doesn't support Session variables or Cookies, which are implemented via session variables. Global variables will be set for ALL users, which is not how Session variables work.
However, you can store "session variables" based on the User.Identity.Name or the underlying User.Identity.Claims.AspNet.Identity.SecurityStamp into a database along with a timestamp and viola! You have implemented primitive session variables. I had a very specific need to save two weeks of programming by not interfering with the GUI that our user interface specialist had written. So I returned NoContent() instead of the normal View() and I saved my hacky session variable based on the user's login name.
Am I recommending this for most situations? No. You can use ViewBag or return View(model) and it will work just fine. But if you need to save session variables in MVC for whatever reason, this code works. The code below is in production and works.
To retrieve the data...
string GUID = merchdata.GetGUIDbyIdentityName(User.Identity.Name);
internal string GetGUIDbyIdentityName(string name)
{
string retval = string.Empty;
try
{
using (var con = new SqlConnection(Common.DB_CONNECTION_STRING_BOARDING))
{
con.Open();
using (var command = new SqlCommand("select GUID from SessionVariablesByIdentityName md where md.IdentityName = '" + name + "' and LastSaved > getdate() - 1", con))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
retval = reader["GUID"].ToString();
}
}
}
}
}
catch (Exception ex)
{
}
return retval;
}
To save the data...
merchdata.SetGUIDbyIdentityName(User.Identity.Name, returnedGUID);
internal void SetGUIDbyIdentityName(string name, string returnedGUID)
{
RunSQL("exec CRUDSessionVariablesByIdentityName #GUID='" + returnedGUID + "', #IdentityName = '" + name + "'");
}
internal void RunParameterizedSQL(SqlConnection cn, SqlCommand cmd, object sqlStr)
{
string retval = string.Empty;
try
{
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
}
BTW: The SQL table (named SessionVariablesByIdentityName here) is fairly straightforward and can store lots of other things too. I have a LastSaved datetime field in there so I don't bother retrieving old data from yesterday. For example.

Validating a field based on a different database table / entity

I am writing an MVC 4 application, and using Entity Framework 4.1. I have a validation question which I cannot seem to find the answer to.
Essentially, I have an Entity (object) called "Product" which contains a field "Name", which must follow strict naming conventions which are defined in a separate Entity called "NamingConvention". When the user enters a value, the system needs to check it against the rules established in the NamingConvention entity, and return an error if need be.
Where should this validation be done, and how? I need to check the NamingConvention entity when doing the validation, which means I would need a database context since I'm referencing a different entity. Is there any validation method which won't require me to create a new context? I was thinking of doing the validation in the Controller, since it already creates a data context, but this doesn't seem like the right place to do it.
Thanks for any help!
I have done things like this using a JQuery post (ajax) call from the webpage where the name is being entered. You then post (the value of name) to a method on your controller which can return a JSON value that contains a flag saying if the validation passed and also a message that you want to return to your user. For example :
Javascript in webpage :
$("#name").change(function () {
var nameVal = $(this).val();
$.post(getRoot() + "/NameController/ValidateName", { name: nameVal },
function (data) {
if (data.valid == "true") {
alert("A valid name was chosen");
} else
{
alert(data.message);
}
}, "json");
});
Controller (NameController) Code :
[HttpPost]
public ActionResult ValidateName(string name)
{
// actual validation carried out in a static utility class (Utils.IsNameValid)
// if you are loading the same validation rules from your table each time
// consider caching the data in the application cache or a static List.
bool nameIsValid = Utils.IsNameValid(name, out string ErrorMessage);
JsonResult result = new JsonResult();
result.Data = new { valid = (nameIsValid "true" : "false"), message = ErrorMessage };
return result;
}
I'm using EF 5 but believe you can use this method ... apologies in advance if I'm misleading you with this answer.
You could do the validation within your context (or a context decorator)
public override int SaveChanges()
{
var products = this.GetChangedProducts();
foreach (var product in products)
{
this.ValidateName(product);
}
return base.SaveChanges();
}
private IEnumerable<Product> GetChangedProducts()
{
return (
from entry in _context.ChangeTracker.Entries()
where entry.State != EntityState.Unchanged
select entry.Entity)
.OfType<Product>();
}
private void ValidateName(Product product)
{
//validate here
}

Resources