ServiceStack NullReferenceException - asp.net-mvc-4

I am new to servicestack and am really enjoying it, however I can not for the life of me figure of why this is occuring.
I have mapped it as ROUTES.Add<images>("/Images"); in the APPHOST.cs
I use Rest Console(chrome plugin) to test and POST the following JSON:
(I know it is not and actual base64 encoding).
Any thoughts would be appreciated.
// Testing JSON
{"base64Encoding":"asdasdasdasd"}
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using ServiceStack.ServiceInterface;
using RewoServiceLayer.RequestDTOs;
using System.Data.Entity;
using ServiceStack.ServiceInterface.ServiceModel;
using ServiceStack.Configuration;
using ServiceStack.Common;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.WebHost.Endpoints;
using System.IO;
using System.Web.Hosting;
namespace Blah.Services
{
public class ImageResponse
{
public images image { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
public class ImageRequest
{
public String base64Encoding;
}
public class ImageService : Service
{
//Give me something that looks like this:
//"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABG4...YII=";
public ImageResponse Post(ImageRequest request)
{
String base64EncodingImg = request.base64Encoding;
using (var db = new BlahDB())
{
//save to db
images imgToSave = new images();
//get base64
string base64 = base64EncodingImg.Substring(base64EncodingImg.IndexOf(',') + 1);
base64 = base64.Trim('\0');
byte[] imgBinData = Convert.FromBase64String(base64);
//get the
imgToSave.image_type = base64EncodingImg.Substring(0,base64EncodingImg.IndexOf(','));
db.images.Add(imgToSave);
db.SaveChanges();
//then save string contents to disk using ID
imgToSave.image_disk_loc = getPathFromImage(imgToSave);
writeByteArrToDisk(imgBinData, imgToSave.image_disk_loc);
db.SaveChanges();
ImageResponse imgResponse = new ImageResponse();
imgResponse.image = imgToSave;
return imgResponse;
}
}
private Boolean writeByteArrToDisk(byte[] toWrite, String path)
{
try
{
File.WriteAllBytes(path, toWrite);
return true;
}
catch (Exception e)
{
return false;
}
}
private String getAbsolutePathToImagesFolder()
{
return HostingEnvironment.MapPath(#"~/App_Data/UploadedImages");
}
private String getPathFromImage(images imgModel)
{
if (imgModel.image_disk_loc.IsNullOrEmpty())
{
return getAbsolutePathToImagesFolder() + imgModel.image_id;
}
else
{
return imgModel.image_disk_loc;
}
}
}
}
I am getting the following error and it does not hit the Post Method when I debug:
{
"responseStatus": {
"errorCode": "NullReferenceException",
"message": "Object reference not set to an instance of an object.",
"stackTrace": " at ServiceStack.WebHost.Endpoints.Utils.FilterAttributeCache.GetRequestFilterAttributes(Type requestDtoType)\r\n at ServiceStack.WebHost.Endpoints.EndpointHost.ApplyRequestFilters(IHttpRequest httpReq, IHttpResponse httpRes, Object requestDto)\r\n at ServiceStack.WebHost.Endpoints.RestHandler.ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, String operationName)"
}
}

I can't see your images class but I think you need to change
ROUTES.Add<images>("/Images");
To
ROUTES.Add<ImageRequest>("/Images")
since the Post on your ImageService is looking for a request of type ImageRequest.

Related

Asp.Net Core 3.0 Custom Control Validation is not working

I have created a custom dropdown as follows, which we can use the json file as data source. Control is rendering properly, but unobtrussive validations are not working. Kindly help me to identify what I have missed.
My Control
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace MyApp.UserControls
{
[HtmlTargetElement("json-select", Attributes = ForAttributeName + "," + ItemsAttributeName + ",class")]
public class JsonDropdownList : TagHelper
{
private const string ForAttributeName = "asp-for";
private const string ItemsAttributeName = "asp-itemsource";
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
[HtmlAttributeName(ItemsAttributeName)]
public string ItemSource { get; set; }
[HtmlAttributeName("class")]
public string ClassName { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.SuppressOutput();
output.Content.Clear();
output.Content.AppendHtml(this.GenerateDropDownList(ItemSource));
}
private IHtmlContent GenerateDropDownList(string path)
{
var items = this.ReadJson(path);
TagBuilder tb = new TagBuilder("select");
tb.MergeAttribute("id", For.Name);
tb.MergeAttribute("name", For.Name);
if (For.Metadata.IsRequired)
{
tb.Attributes.Add("data-val", "true");
tb.Attributes.Add("required", "required");
}
tb.AddCssClass(ClassName);
tb.InnerHtml.AppendHtml(this.GenerateDropDownListItems());
foreach (var item in items)
{
tb.InnerHtml.AppendHtml(this.GenerateDropDownListItems(item));
}
return tb;
}
private IHtmlContent GenerateDropDownListItems(SelectListItem item = null, bool selected = false)
{
TagBuilder tagBuilder = new TagBuilder("option");
if (item != null)
{
tagBuilder.MergeAttribute("value", item?.Value);
tagBuilder.InnerHtml.AppendHtml(item?.Text);
}
else
{
tagBuilder.InnerHtml.AppendHtml("--select--");
}
return tagBuilder;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public IEnumerable<SelectListItem> ReadJson(string JsonPath)
{
try
{
using (StreamReader sr = new StreamReader(JsonPath))
{
string json = sr.ReadToEnd();
List<SelectListItem> items = JsonConvert.DeserializeObject<List<SelectListItem>>(json);
return items;
}
}
catch (Exception ex)
{
return null;
}
}
}
}
I have used the same in cshtml as follows
<json-select asp-for="Title" asp-itemsource="#ViewBag.TitleJson" class="form-control">
</json-select>
<span asp-validation-for="Title" class="text-danger"></span>
ViewBag.TitleJson is nothing but my json file path.
my JSON:
[
{
"Text": "Mr",
"Value": "Mr"
},
{
"Text": "Mrs",
"Value": "Mrs"
},
{
"Text": "Ms",
"Value": "Ms"
}
]
Finally I found the answer as follows
private IHtmlContent GenerateDropDownListItems(SelectListItem item = null, bool selected = false)
{
TagBuilder tagBuilder = new TagBuilder("option");
if (item != null)
{
tagBuilder.MergeAttribute("value", item?.Value);
tagBuilder.InnerHtml.AppendHtml(item?.Text);
}
else
{
tagBuilder.MergeAttribute("value", string.Empty);
tagBuilder.InnerHtml.AppendHtml("--select--");
}
return tagBuilder;
}

Custom model binder with inheritance using Web API and RavenDB

I'm developing a simple web app where I need to bind all types implementing and interface of a specific type. My interface has one single property like this
public interface IContent {
string Id { get;set; }
}
a common class using this interface would look like this
public class Article : IContent {
public string Id { get;set; }
public string Heading { get;set; }
}
to be clean here the article class is just one of many different classes implementing IContent so therefor I need a generic way of storing and updating these types.
So in my controller I have the put method like this
public void Put(string id, [System.Web.Http.ModelBinding.ModelBinder(typeof(ContentModelBinder))] IContent value)
{
// Store the updated object in ravendb
}
and the ContentBinder
public class ContentModelBinder : System.Web.Http.ModelBinding.IModelBinder {
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
actionContext.ControllerContext.Request.Content.ReadAsAsync<Article>().ContinueWith(task =>
{
Article model = task.Result;
bindingContext.Model = model;
});
return true;
}
}
The code above does not work because it does not seem to get hold of the Heading property even though if I use the default model binder it binds the Heading correctly.
So, in the BindModel method I guess I need to load the correct object from ravendb based on the Id and then update the complex object using some kind of default model binder or so? This is where I need some help.
Marcus, following is an example which would work fine for both Json and Xml formatter.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
// this is ignored
public DateTime DateOfBirth { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}
I used #kiran-challa solution and added TypeNameHandling on Json media type formatter's SerializerSettings.

WebApi Model Binding For Inherited Types

I'm looking to handle model binding for an inherited type in WebApi, and what I'm really looking to do is to handle the binding using the default model binding (other than selecting the type where it's unable to do so), but I'm missing something fundamental.
So say I have the types:
public abstract class ModuleVM
{
public abstract ModuleType ModuleType { get; }
}
public class ConcreteVM : ModuleVM
{
}
Using an MVC controller, I would do something like this:
public class ModuleMvcBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType == typeof(ModuleVM))
{
// Just hardcoding the type for simplicity
Type instantiationType = typeof(ConcreteVM);
var obj = Activator.CreateInstance(instantiationType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
bindingContext.ModelMetadata.Model = obj;
return obj;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class ModuleMvcBinderAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new ModuleMvcBinder();
}
}
Then use the attribute on the controller and all is well, and I'm leveraging the DefaultModelBinder for the real work and I'm essentially just providing the correct object instantiation.
So how do I do the same for the WebApi version?
If I use a custom model binder (e.g. Error implementing a Custom Model Binder in Asp.Net Web API), my problem is (I believe) that in the BindModel method I haven't found a good way to use the "standard" http binding once I instantiate the object. I can do it specifically for JSON (Deserialising Json to derived types in Asp.Net Web API) or XML (Getting my Custom Model bound to my POST controller) as suggested in other posts, but it seems to me that's defeating the point since web api should be seperating that, and is - it just doesn't know how to determine the type. (All concrete types naturally are handled just fine.)
Am I overlooking something obvious I should be directing the BindModel call to after instantiating the object?
Following is an example where I have inheritance in my types and after some settings (like decorating with KnownType attributes, required by Xml formatter's datacontractserializer) and TypeNameHandling setting on Json formatter, we can expect consistent behavior across both xml/json requests.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}

MVC Web Api returning serialized response instead of css

I am having an issue returning css from a web api controller. The code takes a request for a css file and returns it after reading it from the database.
The problem is that the web api code seems to be serializing the response and returning that instead of the css itself.
Here you can see a link tag that the browser is sending to the server which should return css. You can also see that the response looks like a serialization of my css instead of just the css string.
My request and response headers:
My controller looks like this:
public HttpResponseMessage Get(string fileName, string siteId, int id)
{
var fileData = ReadSomeCssFromTheDatabase();
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(fileData);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/css");
result.Headers.CacheControl = new CacheControlHeaderValue();
result.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
result.Headers.CacheControl.MustRevalidate = true;
return result;
}
There is a “text/css” formatter installed that is being created but not being hit for some reason.
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var taskCompletionSource = new TaskCompletionSource<object>();
try
{
var memoryStream = new MemoryStream();
readStream.CopyTo(memoryStream);
var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
taskCompletionSource.SetResult(s);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
return taskCompletionSource.Task;
}
public override bool CanReadType(Type type)
{
return type == typeof(string);
}
public override bool CanWriteType(Type type)
{
return false;
}
}
What am I doing wrong?
Your formatter would not be hit because you are not going through content negotiation process (as you are returning HttpResponseMessage in your action...you could use Request.CreateResponse<> to make conneg process run)
You are trying to 'write' the css content right?...but i see that CanWriteType is returning 'false' and also you seem to be overriding ReadFromStreamAsync instead of WriteToStreamAsync?
An example of how you could do(from what i understood about the above scenario):
public class DownloadFileInfo
{
public string FileName { get; set; }
public string SiteId { get; set; }
public int Id { get; set; }
}
public HttpResponseMessage Get([FromUri]DownloadFileInfo info)
{
// validate the input
//Request.CreateResponse<> would run content negotiation and get the appropriate formatter
//if you are asking for text/css in Accept header OR if your uri ends with .css extension, you should see your css formatter getting picked up.
HttpResponseMessage response = Request.CreateResponse<DownloadFileInfo>(HttpStatusCode.OK, info);
response.Headers.CacheControl = new CacheControlHeaderValue();
response.Headers.CacheControl.MaxAge = TimeSpan.FromHours(0);
response.Headers.CacheControl.MustRevalidate = true;
return response;
}
public class CssFormatter : MediaTypeFormatter
{
public CssFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/css"));
}
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
return type == typeof(DownloadFileInfo);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
//use the 'value' having DownloadFileInfo object to get the details from the database.
// Fead from database and if you can get it as a Stream, then you just need to copy it to the 'writeStream'
}
}

Creating WCF Data Service operation with in-memory objects

I am trying to create a WCF DataService using in-memory object graph. This means that the backend is not an Entity Framework store, but a bunch of objects that reside in memory.
I am trying to create a service operation called GetUsersByName that has a single parameter for name and returns the matching users as an IQueryable collection.
I followed the documentation and added the access rules for this operation
config.SetServiceOperationAccessRule("GetUsersByName", ServiceOperationRights.All);
But when the SetServiceOperationAccessRule method is called I receive an exception on the client:
System.AggregateException was unhandled.
Here is the full code for my console application
using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.Data.Services;
using System.Data.Services.Common;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Net;
using System.IO;
namespace WCF_OData
{
class Program
{
static void Main(string[] args)
{
string serviceAddress = "http://localhost:8080";
Uri[] uriArray = { new Uri(serviceAddress) };
Type serviceType = typeof(UserDataService);
using (var host = new DataServiceHost(serviceType, uriArray)) {
host.Open();
var client = new HttpClient() { BaseAddress = new Uri(serviceAddress) };
Console.WriteLine("Client received: {0}", client.GetStringAsync("Users?$format=json").Result);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:8080");
request.Method = "GET";
request.Accept = #"application/json";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
Console.WriteLine(response.StatusCode);
Console.WriteLine(response.ContentType);
Console.WriteLine((new StreamReader(response.GetResponseStream())).ReadToEnd());
}
Console.WriteLine("Press any key to stop service");
Console.ReadKey();
}
}
}
[EnableJsonSupport]
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class UserDataService : DataService<UserService> {
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Users", EntitySetRights.All);
config.SetServiceOperationAccessRule("GetUsersByName", ServiceOperationRights.All);
config.UseVerboseErrors = true;
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
public class UserService
{
private List<User> _List = new List<User>();
public UserService()
{
_List.Add(new User() { ID = 1, UserName = "John Doe" });
_List.Add(new User() { ID = 2, UserName = "Jane Doe" });
}
public IQueryable<User> Users
{
get
{
HttpContext x = HttpContext.Current;
return _List.AsQueryable<User>();
}
}
[OperationContract]
[WebGet(UriTemplate="GetUsersByName")]
public IQueryable<User> GetUsersByName(string name)
{
return new List<User>().AsQueryable();
}
}
[DataServiceKey("ID")]
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
}
}
It looks like there are a few things going on here, so this may take a couple of iterations to work through. The first problem that should be fixed is the service operation. Service operations need to be declared on the class that inherits from DataService: "Service operations are methods added to the data service class that derives from DataService". Here's a sample:
using System.Data.Entity;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace Scratch.Web
{
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ScratchService : DataService<ScratchEntityFrameworkContext>
{
static ScratchService()
{
Database.SetInitializer(new ScratchEntityFrameworkContextInitializer());
}
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
config.UseVerboseErrors = true;
}
[WebGet]
public IQueryable<Product> FuzzySearch(string idStartsWith)
{
var context = new ScratchEntityFrameworkContext();
return context.Products.ToList().Where(p => p.ID.ToString().StartsWith(idStartsWith)).AsQueryable();
}
}
}
You should then be able to call your service operation from a browser, with a URL format similar to the following: http://localhost:59803/ScratchService.svc/FuzzySearch()?idStartsWith='1'
Can we start by trying to get this functional in a browser and then see whether the AggregateException still happens?