I just started tinkering with the WCF REST Starter Kit, I watched the screencasts and... I suspect I'm just being stupid. :) Out of the box (with the provided templates) I can create REST services for a singleton resource or a collection of a resource. But what about support for different kinds of resources in the same project? In example if I have Books, I also want to have Authors and Publishers. With the provided templates I don't see an easy way to accomplish this. Creating a service (and thus a VS project) for each resource sounds ridiculous to me. I need to support multiple resource types in a single service so that they can be accessed under a similar URL, so that the user has only to replace the last part, like http://service/books to get all books and http://service/authors/32 to get a particular author.
Can anyone shed some light on this? I know this can be created with a plain WCF service, but the Starter Kit has all the boilerplate in place already, so why not use it? How would one approach a template generated project to add support for different resource types?
Thanks.
I think your overthinking the WCF REST Starter Kit. Try to think of the WCF REST Starter Kit as just a WCF Service that's configured for easy setup in the http environment. The default templates that are setup for the WCF REST Starter Kit are meant to be used as a sample. You will have to just create your own signature or adapt theirs to meet your needs.
The key parts that you'll want to focus on are the code in the .svc file (you can't access it double clicking, you'll have to choose open with) and the [ServiceContract] interfaces.
Modify the [ServiceContract] interface in the code behind provided to look just like it would for a regular WCF Service.
Here is a sample of an ATOM Pub Feed modified for your needs
[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceContract]
public partial class LibraryFeed
{
public LibraryFeed()
{
}
/// <summary>
/// Returns an Atom feed.
/// </summary>
/// <returns>Atom feed in response to a HTTP GET request at URLs conforming to the URI template of the WebGetAttribute.</returns>
[WebHelp(Comment = "Gets a list of Books.")]
[WebGet(UriTemplate = "/books/?numItems={numItems}")]
[OperationContract]
public Atom10FeedFormatter GetBooks(int numItems)
{
var books = GetBooks();
List<SyndicationItem> items = GetItems(books, numItems);
SyndicationFeed feed = CreateFeed(items);
WebOperationContext.Current.OutgoingResponse.ContentType = ContentTypes.Atom;
return feed.GetAtom10Formatter();
}
/// <summary>
/// Returns an Atom feed.
/// </summary>
/// <returns>Atom feed in response to a HTTP GET request at URLs conforming to the URI template of the WebGetAttribute.</returns>
[WebHelp(Comment = "Gets a single author.")]
[WebGet(UriTemplate = "/authors/?numItems={numItems}")]
[OperationContract]
public Atom10FeedFormatter GetAuthors(int numItems)
{
var authors = GetAuthors();
List<SyndicationItem> items = GetItems(authors, numItems);
SyndicationFeed feed = CreateFeed(items);
WebOperationContext.Current.OutgoingResponse.ContentType = ContentTypes.Atom;
return feed.GetAtom10Formatter();
}
/// <summary>
/// Returns an Atom feed.
/// </summary>
/// <returns>Atom feed in response to a HTTP GET request at URLs conforming to the URI template of the WebGetAttribute.</returns>
[WebHelp(Comment = "Gets a list of Authors.")]
[WebGet(UriTemplate = "/authors/{id}")]
[OperationContract]
public Atom10FeedFormatter GetAuthor(string id)
{
var author = GetSingleAuthor(id);
WebOperationContext.Current.OutgoingResponse.ContentType = ContentTypes.Atom;
return GetItem(author.GetAtom10Formatter());
}
}
Related
I have to provide some read endpoints for our EF6 entities on an ASP.NET API that conform to the OData specification. Entity retrieval works well based upon functions that accept a System.Web.Http.OData.Query.ODataQueryOptions<TEntity> instance.
Now, according to the docs, that implementation of OData does not support $count.
We would, however, like to offer at least the capability to retrieve the total count of a (filtered) data set as shown in the docs, like (by slightly combining several of those samples):
http://host/service/Products/$count($filter=Price gt 5.00)
(Based on the spec, I understand that this should be a valid, specification-conformant OData query for the number of products whose price is greater than 5¤. Please correct me if I'm wrong.)
Now, retrieving the count based on the IQueryable returned from ODataQuerySettings.ApplyTo is trivial. So is capturing requests to this route:
[Route("$count({queryOptions})")]
public int Count(ODataQueryOptions<ProductEntity> queryOptions)
The only bit that is missing is that the queryOptions portion of the route should be parsed into the ODataQueryOptions<ProductEntity> instance. On other OData endpoints, this works without any further ado. However, even when I call this endpoint with a $filter, all I am getting is an "empty" (i.e. initialized to default values) ODataQueryOptions<ProductEntity> instance with no filter set.
Alternatively, I can declare my web API endpoint like this:
[Route("$count({queryOptions})")]
public int Count(string rawQueryOptions)
Within this method, rawQueryOptions contains the query options that I wish to pass to OData, that is, parse to populate an ODataQueryOptions<ProductEntity> instance.
This must be very straightforward as the very same thing happens for any other OData endpoint. For a comparison:
[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)
This works; the query options are populated as expected, unlike it is the case with my above endpoint.
How can I populate my OData query options instance based on the string extracted from my route?
Some more things I have tried:
Applied [FromUri] to the queryOptions parameter.
Applied [ODataQueryParameterBinding] to the queryOptions parameter.
Although the syntax is a little bit different to your request, the .Net OData Implementation has the support you need OOTB, if you're asking this question at all, that indicates that you are trying to add OData support to your standard API.
Given that you have EF6 already on an ASP.Net API... Why not just expose the OData controllers on another route? In this way you get the full implementation of query support without ever needing to parse the QueryOptions at all.
Updated
If adding new controllers in a separate route is not suitable you can easily upgrade your existing ApiControllers and Implement OData routes in place of the existing ones.
ODataController inherits from ApiController but includes some helper methods that simplify working with OData response conventions, so upgrading in place is generally non-breaking.
As an example, the following is the only controller code that is needed to allow all the supported OData Query Options to return data from an EF DbSet, this includes full support for $select, $expand, $filter, $apply and even $count across a nested $filter.
public partial class ResidentsController : ODataController
{
protected MyEF.Context db = new MyEF.Context();
[EnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<MyEF.Resident> options)
{
return Ok(db.Residents);
}
}
The magic that allows this is not the ODataController itself, the EnableQueryAttribute parses/translates the QueryOptions and applies this to the Linq to Entities expression that is returned from the method.
The final component to make this work is to register the routes, this is a little bit more involved than standard API because you need to define the EdmModel first, but in doing so we never need to parse the incoming query parameters.
a minimal example to configure the model and routes for the above controller might look like this:
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Resident>("Residents");
IEdmModel model = builder.GetEdmModel();
// To enable $select and $filter on all fields by default
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
// can also be configured like this
config.SetDefaultQuerySettings(new Microsoft.AspNet.OData.Query.DefaultQuerySettings()
{
EnableCount = true,
EnableExpand = true,
EnableFilter = true,
EnableOrderBy = true,
EnableSelect = true,
MaxTop = null
});
// Map the routes from the model using OData Conventions
config.MapODataServiceRoute("odata", "odata", model);
}
How to Configure the $count syntax you desire
although your expected syntax for count of filtered collections is not supported OOTB, the syntax that is supported is very close, so you could easily manipulate the query with a URL re-write module
Your expected syntax:
http://host/service/Products/$count($filter=Price gt 5.00)
.Net Supported syntax
http://host/service/Products/$count?$filter=Price gt 5.00
OwinMiddleware:
/// <summary>
/// Rewrite incoming OData requests that are implemented differently in the .Net pipeline
/// </summary>
public class ODataConventionUrlRewriter : OwinMiddleware
{
private static readonly PathString CountUrlSegments = PathString.FromUriComponent("/$count");
public ODataConventionUrlRewriter(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
// OData spec says this should work: http://host/service/Products/$count($filter=Price gt 5.00)
// But in .Net the filter needs to be in the query: http://host/service/Products/$count?$filter=Price gt 5.00
var regex = new System.Text.RegularExpressions.Regex(#"\/\$count\((.+)\)$");
var match = regex.Match(context.Request.Path.Value);
if(match != null && match.Success)
{
// So move the $filter expression to a query option
// We have to use redirect here, we can't affect the query inflight
context.Response.Redirect($"{context.Request.Uri.GetLeftPart(UriPartial.Authority)}{regex.Replace(context.Request.Path.Value, "/$count")}?{match.Groups[1].Value}");
}
else
await Next.Invoke(context);
}
}
Add to Startup.cs, before registering OData routes
app.Use(typeof(ODataConventionUrlRewriter));
I would like to know what is the best practice with ServiceStack registering routes.
Define routes using Routes.Add in AppHost
Define routes using RouteAttiribute decorator on DTO's
I have read some where that doing it in AppHost is preferable because it de-couples route definition from DTO's. But most of the examples I see are using the decorator pattern.
So which is better? Are there any arguments to use one over the other?
Decorating your DTOs with route attributes has become the best practice. The release of SerivceStack's "new API" has indicated the many benefits of this but most importantly it promotes a more "succinct, typed, end-to-end client API".
By using the Route attributes in addition to marking your DTOs with one of the IReturn, IReturn<T> or IReturnVoid interfaces you can use the full features now offered by ServiceStack.
Advantages
You can dynamically discover urls to consume endpoints based on the the DTOs alone.
Your consuming service client code (if c#) becomes more strongly typed and it becomes trivial to change existing routes/DTOs
Keeps the AppHost clean and slim. I’m a believe that the less code in the AppHost the better.
code can become more testable
works alongside adding routes in AppHost (you don't have to choose just one)
and best of all it all just works!
For the given DTO you can use the ToUrl extension method to get the generate the route by discovery the attributes.
[Route("/route/{Id}/{Name}")]
public class ExampleDTO : IReturn
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
var url = new ExampleDTO() { Id = 1, Name = "Test", Value = "Foo" }.ToUrl("GET");
// generates /route/1/Test?value=Foo
The ServiceClients will do all this under the hood. Your consuming code can become:
ExampleDTO response = new JsonServiceClient(“http://api.com/)
.Get(new ExampleDTO() { Id = 1, Name = "Test", Value = "Foo" });
What about decoupling?
I would argue that there is no big need to decouple the DTOs from the Routes. I want my fellow developers to know that the Request should not be changed without changing the route. I have seen a lot of runtime errors because I forget to update the route in the AppHost after changing a DTO.
I have an ODataController that is returning a PageResult.
Api Example:
public PageResult<Customer> Get(ODataQueryOptions options) {
// cut some stuff out...
PageResult<Customer> result = new PageResult<Customer>(
searchResults as IEnumerable<Customer>,
Request.GetNextPageLink(),
Request.GetInlineCount());
return result;
When I debug this, it seems to be fine and have a PageResult class built up correctly to return. On the Web side..
Web Example
using (var client = new HttpClient()) {
client.BaseAddress = new Uri(testURL);
string searchUrl = "api/customer?$top=1&$skip=0";
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose"));
HttpResponseMessage response = client.GetAsync(searchUrl).Result;
The response is a StatusCode 406, with a reason phrase stating the content was not acceptable. It also does this if I define a new MediaTypeWithQualityHeaderValue("application/json").
What do I need to change so that I successfully consume this Api in the controller before passing it on to the view?
I think you are missing the first two steps of building an OData service. ODataController, as the name says, only works with OData routes. You need to build an EDM model representing your OData service, and, add an OData route exposing that EDM model. Refer to this official documentation and blog post for details on how to build OData services.
I am building an ASP.NET Web API application that returns an Atom or an RSS feed. To do this, it builds a System.ServiceModel.Syndication.SyndicationFeed and a custom MediaTypeFormatter is responsible for handling the HTTP Accept Header, converting the SyndicationFeed to either an Atom10FeedFormatter or an Rss20FeedFormatter, and streaming the result to the response stream. So far, so good.
My controller looks something like this:
public class FeedController : ApiController
{
public HttpResponseMessage Get()
{
FeedRepository feedRepository = new FeedRepository();
HttpResponseMessage<SyndicationFeed> successResponseMessage = new HttpResponseMessage<SyndicationFeed>(feedRepository.GetSyndicationFeed());
return successResponseMessage;
}
}
What I would like to do is make use of the built-in OData querying to filter my feed, but changing the return type of the Get() method to IQueryable<SyndicationFeed> obviously will not work since a SyndicationFeed does not implement IQueryable.
Is there a way to use the built in OData querying on the IEnumerable<SyndicationItem> property on the SyndicationFeed?
This question is no longer relevant, since Microsoft remove the rudimentary support for OData querying that was in the Beta build of Web API.
A future version will include more complete OData support. There is an early build of this available via CodePlex and NuGet and there are more details here: http://blogs.msdn.com/b/alexj/archive/2012/08/15/odata-support-in-asp-net-web-api.aspx
The System.Linq namespace provides an extension method named AsQueryable to the IEnumerable interface. Your code would look along the lines of this:
public class FeedController : ApiController
{
public IQueryable<SyndicationFeed> Get()
{
FeedRepository feedRepository = new FeedRepository();
//TODO: Make sure your property handles empty/null results:
return feedRepository.GetSyndicationFeed()
.YourEnumerableProperty.AsQueryable();
}
}
You don't have to return IQuerable from controller when working with OData.
Check "Invoking Query Options Directly" section at https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options
For your case it will looks like:
public SyndicationFeed Get(ODataQueryOptions<SyndicationItem> opts)
{
var settings = new ODataValidationSettings();
opts.Validate(settings);
SyndicationFeed result = feedRepository.GetSyndicationFeed();
result.Items = opts.ApplyTo(result.Items.AsQuerable()).ToArray();
return result;
}
I'm building an ASP.NET MVC3 Website with EF and DB First Approach. I need to come up with a reliable mechanism for database context switching in runtime for users. I've got several databases (same schema) that are used in remote "workshops" and application users in company headquaters need to have the ability to switch between databases at any time.
First I have implemented a base controller, that had ChangeDbContext(string dbname). It was persisting selected dbName to Session, and then I was retrieving from Session in OnActionExecuting method. However it turned out to be not reliable because session behaved unpredicatble (random expiration etc.) So I'm looking a smart way to replace Session with something else.
I could use advices on :
- where to put EntityFramework object initialization (BaseController Constructor ?)
- are there any additional changes that I should do to utilize Impersonation with WindowsAuth for DB connection ?
First, you need to insure your application session can survive restarts and app pool recycles. See this
Second, you need to inject the connection string for your DBContext based on the authenticated user request.
I assume you’ve got a database full of users so what you need to do is save a list of possible connection strings in a SQL table and relate them back to their associated user accounts. Once you’ve authenticated the user you need to retrieve the connection string associated with the user account. You don't want to store your connection string in a session or any other mechanism that could potentially expose sensitive data to a web client. So in summary this what you need to do.
You will want to retrieve your connection string for each request base on the authenticated user.
Inject the connection string into your DBContext.
Make your database calls as necessary.
Money!
Injecting strings into entity is easy.
If you're using EF 4.1 Code first your DBContext would look something like this.
EF 4.1 accepts normal ADO.NET connection strings.
public class ExampleProvider : DbContext, IExampleProvider
{
private readonly string _nameOrStringConnection;
public ExampleProvider()
{
}
public ExampleProvider(string nameOrStringConnection)
: base(nameOrStringConnection)
{
_nameOrStringConnection = nameOrStringConnection;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Example>().ToTable("example");
modelBuilder.Entity<Example>().HasKey(x => x.ExampleId);
}
public DbSet<Example> Examples { get; set; }
}
If you're using EF.edmx you will need to make sure that your injected connection string includes the edmx metadata files info like this...
..."metadata=res:///ExampleModel.csdl|res:///ExampleModel.ssdl|res://*/ExampleModel.msl;...
If you look in the edmx designer file you will see your DBContext has several constructor overloads. Use the second or third overload per your needs.
#region Contexts
/// <summary>
/// No Metadata Documentation available.
/// </summary>
public partial class Entities : ObjectContext
{
#region Constructors
/// <summary>
/// Initializes a new Entities object using the connection string found in the 'Entities' section of the application configuration file.
/// </summary>
public Entities() : base("name=Entities", "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialize a new Entities object.
/// </summary>
public Entities(string connectionString) : base(connectionString, "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialize a new Entities object.
/// </summary>
public Entities(EntityConnection connection) : base(connection, "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
#endregion
/// incomplete file
Good luck!
Cookies can be persisted for a long expiry, well longer than a session anyway. You could also look at a hidden page variable or mangled URL.
1) Sessions doesn't expire randomply...but after the time you set in the we.config...default is 10 min. Seesion MUST expire because there is no way to know that an user left our web site...so if they stop accessing pages for, say 10 min, we ASSUME, they went away...You can increase this time but the problem remain.
2) Tou can store directly the information in a cookie. Now since the cookie only waste resources on the browser (very little space), you can make the cookie persistent...so that it never expire
3) As an alternative to cookies you can store this information together with the credential information of users (login name etc.) You can use the Profile provider to define a property DBChosen.