I have an MVC 4 Web API. Usually I want responses to return all properties, but there is one place I only want to return only non-null values. I can setup either behavior by setting the JsonSerializerSettings of the Formatters.JsonFormatter.SerializerSettings.NullValueHandling of the GlobalConfiguration.Configuration instance in the global file but I want to use both depending on the response. Is there an easy way to configure the request scope from within an API controller action?
By changing your controller action to return HttpResponseMessage you can get more control over how your content is returned for a particular action. e.g.
public HttpResponseMessage Get() {
var foo = new Foo();
var objectContent = new ObjectContent<Foo>(foo, new JsonFormatter()
{SerializerSettings.NullValueHandling = ???})
return new HttpResponseMessage() {Content = objectContent};
}
this would probably be easier to do on the client side with a dynamic language like javascript.
var keys = Object.keys(json);
for(var i = 0; i < keys.length; i++) {
var propertyName = keys[i];
if(json[propertyName] === undefined || v[propertyName] === null) {
json.remove(propertyName);
}
}
return json;
Related
I am using a WCF Data Services class that exposes an entity framework model via the OData protocol like so:
public class Service : EntityFrameworkDataService<MyEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
}
I consume this service through a service reference in a web solution. I am having problems including all the navigation properties for the entity. I cannot use the following syntax because I do not know what type of entity the user may be requesting:
I CANNOT USE
MyEntities.Customer.Expand("Address");
or
MyEntities.Customer.Include("Address");
What I am currently doing is building a URI string with the $expand=Entity1,Entity2 syntax and then executing that against my service as follows:
public static QueryOperationResponse<object> GetList(string entitySetName, params string[] preloads)
{
StringBuilder stringBuilder = new StringBuilder();
string queryString = string.Empty;
object result = null;
Uri dataAccessURI;
stringBuilder.Append(ServiceReferenceURI.AbsoluteUri);
stringBuilder.Append(entitySetName);
if (preloads != null)
{
for (int i = 0; i <= preloads.Length - 1; i++)
{
queryString = i == 0 ? "?$expand=" : ",";
stringBuilder.AppendFormat("{0}{1}", queryString, preloads[i]);
}
}
dataAccessURI = new Uri(stringBuilder.ToString());
try
{
result = TitanEntities.Execute<object>(dataAccessURI, "GET", true);
}
catch (Exception ex)
{
// log any errors to the console
WriteConsoleMessage(ex.Message, DataAccessEventType.Error);
}
return (QueryOperationResponse<object>)result;
resulting URI string is similar to this:
http://192.168.0.196/Service.svc/AliquotPreparation?$expand=Aliquot,AliquotPrepBatch,AnalysisPreparationMethod,Unit,Employee,Unit,PreparationMethod,State
To me this is a crappy implementation. It is all I could come up with right now though. The problem is, if there are A LOT of navigation properties the $expand command gets too long and the URI reaches it's character limit!
So how can I implement this through a service reference? I would greatly appreciate someone's help!!!
I have a working OData controller, which supports all the normal get/put etc.
What I want to do is pass a normal odata $filter string from the client, parse and execute the filter on the server and run some code on the resulting IEnumerable.
I've messed around with ODataQueryContext, ODataQueryOptions, FilterQueryOption etc, but not really got anywhere.
Does anyone have any working examples?
Edit: I've added my function skeleton, just need to fill in the blanks
public HttpResponseMessage GetJobs(string filter)
{
*** How to convert the filter into IQueryable<Job> ***
var queryable = ?????
var settings = new ODataQuerySettings();
var jobs = queryOptions.ApplyTo(querable, settings) as IQueryable<Job>;
CsvSerializer csvSerializer = new CsvSerializer();
string csv = csvSerializer.Serialise(jobs);
string fileName = string.Format("{0} Jobs.csv", filter);
return CreateCsvResponseMessage(csv, fileName);
}
I recently had a scenario where I needed this sort of feature as well. This is what I came up with.
private static IQueryable<T> ApplyODataFilter<T>(IQueryable<T> data, string filterString) where T : class
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<T>(typeof(T).Name);
ODataQueryContext context = new ODataQueryContext(builder.GetEdmModel(), typeof(T), new ODataPath());
ODataQueryOptionParser queryOptionParser = new ODataQueryOptionParser(
context.Model,
context.ElementType,
context.NavigationSource,
new Dictionary<string, string> { { "$filter", filterString } });
FilterQueryOption filter = new FilterQueryOption(filterString, context, queryOptionParser);
IQueryable query2 = filter.ApplyTo(data, new ODataQuerySettings());
return query2.Cast<T>();
}
Try using OData code generator to generate client side code. you can following the following blog:
How to use OData Client Code Generator to generate client-side proxy class
The for the filter, the following is an example:
var q2 = TestClientContext.CreateQuery<Type>("Accounts").Where(acct => acct.Birthday > new DateTimeOffset(new DateTime(2013, 10, 1)));
There are some sample code in the codeplex to show how to do query.
Check this:
https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v3/ODataQueryableSample/Program.cs
Update:
There is some sample code in the controller of the sample I gave you.
Write your code as below:
public IQueryable<Order> Get(ODataQueryOptions queryOptions)
{
if (queryOptions.Filter != null)
{
var settings = new ODataQuerySettings();
var filterResult = queryOptions.ApplyTo(OrderList.AsQueryable(), settings) as IQueryable<Order>;
// Use the filter result here.
}
}
Update 2:
You can get the raw string of the filter from ODataQueryOptions.
public IQueryable<Order> Get(ODataQueryOptions queryOptions)
{
string filterString = queryOptions.Filter.RawValue;
// Use the filterString
}
Update 3:
(Note: ODataProperties is an extension method in static class
System.Web.Http.OData.Extensions.HttpRequestMessageExtensions)
public HttpResponseMessage GetJobs(string filter)
{
var context = new ODataQueryContext(Request.ODataProperties().Model, typeof(Job));
var filterQueryOption = new FilterQueryOption(filter, context);
IQueryable<Job> queryable = GetAllJobs();
var settings = new ODataQuerySettings();
var jobs = filterQueryOption.ApplyTo(queryable, settings) as IQueryable<Job>;
CsvSerializer csvSerializer = new CsvSerializer();
string csv = csvSerializer.Serialise(jobs);
string fileName = string.Format("{0} Jobs.csv", filter);
return CreateCsvResponseMessage(csv, fileName);
}
I have a list of URLs that I obtained by querying Google Analytics data. I want to run each of these URLs through the MVC pipeline to get the ActionResult. The action result contains the view model from which I can extract some important information.
Based on the extensibility of MVC, I thought this would be easy. I thought I could mock up a HttpRequest using the string URL and pass it through the routing and controller. My end point would be invoking the action method which would return the ActionResult. I'm finding bits and pieces of what I need, but a lot of the methods are protected within the various classes and the documentation on them is pretty sparse.
I somehow want to reach in to the ControllerActionInvoker and get the result of the call to the protected function InvokeActionMethod.
First of all, Darin's answer got me started, but there's a lot more detail to the final solution, so I'm adding a separate answer. This one is complex, so bear with me.
There are 4 steps to getting the ViewResult from a URL:
Mock the RequestContext via the routing system (Darin's answer got me started on this).
Uri uri = new Uri(MyStringUrl);
var request = new HttpRequest(null, uri.Scheme + "://" + uri.Authority + uri.AbsolutePath, string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1));
var response = new HttpResponse(new StringWriter());
var context = new HttpContext(request, response);
var contextBase = new HttpContextWrapper(context);
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
// We shouldn't have to do this, but the way we are mocking the request doesn't seem to pass the querystring data through to the route data.
foreach (string key in request.QueryString.Keys)
{
if (!routeData.Values.ContainsKey(key))
{
routeData.Values.Add(key, request.QueryString[key]);
}
}
var requestContext = new System.Web.Routing.RequestContext(contextBase, routeData);
Subclass your controller. Add a public method that allows you to call the protected Execute(RequestContext) method.
public void MyExecute(System.Web.Routing.RequestContext requestContext)
{
this.Execute(requestContext);
}
In the same subclassed controller, Add a public event that hooks in to the protected OnActionExecuted event. This allows you to reach in a grab the ViewResult via the ActionExecutedContext.
public delegate void MyActionExecutedHandler(ActionExecutedContext filterContext);
public event MyActionExecutedHandler MyActionExecuted;
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (MyActionExecuted != null)
{
MyActionExecuted(filterContext);
}
}
Tie everything together by instantiating an instance of the new controller subclass, adding an event handler, and calling the new public execute method (passing in the mocked RequestContext). The event handler will give you access to the ViewResult.
using (MyCompany.Controllers.MyController c = new Controllers.MyController())
{
c.MyActionExecuted += GrabActionResult;
try
{
c.MyExecute(requestContext);
}
catch (Exception)
{
// Handle an exception.
}
}
and here's the event handler:
private void GrabActionResult(System.Web.Mvc.ActionExecutedContext context)
{
if (context.Result.GetType() == typeof(ViewResult))
{
ViewResult result = context.Result as ViewResult;
}
else if (context.Result.GetType() == typeof(RedirectToRouteResult))
{
// Handle.
}
else if (context.Result.GetType() == typeof(HttpNotFoundResult))
{
// Handle.
}
else
{
// Handle.
}
}
The difficulty here consists into parsing the url into its constituent controller and action. Here's how this could be done:
var url = "http://example.com/Home/Index";
var request = new HttpRequest(null, url, "");
var response = new HttpResponse(new StringWriter.Null);
var context = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var values = routeData.Values;
var controller = values["controller"];
var action = values["action"];
Now that you know the controller and the action you could use reflection to instantiate and execute it.
Try this:
object result = null;
Type controller = Type.GetType("MvcApplication4.Controllers.HomeController");
if (controller != null)
{
object controllerObj = Activator.CreateInstance(controller, null);
if (controller.GetMethod("ActionName") != null)
{
result = ((ViewResult)controller.GetMethod("ActionName").Invoke(controllerObj, null)).ViewData.Model;
}
}
I assumed normal routes are configured in the application and can be retrieved using regex or string operations. Following your discussion, I learned that you guys want to really follow through the MVC pipeline by digging into the framework by not using reflection or any hardcording techniques. However, I tried to search to minimize hardcoding by trying to match the url with the routes configured in the application by following this thread
How to determine if an arbitrary URL matches a defined route
Also, I came across other thread which creates httprequest to access routedata object but again reflection needs to be used for this.
String URL to RouteValueDictionary
Thanks Ben Mills, this got me started with my own problem. However I found that I didn't have to do 2, 3 or 4, by doing the following.
Uri uri = new Uri(MyStringUrl);
var absoluteUri = uri.Scheme + "://" + uri.Authority + uri.AbsolutePath;
var query = string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1);
var request = new HttpRequest(null, absoluteUri, query);
Getting access to the string writer is important.
var sw = new StringWriter();
var response = new HttpResponse(sw);
var context = new HttpContext(request, response);
var contextBase = new HttpContextWrapper(context);
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
If we assign the RouteData to the request context we can use the MVC pipeline as intended.
request.RequestContext.RouteData = routeData;
var controllerName = routeData.GetRequiredString("controller");
var factory = ControllerBuilder.Current.GetControllerFactory();
var contoller = factory.CreateController(request.RequestContext, controllerName);
controller.Execute(request.RequestContext);
var viewResult = sw.ToString(); // this is our view result.
factory.ReleaseController(controller);
sw.Dispose();
I hope this helps someone else wanting to achieve similar things.
I want to seamlessly keep state for all the anonymous visitors of the web site. For example to show hint only on the first visit and initially prevent "vote" button from appearing via-cookie analyzing java-script on the client (of cause with the second-level ip-based filtering on the server side).
How can I manage these cookies in the client browser. Ideally I'd like to use them as a dictionary that I can read and write from both on the server and on the client. If this is all a basic stuff, please just show how I can assign "hasVoted" bool value to the cookie of the user and read it off on the client and on server.
If anything is fundamentally wrong with my idea, please let me know =)
After they vote (which I assume happens in a POST), you'll want to set the cookie:
[HttpPost]
public ActionResult Vote()
{
HttpCookie hasVoted = new HttpCookie("hasVoted", true);
Request.Cookies.Add(hasVoted);
return RedirectToAction("Index");
}
And get it like this:
[HttpGet]
public ActionResult Index()
{
bool hasVoted = Request.Cookies["hasVoted"].Value;
return View();
}
If I was doing this I would take that hasVoted bool, wack it in a ViewModel and set the visibility of the button based on that value in your .aspx page. If you really want to though, you can read and write to cookies using javascript:
function createCookie(name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function eraseCookie(name) {
createCookie(name,"",-1);
}
Check this out http://www.quirksmode.org/js/cookies.html
The recommended approach for passing lists of values as a QueryString is
www.site.com/search?value=1&value=2&value=3&value=4
ASP.NET handles this well:
string value = QueryString.Get("value"); // returns "1,2,3,4"
But I can't figure out a way of passing these values by into RouteData. The obvious approach would be to add
int[] value = {1,2,3,4};
into the RouteData and have super smart MVC sort things out for me. Unfortunately MVC is dump when it comes to passing arrays into RouteData, it basically calls .ToString() adding value=int[] to my QueryString.
I tried adding the values to RouteValueDictionary (but being a dictionary can't handle this:)
RouteValueDictionary dict = new RouteValueDictionary();
dict.Add("value","1");
dict.Add("value","2"); // Throws Exception (Keys must be unique)
I could try passing values like this:
www.site.com/search?value=1,2,3,4
but this is Encoded to the URL as
www.site.com/search?value=1%2C2%2C3%2C4
I'm not sure if this is a bad thing or not,but it sure looks bad.
So, how do you pass lists of values as RouteData in ASP.NET MVC. Is there any way I can add to MVC to make it handle an int[]? Or is the underlying Dictionary-based data structure a show-stopper for passing lists of values easily to ASP.NET MVC?
I've come up with a solution to this myself by making a new class RouteDataList()
public class RouteDataList<T> : List<T>
{
public RouteDataList(IEnumerable<T> enumerable) : base(enumerable) { }
public RouteDataList() : base() { }
public override string ToString()
{
string output = "";
for (int i = 0; i < this.Count; i++)
{
output += i < this.Count - 1 ? this[i] + "-" : this[i].ToString();
}
return output;
}
public static List<Int32> ParseInts(string input)
{
if (String.IsNullOrEmpty(input)) return null;
List<Int32> parsedList = new List<int>();
string[] split = input.Split('-');
foreach (string s in split)
{
int value;
if(Int32.TryParse(s, out value)) parsedList.Add(value);
}
return parsedList;
}
}
Use as follows:
RouteDataList<Int32> valuelist = new RouteDataList<Int32>(){5,6,7,8};
RouteDataList<Int32> anothervaluelist = new RouteDataList<Int32>(){12,13,14,15};
Then Pass to any function that takes a RouteValueDictionary/Anonymous Type:
return RedirectToAction("View", "Browse", new {valuelist, anothervaluelist } );
// Produces http://www.site.com/browse/view?valuelist=5-6-7-8&anothervaluelist=12-13-14-15
// To Parse back to a list:
List<Int32> values = RouteDataList<Int32>.ParseInts(HttpContext.Current.Request.QueryString["valuelist"])